Spaces:
Sleeping
Sleeping
File size: 1,390 Bytes
e327f0d | 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 | 'use client';
import { useEffect } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { Spinner } from '@arac-hasar/ui';
import { useAuth } from '@/lib/auth-context';
interface AuthGuardProps {
children: React.ReactNode;
/** Require admin role. */
requireAdmin?: boolean;
fallback?: React.ReactNode;
}
/**
* Client-side guard for protected pages. Middleware already redirects
* unauthenticated users at the edge — this is the in-app belt + suspenders
* for hydration / SPA navigation cases.
*/
export function AuthGuard({
children,
requireAdmin = false,
fallback,
}: AuthGuardProps) {
const router = useRouter();
const pathname = usePathname();
const { isAuthenticated, isAdmin, loading } = useAuth();
useEffect(() => {
if (loading) return;
if (!isAuthenticated) {
const next = encodeURIComponent(pathname ?? '/');
router.replace(`/login?next=${next}`);
return;
}
if (requireAdmin && !isAdmin) {
router.replace('/dashboard?error=forbidden');
}
}, [loading, isAuthenticated, isAdmin, requireAdmin, router, pathname]);
if (loading || !isAuthenticated || (requireAdmin && !isAdmin)) {
return (
fallback ?? (
<div className="flex min-h-[40vh] items-center justify-center">
<Spinner size="lg" />
</div>
)
);
}
return <>{children}</>;
}
|