import React, { useState, useEffect } from 'react'; import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary'; import { useTranslation } from 'react-i18next'; import { toast } from 'sonner'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from '../Dialog/Dialog'; import { ScrollArea } from '../ScrollArea/ScrollArea'; import { Icons } from '../Icons'; const isProduction = process.env.NODE_ENV === 'production'; interface ErrorBoundaryError extends Error { message: string; stack?: string; } interface DefaultFallbackProps { error: ErrorBoundaryError; context: string; resetErrorBoundary: () => void; } interface ErrorBoundaryProps { context?: string; onReset?: () => void; onError?: (error: ErrorBoundaryError, componentStack: string, context: string) => void; fallbackComponent?: React.ComponentType; children: React.ReactNode; fallbackRoute?: string | null; isPage?: boolean; } const DefaultFallback = ({ error, context, resetErrorBoundary = () => {}, }: DefaultFallbackProps) => { const { t } = useTranslation('ErrorBoundary'); const [showDetails, setShowDetails] = useState(false); const title = `${t('Something went wrong')}${!isProduction && ` ${t('in')} ${context}`}.`; const subtitle = t('Sorry, something went wrong there. Try again.'); const copyErrorDetails = () => { const errorDetails = ` Context: ${context} Error Message: ${error.message} Stack: ${error.stack} `; navigator.clipboard.writeText(errorDetails); toast.success(t('Copied to clipboard')); }; useEffect(() => { toast.error(title, { description: subtitle, action: { label: t('Show Details'), onClick: () => setShowDetails(true), }, duration: 0, }); }, [error]); if (isProduction) { return null; } return ( <> {title} {subtitle}

{t('Context')}: {context}

{t('Error Message')}: {error.message}

                  Stack: {error.stack}
                
); }; const ErrorBoundary = ({ context = 'OHIF', onReset = () => {}, onError = () => {}, fallbackComponent: FallbackComponent = DefaultFallback, children, fallbackRoute = null, isPage, }: ErrorBoundaryProps) => { const [error, setError] = useState(null); const onResetHandler = () => { setError(null); onReset(); }; // Add error event listener to window useEffect(() => { let errorTimeout: NodeJS.Timeout; const handleError = (event: ErrorEvent) => { event.preventDefault(); clearTimeout(errorTimeout); errorTimeout = setTimeout(() => { setError(event.error); onErrorHandler(event.error, null); }, 100); }; const handleRejection = (event: PromiseRejectionEvent) => { event.preventDefault(); clearTimeout(errorTimeout); errorTimeout = setTimeout(() => { setError(event.reason); onErrorHandler(event.reason, null); }, 100); }; window.addEventListener('error', handleError); window.addEventListener('unhandledrejection', handleRejection); return () => { clearTimeout(errorTimeout); window.removeEventListener('error', handleError); window.removeEventListener('unhandledrejection', handleRejection); }; }, []); const onErrorHandler = (error: ErrorBoundaryError, componentStack: string) => { console.debug(`${context} Error Boundary`, error, componentStack, context); onError(error, componentStack, context); }; return ( ( )} onReset={onResetHandler} onError={onErrorHandler} > <> {children} {error && ( setError(null)} /> )} ); }; export { ErrorBoundary };