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 { Capture } from './tools/slatematch/Capture'
import { CaptureReview } from './screens/CaptureReview'
import { OpportunityDetail } from './tools/slatematch/OpportunityDetail'
import { Documents } from './tools/slatematch/Documents'
import { ScopeReview } from './tools/slatematch/ScopeReview'
import { BuildRequirements } from './tools/slatematch/BuildRequirements'
import { AmendmentView } from './tools/slatematch/AmendmentView'
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 { Mailbox } from './screens/Mailbox'
import { Bench } from './screens/Bench'
import { Duplicates } from './screens/Duplicates'
import { Careers } from './screens/Careers'
import { CandidateRecord } from './screens/CandidateRecord'
import { ManagerView } from './screens/ManagerView'
import { Login } from './screens/Login'
import { AcceptInvite } from './screens/AcceptInvite'
import { PublicCareersList, PublicPostingPage, PublicSchedulePage } from './screens/PublicCareers'
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 (
} />
{/* THE PUBLIC CAREERS PAGES SIT OUTSIDE THE GATE, beside the invite acceptance page and for the same
reason: a visitor has no session and never will. Mounting them here means no session check runs,
no authenticated query fires, and the recruiter shell is never constructed for somebody who has no
account. They talk to the public API routes directly with credentials omitted. */}
} />
} />
} />
} />
)
}
// 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 */}
} />
} />
} />
} />
{/* Phase 2 intake screens 3, 4 and 5, plus the amendment view. They nest under one opportunity
because every one of them is scoped to it. */}
} />
} />
} />
} />
{/* shared */}
} />
{/* The personal mailbox belongs to no tool, like settings and the access screen. A recruiter's own
mail connection is not a feature of a purchased product, and the server mounts it outside every
tool gate for exactly that reason. */}
} />
{/* The bench and the duplicate review belong to no tool either. Finding a person you already have,
and noticing you have them twice, are not features of a purchased product, and the server mounts
both outside every tool gate for that reason. */}
} />
} />
} />
{/* The tenant side of careers: what is published, what came back, and interview scheduling. It rides
the recruitMATCH entitlement because publishing a job order is recruiting operations, and the
server gates the publish route on requirement.write independently. */}
} />
} />
{/* 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}>
}