diff --git a/frontend/src/components/common/ErrorBoundary.tsx b/frontend/src/components/common/ErrorBoundary.tsx new file mode 100644 index 0000000..9314071 --- /dev/null +++ b/frontend/src/components/common/ErrorBoundary.tsx @@ -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 ( + + + + + + An unexpected error occurred. Please try again or contact support if + the problem persists. + + + + {error && ( +
+

+ {error.message} +

+
+ )} + {showReset && ( + + )} +
+
+ ); +} + +// ============================================================================ +// Error Boundary Component +// ============================================================================ + +/** + * Error boundary for catching and handling render errors in React components. + * + * @example + * ```tsx + * logError(error)}> + * + * + * ``` + * + * @example With custom fallback + * ```tsx + * }> + * + * + * ``` + */ +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 ( + + ); + } + + return children; + } +} + +export default ErrorBoundary; diff --git a/frontend/src/components/common/index.ts b/frontend/src/components/common/index.ts index 9b337a7..71e0839 100755 --- a/frontend/src/components/common/index.ts +++ b/frontend/src/components/common/index.ts @@ -1,4 +1,4 @@ // Common reusable components // Examples: LoadingSpinner, ErrorBoundary, ConfirmDialog, etc. -export {}; +export { ErrorBoundary } from './ErrorBoundary';