첫 커밋

This commit is contained in:
focp212@naver.com
2025-09-05 15:36:48 +09:00
commit 05238b04c1
825 changed files with 176358 additions and 0 deletions

173
src/hooks/useAuth.tsx Normal file
View File

@@ -0,0 +1,173 @@
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<AuthContextType | null>(null);
interface AuthProviderProps {
children: ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [authState, setAuthState] = useState<AuthState>({
isAuthenticated: false,
user: null,
token: null,
isLoading: true,
});
const login = async (credentials: LoginCredentials): Promise<void> => {
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<void> => {
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<void> => {
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<void> => {
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 <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
};
// 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;
};