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