/** * Frontend API client utilities with standardized error handling */ export interface ApiErrorResponse { error?: string; message?: string; details?: Record; } /** * Parse API response body safely */ async function parseResponseBody( response: Response, ): Promise { try { return (await response.json()) as ApiErrorResponse; } catch { return null; } } /** * Get user-friendly error message based on HTTP status */ export function getErrorMessageForStatus( status: number, serverMessage?: string, ): string { // Use server message if provided and meaningful if (serverMessage && serverMessage.length < 100) { return serverMessage; } switch (true) { case status === 401: return "Please sign in to continue."; case status === 403: return "You don't have permission to access this."; case status === 404: return "The requested resource was not found."; case status === 503: return "Service temporarily unavailable. Please try again in a moment."; case status >= 500: return "Server error. Please try again later."; case status >= 400: return serverMessage || "Invalid request."; default: return "Something went wrong."; } } /** * Check if a status code indicates the request can be retried */ export function isRetryableStatus(status: number): boolean { return status === 503 || status === 429 || status >= 500; } /** * Process an API response and return result or error info */ export async function processApiResponse(response: Response): Promise< | { data: T; error: null } | { data: null; error: { status: number; message: string; retryable: boolean }; } > { if (response.ok) { try { const data = (await response.json()) as T; return { data, error: null }; } catch { // Empty response is ok for some endpoints return { data: null as T, error: null }; } } const body = await parseResponseBody(response); const message = getErrorMessageForStatus( response.status, body?.error || body?.message, ); return { data: null, error: { status: response.status, message, retryable: isRetryableStatus(response.status), }, }; } /** * Simplified fetch with error info returned (no throwing) */ export async function safeFetch( url: string, options?: RequestInit, ): Promise<{ data: T | null; error: { status: number; message: string; retryable: boolean } | null; }> { try { const response = await fetch(url, options); return processApiResponse(response); } catch (_error) { // Network error return { data: null, error: { status: 0, message: "Network error. Check your connection.", retryable: true, }, }; } }