Files
artwork21c.sample.admin.react/src/context/AuthContext.tsx
artwork21c 767435cad4 access_token 쿠키 확인하여 로그인 상태 유지 처리
ProtectedRoute 로 로그인 필요 페이지 접근 관리
import 문을 src/ 포함된 절대경로로 개선
2026-01-02 11:21:56 +09:00

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>
);
};