Routing & Layout
React Router v7 route definitions and the application shell layout for the MetaForge Digital Twin Dashboard. Every page is lazy-loaded for optimal bundle splitting.
src/routes/index.tsx
Central route table. Each page import is wrapped with React.lazy and rendered inside a shared Suspense boundary with a skeleton fallback.
import { lazy, Suspense } from "react";
import {
createBrowserRouter,
RouterProvider,
type RouteObject,
} from "react-router";
import { Layout } from "@/routes/layout";
import { PageSkeleton } from "@/components/ui/page-skeleton";
// ---------------------------------------------------------------------------
// Lazy page imports
// ---------------------------------------------------------------------------
const DashboardPage = lazy(() => import("@/pages/dashboard"));
const SessionsPage = lazy(() => import("@/pages/sessions"));
const SessionDetailPage = lazy(() => import("@/pages/session-detail"));
const AgentsPage = lazy(() => import("@/pages/agents"));
const DigitalTwinPage = lazy(() => import("@/pages/digital-twin"));
const BOMPage = lazy(() => import("@/pages/bom"));
const CompliancePage = lazy(() => import("@/pages/compliance"));
const TestingPage = lazy(() => import("@/pages/testing"));
const SupplyChainPage = lazy(() => import("@/pages/supply-chain"));
const ApprovalsPage = lazy(() => import("@/pages/approvals"));
const SettingsPage = lazy(() => import("@/pages/settings"));
// ---------------------------------------------------------------------------
// Suspense wrapper
// ---------------------------------------------------------------------------
function Suspended({ children }: { children: React.ReactNode }) {
return <Suspense fallback={<PageSkeleton />}>{children}</Suspense>;
}
// ---------------------------------------------------------------------------
// Route definitions
// ---------------------------------------------------------------------------
const routes: RouteObject[] = [
{
element: <Layout />,
children: [
{
index: true,
element: (
<Suspended>
<DashboardPage />
</Suspended>
),
},
{
path: "sessions",
element: (
<Suspended>
<SessionsPage />
</Suspended>
),
},
{
path: "sessions/:id",
element: (
<Suspended>
<SessionDetailPage />
</Suspended>
),
},
{
path: "agents",
element: (
<Suspended>
<AgentsPage />
</Suspended>
),
},
{
path: "digital-twin",
element: (
<Suspended>
<DigitalTwinPage />
</Suspended>
),
},
{
path: "bom",
element: (
<Suspended>
<BOMPage />
</Suspended>
),
},
{
path: "compliance",
element: (
<Suspended>
<CompliancePage />
</Suspended>
),
},
{
path: "testing",
element: (
<Suspended>
<TestingPage />
</Suspended>
),
},
{
path: "supply-chain",
element: (
<Suspended>
<SupplyChainPage />
</Suspended>
),
},
{
path: "approvals",
element: (
<Suspended>
<ApprovalsPage />
</Suspended>
),
},
{
path: "settings",
element: (
<Suspended>
<SettingsPage />
</Suspended>
),
},
],
},
];
// ---------------------------------------------------------------------------
// Router instance & provider
// ---------------------------------------------------------------------------
const router = createBrowserRouter(routes);
export function AppRouter() {
return <RouterProvider router={router} />;
}
src/routes/layout.tsx
Application shell that provides the sidebar, topbar, breadcrumbs, and main content outlet. Responsive: sidebar collapses to an icon rail on narrow viewports and becomes a slide-out sheet on mobile.
import { Outlet, useLocation, Link } from "react-router";
import { useMediaQuery } from "@/hooks/use-media-query";
import { useUIStore } from "@/store/ui-store";
import { Sidebar } from "@/components/sidebar";
import { Topbar } from "@/components/topbar";
import { Sheet, SheetContent } from "@/components/ui/sheet";
import { cn } from "@/lib/utils";
// ---------------------------------------------------------------------------
// Breadcrumb helpers
// ---------------------------------------------------------------------------
interface Crumb {
label: string;
href: string;
}
const ROUTE_LABELS: Record<string, string> = {
"": "Dashboard",
sessions: "Sessions",
agents: "Agents",
"digital-twin": "Digital Twin",
bom: "Bill of Materials",
compliance: "Compliance",
testing: "Testing",
"supply-chain": "Supply Chain",
approvals: "Approvals",
settings: "Settings",
};
function useBreadcrumbs(): Crumb[] {
const { pathname } = useLocation();
const segments = pathname.split("/").filter(Boolean);
const crumbs: Crumb[] = [{ label: "Dashboard", href: "/" }];
let path = "";
for (const segment of segments) {
path += `/${segment}`;
const label = ROUTE_LABELS[segment] ?? segment;
crumbs.push({ label, href: path });
}
return crumbs;
}
// ---------------------------------------------------------------------------
// Breadcrumbs component
// ---------------------------------------------------------------------------
function Breadcrumbs() {
const crumbs = useBreadcrumbs();
return (
<nav aria-label="Breadcrumb" className="flex items-center gap-1.5 text-sm text-muted-foreground px-6 py-2 border-b">
{crumbs.map((crumb, index) => {
const isLast = index === crumbs.length - 1;
return (
<span key={crumb.href} className="flex items-center gap-1.5">
{index > 0 && (
<span aria-hidden="true" className="text-muted-foreground/50">
/
</span>
)}
{isLast ? (
<span className="font-medium text-foreground">{crumb.label}</span>
) : (
<Link
to={crumb.href}
className="hover:text-foreground transition-colors"
>
{crumb.label}
</Link>
)}
</span>
);
})}
</nav>
);
}
// ---------------------------------------------------------------------------
// Layout
// ---------------------------------------------------------------------------
export function Layout() {
const sidebarOpen = useUIStore((s) => s.sidebarOpen);
const sidebarCollapsed = useUIStore((s) => s.sidebarCollapsed);
const toggleSidebar = useUIStore((s) => s.toggleSidebar);
const isMobile = useMediaQuery("(max-width: 768px)");
return (
<div className="flex h-screen overflow-hidden bg-background text-foreground">
{/* ---- Desktop sidebar ---- */}
{!isMobile && (
<aside
className={cn(
"hidden md:flex flex-col border-r bg-sidebar transition-[width] duration-200",
sidebarCollapsed ? "w-16" : "w-64",
)}
>
<Sidebar collapsed={sidebarCollapsed} />
</aside>
)}
{/* ---- Mobile sidebar (sheet / drawer) ---- */}
{isMobile && (
<Sheet open={sidebarOpen} onOpenChange={toggleSidebar}>
<SheetContent side="left" className="w-72 p-0">
<Sidebar collapsed={false} />
</SheetContent>
</Sheet>
)}
{/* ---- Main column ---- */}
<div className="flex flex-1 flex-col overflow-hidden">
<Topbar />
<Breadcrumbs />
<main className="flex-1 overflow-y-auto p-6">
<Outlet />
</main>
</div>
</div>
);
}
src/components/ui/page-skeleton.tsx
Full-page loading skeleton shown while lazy-loaded pages are being fetched.
import { Skeleton } from "@/components/ui/skeleton";
/**
* Placeholder skeleton that mirrors a typical page layout.
* Used as the Suspense fallback for lazy-loaded routes.
*/
export function PageSkeleton() {
return (
<div className="space-y-6 animate-in fade-in duration-300">
{/* Page header */}
<div className="space-y-2">
<Skeleton className="h-8 w-48" />
<Skeleton className="h-4 w-96" />
</div>
{/* Stat cards row */}
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
{Array.from({ length: 4 }).map((_, i) => (
<Skeleton key={i} className="h-28 rounded-xl" />
))}
</div>
{/* Main content area */}
<Skeleton className="h-96 rounded-xl" />
</div>
);
}
src/hooks/use-media-query.ts
Utility hook used by the layout to detect viewport breakpoints.
import { useEffect, useState } from "react";
/**
* Subscribe to a CSS media query and return whether it currently matches.
*
* @param query - A valid CSS media query string, e.g. "(max-width: 768px)".
*/
export function useMediaQuery(query: string): boolean {
const [matches, setMatches] = useState(() => {
if (typeof window === "undefined") return false;
return window.matchMedia(query).matches;
});
useEffect(() => {
const mql = window.matchMedia(query);
function onChange(event: MediaQueryListEvent) {
setMatches(event.matches);
}
mql.addEventListener("change", onChange);
// Sync in case the value changed between render and effect.
setMatches(mql.matches);
return () => mql.removeEventListener("change", onChange);
}, [query]);
return matches;
}