import { toast } from 'sonner'; import { logger } from './logger'; import { supabase } from '@/integrations/supabase/client'; import { breadcrumbManager } from './errorBreadcrumbs'; import { captureEnvironmentContext } from './environmentContext'; export type ErrorContext = { action: string; userId?: string; metadata?: Record; }; export class AppError extends Error { constructor( message: string, public code: string, public userMessage?: string ) { super(message); this.name = 'AppError'; } } export const handleError = ( error: unknown, context: ErrorContext ): string => { // Generate or use existing error ID const errorId = (context.metadata?.requestId as string) || crypto.randomUUID(); const shortErrorId = errorId.slice(0, 8); const errorMessage = error instanceof AppError ? error.userMessage || error.message : error instanceof Error ? error.message : 'An unexpected error occurred'; // Log to console/monitoring with enhanced debugging const stack = error instanceof Error ? error.stack : undefined; logger.error('Error occurred', { ...context, error: error instanceof Error ? error.message : String(error), stack, errorId, errorType: typeof error, errorConstructor: error?.constructor?.name, hasStack: !!stack, }); // Additional debug logging when stack is missing if (!stack) { console.error('[handleError] Error without stack trace:', { type: typeof error, constructor: error?.constructor?.name, error: error, context, errorId }); } // Log to database with breadcrumbs (non-blocking) try { const envContext = captureEnvironmentContext(); const breadcrumbs = breadcrumbManager.getAll(); // Fire-and-forget database logging supabase.rpc('log_request_metadata', { p_request_id: errorId, p_user_id: context.userId || undefined, p_endpoint: context.action, p_method: 'ERROR', p_status_code: 500, p_error_type: error instanceof Error ? error.name : 'UnknownError', p_error_message: errorMessage, p_error_stack: error instanceof Error ? error.stack : undefined, p_user_agent: navigator.userAgent, p_breadcrumbs: JSON.stringify(breadcrumbs), p_timezone: envContext.timezone, p_referrer: document.referrer || undefined, }).then(({ error: dbError }) => { if (dbError) { logger.error('Failed to log error to database', { dbError }); } }); } catch (logError) { logger.error('Failed to capture error context', { logError }); } // Show user-friendly toast with error ID toast.error(context.action, { description: `${errorMessage}\n\nReference ID: ${shortErrorId}`, duration: 5000, }); return errorId; }; export const handleSuccess = ( title: string, description?: string ): void => { toast.success(title, { description, duration: 3000 }); }; export const handleInfo = ( title: string, description?: string ): void => { toast.info(title, { description, duration: 4000 }); }; /** * Handle non-critical errors (background failures) that should be logged * to the database WITHOUT showing user toasts * Use this for fire-and-forget operations where user shouldn't be interrupted */ export const handleNonCriticalError = ( error: unknown, context: ErrorContext ): string => { const errorId = crypto.randomUUID(); const shortErrorId = errorId.slice(0, 8); const errorMessage = error instanceof AppError ? error.userMessage || error.message : error instanceof Error ? error.message : 'An unexpected error occurred'; // Log to console/monitoring (same as handleError) logger.error('Non-critical error occurred', { ...context, error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined, errorId, severity: 'low', }); // Log to database with breadcrumbs (non-blocking, fire-and-forget) try { const envContext = captureEnvironmentContext(); const breadcrumbs = breadcrumbManager.getAll(); supabase.rpc('log_request_metadata', { p_request_id: errorId, p_user_id: context.userId || undefined, p_endpoint: context.action, p_method: 'NON_CRITICAL_ERROR', p_status_code: 500, p_error_type: error instanceof Error ? error.name : 'UnknownError', p_error_message: errorMessage, p_error_stack: error instanceof Error ? error.stack : undefined, p_user_agent: navigator.userAgent, p_breadcrumbs: JSON.stringify(breadcrumbs), p_timezone: envContext.timezone, p_referrer: document.referrer || undefined, }).then(({ error: dbError }) => { if (dbError) { logger.error('Failed to log non-critical error to database', { dbError }); } }); } catch (logError) { logger.error('Failed to capture non-critical error context', { logError }); } // NO TOAST - This is the key difference from handleError() return errorId; }; /** * Type-safe error message extraction utility * Use this instead of `error: any` in catch blocks */ export function getErrorMessage(error: unknown): string { if (error instanceof Error) { return error.message; } if (typeof error === 'string') { return error; } if (error && typeof error === 'object' && 'message' in error) { return String(error.message); } return 'An unexpected error occurred'; } /** * Type guard to check if error has a code property */ export function hasErrorCode(error: unknown): error is { code: string } { return ( error !== null && typeof error === 'object' && 'code' in error && typeof (error as { code: unknown }).code === 'string' ); }