BillTracker/client/components/ErrorBoundary.jsx

119 lines
4.7 KiB
JavaScript

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;