From 43c49989deab0cfd5a9989bab6b40346686516f2 Mon Sep 17 00:00:00 2001 From: armorbreak001 Date: Tue, 14 Apr 2026 21:13:21 +0800 Subject: [PATCH] feat(frontend): add robust error boundary wrapper component - Create ErrorBoundary in components/ui with dark theme styling - Implements getDerivedStateFromError and componentDidCatch - Fallback UI: Something went wrong + Try Again / Go Home buttons - onError callback prop for external analytics reporting - Development mode: expandable error details (stack trace) - Uses Tailwind classes matching app glassmorphism dark theme - Accessible: role=alert, proper button labels --- frontend/src/components/ui/ErrorBoundary.tsx | 137 +++++++++++++++++++ frontend/src/components/ui/index.ts | 2 + 2 files changed, 139 insertions(+) create mode 100644 frontend/src/components/ui/ErrorBoundary.tsx 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";