import { useEffect, useState, type ComponentType, type CSSProperties } from 'react' import { NavLink, Outlet, useLocation } from 'react-router-dom' import { BrainCircuit, Briefcase, Users2, FileDown, TrendingUp, CheckSquare, Building2, ClipboardList, Crosshair, Gauge, Globe, Inbox, Layers, LogOut, Menu, X, Radar, Scale, Search, Settings as SettingsIcon, ShieldCheck, Users, } from 'lucide-react' import { useAuth } from '../lib/useAuth' import { hasTool } from '../lib/can' import { CAP, can } from '../lib/can' import { orgLogoURL } from '../lib/org' import { useOrgBranding } from '../lib/orgQueries' type Icon = ComponentType<{ size?: number; className?: string; style?: CSSProperties }> type NavItem = { to: string; label: string; icon: Icon } type Tool = { key: string // tool is the server side entitlement name this group belongs to. The navigation is filtered by it, so // a user without the grant never sees the group. The API refuses independently. tool: string name: string tagline: string accent: string basePath: string icon: Icon match: (path: string) => boolean nav: NavItem[] } // The three tools under the missionMATCH umbrella. The switcher routes between them; each owns its // accent and its own nav. missionMATCH (gold) is the home tool and the default after login. const TOOLS: Tool[] = [ { key: 'mission', tool: 'missionmatch', name: 'missionMATCH', tagline: 'Command Center', accent: 'var(--color-gold)', basePath: '/command', icon: Radar, match: (p) => p === '/' || p.startsWith('/command') || p.startsWith('/compliance'), nav: [ { to: '/command', label: 'Command Center', icon: Radar }, { to: '/command/candidates', label: 'Candidates', icon: Users }, { to: '/command/ask', label: 'Ask the Brain', icon: BrainCircuit }, { to: '/compliance', label: 'Compliance', icon: Scale }, ], }, { key: 'screen', tool: 'screenmatch', name: 'screenMATCH', tagline: 'Qualification', accent: 'var(--color-screen)', basePath: '/screen', icon: Crosshair, match: (p) => p.startsWith('/screen') || p.startsWith('/requirements') || p.startsWith('/evaluate'), nav: [ { to: '/screen', label: 'Screen Candidate', icon: Crosshair }, { to: '/requirements', label: 'Opportunities', icon: ClipboardList }, ], }, { key: 'slate', tool: 'slatematch', name: 'slateMATCH', tagline: 'Proposal Slates', accent: 'var(--color-slate-accent)', basePath: '/slate', icon: Layers, match: (p) => p.startsWith('/slate'), nav: [ { to: '/slate', label: 'Candidate Slate', icon: Layers }, { to: '/slate/capture', label: 'Capture', icon: Crosshair }, { to: '/slate/review', label: 'Capture review', icon: Crosshair }, ], }, { key: 'recruit', tool: 'recruitmatch', name: 'recruitMATCH', tagline: 'Recruiting Operations', accent: 'var(--color-recruit)', basePath: '/recruit', icon: ClipboardList, match: (p) => p.startsWith('/recruit'), nav: [ { to: '/recruit', label: 'Dashboard', icon: ClipboardList }, { to: '/recruit/clients', label: 'Clients', icon: Building2 }, { to: '/recruit/requisitions', label: 'Job orders', icon: Briefcase }, { to: '/recruit/careers', label: 'Careers and applicants', icon: Globe }, { to: '/recruit/tasks', label: 'My work', icon: CheckSquare }, { to: '/recruit/manager', label: 'My desks', icon: Gauge }, { to: '/recruit/analytics', label: 'Analytics', icon: TrendingUp }, { to: '/recruit/reports', label: 'Reports', icon: FileDown }, ], }, ] function activeTool(path: string): Tool { return TOOLS.find((t) => t.match(path)) ?? TOOLS[0] } // Shared and legacy routes belong to no tool; give them their own header title so it never reads the // fallback tool's first nav label (for example the Settings page must not show "Command Center"). const SHARED_TITLES: Record = { '/settings': 'Settings', '/admin': 'Accounts and access', '/people': 'Accounts and access', '/mailbox': 'Your mailbox', '/bench': 'Find people', '/bench/duplicates': 'Possible duplicates', } function isSharedPath(path: string): boolean { return path in SHARED_TITLES } function headerTitle(tool: Tool, path: string): string { if (SHARED_TITLES[path]) return SHARED_TITLES[path] const item = tool.nav.find((n) => (path === n.to ? true : path.startsWith(n.to) && n.to !== tool.basePath)) return item?.label ?? tool.nav[0]?.label ?? tool.name } export function AppShell() { const { pathname } = useLocation() // THE SIDEBAR IS A DRAWER BELOW THE LARGE BREAKPOINT. // // The shell was a fixed two column grid, 248 pixels of navigation and then the content. On a 390 pixel // phone that is sixty three percent of the screen spent on the menu, and the content ran off the right // edge: a visual sweep found HORIZONTAL OVERFLOW on all twenty routes at that width, and a screenshot shows // numbers and headings clipped mid word. The product was unusable on a phone, which is exactly where a // recruiter checks who is rolling off between meetings. const [navOpen, setNavOpen] = useState(false) // Navigating closes it. Without this, tapping a link on a phone leaves the drawer covering the screen it // just opened. useEffect(() => setNavOpen(false), [pathname]) const { principal, logout } = useAuth() const tool = activeTool(pathname) // On a shared or legacy route no tool is truly active, so no tool chip lights and the header stays // neutral rather than borrowing the fallback tool's identity. const shared = isSharedPath(pathname) // Only the tools this user was granted. Filtered from the same list the switcher and the header read, // so an unentitled tool cannot appear in one and not the other. const visibleTools = TOOLS.filter((t) => hasTool(principal, t.tool)) const branding = useOrgBranding() const orgLogo = orgLogoURL(branding.data) const orgName = branding.data?.display_name || branding.data?.name || "" return (
{/* The scrim. It only exists while the drawer is open and only below the breakpoint, and tapping it closes the drawer, which is the gesture everybody already expects. */} {navOpen && (

{headerTitle(tool, pathname)}

{shared ? 'missionMATCH' : `${tool.name} · ${tool.tagline}`}

{/* The compliance badge is the first thing to go on a narrow screen: it is reassurance, not a control, and the sign out button behind it is not. */} FIPS 140-3 • RLS isolated {/* The signed in identity, linked to the screen that edits it. The name is here because a rename has to be VISIBLE to be believed: the header is the one place a person looks at on every screen, so it is where the profile edit proves it propagated. */} {principal && ( {principal.full_name || 'Your profile'} {principal.role} )}
) }