2025-12-30 17:00:18 +09:00
|
|
|
'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 해야 함)
|
|
|
|
|
*/
|
fix: 프로젝트 전체 TypeScript 타입에러 408개 수정 (tsc --noEmit 0 errors)
- 공통 템플릿 타입 수정 (IntegratedDetailTemplate, UniversalListPage)
- 페이지(app/[locale]) 타입 호환성 수정 (80개)
- 재고/자재 모듈 타입 수정 (StockStatus, ReceivingManagement)
- 생산 모듈 타입 수정 (WorkOrders, WorkerScreen, WorkResults)
- 주문/출고 모듈 타입 수정 (ShipmentManagement, Orders)
- 견적/단가 모듈 타입 수정 (Quotes, Pricing)
- 건설 모듈 타입 수정 (49개, 17개 하위 모듈)
- HR 모듈 타입 수정 (CardManagement, VacationManagement 등)
- 설정 모듈 타입 수정 (PermissionManagement, AccountManagement 등)
- 게시판 모듈 타입 수정 (BoardManagement, BoardList 등)
- 회계 모듈 타입 수정 (VendorManagement, BadDebtCollection 등)
- 기타 모듈 타입 수정 (CEODashboard, clients, vehicle 등)
- 유틸/훅/API 타입 수정 (hooks, contexts, lib)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 10:07:58 +09:00
|
|
|
checkAuthError: (response: unknown) => response is ApiErrorResponse;
|
2025-12-30 17:00:18 +09:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 수동으로 인증 에러 처리 (로그아웃 후 로그인 페이지 이동)
|
|
|
|
|
*/
|
|
|
|
|
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 응답에서 인증 에러 체크
|
|
|
|
|
* 인증 에러면 자동으로 로그인 페이지 이동
|
|
|
|
|
*/
|
fix: 프로젝트 전체 TypeScript 타입에러 408개 수정 (tsc --noEmit 0 errors)
- 공통 템플릿 타입 수정 (IntegratedDetailTemplate, UniversalListPage)
- 페이지(app/[locale]) 타입 호환성 수정 (80개)
- 재고/자재 모듈 타입 수정 (StockStatus, ReceivingManagement)
- 생산 모듈 타입 수정 (WorkOrders, WorkerScreen, WorkResults)
- 주문/출고 모듈 타입 수정 (ShipmentManagement, Orders)
- 견적/단가 모듈 타입 수정 (Quotes, Pricing)
- 건설 모듈 타입 수정 (49개, 17개 하위 모듈)
- HR 모듈 타입 수정 (CardManagement, VacationManagement 등)
- 설정 모듈 타입 수정 (PermissionManagement, AccountManagement 등)
- 게시판 모듈 타입 수정 (BoardManagement, BoardList 등)
- 회계 모듈 타입 수정 (VendorManagement, BadDebtCollection 등)
- 기타 모듈 타입 수정 (CEODashboard, clients, vehicle 등)
- 유틸/훅/API 타입 수정 (hooks, contexts, lib)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 10:07:58 +09:00
|
|
|
const checkAuthError = useCallback((response: unknown): response is ApiErrorResponse => {
|
2025-12-30 17:00:18 +09:00
|
|
|
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 };
|
|
|
|
|
}
|