161 lines
4.5 KiB
TypeScript
161 lines
4.5 KiB
TypeScript
import React, { createContext, useReducer, useEffect } from 'react';
|
|
|
|
import { isTokenValid, getAccessTokenFromCookie, getUserFromToken } from 'src/axios/authService';
|
|
|
|
// 사용자 타입 정의
|
|
export interface Member {
|
|
memberId: string;
|
|
memberName: string;
|
|
}
|
|
|
|
// 인증 상태 타입 정의
|
|
interface AuthState {
|
|
isAuthenticated: boolean;
|
|
member: Member | null;
|
|
loading: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
// 액션 타입 정의
|
|
type AuthAction =
|
|
| { type: 'LOGIN_SUCCESS'; payload: { member: Member; token: string } }
|
|
| { type: 'LOGOUT' }
|
|
| { type: 'AUTH_ERROR'; payload: string }
|
|
| { type: 'CLEAR_ERROR' }
|
|
| { type: 'SET_LOADING' }
|
|
| { type: 'MEMBER_LOADED'; payload: Member };
|
|
|
|
// 초기 상태
|
|
const initialState: AuthState = {
|
|
isAuthenticated: false,
|
|
member: null,
|
|
loading: true,
|
|
error: null
|
|
};
|
|
|
|
// Context 타입 정의
|
|
export interface AuthContextType {
|
|
state: AuthState;
|
|
dispatch: React.Dispatch<AuthAction>;
|
|
login: (token: string, member: Member) => void;
|
|
logout: () => void;
|
|
}
|
|
|
|
// Context 생성
|
|
export const AuthContext = createContext<AuthContextType>({
|
|
state: initialState,
|
|
dispatch: () => null,
|
|
login: () => null,
|
|
logout: () => null
|
|
});
|
|
|
|
// 리듀서 함수
|
|
const authReducer = (state: AuthState, action: AuthAction): AuthState => {
|
|
switch (action.type) {
|
|
case 'LOGIN_SUCCESS':
|
|
localStorage.setItem('accessToken', action.payload.token);
|
|
return {
|
|
...state,
|
|
isAuthenticated: true,
|
|
member: action.payload.member,
|
|
loading: false,
|
|
error: null
|
|
};
|
|
case 'LOGOUT':
|
|
localStorage.removeItem('accessToken');
|
|
return {
|
|
...state,
|
|
isAuthenticated: false,
|
|
member: null,
|
|
loading: false,
|
|
error: null
|
|
};
|
|
case 'AUTH_ERROR':
|
|
localStorage.removeItem('accessToken');
|
|
return {
|
|
...state,
|
|
isAuthenticated: false,
|
|
member: null,
|
|
loading: false,
|
|
error: action.payload
|
|
};
|
|
case 'CLEAR_ERROR':
|
|
return {
|
|
...state,
|
|
error: null
|
|
};
|
|
case 'SET_LOADING':
|
|
return {
|
|
...state,
|
|
loading: true
|
|
};
|
|
case 'MEMBER_LOADED':
|
|
return {
|
|
...state,
|
|
isAuthenticated: true,
|
|
member: action.payload,
|
|
loading: false
|
|
};
|
|
default:
|
|
return state;
|
|
}
|
|
};
|
|
|
|
// Provider 컴포넌트
|
|
export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [state, dispatch] = useReducer(authReducer, initialState);
|
|
|
|
// 로그인 함수
|
|
const login = (token: string, member: Member) => {
|
|
dispatch({
|
|
type: 'LOGIN_SUCCESS',
|
|
payload: { token, member }
|
|
});
|
|
};
|
|
|
|
// 로그아웃 함수
|
|
const logout = () => {
|
|
dispatch({ type: 'LOGOUT' });
|
|
};
|
|
|
|
// 초기 인증 상태 확인
|
|
useEffect(() => {
|
|
const loadUser = async () => {
|
|
// localStorage 또는 Cookie에서 토큰 확인
|
|
let token = localStorage.getItem('accessToken') || getAccessTokenFromCookie();
|
|
|
|
if (!token || !isTokenValid(token)) {
|
|
dispatch({ type: 'LOGOUT' });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 토큰이 쿠키에만 있고 localStorage에 없으면 저장해줌 (일관성 유지)
|
|
if (!localStorage.getItem('accessToken')) {
|
|
localStorage.setItem('accessToken', token);
|
|
}
|
|
|
|
const decodedToken = getUserFromToken(token);
|
|
if (decodedToken) {
|
|
const member: Member = {
|
|
memberId: decodedToken.memberId,
|
|
memberName: decodedToken.memberName
|
|
};
|
|
dispatch({ type: 'MEMBER_LOADED', payload: member });
|
|
} else {
|
|
dispatch({ type: 'AUTH_ERROR', payload: 'Invalid token' });
|
|
}
|
|
} catch (error) {
|
|
dispatch({ type: 'AUTH_ERROR', payload: 'Authentication failed' });
|
|
}
|
|
};
|
|
|
|
loadUser();
|
|
}, []);
|
|
|
|
return (
|
|
<AuthContext.Provider value={{ state, dispatch, login, logout }}>
|
|
{children}
|
|
</AuthContext.Provider>
|
|
);
|
|
}; |