/** * Error Handler Utility * Provides centralized error handling with multilingual support * Preserves backend error messages which are already localized in 5 languages: * - English (en) * - Spanish (es) * - French (fr) * - Portuguese (pt) * - Chinese (zh) */ export interface ErrorResponse { code: string; message: string; field?: string; details?: any; } export interface ExtractedError { code: string; message: string; field?: string; details?: any; isBackendError: boolean; } /** * Extract error information from various error types * Prioritizes backend error messages to preserve multilingual support */ export function extractError(error: any): ExtractedError { // Default error const defaultError: ExtractedError = { code: 'UNKNOWN_ERROR', message: 'An unexpected error occurred. Please try again.', isBackendError: false, }; // If no error, return default if (!error) { return defaultError; } // Axios error response with backend error if (error.response?.data?.error) { const backendError = error.response.data.error; return { code: backendError.code || 'BACKEND_ERROR', message: backendError.message || defaultError.message, field: backendError.field, details: backendError.details, isBackendError: true, }; } // Axios error response with message if (error.response?.data?.message) { return { code: error.response.data.code || 'BACKEND_ERROR', message: error.response.data.message, field: error.response.data.field, details: error.response.data.details, isBackendError: true, }; } // Network errors if (error.code === 'ERR_NETWORK' || error.message === 'Network Error') { return { code: 'NETWORK_ERROR', message: 'Unable to connect to the server. Please check your internet connection.', isBackendError: false, }; } // Timeout errors if (error.code === 'ECONNABORTED' || error.message?.includes('timeout')) { return { code: 'TIMEOUT_ERROR', message: 'Request timed out. Please try again.', isBackendError: false, }; } // HTTP status code errors if (error.response?.status) { const status = error.response.status; if (status === 401) { return { code: 'UNAUTHORIZED', message: 'Your session has expired. Please log in again.', isBackendError: false, }; } if (status === 403) { return { code: 'FORBIDDEN', message: 'You do not have permission to perform this action.', isBackendError: false, }; } if (status === 404) { return { code: 'NOT_FOUND', message: 'The requested resource was not found.', isBackendError: false, }; } if (status === 429) { return { code: 'RATE_LIMIT_EXCEEDED', message: 'Too many requests. Please try again later.', isBackendError: false, }; } if (status >= 500) { return { code: 'SERVER_ERROR', message: 'A server error occurred. Please try again later.', isBackendError: false, }; } } // Generic error with message if (error.message) { return { code: error.code || 'ERROR', message: error.message, isBackendError: false, }; } // String error if (typeof error === 'string') { return { code: 'ERROR', message: error, isBackendError: false, }; } return defaultError; } /** * Format error message for display * Preserves backend messages (which are already localized) */ export function formatErrorMessage(error: any): string { const extracted = extractError(error); return extracted.message; } /** * Get user-friendly error message based on error code * Only used for client-side errors; backend errors use their own messages */ export function getUserFriendlyMessage(errorCode: string): string { const messages: Record = { // Network errors NETWORK_ERROR: 'Unable to connect to the server. Please check your internet connection.', TIMEOUT_ERROR: 'Request timed out. Please try again.', // Authentication errors UNAUTHORIZED: 'Your session has expired. Please log in again.', FORBIDDEN: 'You do not have permission to perform this action.', // HTTP errors NOT_FOUND: 'The requested resource was not found.', RATE_LIMIT_EXCEEDED: 'Too many requests. Please try again later.', SERVER_ERROR: 'A server error occurred. Please try again later.', // Generic errors UNKNOWN_ERROR: 'An unexpected error occurred. Please try again.', ERROR: 'An error occurred. Please try again.', }; return messages[errorCode] || messages.UNKNOWN_ERROR; } /** * Check if error is a specific type */ export function isErrorType(error: any, errorCode: string): boolean { const extracted = extractError(error); return extracted.code === errorCode; } /** * Check if error is a network error */ export function isNetworkError(error: any): boolean { return isErrorType(error, 'NETWORK_ERROR'); } /** * Check if error is an authentication error */ export function isAuthError(error: any): boolean { return isErrorType(error, 'UNAUTHORIZED') || isErrorType(error, 'AUTH_TOKEN_EXPIRED'); } /** * Check if error is a validation error */ export function isValidationError(error: any): boolean { const extracted = extractError(error); return extracted.code.startsWith('VALIDATION_') || extracted.field !== undefined; } /** * Get field-specific error message */ export function getFieldError(error: any, fieldName: string): string | null { const extracted = extractError(error); if (extracted.field === fieldName) { return extracted.message; } if (extracted.details && typeof extracted.details === 'object') { return extracted.details[fieldName] || null; } return null; } /** * Log error for debugging (can be extended with error tracking service) */ export function logError(error: any, context?: string): void { const extracted = extractError(error); console.error('[Error Handler]', { context, code: extracted.code, message: extracted.message, field: extracted.field, details: extracted.details, isBackendError: extracted.isBackendError, originalError: error, }); // TODO: Send to error tracking service (Sentry, LogRocket, etc.) // Example: // if (window.Sentry) { // window.Sentry.captureException(error, { // tags: { code: extracted.code }, // contexts: { errorHandler: { context } }, // }); // } } /** * Handle error and return user-friendly message * Main function to use in catch blocks */ export function handleError(error: any, context?: string): string { logError(error, context); return formatErrorMessage(error); }