diff --git a/frontend/src/components/ui/ErrorBoundary.tsx b/frontend/src/components/ui/ErrorBoundary.tsx new file mode 100644 index 0000000..3068bb9 --- /dev/null +++ b/frontend/src/components/ui/ErrorBoundary.tsx @@ -0,0 +1,137 @@ +'use client' + +import React, { ReactNode, ErrorInfo, Component } from 'react' +import { AlertTriangle, RefreshCw, Home } from 'lucide-react' +import { cn } from '@/lib/utils' + +/* ---------- types ---------- */ + +interface ErrorBoundaryProps { + children: ReactNode + /** Optional custom fallback UI */ + fallback?: ReactNode + /** Callback for error reporting / analytics */ + onError?: (error: Error, errorInfo: ErrorInfo) => void + /** Custom class for the wrapper */ + className?: string +} + +interface ErrorBoundaryState { + hasError: boolean + error: Error | null + errorInfo: ErrorInfo | null +} + +/* ---------- component ---------- */ + +class ErrorBoundaryInner extends Component { + constructor(props: ErrorBoundaryProps) { + super(props) + this.state = { hasError: false, error: null, errorInfo: null } + } + + static getDerivedStateFromError(error: Error): Partial { + return { hasError: true, error } + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo): void { + this.setState({ errorInfo }) + + // Report to external analytics service if provided + if (this.props.onError) { + this.props.onError(error, errorInfo) + } + + // Always log to console as fallback + console.error('[ErrorBoundary]', error, errorInfo) + } + + reset = (): void => { + this.setState({ hasError: false, error: null, errorInfo: null }) + } + + render(): ReactNode { + if (this.state.hasError) { + // Use custom fallback if provided + if (this.props.fallback) { + return this.props.fallback + } + + // Default dark-themed fallback UI + return ( +
+ {/* Icon */} +
+ +
+ + {/* Message */} +

+ Something went wrong +

+

+ This component crashed unexpectedly. You can try again or go back to + the home page. +

+ + {/* Error details (dev only) */} + {process.env.NODE_ENV === 'development' && this.state.error && ( +
+ + Error Details (Development) + +
+                {this.state.error.toString()}
+                {'\n\n'}
+                {this.state.errorInfo?.componentStack}
+              
+
+ )} + + {/* Actions */} +
+ + +
+
+ ) + } + + return this.props.children + } +} + +/* ---------- public wrapper ---------- */ + +export function ErrorBoundary(props: ErrorBoundaryProps): ReactNode { + return +} + +export default ErrorBoundary diff --git a/frontend/src/components/ui/index.ts b/frontend/src/components/ui/index.ts index 531166f..1949913 100644 --- a/frontend/src/components/ui/index.ts +++ b/frontend/src/components/ui/index.ts @@ -14,3 +14,5 @@ export { DropdownMenuItem, } from "./DropdownMenu"; export { Pagination } from "./Pagination"; +export { DataTable } from "./DataTable"; +export { ErrorBoundary } from "./ErrorBoundary";