import axios, { AxiosResponse, AxiosError } from "axios"; // API 기본 URL 설정 export const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8080/api"; // JWT 토큰 관리 유틸리티 const TokenManager = { getToken: (): string | null => { if (typeof window !== "undefined") { return localStorage.getItem("authToken"); } return null; }, isTokenExpired: (token: string): boolean => { try { const payload = JSON.parse(atob(token.split(".")[1])); return payload.exp * 1000 < Date.now(); } catch { return true; } }, }; // Axios 인스턴스 생성 export const apiClient = axios.create({ baseURL: API_BASE_URL, timeout: 10000, headers: { "Content-Type": "application/json", }, withCredentials: true, // 쿠키 포함 }); // 요청 인터셉터 apiClient.interceptors.request.use( (config) => { // JWT 토큰 추가 const token = TokenManager.getToken(); console.log("🔍 API 요청 토큰 확인:", { hasToken: !!token, tokenLength: token ? token.length : 0, tokenStart: token ? token.substring(0, 30) + "..." : "없음", url: config.url, method: config.method, }); if (token && !TokenManager.isTokenExpired(token)) { config.headers.Authorization = `Bearer ${token}`; console.log("✅ JWT 토큰 추가됨:", token.substring(0, 50) + "..."); console.log("🔑 Authorization 헤더:", `Bearer ${token.substring(0, 30)}...`); } else if (token && TokenManager.isTokenExpired(token)) { console.warn("❌ 토큰이 만료되었습니다."); // 토큰 제거 if (typeof window !== "undefined") { localStorage.removeItem("authToken"); } } else { console.warn("⚠️ 토큰이 없습니다."); } // 언어 정보를 쿼리 파라미터에 추가 (GET 요청 시에만) if (config.method?.toUpperCase() === "GET") { // 우선순위: 전역 변수 > localStorage > 기본값 let currentLang = "KR"; // 기본값 if (typeof window !== "undefined") { // 1순위: 전역 변수에서 확인 if ((window as any).__GLOBAL_USER_LANG) { currentLang = (window as any).__GLOBAL_USER_LANG; } // 2순위: localStorage에서 확인 (새 창이나 페이지 새로고침 시) else { const storedLocale = localStorage.getItem("userLocale"); if (storedLocale) { currentLang = storedLocale; } } } console.log("🌐 API 요청 시 언어 정보:", { currentLang, globalVar: (window as any).__GLOBAL_USER_LANG, localStorage: typeof window !== "undefined" ? localStorage.getItem("userLocale") : null, url: config.url, }); if (config.params) { config.params.userLang = currentLang; } else { config.params = { userLang: currentLang }; } } console.log("📡 API 요청:", config.method?.toUpperCase(), config.url, config.params, config.data); console.log("📋 요청 헤더:", config.headers); return config; }, (error) => { console.error("❌ API 요청 오류:", error); return Promise.reject(error); }, ); // 응답 인터셉터 apiClient.interceptors.response.use( (response: AxiosResponse) => { console.log("✅ API 응답:", response.status, response.config.url, response.data); return response; }, (error: AxiosError) => { console.error("❌ API 응답 오류:", { status: error.response?.status, statusText: error.response?.statusText, url: error.config?.url, data: error.response?.data, message: error.message, headers: error.config?.headers, }); // 401 에러 시 상세 정보 출력 if (error.response?.status === 401) { console.error("🚨 401 Unauthorized 오류 상세 정보:", { url: error.config?.url, method: error.config?.method, headers: error.config?.headers, requestData: error.config?.data, responseData: error.response?.data, token: TokenManager.getToken() ? "존재" : "없음", }); } // 401 에러 시 토큰 제거 및 로그인 페이지로 리다이렉트 if (error.response?.status === 401 && typeof window !== "undefined") { console.log("🔄 401 에러 감지 - 토큰 제거 및 로그인 페이지로 리다이렉트"); localStorage.removeItem("authToken"); // 로그인 페이지가 아닌 경우에만 리다이렉트 if (window.location.pathname !== "/login") { window.location.href = "/login"; } } return Promise.reject(error); }, ); // 공통 응답 타입 export interface ApiResponse { success: boolean; data?: T; message?: string; errorCode?: string; } // API 호출 헬퍼 함수 export const apiCall = async ( method: "GET" | "POST" | "PUT" | "DELETE", url: string, data?: any, ): Promise> => { try { const response = await apiClient.request({ method, url, data, }); return response.data; } catch (error: any) { console.error("API 호출 실패:", error); return { success: false, message: error.response?.data?.message || error.message || "알 수 없는 오류가 발생했습니다.", errorCode: error.response?.data?.errorCode, }; } };