import axios from 'axios'; // Получаем URL API из переменных окружения или используем значение по умолчанию // Для клиентских запросов (из браузера) export const PUBLIC_API_URL = process.env.NEXT_PUBLIC_API_URL || '/api'; // Для серверных запросов (SSR) export const SERVER_API_URL = process.env.NEXT_SERVER_API_URL || 'http://localhost/api'; // Базовый URL для статических ресурсов export const PUBLIC_BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost'; // Определяем, выполняется ли код на сервере или в браузере export const isServer = typeof window === 'undefined'; // Константа для ключа токена в localStorage const TOKEN_KEY = 'token'; // Отладочный режим для API export const apiStatus = { // Используем переменную окружения для включения/выключения отладки debugMode: process.env.NEXT_PUBLIC_DEBUG === 'true', connectionError: false, lastConnectionError: null as Error | null, isAuthenticated: false as boolean, // Флаг аутентификации пользователя (не const) SUCCESS: 'success', ERROR: 'error' }; // Для отладки выводим URL API в консоль console.log('🔍 Режим отладки API включен'); console.error('🌐 Клиентский API URL:', PUBLIC_API_URL); console.log('🖥️ Серверный API URL:', SERVER_API_URL); console.log('📁 BASE URL:', PUBLIC_BASE_URL); console.log('🔄 Среда выполнения:', isServer ? 'Сервер' : 'Браузер'); console.log('📋 ENV переменные:'); console.log(' - NEXT_PUBLIC_API_URL:', process.env.NEXT_PUBLIC_API_URL); console.log(' - NEXT_SERVER_API_URL:', process.env.NEXT_SERVER_API_URL); console.log(' - NEXT_PUBLIC_BASE_URL:', process.env.NEXT_PUBLIC_BASE_URL); console.log(' - NEXT_PUBLIC_DEBUG:', process.env.NEXT_PUBLIC_DEBUG); // Проверяем, не содержит ли URL порт 8000 if (PUBLIC_API_URL.includes(':8000')) { console.warn('⚠️ Клиентский API URL содержит порт 8000, что может вызывать проблемы при работе через Nginx'); } if (apiStatus.debugMode) { } // Получение токена из localStorage const getToken = (): string | null => { try { return localStorage.getItem(TOKEN_KEY); } catch (error) { console.error('Ошибка при получении токена (api.ts):', error); return null; } }; // Создаем экземпляр клиента Axios с базовыми настройками // Используем разные URL для клиента и сервера const instance = axios.create({ baseURL: isServer ? SERVER_API_URL : PUBLIC_API_URL, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, timeout: 30000, // 30 секунд таймаут для запросов }); // Логируем, какой URL используется if (apiStatus.debugMode) { console.log('🔌 Axios использует baseURL:', instance.defaults.baseURL); } // Добавляем перехватчик для исходящих запросов instance.interceptors.request.use( (config) => { // Получаем токен из localStorage (если он есть) const token = typeof window !== 'undefined' ? getToken() : null; // Если токен есть, добавляем его в заголовки запроса if (token) { config.headers = config.headers || {}; config.headers.Authorization = `Bearer ${token}`; apiStatus.isAuthenticated = true; } else { apiStatus.isAuthenticated = false; } // Логируем URL запроса в режиме отладки if (apiStatus.debugMode) { console.log(`API Request: ${config.method?.toUpperCase()} ${config.url}`); } return config; }, (error) => { // Обработка ошибок при подготовке запроса console.error('API Request Error:', error); return Promise.reject(error); } ); // Добавляем перехватчик для входящих ответов instance.interceptors.response.use( function(response: any) { // Обработка успешного ответа if (apiStatus.debugMode) { console.log(`API Response ${response.status} for ${response.config.url}`); } return response.data; }, function(error: any) { // Проверяем, является ли ошибка 401 (неавторизован) для запроса корзины // и пользователь не аутентифицирован - это ожидаемое поведение if (error.response && error.response.status === 401) { // Для запросов к корзине, когда пользователь не аутентифицирован, // просто возвращаем пустой объект без логирования ошибки if (!apiStatus.isAuthenticated && error.config.url && (error.config.url.includes('/cart') || error.config.url.includes('/wishlist'))) { return Promise.resolve({}); } // Логируем 401 ошибку, но НЕ удаляем токен, чтобы избежать проблем при перезагрузке console.log('Получен 401 ответ от API. Возможно, токен истек или недействителен.'); // Больше не перенаправляем на страницу входа автоматически // это должно делаться на уровне компонентов проверки авторизации } // Логируем ошибки в режиме отладки if (apiStatus.debugMode) { if (error.response) { console.error(`API Error ${error.response.status}: ${error.response.statusText}`); console.error('API Error Response Data:', error.response.data); } else if (error.request) { console.error('API No Response Received:', error.request); } else { console.error('API Request Setup Error:', error.message); } } return Promise.reject(error); } ); // Общий интерфейс для ответа API export interface ApiResponse { success: boolean; data: T; } export interface ApiErrorResponse { success: false; error: string; details?: any; } // API клиент const api = { // GET запрос get: async (url: string, params = {}): Promise => { try { return await instance.get(url, { params }) as unknown as T; } catch (error) { if (apiStatus.debugMode) { console.error(`GET Error for ${url}:`, error); } throw error; } }, // POST запрос post: async (url: string, data = {}, config = {}): Promise => { try { return await instance.post(url, data, config) as unknown as T; } catch (error) { if (apiStatus.debugMode) { console.error(`POST Error for ${url}:`, error); } throw error; } }, // PUT запрос put: async (url: string, data = {}, config = {}): Promise => { try { return await instance.put(url, data, config) as unknown as T; } catch (error) { if (apiStatus.debugMode) { console.error(`PUT Error for ${url}:`, error); } throw error; } }, // DELETE запрос delete: async (url: string, config = {}): Promise => { try { return await instance.delete(url, config) as unknown as T; } catch (error) { if (apiStatus.debugMode) { console.error(`DELETE Error for ${url}:`, error); } throw error; } }, }; // Функция для выполнения запросов к API с унифицированным обработчиком ошибок export async function fetchApi( url: string, options: RequestInit = {} ): Promise> { try { const token = typeof window !== 'undefined' ? getToken() : null; const headers: Record = { 'Content-Type': 'application/json', 'Accept': 'application/json', ...(options.headers as Record || {}) }; if (token) { headers['Authorization'] = `Bearer ${token}`; } // Выбираем правильный базовый URL в зависимости от среды выполнения const baseUrl = isServer ? SERVER_API_URL : PUBLIC_API_URL; // Логируем запрос в режиме отладки if (apiStatus.debugMode) { console.log(`🔄 fetchApi запрос: ${baseUrl}${url}`); } const response = await fetch(`${baseUrl}${url}`, { ...options, headers, }); const data = await response.json(); if (!response.ok) { return { success: false, data: data as T, }; } return { success: true, data: data as T, }; } catch (error) { return { success: false, data: {} as T, }; } } export default api;