import React, { Component, ErrorInfo, ReactNode } from 'react'; import { Box, Button, Typography, Paper, Container, Stack, Alert, Collapse, Divider } from '@mui/material'; import { ErrorOutline as ErrorIcon, Refresh as RefreshIcon, Home as HomeIcon, ExpandMore as ExpandMoreIcon, BugReport as BugReportIcon } from '@mui/icons-material'; interface ErrorBoundaryProps { children: ReactNode; fallback?: ReactNode; onError?: (error: Error, errorInfo: ErrorInfo) => void; showDetails?: boolean; context?: string; // Context for better error messages (e.g., "Onboarding Wizard") } interface ErrorBoundaryState { hasError: boolean; error: Error | null; errorInfo: ErrorInfo | null; showDetails: boolean; } /** * ErrorBoundary Component * * Catches JavaScript errors anywhere in the child component tree, * logs those errors, and displays a fallback UI instead of blank screen. * * Usage: * * * */ class ErrorBoundary extends Component { constructor(props: ErrorBoundaryProps) { super(props); this.state = { hasError: false, error: null, errorInfo: null, showDetails: false, }; } static getDerivedStateFromError(error: Error): Partial { // Update state so the next render will show the fallback UI return { hasError: true, error, }; } componentDidCatch(error: Error, errorInfo: ErrorInfo) { // Log error details console.error('ErrorBoundary caught an error:', error, errorInfo); // Update state with error info this.setState({ error, errorInfo, }); // Call custom error handler if provided if (this.props.onError) { this.props.onError(error, errorInfo); } // Send to error tracking service (Sentry, LogRocket, etc.) this.logErrorToService(error, errorInfo); } logErrorToService(error: Error, errorInfo: ErrorInfo) { try { // Import error reporting utility import('../../utils/errorReporting').then(({ reportError }) => { reportError({ error, context: this.props.context || 'ErrorBoundary', metadata: { componentStack: errorInfo.componentStack, errorBoundary: true, }, severity: 'high', // Rendering errors are high severity timestamp: new Date().toISOString(), }); }).catch(console.error); // Log to console with detailed info console.group('🚨 Error Boundary - Error Details'); console.error('Error:', error); console.error('Error Info:', errorInfo); console.error('Component Stack:', errorInfo.componentStack); console.error('Context:', this.props.context || 'Global'); console.error('Timestamp:', new Date().toISOString()); console.groupEnd(); } catch (loggingError) { console.error('Failed to log error:', loggingError); } } handleReset = () => { this.setState({ hasError: false, error: null, errorInfo: null, showDetails: false, }); }; handleReload = () => { window.location.reload(); }; handleGoHome = () => { window.location.href = '/'; }; toggleDetails = () => { this.setState((prevState) => ({ showDetails: !prevState.showDetails, })); }; render() { if (this.state.hasError) { const { error } = this.state; // Custom fallback UI provided if (this.props.fallback) { return this.props.fallback; } // Default fallback UI const { errorInfo, showDetails } = this.state; const { context, showDetails: showDetailsDefault } = this.props; return ( {/* Error Icon */} {/* Error Title */} Oops! Something went wrong {/* Context Message */} {context && ( An error occurred in: {context} )} {/* User-friendly message */} We're sorry for the inconvenience. The error has been logged and our team will investigate. In the meantime, you can try refreshing the page or returning to the home page. {/* Action Buttons */} {/* Error Details Toggle (for developers/debugging) */} {(showDetailsDefault || process.env.NODE_ENV === 'development') && ( <> } sx={{ textAlign: 'left', '& .MuiAlert-message': { width: '100%', }, }} > Error Message: {error?.toString()} {error?.stack && ( <> Stack Trace: {error.stack} )} {errorInfo?.componentStack && ( <> Component Stack: {errorInfo.componentStack} )} )} {/* Help Text */} Error ID: {Date.now().toString(36)} • Timestamp: {new Date().toLocaleString()} ); } // No error, render children normally return this.props.children; } } export default ErrorBoundary;