diff --git a/src/app-root.tsx b/src/app-root.tsx
new file mode 100644
index 00000000..b3100ba5
--- /dev/null
+++ b/src/app-root.tsx
@@ -0,0 +1,5 @@
+import { Routes } from "@/app-routes";
+
+export function AppRoot() {
+ return ;
+}
diff --git a/src/app-routes.tsx b/src/app-routes.tsx
new file mode 100644
index 00000000..c5958fa8
--- /dev/null
+++ b/src/app-routes.tsx
@@ -0,0 +1 @@
+export { Routes } from "@/routing";
diff --git a/src/app/404.tsx b/src/app/404.tsx
new file mode 100644
index 00000000..84eb0d25
--- /dev/null
+++ b/src/app/404.tsx
@@ -0,0 +1,21 @@
+import { Link } from "react-router-dom";
+import { Box, Button, Typography } from "@mui/material";
+
+export default function NotFoundPage() {
+ return (
+
+
+ Page not found
+
+
+ The page you are looking for does not exist or has been moved.
+
+
+
+ );
+}
diff --git a/src/app/_app.tsx b/src/app/_app.tsx
new file mode 100644
index 00000000..d7bac7a0
--- /dev/null
+++ b/src/app/_app.tsx
@@ -0,0 +1,2 @@
+/** Generouted reserved filename — root shell for all routes. */
+export { default } from "./_components/root-layout";
diff --git a/src/app/_components/root-layout.tsx b/src/app/_components/root-layout.tsx
new file mode 100644
index 00000000..eab85368
--- /dev/null
+++ b/src/app/_components/root-layout.tsx
@@ -0,0 +1,11 @@
+import { Outlet } from "react-router";
+
+import { AuthProvider } from "@/providers/auth-provider";
+
+export default function RootLayout() {
+ return (
+
+
+
+ );
+}
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 00000000..6643603c
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,19 @@
+import React from "react";
+import ReactDOM from "react-dom/client";
+import { AppRoot } from "@/app-root";
+import { AppProviders } from "@/providers/AppProviders";
+import "@/styles/globals.css";
+
+const rootElement = document.getElementById("root");
+
+if (!rootElement) {
+ throw new Error("Root element #root not found");
+}
+
+ReactDOM.createRoot(rootElement).render(
+
+
+
+
+ ,
+);
diff --git a/src/router.ts b/src/router.ts
new file mode 100644
index 00000000..60236af4
--- /dev/null
+++ b/src/router.ts
@@ -0,0 +1,74 @@
+// Generouted, changes to this file will be overridden
+/* eslint-disable */
+
+import { components, hooks, utils } from "@generouted/react-router/client";
+
+export type Path =
+ | `///src/app`
+ | `///src/app/accounts`
+ | `///src/app/accounts/edit/:id`
+ | `///src/app/accounts/new`
+ | `///src/app/assets`
+ | `///src/app/assets/edit/:id`
+ | `///src/app/assets/new`
+ | `///src/app/calendar`
+ | `///src/app/calendar/edit/:id`
+ | `///src/app/calendar/new`
+ | `///src/app/contacts`
+ | `///src/app/contacts/edit/:id`
+ | `///src/app/contacts/new`
+ | `///src/app/dashboard`
+ | `///src/app/employees`
+ | `///src/app/employees/edit/:id`
+ | `///src/app/employees/new`
+ | `///src/app/followups`
+ | `///src/app/followups/edit/:id`
+ | `///src/app/followups/new`
+ | `///src/app/locations`
+ | `///src/app/locations/edit/:id`
+ | `///src/app/locations/new`
+ | `///src/app/login`
+ | `///src/app/pmschedules`
+ | `///src/app/pmschedules/edit/:id`
+ | `///src/app/pmschedules/new`
+ | `///src/app/settings/dropdowns`
+ | `///src/app/settings/task-templates`
+ | `///src/app/uplifts`
+ | `///src/app/v/:token`
+ | `///src/app/v/:token/dashboard`
+ | `///src/app/v/:token/dispatch/:id`
+ | `///src/app/v/:token/pos`
+ | `///src/app/vendor-pos`
+ | `///src/app/vendor-pos/:id`
+ | `///src/app/vendors`
+ | `///src/app/vendors/edit/:id`
+ | `///src/app/vendors/new`
+ | `///src/app/workorders`
+ | `///src/app/workorders/:id`
+ | `///src/app/workorders/edit/:id`
+ | `///src/app/workorders/new`;
+
+export type Params = {
+ "///src/app/accounts/edit/:id": { id: string };
+ "///src/app/assets/edit/:id": { id: string };
+ "///src/app/calendar/edit/:id": { id: string };
+ "///src/app/contacts/edit/:id": { id: string };
+ "///src/app/employees/edit/:id": { id: string };
+ "///src/app/followups/edit/:id": { id: string };
+ "///src/app/locations/edit/:id": { id: string };
+ "///src/app/pmschedules/edit/:id": { id: string };
+ "///src/app/v/:token": { token: string };
+ "///src/app/v/:token/dashboard": { token: string };
+ "///src/app/v/:token/dispatch/:id": { token: string; id: string };
+ "///src/app/v/:token/pos": { token: string };
+ "///src/app/vendor-pos/:id": { id: string };
+ "///src/app/vendors/edit/:id": { id: string };
+ "///src/app/workorders/:id": { id: string };
+ "///src/app/workorders/edit/:id": { id: string };
+};
+
+export type ModalPath = never;
+
+export const { Link, Navigate } = components();
+export const { useModals, useNavigate, useParams } = hooks();
+export const { redirect } = utils();
diff --git a/src/routing/app-shell.tsx b/src/routing/app-shell.tsx
new file mode 100644
index 00000000..956485f3
--- /dev/null
+++ b/src/routing/app-shell.tsx
@@ -0,0 +1,52 @@
+import { ComponentType, Suspense } from "react";
+import { Outlet, useLocation } from "react-router";
+import type { RouteObject } from "react-router";
+
+import type { RouteModule } from "@/routing/route-module";
+
+function Modals({ modalRoutes }: { modalRoutes: Record }) {
+ const modalPath = useLocation().state?.modal as string | undefined;
+ const Modal = modalPath ? modalRoutes[modalPath] : undefined;
+
+ if (!Modal) {
+ return null;
+ }
+
+ return ;
+}
+
+export function createAppShellRoute(
+ appLayout: RouteModule | undefined,
+ modalRoutes: Record,
+): Pick {
+ const AppLayout = appLayout?.default ?? Outlet;
+
+ function Layout() {
+ return (
+ <>
+
+
+ >
+ );
+ }
+
+ function AppShell() {
+ const Pending = appLayout?.Pending;
+
+ if (Pending) {
+ return (
+ }>
+
+
+ );
+ }
+
+ return ;
+ }
+
+ return {
+ Component: appLayout?.default ? AppShell : Layout,
+ ErrorBoundary: appLayout?.Catch,
+ loader: appLayout?.Loader,
+ };
+}
diff --git a/src/routing/build-route-tree.ts b/src/routing/build-route-tree.ts
new file mode 100644
index 00000000..25e1b492
--- /dev/null
+++ b/src/routing/build-route-tree.ts
@@ -0,0 +1,27 @@
+import { Fragment, type ComponentType } from "react";
+import type { RouteObject } from "react-router";
+import {
+ generateModalRoutes,
+ generatePreservedRoutes,
+ generateRegularRoutes,
+} from "@generouted/react-router/core";
+
+import { MODALS, PRESERVED, ROUTES } from "@/routing/glob-imports";
+import { remapAppToPages } from "@/routing/remap-routes";
+import { createLazyRoute, type RouteModule } from "@/routing/route-module";
+import { createAppShellRoute } from "@/routing/app-shell";
+
+const preservedRoutes = generatePreservedRoutes(remapAppToPages(PRESERVED));
+const modalRoutes = generateModalRoutes(remapAppToPages(MODALS)) as Record;
+
+const regularRoutes = generateRegularRoutes(remapAppToPages(ROUTES), (moduleLoader, key) =>
+ createLazyRoute(moduleLoader as () => Promise, key),
+);
+
+const appLayout = preservedRoutes?.["_app"] as RouteModule | undefined;
+const notFound = preservedRoutes?.["404"] as RouteModule | undefined;
+
+const appShell = createAppShellRoute(appLayout, modalRoutes);
+const fallback: RouteObject = { path: "*", Component: notFound?.default ?? Fragment };
+
+export const routes: RouteObject[] = [{ ...appShell, children: [...regularRoutes, fallback] }];
diff --git a/src/routing/constants.ts b/src/routing/constants.ts
new file mode 100644
index 00000000..1c9022bc
--- /dev/null
+++ b/src/routing/constants.ts
@@ -0,0 +1,8 @@
+/** Vite glob root for file-based routes (must match import.meta.glob literals in glob-imports.ts). */
+export const ROUTES_ROOT = "/src/app";
+
+/**
+ * Generouted core derives URL paths assuming this prefix.
+ * See remap-routes.ts — we scan `/src/app/` but remap keys to `/src/pages/` at runtime.
+ */
+export const GENEROUTED_ROOT = "/src/pages";
diff --git a/src/routing/glob-imports.ts b/src/routing/glob-imports.ts
new file mode 100644
index 00000000..e2f9c4a1
--- /dev/null
+++ b/src/routing/glob-imports.ts
@@ -0,0 +1,13 @@
+import type { RouteModule } from "@/routing/route-module";
+
+/** Paths must stay literal — Vite analyzes import.meta.glob at build time. */
+const PRESERVED = import.meta.glob("/src/app/{_app,404}.{jsx,tsx}", { eager: true });
+const MODALS = import.meta.glob("/src/app/**/[+]*.{jsx,tsx}", { eager: true });
+const ROUTES = import.meta.glob([
+ "/src/app/**/[A-Za-z0-9[-]*.{jsx,tsx}",
+ "/src/app/**/_layout.{jsx,tsx}",
+ "!/src/app/404.{jsx,tsx}",
+ "!/src/app/**/_*/**",
+]);
+
+export { MODALS, PRESERVED, ROUTES };
diff --git a/src/routing/index.ts b/src/routing/index.ts
new file mode 100644
index 00000000..f3cd9f4d
--- /dev/null
+++ b/src/routing/index.ts
@@ -0,0 +1,2 @@
+export { Routes } from "@/routing/router";
+export { routes } from "@/routing/build-route-tree";
diff --git a/src/routing/remap-routes.ts b/src/routing/remap-routes.ts
new file mode 100644
index 00000000..11d9c032
--- /dev/null
+++ b/src/routing/remap-routes.ts
@@ -0,0 +1,17 @@
+import { GENEROUTED_ROOT, ROUTES_ROOT } from "@/routing/constants";
+
+/**
+ * Generouted's `generateRegularRoutes` / `generatePreservedRoutes` strip `/src/pages/`
+ * when building URL segments. Our views live under `/src/app/` (architecture convention),
+ * so we remap glob keys before passing them to the core.
+ *
+ * Ref: https://github.com/oedotme/generouted/tree/main/examples/react-router-custom-path
+ */
+export function remapAppToPages(files: Record): Record {
+ return Object.fromEntries(
+ Object.entries(files).map(([key, value]) => [
+ key.replace(`${ROUTES_ROOT}/`, `${GENEROUTED_ROOT}/`),
+ value,
+ ]),
+ );
+}
diff --git a/src/routing/route-module.tsx b/src/routing/route-module.tsx
new file mode 100644
index 00000000..479c8515
--- /dev/null
+++ b/src/routing/route-module.tsx
@@ -0,0 +1,45 @@
+import { ComponentType, Fragment, Suspense } from "react";
+import type { RouteObject } from "react-router";
+
+export type RouteModule = {
+ default?: ComponentType;
+ Pending?: ComponentType;
+ Catch?: ComponentType;
+ Loader?: RouteObject["loader"];
+ Action?: RouteObject["action"];
+};
+
+type ModuleLoader = () => Promise;
+
+export function createLazyRoute(
+ moduleLoader: ModuleLoader,
+ key: string,
+): Pick {
+ const index =
+ /index\.(jsx|tsx)$/.test(key) && !key.includes("pages/index") ? { index: true as const } : {};
+
+ return {
+ ...index,
+ lazy: async () => {
+ const loaded = await moduleLoader();
+ const PageComponent = loaded?.default ?? Fragment;
+ const Pending = loaded?.Pending;
+
+ const Page = () =>
+ Pending ? (
+ }>
+
+
+ ) : (
+
+ );
+
+ return {
+ Component: Page,
+ ErrorBoundary: loaded?.Catch,
+ loader: loaded?.Loader,
+ action: loaded?.Action,
+ };
+ },
+ };
+}
diff --git a/src/routing/router.tsx b/src/routing/router.tsx
new file mode 100644
index 00000000..5c1e19ef
--- /dev/null
+++ b/src/routing/router.tsx
@@ -0,0 +1,9 @@
+import { createBrowserRouter, RouterProvider } from "react-router";
+
+import { routes } from "@/routing/build-route-tree";
+
+const router = createBrowserRouter(routes);
+
+export function Routes() {
+ return ;
+}
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 00000000..4df1f98f
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1,10 @@
+///
+
+interface ImportMetaEnv {
+ readonly VITE_API_URL: string;
+ readonly VITE_API_TARGET: string;
+}
+
+interface ImportMeta {
+ readonly env: ImportMetaEnv;
+}