- Add LicenseConnect.tsx focused OAuth confirmation page in customer SPA - Add /licenses/oauth/validate and /licenses/oauth/confirm API endpoints - Update App.tsx to render license-connect outside BaseLayout (no header/footer) - Add license_activation_method field to product settings in Admin SPA - Create LICENSING_MODULE.md with comprehensive OAuth flow documentation - Update API_ROUTES.md with license module endpoints
200 lines
6.8 KiB
TypeScript
200 lines
6.8 KiB
TypeScript
import React from 'react';
|
|
import { HashRouter, BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
import { HelmetProvider } from 'react-helmet-async';
|
|
import { Toaster } from 'sonner';
|
|
|
|
// Theme
|
|
import { ThemeProvider } from './contexts/ThemeContext';
|
|
import { BaseLayout } from './layouts/BaseLayout';
|
|
|
|
// Pages
|
|
import Shop from './pages/Shop';
|
|
import Product from './pages/Product';
|
|
import Cart from './pages/Cart';
|
|
import Checkout from './pages/Checkout';
|
|
import ThankYou from './pages/ThankYou';
|
|
import Account from './pages/Account';
|
|
import Wishlist from './pages/Wishlist';
|
|
import Login from './pages/Login';
|
|
import ForgotPassword from './pages/ForgotPassword';
|
|
import ResetPassword from './pages/ResetPassword';
|
|
import OrderPay from './pages/OrderPay';
|
|
import { DynamicPageRenderer } from './pages/DynamicPage';
|
|
|
|
// Create QueryClient instance
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
refetchOnWindowFocus: false,
|
|
retry: 1,
|
|
},
|
|
},
|
|
});
|
|
|
|
// Get theme config from window (injected by PHP)
|
|
const getThemeConfig = () => {
|
|
const config = (window as any).woonoowCustomer?.theme;
|
|
|
|
// Default config if not provided
|
|
return config || {
|
|
mode: 'full',
|
|
layout: 'modern',
|
|
colors: {
|
|
primary: '#3B82F6',
|
|
secondary: '#8B5CF6',
|
|
accent: '#10B981',
|
|
},
|
|
typography: {
|
|
preset: 'professional',
|
|
},
|
|
};
|
|
};
|
|
|
|
// Get appearance settings from window
|
|
const getAppearanceSettings = () => {
|
|
return (window as any).woonoowCustomer?.appearanceSettings || {};
|
|
};
|
|
|
|
// Get initial route from data attribute or derive from SPA mode
|
|
const getInitialRoute = () => {
|
|
const appEl = document.getElementById('woonoow-customer-app');
|
|
const initialRoute = appEl?.getAttribute('data-initial-route');
|
|
if (initialRoute) return initialRoute;
|
|
|
|
// Derive from SPA mode if no explicit route
|
|
const spaMode = (window as any).woonoowCustomer?.spaMode || 'full';
|
|
if (spaMode === 'checkout_only') return '/checkout';
|
|
return '/shop'; // Default for full mode
|
|
};
|
|
|
|
// Get front page slug from config
|
|
const getFrontPageSlug = () => {
|
|
return (window as any).woonoowCustomer?.frontPageSlug || null;
|
|
};
|
|
|
|
// Router wrapper component that uses hooks requiring Router context
|
|
function AppRoutes() {
|
|
const initialRoute = getInitialRoute();
|
|
const frontPageSlug = getFrontPageSlug();
|
|
|
|
return (
|
|
<Routes>
|
|
{/* License Connect - Standalone focused page without layout */}
|
|
<Route path="/my-account/license-connect" element={<Account />} />
|
|
|
|
{/* All other routes wrapped in BaseLayout */}
|
|
<Route
|
|
path="/*"
|
|
element={
|
|
<BaseLayout>
|
|
<Routes>
|
|
{/* Root route: If frontPageSlug exists, render it. Else redirect to initialRoute (e.g. /shop) */}
|
|
<Route
|
|
path="/"
|
|
element={
|
|
frontPageSlug ? (
|
|
<DynamicPageRenderer slug={frontPageSlug} />
|
|
) : (
|
|
<Navigate to={initialRoute === '/' ? '/shop' : initialRoute} replace />
|
|
)
|
|
}
|
|
/>
|
|
|
|
{/* Shop Routes */}
|
|
<Route path="/shop" element={<Shop />} />
|
|
<Route path="/product/:slug" element={<Product />} />
|
|
|
|
{/* Cart & Checkout */}
|
|
<Route path="/cart" element={<Cart />} />
|
|
<Route path="/checkout" element={<Checkout />} />
|
|
<Route path="/order-received/:orderId" element={<ThankYou />} />
|
|
<Route path="/checkout/order-received/:orderId" element={<ThankYou />} />
|
|
<Route path="/checkout/pay/:orderId" element={<OrderPay />} />
|
|
|
|
{/* Wishlist - Public route accessible to guests */}
|
|
<Route path="/wishlist" element={<Wishlist />} />
|
|
|
|
{/* Login & Auth */}
|
|
<Route path="/login" element={<Login />} />
|
|
<Route path="/forgot-password" element={<ForgotPassword />} />
|
|
<Route path="/reset-password" element={<ResetPassword />} />
|
|
|
|
{/* My Account */}
|
|
<Route path="/my-account/*" element={<Account />} />
|
|
|
|
{/* Dynamic Pages - CPT content with path base (e.g., /blog/:slug) */}
|
|
<Route path="/:pathBase/:slug" element={<DynamicPageRenderer />} />
|
|
|
|
{/* Dynamic Pages - Structural pages (e.g., /about, /contact) */}
|
|
<Route path="/:slug" element={<DynamicPageRenderer />} />
|
|
</Routes>
|
|
</BaseLayout>
|
|
}
|
|
/>
|
|
</Routes>
|
|
);
|
|
}
|
|
|
|
// Get router config from WordPress
|
|
const getRouterConfig = () => {
|
|
const config = (window as any).woonoowCustomer;
|
|
return {
|
|
useBrowserRouter: config?.useBrowserRouter ?? true,
|
|
basePath: config?.basePath ?? '/store',
|
|
};
|
|
};
|
|
|
|
// Router wrapper that conditionally uses BrowserRouter or HashRouter
|
|
function RouterProvider({ children }: { children: React.ReactNode }) {
|
|
const { useBrowserRouter, basePath } = getRouterConfig();
|
|
|
|
if (useBrowserRouter) {
|
|
return <BrowserRouter basename={basePath}>{children}</BrowserRouter>;
|
|
}
|
|
|
|
return <HashRouter>{children}</HashRouter>;
|
|
}
|
|
|
|
function App() {
|
|
const themeConfig = getThemeConfig();
|
|
const appearanceSettings = getAppearanceSettings();
|
|
const toastPosition = (appearanceSettings?.general?.toast_position || 'top-right') as any;
|
|
|
|
// Inject gradient CSS variables
|
|
React.useEffect(() => {
|
|
// appearanceSettings is already the 'data' object from Assets.php injection
|
|
// Structure: { general: { colors: { primary, secondary, accent, text, background, gradientStart, gradientEnd } } }
|
|
const colors = appearanceSettings?.general?.colors;
|
|
if (colors) {
|
|
const root = document.documentElement;
|
|
// Inject all color settings as CSS variables
|
|
if (colors.primary) root.style.setProperty('--wn-primary', colors.primary);
|
|
if (colors.secondary) root.style.setProperty('--wn-secondary', colors.secondary);
|
|
if (colors.accent) root.style.setProperty('--wn-accent', colors.accent);
|
|
if (colors.text) root.style.setProperty('--wn-text', colors.text);
|
|
if (colors.background) root.style.setProperty('--wn-background', colors.background);
|
|
if (colors.gradientStart) root.style.setProperty('--wn-gradient-start', colors.gradientStart);
|
|
if (colors.gradientEnd) root.style.setProperty('--wn-gradient-end', colors.gradientEnd);
|
|
}
|
|
}, [appearanceSettings]);
|
|
|
|
return (
|
|
<HelmetProvider>
|
|
<QueryClientProvider client={queryClient}>
|
|
<ThemeProvider config={themeConfig}>
|
|
<RouterProvider>
|
|
<AppRoutes />
|
|
</RouterProvider>
|
|
|
|
{/* Toast notifications - position from settings */}
|
|
<Toaster position={toastPosition} richColors />
|
|
</ThemeProvider>
|
|
</QueryClientProvider>
|
|
</HelmetProvider>
|
|
);
|
|
}
|
|
|
|
export default App;
|