Files
sam-react-prod/src/contexts/ApiErrorContext.tsx

140 lines
3.6 KiB
TypeScript
Raw Normal View History

'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 (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 };
}