import { useState, useEffect, useCallback } from "react"; import { useRouter } from "next/navigation"; import { apiCall } from "@/lib/api/client"; // 사용자 정보 타입 정의 interface UserInfo { userId: string; userName: string; userNameEng?: string; userNameCn?: string; deptCode?: string; deptName?: string; positionCode?: string; positionName?: string; email?: string; tel?: string; cellPhone?: string; userType?: string; userTypeName?: string; authName?: string; partnerCd?: string; isAdmin: boolean; sabun?: string; photo?: string | null; } // 인증 상태 타입 정의 interface AuthStatus { isLoggedIn: boolean; isAdmin: boolean; userId?: string; deptCode?: string; } // 로그인 결과 타입 정의 interface LoginResult { success: boolean; message: string; errorCode?: string; } // API 응답 타입 정의 interface ApiResponse { success: boolean; message: string; data?: T; errorCode?: string; } /** * JWT 토큰 관리 유틸리티 */ const TokenManager = { getToken: (): string | null => { if (typeof window !== "undefined") { return localStorage.getItem("authToken"); } return null; }, setToken: (token: string): void => { if (typeof window !== "undefined") { localStorage.setItem("authToken", token); } }, removeToken: (): void => { if (typeof window !== "undefined") { localStorage.removeItem("authToken"); } }, isTokenExpired: (token: string): boolean => { try { const payload = JSON.parse(atob(token.split(".")[1])); return payload.exp * 1000 < Date.now(); } catch { return true; } }, }; /** * 인증 상태 관리 훅 * 로그인, 로그아웃, 사용자 정보 조회, 권한 확인 등을 담당 */ export const useAuth = () => { const router = useRouter(); // 상태 관리 const [user, setUser] = useState(null); const [authStatus, setAuthStatus] = useState({ isLoggedIn: false, isAdmin: false, }); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // API 기본 URL 설정 const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080"; /** * 현재 사용자 정보 조회 */ const fetchCurrentUser = useCallback(async (): Promise => { try { console.log("=== fetchCurrentUser 시작 ==="); const response = await apiCall("GET", "/auth/me"); console.log("fetchCurrentUser 응답:", response); if (response.success && response.data) { console.log("사용자 정보 조회 성공:", response.data); return response.data; } console.log("사용자 정보 조회 실패 - 응답이 없음"); return null; } catch (error) { console.error("사용자 정보 조회 실패:", error); return null; } }, []); /** * 인증 상태 확인 */ const checkAuthStatus = useCallback(async (): Promise => { try { console.log("=== checkAuthStatus 시작 ==="); const response = await apiCall("GET", "/auth/status"); console.log("checkAuthStatus 응답:", response); console.log("checkAuthStatus 응답.data:", response.data); console.log("checkAuthStatus 응답.data.isLoggedIn:", response.data?.isLoggedIn); console.log("checkAuthStatus 응답.data.isAuthenticated:", (response.data as any)?.isAuthenticated); if (response.success && response.data) { console.log("인증 상태 확인 성공:", response.data); // 백엔드에서 isAuthenticated를 반환하므로 isLoggedIn으로 매핑 const mappedData = { isLoggedIn: (response.data as any).isAuthenticated || response.data.isLoggedIn || false, isAdmin: response.data.isAdmin || false, }; console.log("매핑된 인증 상태:", mappedData); return mappedData; } console.log("인증 상태 확인 실패 - 응답이 없음"); return { isLoggedIn: false, isAdmin: false, }; } catch (error) { console.error("인증 상태 확인 실패:", error); return { isLoggedIn: false, isAdmin: false, }; } }, []); /** * 사용자 데이터 새로고침 */ const refreshUserData = useCallback(async () => { try { setLoading(true); // JWT 토큰 확인 const token = TokenManager.getToken(); if (!token) { setUser(null); setAuthStatus({ isLoggedIn: false, isAdmin: false }); router.push("/login"); return; } // 병렬로 사용자 정보와 인증 상태 조회 const [userInfo, authStatusData] = await Promise.all([fetchCurrentUser(), checkAuthStatus()]); console.log("=== refreshUserData 디버깅 ==="); console.log("userInfo:", userInfo); console.log("authStatusData:", authStatusData); console.log("authStatusData.isLoggedIn:", authStatusData?.isLoggedIn); setUser(userInfo); setAuthStatus(authStatusData); // 디버깅용 로그 if (userInfo) { console.log("사용자 정보 업데이트:", { userId: userInfo.userId, userName: userInfo.userName, hasPhoto: !!userInfo.photo, photoStart: userInfo.photo ? userInfo.photo.substring(0, 50) + "..." : "null", }); } // 로그인되지 않은 상태인 경우 토큰 제거 (리다이렉트는 useEffect에서 처리) if (!authStatusData.isLoggedIn) { console.log("로그인되지 않은 상태 - 사용자 정보 제거"); TokenManager.removeToken(); setUser(null); setAuthStatus({ isLoggedIn: false, isAdmin: false }); } else { console.log("로그인된 상태 - 사용자 정보 유지"); } } catch (error) { console.error("사용자 데이터 새로고침 실패:", error); setError("사용자 정보를 불러오는데 실패했습니다."); // 오류 발생 시 토큰 제거 및 로그인 페이지로 리다이렉트 TokenManager.removeToken(); setUser(null); setAuthStatus({ isLoggedIn: false, isAdmin: false }); router.push("/login"); } finally { setLoading(false); } }, [fetchCurrentUser, checkAuthStatus, router]); /** * 로그인 처리 */ const login = useCallback( async (userId: string, password: string): Promise => { try { setLoading(true); setError(null); const response = await apiCall("POST", "/auth/login", { userId, password, }); if (response.success && response.data?.token) { // JWT 토큰 저장 TokenManager.setToken(response.data.token); // 로그인 성공 시 사용자 정보 및 인증 상태 업데이트 await refreshUserData(); return { success: true, message: response.message || "로그인에 성공했습니다.", }; } else { return { success: false, message: response.message || "로그인에 실패했습니다.", errorCode: response.errorCode, }; } } catch (error: any) { const errorMessage = error.message || "로그인 중 오류가 발생했습니다."; setError(errorMessage); return { success: false, message: errorMessage, }; } finally { setLoading(false); } }, [apiCall, refreshUserData], ); /** * 로그아웃 처리 */ const logout = useCallback(async (): Promise => { try { setLoading(true); const response = await apiCall("POST", "/auth/logout"); // JWT 토큰 제거 TokenManager.removeToken(); // 로그아웃 API 호출 성공 여부와 관계없이 클라이언트 상태 초기화 setUser(null); setAuthStatus({ isLoggedIn: false, isAdmin: false, }); setError(null); // 로그인 페이지로 리다이렉트 router.push("/login"); return response.success; } catch (error) { console.error("로그아웃 처리 실패:", error); // 오류가 발생해도 JWT 토큰 제거 및 클라이언트 상태 초기화 TokenManager.removeToken(); setUser(null); setAuthStatus({ isLoggedIn: false, isAdmin: false, }); router.push("/login"); return false; } finally { setLoading(false); } }, [apiCall, router]); /** * 메뉴 접근 권한 확인 */ const checkMenuAuth = useCallback(async (menuUrl: string): Promise => { try { const response = await apiCall<{ menuUrl: string; hasAuth: boolean }>("GET", "/auth/menu-auth"); if (response.success && response.data) { return response.data.hasAuth; } return false; } catch (error) { console.error("메뉴 권한 확인 실패:", error); return false; } }, []); /** * 초기 인증 상태 확인 */ useEffect(() => { // 로그인 페이지에서는 인증 상태 확인하지 않음 if (window.location.pathname === "/login") { return; } // 토큰이 있는 경우에만 인증 상태 확인 const token = TokenManager.getToken(); if (token && !TokenManager.isTokenExpired(token)) { refreshUserData(); } else if (!token) { // 토큰이 없으면 로그인 페이지로 리다이렉트 router.push("/login"); } }, []); // refreshUserData 의존성 제거 /** * 세션 만료 감지 및 처리 */ useEffect(() => { const handleSessionExpiry = () => { TokenManager.removeToken(); setUser(null); setAuthStatus({ isLoggedIn: false, isAdmin: false, }); setError("세션이 만료되었습니다. 다시 로그인해주세요."); router.push("/login"); }; // 전역 에러 핸들러 등록 (401 Unauthorized 응답 처리) const originalFetch = window.fetch; window.fetch = async (...args) => { const response = await originalFetch(...args); if (response.status === 401 && window.location.pathname !== "/login") { handleSessionExpiry(); } return response; }; return () => { window.fetch = originalFetch; }; }, [router]); return { // 상태 user, authStatus, loading, error, // 계산된 값 isLoggedIn: authStatus.isLoggedIn, isAdmin: authStatus.isAdmin, userId: user?.userId, userName: user?.userName, // 함수 login, logout, checkMenuAuth, refreshUserData, // 유틸리티 clearError: () => setError(null), }; }; export default useAuth;