/ src / components / ErrorBoundary.tsx
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  }