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 children: React.ReactNode } export function ProtectedRoute({ requiredRole, children }: ProtectedRouteProps) { const { isAuthenticated, isLoading, user } = useAuthStore() const location = useLocation() const { effectiveRole } = 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 } 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 } } // L1 users landing on / (e.g. post-login) get redirected to their workspace. // Does not fire when already on /l1 or any other path, preventing loops. if (effectiveRole === 'l1_tech' && location.pathname === '/') { return } // L1 users hitting engineer-only AI surfaces (Pilot / Assistant) get pushed // back to /l1 — POST /api/v1/ai-sessions rejects them with 403 anyway, so // this just turns a backend error into a clean route-level redirect. if ( effectiveRole === 'l1_tech' && (location.pathname.startsWith('/pilot') || location.pathname.startsWith('/assistant')) ) { return } return <>{children} } export default ProtectedRoute