frigate/web/src/components/auth/ProtectedRoute.tsx
Josh Hawkins 889dfca36c
Frontend fixes (#22309)
* prevent unnecessary reloads in useUserPersistence hook

* always render ProtectedRoute (handling undefined roles internally) and add Suspense fallback

* add missing i18n namespaces

react 19 enforces Suspense more strictly, so components using useTranslation() with unloaded namespaces would suspend, blanking the content behind the empty Suspense fallback

* add missing namespace

* remove unneeded

* remove modal from actions dropdown
2026-03-07 06:43:00 -07:00

76 lines
2.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useContext, useEffect } from "react";
import { Navigate, Outlet } from "react-router-dom";
import { AuthContext } from "@/context/auth-context";
import ActivityIndicator from "../indicators/activity-indicator";
import {
isRedirectingToLogin,
setRedirectingToLogin,
} from "@/api/auth-redirect";
export default function ProtectedRoute({
requiredRoles,
}: {
requiredRoles?: string[];
}) {
const { auth } = useContext(AuthContext);
// Redirect to login page when not authenticated
// don't use <Navigate> because we need a full page load to reset state
useEffect(() => {
if (
!auth.isLoading &&
auth.isAuthenticated &&
!auth.user &&
!isRedirectingToLogin()
) {
setRedirectingToLogin(true);
window.location.href = "/login";
}
}, [auth.isLoading, auth.isAuthenticated, auth.user]);
// Show loading indicator during redirect to prevent React from attempting to render
// lazy components, which would cause error #426 (suspension during synchronous navigation)
if (isRedirectingToLogin()) {
return (
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
);
}
// Wait for config to provide required roles
if (!requiredRoles) {
return (
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
);
}
if (auth.isLoading) {
return (
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
);
}
// Unauthenticated mode
if (!auth.isAuthenticated) {
return <Outlet />;
}
// Authenticated mode (external port): require login
if (!auth.user) {
return (
<ActivityIndicator className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />
);
}
// If role is null (shouldnt happen if isAuthenticated, but type safety), fallback
// though isAuthenticated should catch this
if (auth.user.role === null) {
return <Outlet />;
}
if (!requiredRoles.includes(auth.user.role)) {
return <Navigate to="/unauthorized" replace />;
}
return <Outlet />;
}