export type Result = | { success: true; data: T } | { success: false; error: E }; export type ApiError = { code: string; message: string; statusCode?: number; }; /** * Backend errors arrive as snake_case i18n-style codes (e.g. * "unable_to_log_in"). Map the known ones to friendly copy and prettify * anything else so raw codes never reach the UI. `code` stays raw for * programmatic checks. */ const API_ERROR_MESSAGES: Record = { unable_to_log_in: "Incorrect email or password.", invalid_refresh_token: "Your session has expired. Please sign in again.", session_expired: "Your session has expired. Please sign in again.", session_not_found: "Your session has expired. Please sign in again.", user_not_found: "No account found for these credentials.", }; const SNAKE_CASE_CODE = /^[a-z0-9]+(?:_[a-z0-9]+)+$/; function humanizeApiMessage(raw: string): string { const known = API_ERROR_MESSAGES[raw]; if (known) return known; if (SNAKE_CASE_CODE.test(raw)) { const text = raw.replaceAll("_", " "); return `${text.charAt(0).toUpperCase()}${text.slice(1)}.`; } return raw; } export function extractApiError(err: unknown): ApiError { if (err && typeof err === "object") { const obj = err as Record; const response = obj.response as Record | undefined; if (response) { const statusCode = response.status as number | undefined; const data = response.data as Record | undefined; return { code: (data?.message as string) || (data?.error as string) || "api_error", message: humanizeApiMessage( (data?.message as string) || (data?.error as string) || "An unexpected error occurred", ), statusCode, }; } if (obj.message && typeof obj.message === "string") { return { code: "client_error", message: obj.message }; } } return { code: "unknown_error", message: "An unexpected error occurred" }; }