- 미사용 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>
141 lines
3.6 KiB
TypeScript
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 };
|
|
} |