All files / src/components/auth ProtectedRoute.tsx

100% Statements 49/49
100% Branches 19/19
100% Functions 1/1
100% Lines 49/49

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 671x 1x 1x 1x 1x           16x 16x 16x 16x   16x 12x   11x   12x 2x 2x 2x 2x     12x 2x 2x 2x 1x     1x 2x 2x   12x 5x 5x 5x 3x 5x 2x 2x 2x 2x 5x 5x 16x   16x 1x 1x 1x 1x   1x   16x 11x 11x   4x 4x  
import { useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { useAuthStore } from "../../stores/authStore";
import { syncUser } from "../../api/users";
import { isTokenExpiringSoon, refreshSession } from "../../api/auth";
 
interface ProtectedRouteProps {
  children: React.ReactNode;
}
 
export default function ProtectedRoute({ children }: ProtectedRouteProps) {
  const { isAuthenticated, hasHydrated, user, setUser, logout } =
    useAuthStore();
  const navigate = useNavigate();
 
  useEffect(() => {
    if (!hasHydrated) return;
 
    const token = localStorage.getItem("access_token");
 
    if (!token) {
      logout();
      navigate("/login", { replace: true });
      return;
    }
 
    // Proactive refresh on route entry if token is expiring soon
    if (isTokenExpiringSoon() && !user) {
      (async () => {
        try {
          await refreshSession();
        } catch {
          // Fall through to syncUser / logout below
          void 0;
        }
      })();
    }
 
    if (!user) {
      (async () => {
        try {
          const syncedUser = await syncUser();
          setUser({ id: syncedUser.id, email: syncedUser.email });
        } catch (err) {
          console.error("Auth sync failed:", err);
          logout();
          navigate("/login", { replace: true });
        }
      })();
    }
  }, [hasHydrated, user, navigate, setUser, logout]);
 
  if (!hasHydrated) {
    return (
      <div className="min-h-screen flex items-center justify-center">
        <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" />
      </div>
    );
  }
 
  if (!isAuthenticated) {
    return null;
  }
 
  return <>{children}</>;
}