← back to Grant
components/ErrorBoundary.tsx
127 lines
'use client';
import { Component, ErrorInfo, ReactNode } from 'react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
interface ErrorBoundaryProps {
children: ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
export default 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: ErrorInfo) {
// Log error for debugging -- could be sent to an error reporting service
if (typeof window !== 'undefined') {
console.error('ErrorBoundary caught:', error, errorInfo);
}
}
handleReload = () => {
window.location.reload();
};
handleReset = () => {
this.setState({ hasError: false, error: null });
};
render() {
if (this.state.hasError) {
return (
<div
className="flex flex-col items-center justify-center p-8 text-center"
style={{
minHeight: '60vh',
backgroundColor: 'var(--color-bg)',
}}
>
<div
className="w-16 h-16 rounded-2xl flex items-center justify-center mb-6"
style={{
backgroundColor: 'rgba(239, 68, 68, 0.12)',
border: '1px solid rgba(239, 68, 68, 0.25)',
}}
>
<AlertTriangle size={28} style={{ color: '#f87171' }} />
</div>
<h2
className="text-lg font-bold mb-2"
style={{ color: 'var(--color-text)' }}
>
Something went wrong
</h2>
<p
className="text-sm max-w-md mb-6 leading-relaxed"
style={{ color: 'var(--color-text-muted)' }}
>
An unexpected error occurred while rendering this section.
You can try reloading the page or resetting the view.
</p>
{/* Error details (collapsed by default for dev) */}
{this.state.error && (
<details
className="mb-6 w-full max-w-lg text-left"
style={{
backgroundColor: 'var(--color-surface)',
border: '1px solid var(--color-border)',
borderRadius: 10,
padding: '12px 16px',
}}
>
<summary
className="text-xs font-medium cursor-pointer"
style={{ color: 'var(--color-text-secondary)' }}
>
Error details
</summary>
<pre
className="mt-2 text-xs overflow-auto"
style={{
color: '#f87171',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
maxHeight: 160,
}}
>
{this.state.error.message}
</pre>
</details>
)}
<div className="flex gap-3">
<button
className="btn btn-secondary"
onClick={this.handleReset}
>
Try Again
</button>
<button
className="btn btn-primary"
onClick={this.handleReload}
>
<RefreshCw size={15} />
Reload Page
</button>
</div>
</div>
);
}
return this.props.children;
}
}