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 67 | 1x 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}</>;
}
|