| import { toast } from 'sonner'; | |
| import { BillingError } from './api'; | |
| export interface ApiError extends Error { | |
| status?: number; | |
| code?: string; | |
| details?: any; | |
| response?: Response; | |
| } | |
| export interface ErrorContext { | |
| operation?: string; | |
| resource?: string; | |
| silent?: boolean; | |
| } | |
| const getStatusMessage = (status: number): string => { | |
| switch (status) { | |
| case 400: | |
| return 'Invalid request. Please check your input and try again.'; | |
| case 401: | |
| return 'Authentication required. Please sign in again.'; | |
| case 403: | |
| return 'Access denied. You don\'t have permission to perform this action.'; | |
| case 404: | |
| return 'The requested resource was not found.'; | |
| case 408: | |
| return 'Request timeout. Please try again.'; | |
| case 409: | |
| return 'Conflict detected. The resource may have been modified by another user.'; | |
| case 422: | |
| return 'Invalid data provided. Please check your input.'; | |
| case 429: | |
| return 'Too many requests. Please wait a moment and try again.'; | |
| case 500: | |
| return 'Server error. Our team has been notified.'; | |
| case 502: | |
| return 'Service temporarily unavailable. Please try again in a moment.'; | |
| case 503: | |
| return 'Service maintenance in progress. Please try again later.'; | |
| case 504: | |
| return 'Request timeout. The server took too long to respond.'; | |
| default: | |
| return 'An unexpected error occurred. Please try again.'; | |
| } | |
| }; | |
| const extractErrorMessage = (error: any): string => { | |
| if (error instanceof BillingError) { | |
| return error.detail?.message || error.message || 'Billing issue detected'; | |
| } | |
| if (error instanceof Error) { | |
| return error.message; | |
| } | |
| if (error?.response) { | |
| const status = error.response.status; | |
| return getStatusMessage(status); | |
| } | |
| if (error?.status) { | |
| return getStatusMessage(error.status); | |
| } | |
| if (typeof error === 'string') { | |
| return error; | |
| } | |
| if (error?.message) { | |
| return error.message; | |
| } | |
| if (error?.error) { | |
| return typeof error.error === 'string' ? error.error : error.error.message || 'Unknown error'; | |
| } | |
| return 'An unexpected error occurred'; | |
| }; | |
| const shouldShowError = (error: any, context?: ErrorContext): boolean => { | |
| if (context?.silent) { | |
| return false; | |
| } | |
| if (error instanceof BillingError) { | |
| return false; | |
| } | |
| if (error?.status === 404 && context?.resource) { | |
| return false; | |
| } | |
| return true; | |
| }; | |
| const formatErrorMessage = (message: string, context?: ErrorContext): string => { | |
| if (!context?.operation && !context?.resource) { | |
| return message; | |
| } | |
| const parts = []; | |
| if (context.operation) { | |
| parts.push(`Failed to ${context.operation}`); | |
| } | |
| if (context.resource) { | |
| parts.push(context.resource); | |
| } | |
| const prefix = parts.join(' '); | |
| if (message.toLowerCase().includes(context.operation?.toLowerCase() || '')) { | |
| return message; | |
| } | |
| return `${prefix}: ${message}`; | |
| }; | |
| export const handleApiError = (error: any, context?: ErrorContext): void => { | |
| console.error('API Error:', error, context); | |
| if (!shouldShowError(error, context)) { | |
| return; | |
| } | |
| const rawMessage = extractErrorMessage(error); | |
| const formattedMessage = formatErrorMessage(rawMessage, context); | |
| if (error?.status >= 500) { | |
| toast.error(formattedMessage, { | |
| description: 'Our team has been notified and is working on a fix.', | |
| duration: 6000, | |
| }); | |
| } else if (error?.status === 401) { | |
| toast.error(formattedMessage, { | |
| description: 'Please refresh the page and sign in again.', | |
| duration: 8000, | |
| }); | |
| } else if (error?.status === 403) { | |
| toast.error(formattedMessage, { | |
| description: 'Contact support if you believe this is an error.', | |
| duration: 6000, | |
| }); | |
| } else if (error?.status === 429) { | |
| toast.warning(formattedMessage, { | |
| description: 'Please wait a moment before trying again.', | |
| duration: 5000, | |
| }); | |
| } else { | |
| toast.error(formattedMessage, { | |
| duration: 5000, | |
| }); | |
| } | |
| }; | |
| export const handleNetworkError = (error: any, context?: ErrorContext): void => { | |
| const isNetworkError = | |
| error?.message?.includes('fetch') || | |
| error?.message?.includes('network') || | |
| error?.message?.includes('connection') || | |
| error?.code === 'NETWORK_ERROR' || | |
| !navigator.onLine; | |
| if (isNetworkError) { | |
| toast.error('Connection error', { | |
| description: 'Please check your internet connection and try again.', | |
| duration: 6000, | |
| }); | |
| } else { | |
| handleApiError(error, context); | |
| } | |
| }; | |
| export const handleApiSuccess = (message: string, description?: string): void => { | |
| toast.success(message, { | |
| description, | |
| duration: 3000, | |
| }); | |
| }; | |
| export const handleApiWarning = (message: string, description?: string): void => { | |
| toast.warning(message, { | |
| description, | |
| duration: 4000, | |
| }); | |
| }; | |
| export const handleApiInfo = (message: string, description?: string): void => { | |
| toast.info(message, { | |
| description, | |
| duration: 3000, | |
| }); | |
| }; |