// lib/api.ts import axios, { type AxiosInstance, type AxiosRequestConfig, type AxiosResponse, } from "axios"; import TokenStorage from "./tokenStorage"; const localeToApiLang = (locale: string): string => { const mapping: Record = { tm: "tk", ru: "ru" }; return mapping[locale] || locale; }; class APIClient { private client: AxiosInstance; private baseUrl: string; private isRefreshing = false; private failedQueue: Array<{ resolve: (value?: unknown) => void; reject: (reason?: unknown) => void; }> = []; constructor() { this.baseUrl = process.env.NEXT_PUBLIC_API_URL || "https://api.example.com"; console.log("API URL:", this.baseUrl); this.client = axios.create({ baseURL: `${this.baseUrl}/api/v1`, timeout: 15000, headers: { "Content-Type": "application/json", "Api-Token": process.env.NEXT_PUBLIC_API_TOKEN || "123", }, }); this.setupInterceptors(); } private setupInterceptors(): void { // Request interceptor this.client.interceptors.request.use( (config) => { const token = TokenStorage.getActiveToken(); if (token) { config.headers.Authorization = `Bearer ${token}`; } // Add language parameter (except for POST requests to /orders) const url = config.url || ""; const isOrderPost = config.method?.toLowerCase() === "post" && url.includes("/orders"); if (!isOrderPost) { let lang = "tm"; if (typeof window !== "undefined") { if ((window as any).i18n?.language) { lang = localeToApiLang((window as any).i18n.language); } else { const pathLocale = window.location.pathname.split("/")[1]; if (pathLocale === "tm" || pathLocale === "ru") { lang = localeToApiLang(pathLocale); } } } const separator = url.includes("?") ? "&" : "?"; config.url = `${url}${separator}lang=${lang}`; } return config; }, (error) => Promise.reject(error), ); // Response interceptor this.client.interceptors.response.use( (response) => response, async (error) => { const originalRequest = error.config; if (error.response?.status === 401 && !originalRequest._retry) { if (this.isRefreshing) { return new Promise((resolve, reject) => { this.failedQueue.push({ resolve, reject }); }) .then(() => this.client(originalRequest)) .catch((err) => Promise.reject(err)); } originalRequest._retry = true; this.isRefreshing = true; try { const guestTokenResponse = await axios.post( `${this.baseUrl}/api/v1/auth/guest-token`, {}, { headers: { "Content-Type": "application/json", "Api-Token": process.env.NEXT_PUBLIC_API_TOKEN || "123", }, }, ); const newToken = guestTokenResponse.data?.token || guestTokenResponse.data?.data; if (newToken) { TokenStorage.setGuestToken(newToken); this.processQueue(null); return this.client(originalRequest); } } catch (refreshError) { this.processQueue(refreshError); TokenStorage.clearTokens(); if (typeof window !== "undefined") { window.location.href = "/login"; } return Promise.reject(refreshError); } finally { this.isRefreshing = false; } } if ( error.response?.data && typeof error.response.data === "string" && error.response.data.includes("") ) { return Promise.reject({ ...error, response: { ...error.response, data: { message: "Server returned HTML instead of JSON" }, }, }); } return Promise.reject(error); }, ); } private processQueue(error: any): void { this.failedQueue.forEach((promise) => { if (error) { promise.reject(error); } else { promise.resolve(); } }); this.failedQueue = []; } get( url: string, config?: AxiosRequestConfig, ): Promise> { return this.client.get(url, config); } post( url: string, data?: any, config?: AxiosRequestConfig, ): Promise> { return this.client.post(url, data, config); } put( url: string, data?: any, config?: AxiosRequestConfig, ): Promise> { return this.client.put(url, data, config); } patch( url: string, data?: any, config?: AxiosRequestConfig, ): Promise> { return this.client.patch(url, data, config); } delete( url: string, config?: AxiosRequestConfig, ): Promise> { return this.client.delete(url, config); } } export const apiClient = new APIClient();