import { Navigate, useLocation } from 'react-router-dom' import { useAuthStore } from '@/store/authStore' import { usePermissions, type EffectiveRole } from '@/hooks/usePermissions' import { Spinner } from '@/components/common/Spinner' interface ProtectedRouteProps { requiredRole?: EffectiveRole // Gate on account-management capability (owner OR account-admin OR super_admin), // mirroring backend require_account_owner_or_admin. Use instead of // requiredRole="owner" when account admins must also pass — the role hierarchy // has no 'admin' rung, so requiredRole alone wrongly bounces admins. requireAccountManager?: boolean children: React.ReactNode } export function ProtectedRoute({ requiredRole, requireAccountManager, children }: ProtectedRouteProps) { const { isAuthenticated, isLoading, user } = useAuthStore() const location = useLocation() const { effectiveRole, canManageAccount } = usePermissions() if (isLoading) { return (
) } if (!isAuthenticated) { return } // Enforce must_change_password — redirect unless already on /change-password if (user?.must_change_password && location.pathname !== '/change-password') { return } // L1 techs are confined to their focused surface. The sidebar only exposes // /l1*, /guides, and /account for them, so any other authed path (the engineer // dashboard at /home, /pilot, /trees/*, /escalations, …) bounces to /l1. This // also covers post-login landing: auth sends users to /home, which is not in // the allowlist, so l1_tech users end up on /l1. Engineer-only AI surfaces // (/pilot, /assistant) would 403 at POST /api/v1/ai-sessions anyway — this // turns that backend error into a clean redirect. Runs before the requiredRole // check so L1 users never trip the engineer-route role logic. if (effectiveRole === 'l1_tech') { const L1_ALLOWED_PREFIXES = ['/l1', '/guides', '/account', '/change-password'] const allowed = L1_ALLOWED_PREFIXES.some( (p) => location.pathname === p || location.pathname.startsWith(p + '/'), ) if (!allowed) { return } } if (requireAccountManager && !canManageAccount) { return } if (requiredRole) { const ROLE_HIERARCHY: Record = { super_admin: 5, owner: 4, engineer: 3, l1_tech: 2, viewer: 1, } if (ROLE_HIERARCHY[effectiveRole] < ROLE_HIERARCHY[requiredRole]) { return } } return <>{children} } export default ProtectedRoute