import { useMemo } from 'react'; import { Navigate, useLocation } from 'react-router-dom'; import { useAtom } from 'jotai'; import { backendUserAtom, isAuthenticatedAtom, authInitialCheckDoneAtom, guestSessionAtom, guestSessionHydratedAtom, } from '@/atoms/auth'; import { hasSessionToken } from '@/services/backend'; import { ROUTES } from '@/utils/constants'; interface ProtectedRouteProps { children: React.ReactNode; /** Allow access while name/age are still missing (profile completion page). */ allowIncompleteProfile?: boolean; } const AuthBootSpinner = () => (
); export function ProtectedRoute({ children, allowIncompleteProfile = false }: ProtectedRouteProps) { const [isAuthenticated] = useAtom(isAuthenticatedAtom); const [backendUser] = useAtom(backendUserAtom); const [authInitialCheckDone] = useAtom(authInitialCheckDoneAtom); const [guestSession] = useAtom(guestSessionAtom); const [guestSessionHydrated] = useAtom(guestSessionHydratedAtom); const location = useLocation(); // Stabilize Navigate `state` — a new object every render re-triggers Navigate's // useEffect (deps include `state`) and can blow the update depth limit. const loginRedirectState = useMemo( () => ({ from: { pathname: location.pathname } }), [location.pathname], ); const hasToken = hasSessionToken(); const canUseBackendUser = Boolean(backendUser && hasToken); if (!authInitialCheckDone) { return ; } if (!guestSessionHydrated) { return ; } if (!isAuthenticated && !guestSession) { return ; } // Incomplete-profile gate only when a live session exists. Stale backendUser // without a token must not bounce / ↔ /profile forever. if (!allowIncompleteProfile && canUseBackendUser && !backendUser!.profileComplete) { return ; } return
{children}
; }