import type { ReactNode } from 'react' import { Loader2 } from 'lucide-react' import { Navigate, Route, Routes } from 'react-router-dom' import { AppShell } from './components/AppShell' import { ScreeningQueue } from './tools/screenmatch/ScreeningQueue' import { CandidateEvaluation } from './tools/screenmatch/CandidateEvaluation' import { ScreenCompare } from './tools/screenmatch/ScreenCompare' import { AliasSettings } from './tools/screenmatch/AliasSettings' import { Requirements } from './screens/Requirements' import { Candidates } from './tools/missionmatch/Candidates' import { RecruiterDashboard } from './tools/recruitmatch/RecruiterDashboard' import { ClientDetail } from './tools/recruitmatch/ClientDetail' import { Clients } from './tools/recruitmatch/Clients' import { RequisitionDetail } from './tools/recruitmatch/RequisitionDetail' import { Pipeline } from './tools/recruitmatch/Pipeline' import { Assignment } from './tools/recruitmatch/Assignment' import { Analytics } from './tools/recruitmatch/Analytics' import { Reports } from './tools/recruitmatch/Reports' import { Tasks } from './tools/recruitmatch/Tasks' import { Requisitions } from './tools/recruitmatch/Requisitions' import { Slate } from './tools/slatematch/Slate' import { CommandCenter } from './tools/missionmatch/CommandCenter' import { Ask } from './tools/missionmatch/Ask' import { Compliance } from './screens/Compliance' import { NotFound } from './screens/NotFound' import { Admin } from './screens/Admin' import { Settings } from './screens/Settings' import { Login } from './screens/Login' import { AcceptInvite } from './screens/AcceptInvite' import { AuthProvider } from './lib/AuthContext' import { useAuth } from './lib/useAuth' import { homePath, toolForPath } from './lib/homeTool' import { hasTool } from './lib/can' import { useLocation } from 'react-router-dom' export default function App() { return ( } /> } /> ) } // Gate renders the app only for a validated session; otherwise the login. The session check runs once on // load, so a reload never flashes the app before auth resolves. function Gate() { const { principal, loading } = useAuth() if (loading) { return (
) } if (!principal) { return } return ( }> {/* The landing route follows ENTITLEMENT, not a hard coded tool. It was an unconditional redirect to /command, which is missionMATCH, so a user without that grant was sent to a tool the navigation hides from them and met a permanent error with a retry that could never succeed. */} } /> {/* missionMATCH */} } /> } /> } /> } /> {/* screenMATCH */} } /> } /> } /> } /> } /> {/* recruitMATCH */} } /> } /> } /> } /> } /> } /> } /> } /> } /> } /> {/* slateMATCH */} } /> {/* shared */} } /> {/* One access surface. /people is kept as an alias so existing links and bookmarks keep working after the rename rather than 404ing into the NotFound screen. */} } /> } /> {/* Last, so it matches only when nothing else did. Without it a mistyped address rendered the shell with an empty outlet, which reads as a crash rather than a wrong URL. */} } /> {/* Legacy paths kept so existing links and deep links keep resolving after the tool split. /pipeline (a placeholder) and /dashboard (the pre split dashboard) used to live here too. No link in the app reached either one, and the real pipeline is recruitMATCH's /recruit/requisitions/:reqId/pipeline, so both were removed rather than left as screens only a typed URL could find. */} } /> } /> } /> ) } // ToolGuard redirects a route belonging to a tool the user was not granted back to their own home tool. // // The route table registers every tool for every authenticated user, and cross tool links exist (a // candidate card links into screenMATCH to evaluate), so without this a user can be walked by a BUTTON // into a tool whose every request the API refuses. The server side gate is the enforcement; this only // stops the interface offering a journey that ends in an error panel. function ToolGuard({ children }: { children: ReactNode }) { const { principal } = useAuth() const { pathname } = useLocation() const needed = toolForPath(pathname) if (needed && !hasTool(principal, needed)) { return } return <>{children} }