export const getBaseUrl = (): string => { if (typeof window !== 'undefined') { const host = window.location.hostname; if (host === 'localhost' || host === '127.0.0.1') { return process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'; } return 'https://ifixkartbe.trionixsolution.com'; } return process.env.NEXT_PUBLIC_API_URL || 'https://ifixkartbe.trionixsolution.com'; }; export const API_BASE_URL = getBaseUrl(); export function getMediaUrl(url: string | null | undefined): string { if (!url) return ''; if (url.startsWith('http://') || url.startsWith('https://') || url.startsWith('//')) return url; const base = getBaseUrl(); return `${base}${url.startsWith('/') ? '' : '/'}${url}`; } let accessToken: string | null = null; export function setAccessToken(token: string | null): void { accessToken = token; } export function getAccessToken(): string | null { return accessToken; } export function clearTokens(): void { accessToken = null; } export class ApiError extends Error { status: number; constructor(status: number, message: string) { super(message); this.status = status; this.name = 'ApiError'; } } function formatApiErrorDetail(err: unknown): string { const detail = err && typeof err === 'object' ? (err as { detail?: unknown; message?: unknown }).detail ?? (err as { message?: unknown }).message : err; if (typeof detail === 'string' && detail.trim()) return detail; if (Array.isArray(detail)) { return detail .map((item) => { if (typeof item === 'string') return item; if (item && typeof item === 'object') { const row = item as { msg?: string; message?: string }; return row.msg || row.message || ''; } return ''; }) .filter(Boolean) .join('; '); } if (detail && typeof detail === 'object') { const row = detail as { msg?: string; message?: string }; if (row.msg || row.message) return String(row.msg || row.message); } return ''; } interface FetchOptions extends RequestInit { skipAuth?: boolean; } export async function apiFetch( endpoint: string, options: FetchOptions = {} ): Promise { const { skipAuth = false, headers: customHeaders, ...rest } = options; const baseUrl = getBaseUrl(); const headers: Record = { 'Content-Type': 'application/json', ...(customHeaders as Record), }; if (!skipAuth) { const token = getAccessToken(); if (token) { headers['Authorization'] = `Bearer ${token}`; } } try { const response = await fetch(`${baseUrl}${endpoint}`, { ...rest, headers, credentials: 'include', }); if (response.status === 401 && !skipAuth) { const refreshed = await refreshAccessToken(); if (refreshed) { headers['Authorization'] = `Bearer ${getAccessToken()}`; const retryResponse = await fetch(`${baseUrl}${endpoint}`, { ...rest, headers, credentials: 'include', }); if (!retryResponse.ok) { const err = await retryResponse.json().catch(() => ({ detail: 'Request failed' })); throw new ApiError(retryResponse.status, formatApiErrorDetail(err) || 'Request failed'); } return retryResponse.json(); } clearTokens(); throw new ApiError(401, 'Session expired'); } if (!response.ok) { const err = await response.json().catch(() => ({ detail: 'Request failed' })); throw new ApiError(response.status, formatApiErrorDetail(err) || 'Request failed'); } return response.json(); } catch (error: any) { if (error instanceof ApiError) { throw error; } throw new ApiError( 503, `API Server is offline or unreachable. Please ensure the backend service at ${baseUrl} is running.` ); } } export async function refreshAccessToken(): Promise { const baseUrl = getBaseUrl(); try { const response = await fetch(`${baseUrl}/api/v1/customer/auth/refresh`, { method: 'POST', credentials: 'include', }); if (!response.ok) return false; const data = await response.json(); setAccessToken(data.access_token); return true; } catch { return false; } } export interface RestoredSession { access_token: string; customer_id: string; email: string; first_name: string; } /** * Bootstrap customer session from HttpOnly refresh_token cookie. * Returns token payload on success, null if no valid session. */ export async function restoreCustomerSession(): Promise { const baseUrl = getBaseUrl(); try { const response = await fetch(`${baseUrl}/api/v1/customer/auth/refresh`, { method: 'POST', credentials: 'include', }); if (!response.ok) return null; const data = await response.json(); if (!data?.access_token) return null; setAccessToken(data.access_token); return { access_token: data.access_token, customer_id: data.customer_id, email: data.email, first_name: data.first_name, }; } catch { return null; } }