/** * Normalizes a URL to ensure it has the correct /api prefix * This prevents double prefixing which causes API errors */ function normalizeApiUrl(url: string): string { // Remove any existing /api or api prefix const cleanPath = url.replace(/^\/?(api\/)+/, ''); // Add a single /api prefix return `/api/${cleanPath.replace(/^\//, '')}`; } /** * Get the authentication token from cookies or localStorage */ function getAuthToken(): string | null { if (typeof document === 'undefined') return null; // Guard for SSR return document.cookie .split('; ') .find(row => row.startsWith('Authorization=')) ?.split('=')[1] || localStorage.getItem('Authorization'); } /** * Creates standard API request headers with authentication * * @param token Optional auth token (fetched automatically if not provided) * @param customHeaders Additional headers to include * @returns Headers object ready for fetch requests */ function createApiHeaders(token?: string | null, customHeaders: Record = {}): Headers { const headers = new Headers({ 'Content-Type': 'application/json', 'accept': '*/*', ...customHeaders }); const authToken = token || getAuthToken(); if (authToken) { headers.set('authorization', `Bearer ${authToken}`); } return headers; } /** * Simple client-side fetch function for making API calls with Authorization header. * Ensures all requests go through the Next.js API proxy. */ export async function clientFetch(url: string, options: RequestInit = {}): Promise { try { // Create headers with authentication const headers = createApiHeaders(null, options.headers as Record); // Normalize URL to ensure it uses the Next.js API proxy const fullUrl = normalizeApiUrl(url); const res = await fetch(fullUrl, { ...options, headers, credentials: 'include', mode: 'cors', referrerPolicy: 'strict-origin-when-cross-origin' }); if (!res.ok) { const errorData = await res.json().catch(() => ({})); const errorMessage = errorData.message || errorData.error || `Request failed: ${res.status} ${res.statusText}`; throw new Error(errorMessage); } // Handle 204 No Content responses if (res.status === 204) { return {} as T; } return await res.json(); } catch (error) { console.error(`Client fetch error at ${url}:`, error); throw error; } } /** * Get a cookie value by name */ export function getCookie(name: string): string | undefined { return document.cookie .split('; ') .find(row => row.startsWith(`${name}=`)) ?.split('=')[1]; }