2025-08-21 09:41:46 +09:00
|
|
|
"use client";
|
|
|
|
|
|
2025-08-21 13:28:49 +09:00
|
|
|
import { useEffect, ReactNode, useState } from "react";
|
2025-08-21 09:41:46 +09:00
|
|
|
import { useRouter } from "next/navigation";
|
|
|
|
|
import { useAuth } from "@/hooks/useAuth";
|
|
|
|
|
|
|
|
|
|
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();
|
2025-08-21 13:28:49 +09:00
|
|
|
const [redirectCountdown, setRedirectCountdown] = useState<number | null>(null);
|
|
|
|
|
const [authDebugInfo, setAuthDebugInfo] = useState<any>({});
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
2025-08-21 13:28:49 +09:00
|
|
|
console.log("=== AuthGuard 디버깅 ===");
|
|
|
|
|
console.log("requireAuth:", requireAuth);
|
|
|
|
|
console.log("requireAdmin:", requireAdmin);
|
|
|
|
|
console.log("loading:", loading);
|
|
|
|
|
console.log("isLoggedIn:", isLoggedIn);
|
|
|
|
|
console.log("isAdmin:", isAdmin);
|
|
|
|
|
console.log("error:", error);
|
|
|
|
|
|
|
|
|
|
// 토큰 확인을 더 정확하게
|
|
|
|
|
const token = localStorage.getItem("authToken");
|
|
|
|
|
console.log("AuthGuard localStorage 토큰:", token ? "존재" : "없음");
|
|
|
|
|
console.log("현재 경로:", window.location.pathname);
|
|
|
|
|
|
|
|
|
|
// 디버깅 정보 수집
|
|
|
|
|
setAuthDebugInfo({
|
|
|
|
|
requireAuth,
|
|
|
|
|
requireAdmin,
|
|
|
|
|
loading,
|
|
|
|
|
isLoggedIn,
|
|
|
|
|
isAdmin,
|
|
|
|
|
error,
|
|
|
|
|
hasToken: !!token,
|
|
|
|
|
currentPath: window.location.pathname,
|
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
|
tokenLength: token ? token.length : 0,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
console.log("AuthGuard: 로딩 중 - 대기");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 토큰이 있는데도 인증이 안 된 경우, 잠시 대기
|
|
|
|
|
if (token && !isLoggedIn && !loading) {
|
|
|
|
|
console.log("AuthGuard: 토큰은 있지만 인증이 안됨 - 잠시 대기");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-08-21 09:41:46 +09:00
|
|
|
|
|
|
|
|
// 인증이 필요한데 로그인되지 않은 경우
|
|
|
|
|
if (requireAuth && !isLoggedIn) {
|
2025-08-21 13:28:49 +09:00
|
|
|
console.log("AuthGuard: 인증 필요하지만 로그인되지 않음 - 5초 후 리다이렉트");
|
|
|
|
|
console.log("리다이렉트 대상:", redirectTo);
|
|
|
|
|
|
|
|
|
|
setRedirectCountdown(5);
|
|
|
|
|
const countdownInterval = setInterval(() => {
|
|
|
|
|
setRedirectCountdown((prev) => {
|
|
|
|
|
if (prev === null || prev <= 1) {
|
|
|
|
|
clearInterval(countdownInterval);
|
|
|
|
|
router.push(redirectTo);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return prev - 1;
|
|
|
|
|
});
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 관리자 권한이 필요한데 관리자가 아닌 경우
|
|
|
|
|
if (requireAdmin && !isAdmin) {
|
2025-08-21 13:28:49 +09:00
|
|
|
console.log("AuthGuard: 관리자 권한 필요하지만 관리자가 아님 - 5초 후 리다이렉트");
|
|
|
|
|
console.log("리다이렉트 대상:", redirectTo);
|
|
|
|
|
|
|
|
|
|
setRedirectCountdown(5);
|
|
|
|
|
const countdownInterval = setInterval(() => {
|
|
|
|
|
setRedirectCountdown((prev) => {
|
|
|
|
|
if (prev === null || prev <= 1) {
|
|
|
|
|
clearInterval(countdownInterval);
|
|
|
|
|
router.push(redirectTo);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
return prev - 1;
|
|
|
|
|
});
|
|
|
|
|
}, 1000);
|
|
|
|
|
|
2025-08-21 09:41:46 +09:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-21 13:28:49 +09:00
|
|
|
console.log("AuthGuard: 모든 인증 조건 통과 - 컴포넌트 렌더링");
|
|
|
|
|
}, [requireAuth, requireAdmin, loading, isLoggedIn, isAdmin, error, redirectTo, router]);
|
|
|
|
|
|
|
|
|
|
// 로딩 중일 때 fallback 또는 기본 로딩 표시
|
2025-08-21 09:41:46 +09:00
|
|
|
if (loading) {
|
2025-08-21 13:28:49 +09:00
|
|
|
console.log("AuthGuard: 로딩 중 - fallback 표시");
|
2025-08-21 09:41:46 +09:00
|
|
|
return (
|
2025-08-21 13:28:49 +09:00
|
|
|
<div>
|
2025-10-02 14:34:15 +09:00
|
|
|
<div className="mb-4 rounded bg-primary/20 p-4">
|
2025-08-21 13:28:49 +09:00
|
|
|
<h3 className="font-bold">AuthGuard 로딩 중...</h3>
|
|
|
|
|
<pre className="text-xs">{JSON.stringify(authDebugInfo, null, 2)}</pre>
|
2025-08-21 09:41:46 +09:00
|
|
|
</div>
|
2025-08-21 13:28:49 +09:00
|
|
|
{fallback || <div>로딩 중...</div>}
|
|
|
|
|
</div>
|
2025-08-21 09:41:46 +09:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-21 13:28:49 +09:00
|
|
|
// 인증 실패 시 fallback 또는 기본 메시지 표시
|
|
|
|
|
if (requireAuth && !isLoggedIn) {
|
|
|
|
|
console.log("AuthGuard: 인증 실패 - fallback 표시");
|
2025-08-21 09:41:46 +09:00
|
|
|
return (
|
2025-08-21 13:28:49 +09:00
|
|
|
<div>
|
2025-10-02 14:34:15 +09:00
|
|
|
<div className="mb-4 rounded bg-destructive/20 p-4">
|
2025-08-21 13:28:49 +09:00
|
|
|
<h3 className="font-bold">인증 실패</h3>
|
|
|
|
|
{redirectCountdown !== null && (
|
2025-10-02 14:34:15 +09:00
|
|
|
<div className="mb-2 text-destructive">
|
2025-08-21 13:28:49 +09:00
|
|
|
<strong>리다이렉트 카운트다운:</strong> {redirectCountdown}초 후 {redirectTo}로 이동
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<pre className="text-xs">{JSON.stringify(authDebugInfo, null, 2)}</pre>
|
2025-08-21 09:41:46 +09:00
|
|
|
</div>
|
2025-08-21 13:28:49 +09:00
|
|
|
{fallback || <div>인증이 필요합니다.</div>}
|
2025-08-21 09:41:46 +09:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (requireAdmin && !isAdmin) {
|
2025-08-21 13:28:49 +09:00
|
|
|
console.log("AuthGuard: 관리자 권한 없음 - fallback 표시");
|
2025-08-21 09:41:46 +09:00
|
|
|
return (
|
2025-08-21 13:28:49 +09:00
|
|
|
<div>
|
|
|
|
|
<div className="mb-4 rounded bg-orange-100 p-4">
|
|
|
|
|
<h3 className="font-bold">관리자 권한 없음</h3>
|
|
|
|
|
{redirectCountdown !== null && (
|
2025-10-02 14:34:15 +09:00
|
|
|
<div className="mb-2 text-destructive">
|
2025-08-21 13:28:49 +09:00
|
|
|
<strong>리다이렉트 카운트다운:</strong> {redirectCountdown}초 후 {redirectTo}로 이동
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
<pre className="text-xs">{JSON.stringify(authDebugInfo, null, 2)}</pre>
|
2025-08-21 09:41:46 +09:00
|
|
|
</div>
|
2025-08-21 13:28:49 +09:00
|
|
|
{fallback || <div>관리자 권한이 필요합니다.</div>}
|
2025-08-21 09:41:46 +09:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-21 13:28:49 +09:00
|
|
|
console.log("AuthGuard: 인증 성공 - 자식 컴포넌트 렌더링");
|
2025-08-21 09:41:46 +09:00
|
|
|
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;
|