import React, { createContext, useContext, useState, useEffect, useCallback, ReactNode } from 'react'; import { AuthContextType, AuthState, LoginCredentials, RegisterData, UserRole } from '@/types'; import { authService } from '@/utils/auth'; const AuthContext = createContext(null); interface AuthProviderProps { children: ReactNode; } export const AuthProvider: React.FC = ({ children }) => { const [authState, setAuthState] = useState({ isAuthenticated: false, user: null, token: null, isLoading: true, }); const login = async (credentials: LoginCredentials): Promise => { try { setAuthState(prev => ({ ...prev, isLoading: true })); const result = await authService.login(credentials); setAuthState({ isAuthenticated: true, user: result.user, token: result.accessToken, isLoading: false, }); } catch (error) { setAuthState(prev => ({ ...prev, isLoading: false })); throw error; } }; const register = async (data: RegisterData): Promise => { try { setAuthState(prev => ({ ...prev, isLoading: true })); const result = await authService.register(data); setAuthState({ isAuthenticated: true, user: result.user, token: result.accessToken, isLoading: false, }); } catch (error) { setAuthState(prev => ({ ...prev, isLoading: false })); throw error; } }; const logout = useCallback((): void => { authService.logout(); setAuthState({ isAuthenticated: false, user: null, token: null, isLoading: false, }); }, []); const refreshToken = useCallback(async (): Promise => { try { const newToken = await authService.refreshToken(); const user = authService.getCurrentUserFromToken(); setAuthState(prev => ({ ...prev, token: newToken, user, isAuthenticated: true, })); } catch (error) { logout(); throw error; } }, [logout]); const hasRole = (role: UserRole): boolean => { return authService.hasRole(role); }; const hasPermission = (permission: string): boolean => { return authService.hasPermission(permission); }; const loadUserFromToken = useCallback(async (): Promise => { try { if (authService.isAuthenticated()) { const user = authService.getCurrentUserFromToken(); const token = authService.getAccessToken(); if (user && token) { setAuthState({ isAuthenticated: true, user, token, isLoading: false, }); // 토큰 갱신 체크 if (authService.shouldRefreshToken()) { await refreshToken(); } } else { // 토큰이 유효하지 않으면 최신 사용자 정보 가져오기 const currentUser = await authService.getCurrentUser(); setAuthState({ isAuthenticated: true, user: currentUser, token, isLoading: false, }); } } else { setAuthState({ isAuthenticated: false, user: null, token: null, isLoading: false, }); } } catch (error) { console.error('Failed to load user from token:', error); logout(); } }, [refreshToken, logout]); useEffect(() => { loadUserFromToken(); }, [loadUserFromToken]); // 토큰 자동 갱신 체크 useEffect(() => { if(!authState.isAuthenticated){ return () => {}; } const checkTokenExpiration = setInterval(() => { if (authService.shouldRefreshToken()) { refreshToken().catch(() => { logout(); }); } }, 60000); // 1분마다 체크 return () => clearInterval(checkTokenExpiration); }, [authState.isAuthenticated, refreshToken, logout]); const contextValue: AuthContextType = { ...authState, login, register, logout, refreshToken, hasRole, hasPermission, }; return {children}; }; // eslint-disable-next-line react-refresh/only-export-components export const useAuth = (): AuthContextType => { const context = useContext(AuthContext); if (!context) { throw new Error('useAuth must be used within an AuthProvider'); } return context; };