import React, { useEffect } from "react"; import { useAuth } from "~/contexts/auth-context"; import { useLocation, useNavigate } from "react-router"; import toast from "react-hot-toast"; interface GlobalRouteGuardProps { children: React.ReactNode; } // Define protected routes that require authentication const PROTECTED_ROUTES = [ "/dashboard", "/dashboard/projects", "/dashboard/teams", "/dashboard/reports", "/dashboard/settings", "/profile", "/settings", "/admin", ]; // Define public routes that don't require authentication const PUBLIC_ROUTES = [ "/", "/login", "/forgot-password", "/reset-password", "/404", "/unauthorized", ]; // Define routes that authenticated users shouldn't access const AUTH_RESTRICTED_ROUTES = [ "/login", "/forgot-password", "/reset-password", ]; // Define exact routes (for root path handling) const EXACT_ROUTES = ["/", "/login", "/dashboard", "/404", "/unauthorized"]; export function GlobalRouteGuard({ children }: GlobalRouteGuardProps) { const { isAuthenticated, isLoading, token, user, validateToken } = useAuth(); const location = useLocation(); const navigate = useNavigate(); useEffect(() => { // Don't do anything while authentication is loading if (isLoading) return; const currentPath = location.pathname; // Check if current route is protected const isProtectedRoute = PROTECTED_ROUTES.some( (route) => currentPath === route || currentPath.startsWith(route + "/"), ); // Check if current route is auth-restricted (like login page) const isAuthRestrictedRoute = AUTH_RESTRICTED_ROUTES.some( (route) => currentPath === route || currentPath.startsWith(route + "/"), ); // Check if current route is a known public route const isPublicRoute = PUBLIC_ROUTES.some( (route) => currentPath === route || currentPath.startsWith(route + "/"), ); // Check if it's an exact route match const isExactRoute = EXACT_ROUTES.includes(currentPath); // Case 1: User accessing protected route without authentication if (isProtectedRoute && !isAuthenticated) { toast.error("برای دسترسی به این صفحه باید وارد شوید"); // Save the intended destination for after login const returnTo = encodeURIComponent(currentPath + location.search); navigate(`/login?returnTo=${returnTo}`, { replace: true }); return; } // Case 2: User accessing protected route with expired/invalid token if (isProtectedRoute && isAuthenticated && (!token || !token.accessToken)) { toast.error("جلسه کاری شما منقضی شده است. لطفاً دوباره وارد شوید"); // Clear invalid auth data localStorage.removeItem("auth_user"); localStorage.removeItem("auth_token"); navigate("/login", { replace: true }); return; } // Case 3: Authenticated user trying to access auth-restricted routes if (isAuthRestrictedRoute && isAuthenticated && token?.accessToken) { // Get return URL from query params, default to dashboard const searchParams = new URLSearchParams(location.search); const returnTo = searchParams.get("returnTo"); const redirectPath = returnTo && returnTo !== "/login" ? returnTo : "/dashboard"; navigate(redirectPath, { replace: true }); return; } // Case 4: Handle root path redirection if (currentPath === "/") { if (isAuthenticated && token?.accessToken) { navigate("/dashboard", { replace: true }); } else { navigate("/login", { replace: true }); } return; } // Case 5: Unknown/404 routes const isKnownRoute = isProtectedRoute || isPublicRoute || isExactRoute; if ( !isKnownRoute && !currentPath.includes("/404") && !currentPath.includes("/unauthorized") ) { // If user is authenticated, show authenticated 404 if (isAuthenticated && token?.accessToken) { navigate("/404", { replace: true }); } else { // If user is not authenticated, redirect to login toast.error("صفحه مورد نظر یافت نشد. لطفاً وارد شوید"); navigate("/login", { replace: true }); } return; } // Case 6: Validate token for protected routes periodically if (isProtectedRoute && isAuthenticated && token?.accessToken) { const checkTokenValidity = async () => { try { const isValid = await validateToken(); if (!isValid) { toast.error("جلسه کاری شما منقضی شده است"); navigate("/unauthorized?reason=token-expired", { replace: true }); } } catch (error) { console.error("Token validation failed:", error); navigate("/unauthorized?reason=token-expired", { replace: true }); } }; // Only check token validity every 30 seconds to avoid excessive API calls const now = Date.now(); const lastCheck = parseInt( sessionStorage.getItem("lastTokenCheck") || "0", ); if (now - lastCheck > 30000) { // 30 seconds sessionStorage.setItem("lastTokenCheck", now.toString()); checkTokenValidity(); } } }, [ isLoading, isAuthenticated, token, user, location.pathname, location.search, navigate, ]); // Validate token periodically for authenticated users useEffect(() => { if (!isAuthenticated || !token?.accessToken) return; const validateTokenPeriodically = async () => { try { const isValid = await validateToken(); if (!isValid) { toast.error("جلسه کاری شما منقضی شده است. لطفاً دوباره وارد شوید"); navigate("/login", { replace: true }); } } catch (error) { console.error("Token validation error:", error); } }; // Validate token immediately validateTokenPeriodically(); // Set up periodic validation (every 5 minutes) const interval = setInterval(validateTokenPeriodically, 5 * 60 * 1000); return () => clearInterval(interval); }, [isAuthenticated, token, validateToken, navigate]); // Show loading screen while checking authentication if (isLoading) { return (
در حال بررسی احراز هویت...