591b5df250
T-023: Initialize React project - Vite + React 19 + TypeScript scaffold - Tailwind CSS v4 with @tailwindcss/vite plugin - Dependencies: react-router-dom, axios, @tanstack/react-query, lucide-react - Project structure: api/, components/, pages/, context/, types/, hooks/, lib/ T-024: API client and auth context - Axios client with JWT interceptor (auto-attach token, clear on 401) - login() and getMe() API functions - AuthContext: user state, login, logout, isAuthenticated, isLoading - Token persistence via localStorage with hydration on mount - TypeScript types for all backend models T-025: Login page and layout - LoginPage with form, error handling, redirect on success - Layout with sidebar + header + Outlet - Sidebar with role-aware navigation (System only for admin) - ProtectedRoute wrapper with role-based access control - Routes: /login, /dashboard, /techniques, /tests, /system
34 lines
926 B
TypeScript
34 lines
926 B
TypeScript
import { Navigate } from "react-router-dom";
|
|
import { useAuth } from "../context/AuthContext";
|
|
|
|
interface Props {
|
|
children: React.ReactNode;
|
|
roles?: string[];
|
|
}
|
|
|
|
/**
|
|
* Wrapper that redirects to `/login` when the user is not authenticated.
|
|
* Optionally restricts access to specific roles (admins always pass).
|
|
*/
|
|
export default function ProtectedRoute({ children, roles }: Props) {
|
|
const { isAuthenticated, isLoading, user } = useAuth();
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex h-screen items-center justify-center bg-gray-950">
|
|
<div className="h-8 w-8 animate-spin rounded-full border-4 border-cyan-500 border-t-transparent" />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (!isAuthenticated) {
|
|
return <Navigate to="/login" replace />;
|
|
}
|
|
|
|
if (roles && user && !roles.includes(user.role) && user.role !== "admin") {
|
|
return <Navigate to="/dashboard" replace />;
|
|
}
|
|
|
|
return <>{children}</>;
|
|
}
|