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}>
}