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 */}
}
onClick={this.handleReload}
sx={{
flex: 1,
py: 1.5,
background: 'linear-gradient(45deg, #667eea 30%, #764ba2 90%)',
'&:hover': {
background: 'linear-gradient(45deg, #5568d3 30%, #6a3f8f 90%)',
},
}}
>
Reload Page
}
onClick={this.handleGoHome}
sx={{
flex: 1,
py: 1.5,
borderColor: '#667eea',
color: '#667eea',
'&:hover': {
borderColor: '#5568d3',
background: 'rgba(102, 126, 234, 0.05)',
},
}}
>
Go Home
{/* Error Details Toggle (for developers/debugging) */}
{(showDetailsDefault || process.env.NODE_ENV === 'development') && (
<>
}
endIcon={
}
onClick={this.toggleDetails}
sx={{ color: '#666' }}
>
{showDetails ? 'Hide' : 'Show'} Technical Details
}
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;