Files
vexplor/frontend/components/auth/AuthGuard.tsx
2025-08-21 09:41:46 +09:00

120 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useEffect, ReactNode } from "react";
import { useRouter } from "next/navigation";
import { useAuth } from "@/hooks/useAuth";
import { LoadingSpinner } from "@/components/common/LoadingSpinner";
interface AuthGuardProps {
children: ReactNode;
requireAuth?: boolean;
requireAdmin?: boolean;
redirectTo?: string;
fallback?: ReactNode;
}
/**
* 인증 보호 컴포넌트
* 로그인 상태 및 권한에 따라 접근을 제어
*/
export function AuthGuard({
children,
requireAuth = true,
requireAdmin = false,
redirectTo = "/login",
fallback,
}: AuthGuardProps) {
const { isLoggedIn, isAdmin, loading, error } = useAuth();
const router = useRouter();
useEffect(() => {
if (loading) return;
// 인증이 필요한데 로그인되지 않은 경우
if (requireAuth && !isLoggedIn) {
router.push(redirectTo);
return;
}
// 관리자 권한이 필요한데 관리자가 아닌 경우
if (requireAdmin && !isAdmin) {
router.push("/dashboard"); // 또는 권한 없음 페이지
return;
}
}, [isLoggedIn, isAdmin, loading, requireAuth, requireAdmin, redirectTo, router]);
// 로딩 중
if (loading) {
return (
fallback || (
<div className="flex min-h-screen items-center justify-center">
<LoadingSpinner size="lg" text="인증 정보를 확인하고 있습니다..." />
</div>
)
);
}
// 에러 발생
if (error && requireAuth) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<div className="mb-2 text-red-500"></div>
<p className="mb-4 text-gray-600">{error}</p>
<button
onClick={() => router.push("/login")}
className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
>
</button>
</div>
</div>
);
}
// 인증 조건을 만족하지 않는 경우
if (requireAuth && !isLoggedIn) {
return fallback || null;
}
if (requireAdmin && !isAdmin) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="text-center">
<div className="mb-2 text-yellow-500">🔒</div>
<p className="mb-4 text-gray-600"> .</p>
<button
onClick={() => router.push("/dashboard")}
className="rounded bg-gray-500 px-4 py-2 text-white hover:bg-gray-600"
>
</button>
</div>
</div>
);
}
// 모든 조건을 만족하는 경우 자식 컴포넌트 렌더링
return <>{children}</>;
}
/**
* 로그인 여부만 확인하는 간단한 가드
*/
export function RequireAuth({ children }: { children: ReactNode }) {
return <AuthGuard requireAuth={true}>{children}</AuthGuard>;
}
/**
* 관리자 권한을 요구하는 가드
*/
export function RequireAdmin({ children }: { children: ReactNode }) {
return (
<AuthGuard requireAuth={true} requireAdmin={true}>
{children}
</AuthGuard>
);
}
export default AuthGuard;