Files
doormilxpress_astryx/src/App.jsx

203 lines
10 KiB
JavaScript

import { Suspense, lazy } from 'react';
import AppToaster from '@/components/third-party/AppToaster';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClientInstance } from '@/lib/query-client';
import { Navigate, BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import PageNotFound from './lib/PageNotFound';
import { AuthProvider, useAuth } from '@/lib/AuthContext';
import { ZoneProvider } from '@/lib/ZoneContext';
import ScrollToTop from './components/ScrollToTop';
import ProtectedRoute from '@/components/ProtectedRoute';
import AdminLayout from '@/layouts/AdminLayout';
import Login from '@/pages/Login';
/* Every console page is code-split. The Dispatch board alone pulls the map,
the solver payload builders and the comparison panels; loading that on the
way to the Hubs table would make every first paint pay for it. */
/* Doormile-only pages. A client login is sent home rather than shown a page
whose every request the server refuses. */
function StaffOnly({ children }) {
const { isClient } = useAuth();
return isClient ? <Navigate to="/doormile/home" replace /> : children;
}
const Dispatch = lazy(() => import('@/pages/doormile/dispatch/Dispatch.jsx'));
const DispatchPreview = lazy(() => import('@/pages/doormile/dispatch/Preview.jsx'));
const Home = lazy(() => import('@/pages/doormile/home/Home.jsx'));
const Orders = lazy(() => import('@/pages/doormile/orders/Orders'));
const Bookings = lazy(() => import('@/pages/doormile/bookings/Bookings'));
const OrdersPreview = lazy(() => import('@/pages/doormile/orders/OrdersPreview'));
const CreateOrder = lazy(() => import('@/pages/doormile/orders/CreateOrder'));
const MultipleOrders = lazy(() => import('@/pages/doormile/orders/MultipleOrders'));
const Deliveries = lazy(() => import('@/pages/doormile/deliveries/Deliveries'));
const Returns = lazy(() => import('@/pages/doormile/returns/Returns'));
const Tenants = lazy(() => import('@/pages/doormile/clients/Tenants'));
const CreateClient = lazy(() => import('@/pages/doormile/clients/CreateClient'));
const ClientOnboarding = lazy(() => import('@/pages/doormile/clients/ClientOnboarding'));
const CreateCustomer = lazy(() => import('@/pages/doormile/clients/CreateCustomer'));
const Pricing = lazy(() => import('@/pages/doormile/pricing/Pricing'));
const Riders = lazy(() => import('@/pages/doormile/riders/Riders'));
const CreateRider = lazy(() => import('@/pages/doormile/riders/CreateRider'));
const EditRider = lazy(() => import('@/pages/doormile/riders/EditRider'));
const Customers = lazy(() => import('@/pages/doormile/customers/Customers'));
const Hubs = lazy(() => import('@/pages/doormile/hubs/Hubs'));
const Vehicles = lazy(() => import('@/pages/doormile/vehicles/Vehicles'));
const AppUsers = lazy(() => import('@/pages/doormile/appUsers/AppUsers'));
const Tripsheets = lazy(() => import('@/pages/doormile/tripsheets/Tripsheets'));
const Exceptions = lazy(() => import('@/pages/doormile/exceptions/Exceptions'));
const CompetitiveIntel = lazy(() => import('@/pages/doormile/competitiveIntel/CompetitiveIntel'));
const OrdersSummary = lazy(() => import('@/pages/doormile/reports/OrdersSummary'));
const OrdersDetails = lazy(() => import('@/pages/doormile/reports/OrdersDetails'));
const RidersSummary = lazy(() => import('@/pages/doormile/reports/RidersSummary'));
const Profitability = lazy(() => import('@/pages/doormile/reports/Profitability'));
const ViewProfile = lazy(() => import('@/pages/doormile/ViewProfile'));
const Settings = lazy(() => import('@/pages/doormile/settings/Settings'));
const Agents = lazy(() => import('@/pages/doormile/agents/Agents'));
const PageFallback = () => (
<div className="flex min-h-[60vh] items-center justify-center">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-slate-200 border-t-slate-800" />
</div>
);
const AuthenticatedApp = () => {
const { isLoadingAuth, isLoadingPublicSettings } = useAuth();
if (isLoadingPublicSettings || isLoadingAuth) {
return (
<div className="fixed inset-0 flex items-center justify-center">
<div className="w-8 h-8 border-4 border-slate-200 border-t-slate-800 rounded-full animate-spin"></div>
</div>
);
}
return (
<Suspense fallback={<PageFallback />}>
<Routes>
{/* Sign-in — outside the protected boundary. */}
<Route path="/login" element={<Login />} />
<Route element={<ProtectedRoute />}>
{/* Dispatch is the console's home: it is the board operators sit on
all day, and the one page that answers "what is happening right
now" without a filter being set first. */}
<Route path="/" element={<Navigate to="/doormile/home" replace />} />
<Route path="/home" element={<Navigate to="/doormile/home" replace />} />
<Route path="/customers" element={<Navigate to="/doormile/customers" replace />} />
<Route path="/customer" element={<Navigate to="/doormile/customers" replace />} />
<Route path="/doormile" element={<AdminLayout />}>
<Route index element={<Navigate to="/doormile/home" replace />} />
<Route path="home" element={<Home />} />
{/* Control X — the board formerly routed at /doormile/dispatch.
The directory, the component and the .dispatch-container CSS
namespace keep their old names on purpose: they are internal,
and renaming a 5,800-line stylesheet's root class is a large
diff with no user-visible benefit and real risk of missing a
selector. Only what an operator sees and types changes. */}
<Route path="control-x" element={<Dispatch />} />
<Route path="control-x/preview" element={<DispatchPreview />} />
{/* The old paths keep working.
This board is the page operators sit on all day, so it is the
one most likely to be bookmarked, pinned in a browser tab
group, or pasted into a message. Renaming the route without
this would turn every one of those into a 404 — a support
problem with no upside, avoided by two lines. `replace` so the
dead path does not linger in history behind a Back press. */}
<Route path="dispatch" element={<Navigate to="/doormile/control-x" replace />} />
<Route path="dispatch/preview" element={<Navigate to="/doormile/control-x/preview" replace />} />
{/* Static before dynamic, so `orders/create` cannot be read as an
order whose id is "create". */}
<Route path="orders" element={<Orders />} />
<Route path="orders/create" element={<CreateOrder />} />
<Route path="orders/multiple" element={<MultipleOrders />} />
<Route path="orders/createorders" element={<MultipleOrders />} />
<Route path="orders/preview" element={<OrdersPreview />} />
<Route path="bookings" element={<Bookings />} />
<Route path="deliveries" element={<Deliveries />} />
{/* Reverse logistics: parcels going back to the sender (RTO). */}
<Route path="returns" element={<Returns />} />
<Route path="tenants" element={<StaffOnly><Tenants /></StaffOnly>} />
<Route path="clients/create" element={<StaffOnly><CreateClient /></StaffOnly>} />
{/* Owner-only (admin@doormile.com); the page and the server both check. */}
<Route path="clients/onboard" element={<StaffOnly><ClientOnboarding /></StaffOnly>} />
<Route path="customer/create" element={<CreateCustomer />} />
<Route path="pricing" element={<Pricing />} />
<Route path="milers" element={<Riders />} />
<Route path="milers/create" element={<CreateRider />} />
<Route path="milers/edit" element={<EditRider />} />
{/* Backwards-compatible aliases */}
<Route path="riders" element={<Navigate to="/doormile/milers" replace />} />
<Route path="riders/create" element={<Navigate to="/doormile/milers/create" replace />} />
<Route path="riders/edit" element={<Navigate to="/doormile/milers/edit" replace />} />
<Route path="customers" element={<Customers />} />
<Route path="hubs" element={<Hubs />} />
<Route path="vehicles" element={<StaffOnly><Vehicles /></StaffOnly>} />
<Route path="app-users" element={<StaffOnly><AppUsers /></StaffOnly>} />
<Route path="tripsheets" element={<StaffOnly><Tripsheets /></StaffOnly>} />
<Route path="exceptions" element={<Exceptions />} />
<Route path="competitive-intel" element={<StaffOnly><CompetitiveIntel /></StaffOnly>} />
<Route path="reports/orderssummary" element={<OrdersSummary />} />
<Route path="reports/ordersdetails" element={<OrdersDetails />} />
<Route path="reports/riderssummary" element={<RidersSummary />} />
<Route path="reports/profitability" element={<Profitability />} />
<Route path="profile" element={<ViewProfile />} />
<Route path="settings" element={<Settings />} />
{/* The AI_engine swarm. Read-only: it reports what the agents are
and what they last did, and has no control surface, because
every autonomy gate lives in that repository's own env. */}
<Route path="agents" element={<StaffOnly><Agents /></StaffOnly>} />
</Route>
</Route>
<Route path="*" element={<PageNotFound />} />
</Routes>
</Suspense>
);
};
function App() {
return (
<AuthProvider>
<QueryClientProvider client={queryClientInstance}>
<ZoneProvider>
<Router>
<ScrollToTop />
<AuthenticatedApp />
</Router>
{/* Every toast in the app (OpenToast, the design system's `toast`,
direct react-hot-toast calls) renders here: top-right, one card
design. Without a mounted renderer every toast fires into
nothing — including the data layer's failure notices. */}
<AppToaster />
</ZoneProvider>
</QueryClientProvider>
</AuthProvider>
);
}
export default App;