// ============================================================================ // API Client // ============================================================================ import { API_URL, STORAGE_KEYS } from "@/lib/utils/constants"; import type { ApiError } from "@/lib/types/api"; // ============================================================================ // Types // ============================================================================ interface RequestOptions extends RequestInit { params?: Record; requireAuth?: boolean; } // ============================================================================ // Helper Functions // ============================================================================ /** * Build URL with query parameters */ const buildUrl = (endpoint: string, params?: Record): string => { if (!params || Object.keys(params).length === 0) { return endpoint; } const searchParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { if (value !== undefined && value !== null) { searchParams.append(key, String(value)); } }); const queryString = searchParams.toString(); return queryString ? `${endpoint}?${queryString}` : endpoint; }; /** * Get auth token from storage */ const getAuthToken = (): string | null => { if (typeof window === "undefined") return null; return localStorage.getItem(STORAGE_KEYS.ACCESS_TOKEN); }; /** * Clear auth tokens from storage */ export const clearAuthTokens = (): void => { if (typeof window === "undefined") return; localStorage.removeItem(STORAGE_KEYS.ACCESS_TOKEN); localStorage.removeItem(STORAGE_KEYS.REFRESH_TOKEN); localStorage.removeItem(STORAGE_KEYS.USER); }; /** * Save auth tokens to storage */ export const saveAuthTokens = ( accessToken: string, refreshToken?: string ): void => { if (typeof window === "undefined") return; localStorage.setItem(STORAGE_KEYS.ACCESS_TOKEN, accessToken); if (refreshToken) { localStorage.setItem(STORAGE_KEYS.REFRESH_TOKEN, refreshToken); } }; // ============================================================================ // API Client // ============================================================================ /** * Main API request function */ export const apiRequest = async ( endpoint: string, options: RequestOptions = {} ): Promise => { const { params, requireAuth = true, ...fetchOptions } = options; // Build URL const url = buildUrl(`${API_URL}${endpoint}`, params); const headers: HeadersInit = { "Content-Type": "application/json", ...fetchOptions.headers, }; // Add auth token if required if (requireAuth) { const token = getAuthToken(); if (token) { (headers as Record)["Authorization"] = `Bearer ${token}`; } } try { const response = await fetch(url, { ...fetchOptions, headers, }); // Handle 204 No Content if (response.status === 204) { return undefined as T; } // Parse response const data = await response.json(); // Handle errors if (!response.ok) { // FastAPI returns errors in format { "detail": "..." } const error: ApiError = { detail: data.detail || "An error occurred", }; throw error; } return data as T; } catch (error: any) { // Network error or parsing error if (!error.detail) { throw { detail: error.message || "Network error occurred", } as ApiError; } throw error; } }; // ============================================================================ // Convenience Methods // ============================================================================ export const api = { get: (endpoint: string, options?: RequestOptions) => apiRequest(endpoint, { ...options, method: "GET" }), post: (endpoint: string, body?: any, options?: RequestOptions) => apiRequest(endpoint, { ...options, method: "POST", body: body ? JSON.stringify(body) : undefined, }), put: (endpoint: string, body?: any, options?: RequestOptions) => apiRequest(endpoint, { ...options, method: "PUT", body: body ? JSON.stringify(body) : undefined, }), patch: (endpoint: string, body?: any, options?: RequestOptions) => apiRequest(endpoint, { ...options, method: "PATCH", body: body ? JSON.stringify(body) : undefined, }), delete: (endpoint: string, options?: RequestOptions) => apiRequest(endpoint, { ...options, method: "DELETE" }), };