Initial commit
This commit is contained in:
152
src/App.tsx
Normal file
152
src/App.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
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 { LoginPage } from '@/features/auth/LoginPage';
|
||||
import { NearleAdminShell } from '@/features/nearle-admin/NearleAdminShell';
|
||||
import { StoreAdminShell } from '@/features/store-admin/StoreAdminShell';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
const named = <T extends string>(key: T, loader: () => Promise<Record<T, ComponentType>>) =>
|
||||
lazy(() => 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 ConsolePage = named('ConsolePage', () => import('@/features/store-admin/pages/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'));
|
||||
|
||||
function RouteFallback() {
|
||||
return (
|
||||
<div style={{ display: 'grid', placeItems: 'center', padding: 64 }}>
|
||||
<Spinner size="md" label="Loading" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (
|
||||
<Routes>
|
||||
<Route path="/login" element={<LoginPage />} />
|
||||
|
||||
{/* Nearle Admin — the platform workspace */}
|
||||
<Route
|
||||
path="/nearle"
|
||||
element={
|
||||
<RequireRole role="nearle-admin">
|
||||
<Suspense fallback={<RouteFallback />}>
|
||||
<NearleAdminShell />
|
||||
</Suspense>
|
||||
</RequireRole>
|
||||
}
|
||||
>
|
||||
<Route index element={<Navigate to="/nearle/stores" replace />} />
|
||||
<Route path="stores" element={<StoresPage />} />
|
||||
<Route path="stores/:tenantId" element={<StoreDetailPage />} />
|
||||
<Route path="onboard/tenant" element={<OnboardTenantPage />} />
|
||||
{/* 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. */}
|
||||
<Route path="onboard/branch" element={<Navigate to="/nearle/stores" replace />} />
|
||||
<Route path="catalogue" element={<GlobalCataloguePage />} />
|
||||
{/* 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. */}
|
||||
<Route path="*" element={<Navigate to="/nearle/stores" replace />} />
|
||||
</Route>
|
||||
|
||||
{/* Store Admin — the merchant workspace, scoped to one tenant's branches */}
|
||||
<Route
|
||||
path="/admin"
|
||||
element={
|
||||
<RequireRole role="store-admin">
|
||||
<Suspense fallback={<RouteFallback />}>
|
||||
<StoreAdminShell />
|
||||
</Suspense>
|
||||
</RequireRole>
|
||||
}
|
||||
>
|
||||
<Route index element={<Navigate to="/admin/console" replace />} />
|
||||
<Route path="console" element={<ConsolePage />} />
|
||||
<Route path="sales" element={<SalesPage />} />
|
||||
<Route path="inventory" element={<InventoryPage />} />
|
||||
<Route path="reports" element={<ReportsPage />} />
|
||||
<Route path="branches/new" element={<OnboardBranchPage />} />
|
||||
{/* Reached from the account menu, not the nav — setup is not a place
|
||||
anyone works from day to day. See `AppShellProps.manageItems`. */}
|
||||
<Route path="users" element={<UsersPage />} />
|
||||
<Route path="terminals" element={<TerminalsPage />} />
|
||||
{/* 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. */}
|
||||
<Route path="*" element={<Navigate to="/admin/console" replace />} />
|
||||
</Route>
|
||||
|
||||
{/* Not built yet. Named rather than missing, so the redirect has somewhere to land. */}
|
||||
<Route path="/store/*" element={<NotBuiltYet workspace="Store Manager" />} />
|
||||
|
||||
<Route
|
||||
path="*"
|
||||
element={<Navigate to={user ? HOME_ROUTE[user.role] : '/login'} replace />}
|
||||
/>
|
||||
</Routes>
|
||||
);
|
||||
}
|
||||
|
||||
function NotBuiltYet({ workspace }: { workspace: string }) {
|
||||
const { signOut } = useAuth();
|
||||
return (
|
||||
<div style={{ minHeight: '100vh', display: 'grid', placeItems: 'center', padding: 24 }}>
|
||||
<div style={{ maxWidth: 460, textAlign: 'center' }}>
|
||||
<h1 style={{ fontFamily: 'var(--font-display)', fontSize: 22, marginBottom: 8 }}>
|
||||
The {workspace} workspace is not built yet
|
||||
</h1>
|
||||
<p style={{ color: 'var(--color-slate-500)', fontSize: 14, lineHeight: 1.6 }}>
|
||||
This console currently covers the Nearle Admin workspace. Your account signs in
|
||||
correctly — there is simply nothing here for it to open.
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={signOut}
|
||||
style={{
|
||||
marginTop: 20,
|
||||
padding: '9px 16px',
|
||||
borderRadius: 12,
|
||||
border: '1px solid var(--color-slate-200)',
|
||||
background: '#fff',
|
||||
cursor: 'pointer',
|
||||
fontSize: 13,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user