Files
sam-react-prod/src/contexts/ApiErrorContext.tsx
유병철 0db6302652 refactor(WEB): 코드 품질 개선 및 불필요 코드 제거
- 미사용 import/변수/console.log 대량 정리 (100+개 파일)
- ItemMasterContext 간소화 (미사용 로직 제거)
- IntegratedListTemplateV2 / UniversalListPage 개선
- 결재 컴포넌트(ApprovalBox, DraftBox, ReferenceBox) 정리
- HR 컴포넌트(급여/휴가/부서) 코드 간소화
- globals.css 스타일 정리 및 개선
- AuthenticatedLayout 개선
- middleware CSP 정리
- proxy route 불필요 로깅 제거

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-10 20:55:11 +09:00

141 lines
3.6 KiB
TypeScript

'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: (response: unknown) => 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 (typeof window === 'undefined') return;
// 중복 리다이렉트 방지
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((response: unknown): 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 };
}