/** * API Client * Axios instance configured for SmoothSchedule API */ import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios'; import { API_BASE_URL, getSubdomain } from './config'; import { getCookie } from '../utils/cookies'; // Create axios instance const apiClient = axios.create({ baseURL: API_BASE_URL, headers: { 'Content-Type': 'application/json', }, withCredentials: true, // For CORS with credentials }); /** * Get sandbox mode from localStorage * This is set by the SandboxContext when mode changes */ const getSandboxMode = (): boolean => { try { return localStorage.getItem('sandbox_mode') === 'true'; } catch { return false; } }; // Request interceptor - add auth token, business subdomain, and sandbox mode apiClient.interceptors.request.use( (config: InternalAxiosRequestConfig) => { // Add business subdomain header if on business site const subdomain = getSubdomain(); if (subdomain && subdomain !== 'platform') { config.headers['X-Business-Subdomain'] = subdomain; } // Add auth token if available (from cookie) const token = getCookie('access_token'); if (token) { // Use 'Token' prefix for Django REST Framework Token Authentication config.headers['Authorization'] = `Token ${token}`; } // Add sandbox mode header if in test mode const isSandbox = getSandboxMode(); if (isSandbox) { config.headers['X-Sandbox-Mode'] = 'true'; } return config; }, (error) => { return Promise.reject(error); } ); // Response interceptor - handle errors and token refresh apiClient.interceptors.response.use( (response) => response, async (error: AxiosError) => { const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean }; // Handle 401 Unauthorized - token expired if (error.response?.status === 401 && !originalRequest._retry) { originalRequest._retry = true; try { // Try to refresh token (from cookie) const refreshToken = getCookie('refresh_token'); if (refreshToken) { const response = await axios.post(`${API_BASE_URL}/auth/refresh/`, { refresh: refreshToken, }); const { access } = response.data; // Import setCookie dynamically to avoid circular dependency const { setCookie } = await import('../utils/cookies'); setCookie('access_token', access, 7); // Retry original request with new token if (originalRequest.headers) { originalRequest.headers['Authorization'] = `Bearer ${access}`; } return apiClient(originalRequest); } } catch (refreshError) { // Refresh failed - clear tokens and redirect to login on root domain const { deleteCookie } = await import('../utils/cookies'); const { getBaseDomain } = await import('../utils/domain'); deleteCookie('access_token'); deleteCookie('refresh_token'); const protocol = window.location.protocol; const baseDomain = getBaseDomain(); const port = window.location.port ? `:${window.location.port}` : ''; window.location.href = `${protocol}//${baseDomain}${port}/login`; return Promise.reject(refreshError); } } return Promise.reject(error); } ); export default apiClient;