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) { // if just logged out, don't show another auth-required toast const justLoggedOut = sessionStorage.getItem("justLoggedOut") === "1"; if (!justLoggedOut) { toast.remove("auth-required"); toast.error("برای دسترسی به این صفحه باید وارد شوید", { id: "auth-required", }); } else { sessionStorage.removeItem("justLoggedOut"); } // 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.remove("session-expired"); toast.error("جلسه کاری شما منقضی شده است. لطفاً دوباره وارد شوید", { id: "session-expired", }); // 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.remove("not-found-login"); toast.error("صفحه مورد نظر یافت نشد. لطفاً وارد شوید", { id: "not-found-login", }); 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.remove("session-expired-soft"); toast.error("جلسه کاری شما منقضی شده است", { id: "session-expired-soft", }); 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, ]); // Note: periodic validation is handled in the block above and the auth context; avoid duplicating it here. // Show loading screen while checking authentication if (isLoading) { return (
در حال بررسی احراز هویت...