feat(frontend): add ErrorBoundary component

Add React ErrorBoundary component for catching and handling
render errors in component trees with fallback UI.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-31 12:47:38 +01:00
parent 82cb6386a6
commit ab913575e1
2 changed files with 159 additions and 1 deletions

View File

@@ -0,0 +1,158 @@
/**
* Error Boundary Component
*
* Catches JavaScript errors in child component tree and displays fallback UI.
* Prevents the entire app from crashing due to component-level errors.
*
* @see https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
*/
'use client';
import { Component, type ReactNode } from 'react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
// ============================================================================
// Types
// ============================================================================
interface ErrorBoundaryProps {
/** Child components to render */
children: ReactNode;
/** Optional fallback component to render on error */
fallback?: ReactNode;
/** Optional callback when error occurs */
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
/** Whether to show reset button (default: true) */
showReset?: boolean;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
}
// ============================================================================
// Default Fallback Component
// ============================================================================
interface DefaultFallbackProps {
error: Error | null;
onReset: () => void;
showReset: boolean;
}
function DefaultFallback({ error, onReset, showReset }: DefaultFallbackProps) {
return (
<Card className="m-4 border-destructive/50 bg-destructive/5">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-destructive">
<AlertTriangle className="h-5 w-5" aria-hidden="true" />
Something went wrong
</CardTitle>
<CardDescription>
An unexpected error occurred. Please try again or contact support if
the problem persists.
</CardDescription>
</CardHeader>
<CardContent>
{error && (
<div className="mb-4 rounded-md bg-muted p-3">
<p className="font-mono text-sm text-muted-foreground">
{error.message}
</p>
</div>
)}
{showReset && (
<Button
variant="outline"
size="sm"
onClick={onReset}
className="gap-2"
>
<RefreshCw className="h-4 w-4" aria-hidden="true" />
Try again
</Button>
)}
</CardContent>
</Card>
);
}
// ============================================================================
// Error Boundary Component
// ============================================================================
/**
* Error boundary for catching and handling render errors in React components.
*
* @example
* ```tsx
* <ErrorBoundary onError={(error) => logError(error)}>
* <MyComponent />
* </ErrorBoundary>
* ```
*
* @example With custom fallback
* ```tsx
* <ErrorBoundary fallback={<CustomErrorUI />}>
* <MyComponent />
* </ErrorBoundary>
* ```
*/
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 {
// Log error to console in development
console.error('ErrorBoundary caught an error:', error, errorInfo);
// Call optional error callback
this.props.onError?.(error, errorInfo);
}
handleReset = (): void => {
this.setState({ hasError: false, error: null });
};
render(): ReactNode {
const { hasError, error } = this.state;
const { children, fallback, showReset = true } = this.props;
if (hasError) {
if (fallback) {
return fallback;
}
return (
<DefaultFallback
error={error}
onReset={this.handleReset}
showReset={showReset}
/>
);
}
return children;
}
}
export default ErrorBoundary;

View File

@@ -1,4 +1,4 @@
// Common reusable components
// Examples: LoadingSpinner, ErrorBoundary, ConfirmDialog, etc.
export {};
export { ErrorBoundary } from './ErrorBoundary';