119 lines
4.7 KiB
React
119 lines
4.7 KiB
React
|
|
import React from 'react';
|
||
|
|
import { AlertTriangle, RefreshCw } from 'lucide-react';
|
||
|
|
import { Button } from '@/components/ui/button';
|
||
|
|
import { cn } from '@/lib/utils';
|
||
|
|
|
||
|
|
// ────────────────────────────────────────────────────────────────────────────
|
||
|
|
// ErrorBoundary Component
|
||
|
|
// ────────────────────────────────────────────────────────────────────────────
|
||
|
|
class ErrorBoundary extends React.Component {
|
||
|
|
constructor(props) {
|
||
|
|
super(props);
|
||
|
|
this.state = { hasError: false, error: null, componentStack: null };
|
||
|
|
}
|
||
|
|
|
||
|
|
static getDerivedStateFromError(error) {
|
||
|
|
return { hasError: true, error };
|
||
|
|
}
|
||
|
|
|
||
|
|
componentDidCatch(error, info) {
|
||
|
|
console.error('ErrorBoundary caught an error:', {
|
||
|
|
error,
|
||
|
|
componentStack: info?.componentStack,
|
||
|
|
});
|
||
|
|
this.setState({ error, componentStack: info?.componentStack });
|
||
|
|
}
|
||
|
|
|
||
|
|
handleReset = () => {
|
||
|
|
this.setState({ hasError: false, error: null, componentStack: null });
|
||
|
|
};
|
||
|
|
|
||
|
|
handleReload = () => {
|
||
|
|
window.location.reload();
|
||
|
|
};
|
||
|
|
|
||
|
|
render() {
|
||
|
|
if (!this.state.hasError) {
|
||
|
|
return this.props.children;
|
||
|
|
}
|
||
|
|
|
||
|
|
const { error, componentStack } = this.state;
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="min-h-screen bg-background flex items-center justify-center p-4">
|
||
|
|
<div className="max-w-2xl w-full rounded-xl border border-destructive/20 bg-destructive/5 px-6 py-8 text-center">
|
||
|
|
<div className="mx-auto mb-6 h-16 w-16 rounded-full bg-destructive text-destructive-foreground flex items-center justify-center">
|
||
|
|
<AlertTriangle className="h-8 w-8" />
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h1 className="text-2xl font-bold tracking-tight text-foreground mb-2">
|
||
|
|
Something went wrong
|
||
|
|
</h1>
|
||
|
|
|
||
|
|
<p className="text-sm text-muted-foreground mb-6">
|
||
|
|
An unexpected error occurred. You can try to recover by reloading the page or resetting this component.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
{error && (
|
||
|
|
<div className="mb-6 rounded-lg border border-destructive/30 bg-destructive/10 p-4 text-left">
|
||
|
|
<p className="text-xs font-semibold uppercase tracking-wider text-destructive mb-2">
|
||
|
|
Error Message
|
||
|
|
</p>
|
||
|
|
<pre className="text-xs text-destructive-foreground font-mono overflow-auto max-h-32">
|
||
|
|
{String(error)}
|
||
|
|
</pre>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
{componentStack && (
|
||
|
|
<div className="mb-6 rounded-lg border border-destructive/20 bg-destructive/5 p-4">
|
||
|
|
<p className="text-[10px] font-semibold uppercase tracking-wider text-destructive/70 mb-2">
|
||
|
|
Component Stack (for debugging)
|
||
|
|
</p>
|
||
|
|
<pre className="text-[10px] text-destructive-foreground/60 font-mono overflow-auto max-h-24">
|
||
|
|
{componentStack}
|
||
|
|
</pre>
|
||
|
|
</div>
|
||
|
|
)}
|
||
|
|
|
||
|
|
<div className="flex flex-wrap items-center justify-center gap-3">
|
||
|
|
<Button
|
||
|
|
variant="default"
|
||
|
|
onClick={this.handleReset}
|
||
|
|
className="flex items-center gap-2 text-xs"
|
||
|
|
>
|
||
|
|
<RefreshCw className="h-3 w-3" />
|
||
|
|
Try Again
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
variant="outline"
|
||
|
|
onClick={this.handleReload}
|
||
|
|
className="flex items-center gap-2 text-xs border-destructive/30 hover:bg-destructive/10 hover:text-destructive hover:border-destructive/50"
|
||
|
|
>
|
||
|
|
Reload Page
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// ────────────────────────────────────────────────────────────────────────────
|
||
|
|
//withErrorBoundary HOC
|
||
|
|
// ────────────────────────────────────────────────────────────────────────────
|
||
|
|
export function withErrorBoundary(Component, displayName = Component.name || 'Component') {
|
||
|
|
function WrappedComponent(props) {
|
||
|
|
return (
|
||
|
|
<ErrorBoundary>
|
||
|
|
<Component {...props} />
|
||
|
|
</ErrorBoundary>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
WrappedComponent.displayName = `withErrorBoundary(${displayName})`;
|
||
|
|
return WrappedComponent;
|
||
|
|
}
|
||
|
|
|
||
|
|
export default ErrorBoundary;
|