96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
/**
|
||
* Error Boundary component to catch React errors
|
||
*/
|
||
|
||
import { Component, type ReactNode } from 'react';
|
||
|
||
interface ErrorBoundaryProps {
|
||
children: ReactNode;
|
||
fallback?: (error: Error, resetError: () => void) => ReactNode;
|
||
}
|
||
|
||
interface ErrorBoundaryState {
|
||
hasError: boolean;
|
||
error: Error | null;
|
||
}
|
||
|
||
/**
|
||
* Error boundary that catches React errors and displays a fallback UI
|
||
*/
|
||
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||
constructor(props: ErrorBoundaryProps) {
|
||
super(props);
|
||
this.state = {
|
||
hasError: false,
|
||
error: null,
|
||
};
|
||
}
|
||
|
||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||
return {
|
||
hasError: true,
|
||
error,
|
||
};
|
||
}
|
||
|
||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
||
console.error('Error boundary caught error:', error, errorInfo);
|
||
}
|
||
|
||
resetError = (): void => {
|
||
this.setState({
|
||
hasError: false,
|
||
error: null,
|
||
});
|
||
};
|
||
|
||
render(): ReactNode {
|
||
if (this.state.hasError && this.state.error) {
|
||
if (this.props.fallback) {
|
||
return this.props.fallback(this.state.error, this.resetError);
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-6">
|
||
<div className="text-center mb-4">
|
||
<div className="text-6xl mb-4">⚠️</div>
|
||
<h2 className="text-2xl font-bold text-gray-900 mb-2">Something went wrong</h2>
|
||
<p className="text-gray-600 mb-4">
|
||
An unexpected error occurred. Please try refreshing the page.
|
||
</p>
|
||
</div>
|
||
|
||
<details className="mb-4">
|
||
<summary className="cursor-pointer text-sm text-gray-600 hover:text-gray-800 font-medium">
|
||
Error details
|
||
</summary>
|
||
<pre className="mt-2 p-3 bg-gray-50 rounded text-xs text-red-600 overflow-auto">
|
||
{this.state.error.toString()}
|
||
{this.state.error.stack && `\n\n${this.state.error.stack}`}
|
||
</pre>
|
||
</details>
|
||
|
||
<div className="flex gap-3">
|
||
<button
|
||
onClick={() => window.location.reload()}
|
||
className="flex-1 bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 font-medium"
|
||
>
|
||
Refresh Page
|
||
</button>
|
||
<button
|
||
onClick={this.resetError}
|
||
className="flex-1 bg-gray-200 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-300 font-medium"
|
||
>
|
||
Try Again
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return this.props.children;
|
||
}
|
||
}
|