import { AxiosError } from 'axios'; import { toast } from 'sonner'; import { ERROR_CODES, HTTP_STATUS } from '@/utils/constants'; import { CACHE_CONFIG } from './config'; export interface AppError { message: string; code?: string; statusCode?: number; details?: Record; } export class ValidationError extends Error { constructor( message: string, public field?: string ) { super(message); this.name = 'ValidationError'; } } export class NetworkError extends Error { constructor( message: string, public statusCode?: number ) { super(message); this.name = 'NetworkError'; } } export class ApiError extends Error { constructor( message: string, public statusCode?: number, public code?: string, public details?: Record ) { super(message); this.name = 'ApiError'; } } // Error parsing utility export const parseError = (error: unknown): AppError => { // Axios error if (error instanceof AxiosError) { const statusCode = error.response?.status; const responseData = error.response?.data as Record; const message = typeof responseData?.message === 'string' ? responseData.message : error.message; const code = typeof responseData?.code === 'string' ? responseData.code : error.code; return { message, code, statusCode, details: responseData?.details as Record, }; } if (error instanceof ValidationError) { return { message: error.message, code: ERROR_CODES.VALIDATION_ERROR, details: error.field ? { field: error.field } : undefined, }; } if (error instanceof NetworkError) { return { message: error.message, code: ERROR_CODES.NETWORK_ERROR, statusCode: error.statusCode, }; } if (error instanceof ApiError) { return { message: error.message, code: error.code ?? ERROR_CODES.API_ERROR, statusCode: error.statusCode, details: error.details, }; } if (error instanceof Error) { return { message: error.message, code: ERROR_CODES.UNKNOWN_ERROR, }; } return { message: 'An unexpected error occurred', code: ERROR_CODES.UNKNOWN_ERROR, }; }; export const handleMutationError = ( error: unknown, defaultMessage = 'Operation failed' ) => { const parsedError = parseError(error); const userMessage = getErrorMessage(error); toast.error(userMessage || defaultMessage); console.error('Mutation error:', parsedError); return parsedError; }; export const handleQueryError = (error: unknown, context = 'Loading data') => { const parsedError = parseError(error); const userMessage = getErrorMessage(error); console.error('Query error:', parsedError); toast.error(`${context} failed: ${userMessage}`); return parsedError; }; export const handleMutationSuccess = (message: string, data?: unknown) => { toast.success(message); console.info('Mutation success:', { message, data }); }; // Retry logic for network errors export const shouldRetry = (error: unknown, attemptNumber: number): boolean => { if (attemptNumber >= CACHE_CONFIG.RETRY.MAX_QUERY_RETRIES) return false; const parsedError = parseError(error); // Don't retry client errors (4xx) if ( parsedError.statusCode && parsedError.statusCode >= HTTP_STATUS.BAD_REQUEST && parsedError.statusCode < HTTP_STATUS.INTERNAL_SERVER_ERROR ) { return false; } // Retry network errors and server errors (5xx) return true; }; // User-friendly error messages export const getErrorMessage = (error: unknown): string => { const parsedError = parseError(error); switch (parsedError.code) { case ERROR_CODES.NETWORK_ERROR: return 'Network connection failed. Please check your internet connection.'; case ERROR_CODES.VALIDATION_ERROR: return parsedError.message; case ERROR_CODES.UNAUTHORIZED: return 'You are not authorized to perform this action.'; case ERROR_CODES.FORBIDDEN: return 'Access denied.'; case ERROR_CODES.NOT_FOUND: return 'The requested resource was not found.'; case ERROR_CODES.RATE_LIMIT: return 'Too many requests. Please try again later.'; default: return parsedError.message || 'An unexpected error occurred'; } };