feat: fetchWrapper 마이그레이션 및 토큰 리프레시 캐싱 구현
- 40+ actions.ts 파일을 fetchWrapper 패턴으로 마이그레이션 - 토큰 리프레시 캐싱 로직 추가 (refresh-token.ts) - ApiErrorContext 추가로 전역 에러 처리 개선 - HR EmployeeForm 컴포넌트 개선 - 참조함(ReferenceBox) 기능 수정 - juil 테스트 URL 페이지 추가 - claudedocs 문서 업데이트 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
140
src/contexts/ApiErrorContext.tsx
Normal file
140
src/contexts/ApiErrorContext.tsx
Normal file
@@ -0,0 +1,140 @@
|
||||
'use client';
|
||||
|
||||
/**
|
||||
* API 에러 컨텍스트
|
||||
*
|
||||
* Server Action 결과에서 인증 에러를 감지하고
|
||||
* 자동으로 로그인 페이지로 리다이렉트
|
||||
*/
|
||||
|
||||
import { createContext, useContext, useCallback, useRef, type ReactNode } from 'react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { isAuthError, isApiError, type ApiErrorResponse } from '@/lib/api/errors';
|
||||
import { callLogoutAPI } from '@/lib/auth/logout';
|
||||
|
||||
interface ApiErrorContextType {
|
||||
/**
|
||||
* API 응답을 체크하고 인증 에러면 로그인 페이지로 이동
|
||||
* @returns true면 에러가 있음 (호출자는 early return 해야 함)
|
||||
*/
|
||||
checkAuthError: <T>(response: T) => response is ApiErrorResponse;
|
||||
|
||||
/**
|
||||
* 수동으로 인증 에러 처리 (로그아웃 후 로그인 페이지 이동)
|
||||
*/
|
||||
handleAuthError: () => Promise<void>;
|
||||
}
|
||||
|
||||
const ApiErrorContext = createContext<ApiErrorContextType | null>(null);
|
||||
|
||||
/**
|
||||
* API 에러 Provider
|
||||
*
|
||||
* Protected Layout에 추가하여 모든 하위 페이지에서 사용
|
||||
*/
|
||||
export function ApiErrorProvider({ children }: { children: ReactNode }) {
|
||||
const router = useRouter();
|
||||
const isRedirecting = useRef(false);
|
||||
|
||||
/**
|
||||
* 인증 에러 처리 - 로그아웃 후 로그인 페이지 이동
|
||||
*/
|
||||
const handleAuthError = useCallback(async () => {
|
||||
// 중복 리다이렉트 방지
|
||||
if (isRedirecting.current) return;
|
||||
isRedirecting.current = true;
|
||||
|
||||
console.warn('[ApiErrorContext] 인증 에러 감지 - 로그인 페이지로 이동');
|
||||
|
||||
try {
|
||||
// 서버 로그아웃 API 호출 (HttpOnly 쿠키 삭제)
|
||||
await callLogoutAPI();
|
||||
} catch (error) {
|
||||
console.error('[ApiErrorContext] 로그아웃 API 호출 실패:', error);
|
||||
}
|
||||
|
||||
// 로그인 페이지로 이동
|
||||
window.location.href = '/login';
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* API 응답에서 인증 에러 체크
|
||||
* 인증 에러면 자동으로 로그인 페이지 이동
|
||||
*/
|
||||
const checkAuthError = useCallback(<T,>(response: T): response is ApiErrorResponse => {
|
||||
if (isAuthError(response)) {
|
||||
handleAuthError();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isApiError(response)) {
|
||||
console.warn('[ApiErrorContext] API 에러:', response.message);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [handleAuthError]);
|
||||
|
||||
return (
|
||||
<ApiErrorContext.Provider value={{ checkAuthError, handleAuthError }}>
|
||||
{children}
|
||||
</ApiErrorContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 에러 컨텍스트 훅
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { checkAuthError } = useApiError();
|
||||
*
|
||||
* const result = await getEmployees();
|
||||
* if (checkAuthError(result)) return; // 인증 에러면 자동 리다이렉트
|
||||
*
|
||||
* // 정상 데이터 처리...
|
||||
* ```
|
||||
*/
|
||||
export function useApiError() {
|
||||
const context = useContext(ApiErrorContext);
|
||||
|
||||
if (!context) {
|
||||
throw new Error('useApiError must be used within ApiErrorProvider');
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Server Action 결과 래퍼
|
||||
*
|
||||
* Server Action 호출 결과를 자동으로 체크하고
|
||||
* 인증 에러면 null 반환 + 리다이렉트
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const { withAuthCheck } = useApiError();
|
||||
*
|
||||
* const employees = await withAuthCheck(getEmployees());
|
||||
* if (!employees) return; // 에러 발생 시 null
|
||||
*
|
||||
* // 정상 데이터 사용...
|
||||
* ```
|
||||
*/
|
||||
export function useWithAuthCheck() {
|
||||
const { checkAuthError } = useApiError();
|
||||
|
||||
const withAuthCheck = useCallback(async <T,>(
|
||||
promise: Promise<T>
|
||||
): Promise<T | null> => {
|
||||
const result = await promise;
|
||||
|
||||
if (checkAuthError(result)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [checkAuthError]);
|
||||
|
||||
return { withAuthCheck };
|
||||
}
|
||||
Reference in New Issue
Block a user