import { Component, createRef } from 'react'; import type { ErrorInfo, ReactNode } from 'react'; import { AlertTriangle } from 'lucide-react'; import { Button } from '@/components/ui/button'; interface Props { children: ReactNode; } interface State { hasError: boolean; error: Error | null; } /** * App-level catch-all error boundary. Sits above feature-specific * boundaries (e.g. `LazyBoundary` around `` blocks) so any * uncaught render error in a non-lazy subtree still produces a * consistent recovery card instead of a blank page. * * Visually matches `LazyBoundary`: glass card, AlertTriangle icon, * single Try-again CTA. The shared aesthetic is intentional so a user * never sees two different "something went wrong" treatments depending * on which boundary catches the error. * * "Try again" resets the boundary's state, which causes React to * re-render the children. For deterministic errors this just shows the * card again, which is the expected behavior of any error boundary; * for transient errors (stale API response, race) it can recover * cleanly. */ class ErrorBoundary extends Component { public state: State = { hasError: false, error: null, }; /** * Ref on the CTA button so we can focus it when the boundary trips. * When a render error fires, focus is typically inside the now- * unmounted subtree and falls back to ; keyboard users would * otherwise have to tab from the top to reach the recovery action. */ private ctaRef = createRef(); public static getDerivedStateFromError(error: Error): State { return { hasError: true, error }; } public componentDidCatch(error: Error, errorInfo: ErrorInfo) { console.error('ErrorBoundary caught an error:', error, errorInfo); } public componentDidUpdate(_prevProps: Props, prevState: State) { if (!prevState.hasError && this.state.hasError) { this.ctaRef.current?.focus(); } } public render() { if (!this.state.hasError) return this.props.children; return (

Something went wrong

{this.state.error?.message || 'Unknown error'}

); } } export default ErrorBoundary;