import { createEffect, createSignal, createMemo, JSXElement } from "solid-js"; import html from "solid-js/html"; import { useAuth } from "./AuthContext.ts"; import { getToken } from "./useAuthFetch.js"; import { hasPermission } from "./permissions.ts"; import { Loader } from "../kit/General.tsx"; interface ProtectedRouteProps { permissions?: string[]; children?: JSXElement; } export function ProtectedRoute(props: ProtectedRouteProps) { const auth = useAuth(); const [isRedirecting, setIsRedirecting] = createSignal(false); createEffect(() => { // Redirect to login ONLY when there is genuinely no session. A real // 401 clears the token (so getToken() is null here), but a transient // check failure preserves it -- in that case we keep the user here and // show a retry screen instead of logging them out. -mta 6/3/26 if (!auth.loading() && !auth.isAuthenticated() && !getToken() && !isRedirecting()) { setIsRedirecting(true); const returnUrl = encodeURIComponent(window.location.pathname + window.location.search); window.location.replace(`/login?redirect=${returnUrl}`); } }); const hasRequiredPermissions = createMemo(() => { const permissions = props.permissions || []; if (permissions.length === 0) return true; const userPermissions = auth.identity()?.permissions || []; return permissions.every((perm) => hasPermission(userPermissions, perm)); }); const state = createMemo(() => { if (isRedirecting() || auth.loading()) return 'loading'; if (!auth.isAuthenticated()) { // Token still present => the check failed transiently => offer a // retry. No token => the redirect effect is taking over. return getToken() ? 'error' : 'loading'; } if (!hasRequiredPermissions()) return 'unauthorized'; return 'ready'; }); return html`
We couldn't verify your session. You're still signed in -- please try again.