ErrorBoundary.tsx
1 import { Component, ErrorInfo, ReactNode } from 'react'; 2 3 interface ErrorBoundaryState { 4 hasError: boolean; 5 error: Error | null; 6 errorInfo: ErrorInfo | null; 7 } 8 9 interface ErrorBoundaryProps { 10 children: ReactNode; 11 fallback?: ReactNode; 12 } 13 14 15 16 export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> { 17 constructor(props: ErrorBoundaryProps) { 18 super(props); 19 this.state = { 20 hasError: false, 21 error: null, 22 errorInfo: null, 23 }; 24 } 25 26 static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> { 27 return { 28 hasError: true, 29 error, 30 }; 31 } 32 33 componentDidCatch(error: Error, errorInfo: ErrorInfo) { 34 console.error('Error caught by ErrorBoundary:', error, errorInfo); 35 36 this.setState({ 37 error, 38 errorInfo, 39 }); 40 } 41 42 handleReset = () => { 43 this.setState({ 44 hasError: false, 45 error: null, 46 errorInfo: null, 47 }); 48 }; 49 50 render() { 51 if (this.state.hasError) { 52 if (this.props.fallback) { 53 return this.props.fallback; 54 } 55 56 return ( 57 <div className="min-h-screen bg-background flex items-center justify-center p-4"> 58 <div className="max-w-md w-full space-y-4"> 59 <div className="text-center"> 60 <h2 className="text-2xl font-bold text-foreground mb-2"> 61 Something went wrong 62 </h2> 63 <p className="text-muted-foreground"> 64 An unexpected error occurred. The error has been reported. 65 </p> 66 </div> 67 68 <div className="bg-muted p-4 rounded-lg"> 69 <details className="text-sm"> 70 <summary className="cursor-pointer font-medium text-foreground"> 71 Error details 72 </summary> 73 <div className="mt-2 space-y-2"> 74 <div> 75 <strong className="text-foreground">Message:</strong> 76 <p className="text-muted-foreground mt-1"> 77 {this.state.error?.message} 78 </p> 79 </div> 80 {this.state.error?.stack && ( 81 <div> 82 <strong className="text-foreground">Stack trace:</strong> 83 <pre className="text-xs text-muted-foreground mt-1 overflow-auto max-h-32"> 84 {this.state.error.stack} 85 </pre> 86 </div> 87 )} 88 </div> 89 </details> 90 </div> 91 92 <div className="flex gap-2"> 93 <button 94 onClick={this.handleReset} 95 className="flex-1 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors" 96 > 97 Try again 98 </button> 99 <button 100 onClick={() => window.location.reload()} 101 className="flex-1 px-4 py-2 bg-secondary text-secondary-foreground rounded-md hover:bg-secondary/90 transition-colors" 102 > 103 Reload page 104 </button> 105 </div> 106 </div> 107 </div> 108 ); 109 } 110 111 return this.props.children; 112 } 113 }