import { lazy, Suspense, type ComponentType } from 'react'; import { Navigate, Route, Routes } from 'react-router-dom'; import { Spinner } from '@astryxdesign/core/Spinner'; import { RequireRole, useAuth } from '@/auth/AuthContext'; import { HOME_ROUTE } from '@/auth/roles'; import { withStaleChunkRecovery } from '@/lib/staleChunk'; import { LoginPage } from '@/features/auth/LoginPage'; import { NearleAdminShell } from '@/features/nearle-admin/NearleAdminShell'; import { StoreAdminShell } from '@/features/store-admin/StoreAdminShell'; import { StoreUserShell } from '@/features/store-user/StoreUserShell'; /** * Routes are split per page. * * The reference console ships as one 1.8MB chunk with no route splitting, which * means every visitor downloads the spreadsheet importer to look at a dashboard. * That is the one thing from it worth deliberately not copying. */ /** * Split pages recover from a deploy that lands mid-session. * * `withStaleChunkRecovery` is the difference between "Inventory is broken" and * a reload nobody notices: the hashed filename this tab remembers stops * existing the moment a new build ships, and the import 404s. See * `lib/staleChunk.ts` — real errors still surface, only the missing chunk is * retried. */ const named = (key: T, loader: () => Promise>) => lazy(withStaleChunkRecovery(() => loader().then((module) => ({ default: module[key] })))); const StoresPage = named('StoresPage', () => import('@/features/nearle-admin/pages/StoresPage')); const StoreDetailPage = named('StoreDetailPage', () => import('@/features/nearle-admin/pages/StoreDetailPage')); const OnboardTenantPage = named('OnboardTenantPage', () => import('@/features/nearle-admin/pages/OnboardTenantPage')); const GlobalCataloguePage = named('GlobalCataloguePage', () => import('@/features/nearle-admin/pages/GlobalCataloguePage')); const NearleUploadsPage = named('UploadsPage', () => import('@/features/nearle-admin/pages/UploadsPage')); /* One Console for both workspaces — it reads its own scope from BranchScope, which pins a store user to their outlet and lets an admin choose. Both routes below therefore render the same component, not two copies of one board. */ const ConsolePage = named('ConsolePage', () => import('@/features/console/ConsolePage')); const SalesPage = named('SalesPage', () => import('@/features/store-admin/pages/SalesPage')); const InventoryPage = named('InventoryPage', () => import('@/features/store-admin/pages/InventoryPage')); const ReportsPage = named('ReportsPage', () => import('@/features/store-admin/pages/ReportsPage')); const OnboardBranchPage = named('OnboardBranchPage', () => import('@/features/store-admin/pages/OnboardBranchPage')); const UsersPage = named('UsersPage', () => import('@/features/store-admin/pages/UsersPage')); const TerminalsPage = named('TerminalsPage', () => import('@/features/store-admin/pages/TerminalsPage')); const AdminUploadsPage = named('UploadsPage', () => import('@/features/store-admin/pages/UploadsPage')); const ShopProfilePage = named('ShopProfilePage', () => import('@/features/store-admin/pages/ShopProfilePage')); const OnboardingPage = named('OnboardingPage', () => import('@/features/onboarding/OnboardingPage')); /* The Store user workspace reuses the merchant's four pages, pinned to one branch by `BranchScopeProvider pin=`. Only what a shop does differently is its own file: the store catalogue, its customers, its QR and its account. */ const StoreProductsPage = named('StoreProductsPage', () => import('@/features/store-user/pages/StoreProductsPage')); const StoreCustomersPage = named('StoreCustomersPage', () => import('@/features/store-user/pages/StoreCustomersPage')); const StoreStaffPage = named('StoreStaffPage', () => import('@/features/store-user/pages/StoreStaffPage')); const StoreAccountPage = named('StoreAccountPage', () => import('@/features/store-user/pages/StoreAccountPage')); const StoreUploadsPage = named('StoreUploadsPage', () => import('@/features/store-user/pages/StoreUploadsPage')); function RouteFallback() { return (
); } /** * Routes. * * Three workspaces, hard-partitioned by role and by URL prefix, the same way the * three logins imply. Only the Nearle Admin workspace is built so far; the other * two resolve to a placeholder rather than a 404, so a Store Admin signing in * today lands somewhere that explains itself. */ export function App() { const { user } = useAuth(); return ( } /> {/* Nearle Admin — the platform workspace */} }> } > } /> } /> } /> } /> {/* Branch onboarding moved to the Store Admin workspace — the merchant opens their own outlets. Kept as a redirect rather than deleted so a bookmark or a stale link lands on the directory instead of a 404. */} } /> } /> } /> {/* Absorbed here rather than by the global `*`, so a wrong sub-path can never bounce out to a HOME_ROUTE that points back into this workspace and loop. */} } /> {/* Store Admin — the merchant workspace, scoped to one tenant's branches */} }> } > } /> } /> } /> } /> } /> } /> {/* Reached from the account menu, not the nav — setup is not a place anyone works from day to day. See `AppShellProps.manageItems`. */} } /> } /> } /> } /> } /> {/* Catches `/admin/dashboard` and anything else that does not resolve. Without this, an unknown sub-path escapes to the global `*`, which redirects to this role's HOME_ROUTE — and if that is itself an unknown path, the two bounce forever and the page renders blank. */} } /> {/* Store user — one branch, fixed. Same four destinations as the merchant, and mostly the same pages: what differs is the scope, not the shape, so pinning them beats a second set that drifts. */} }> } > } /> } /> } /> } /> } /> {/* Reached from the account menu. A shop does not commission outlets, so there is deliberately no `branches/new` here. */} } /> } /> } /> } /> } /> } /> } /> ); }