'use client'; import React, { Component, ReactNode, ErrorInfo } from 'react'; import { ApiError } from '@/lib/api/base/ApiError'; import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler'; import { DevErrorPanel } from './DevErrorPanel'; import { ErrorDisplay } from './ErrorDisplay'; interface Props { children: ReactNode; fallback?: ReactNode; onError?: (error: Error, errorInfo: ErrorInfo) => void; onReset?: () => void; /** * Whether to show the enhanced dev overlay */ enableDevOverlay?: boolean; /** * Additional context to include with errors */ context?: Record; } interface State { hasError: boolean; error: Error | ApiError | null; errorInfo: ErrorInfo | null; isDev: boolean; } /** * Enhanced React Error Boundary with maximum developer transparency * Integrates with GlobalErrorHandler and provides detailed debugging info */ export class EnhancedErrorBoundary extends Component { private globalErrorHandler: ReturnType; constructor(props: Props) { super(props); this.state = { hasError: false, error: null, errorInfo: null, isDev: process.env.NODE_ENV === 'development', }; this.globalErrorHandler = getGlobalErrorHandler(); } static getDerivedStateFromError(error: Error): State { // Don't catch Next.js navigation errors (redirect, notFound, etc.) if (error && typeof error === 'object' && 'digest' in error) { const digest = (error as any).digest; if (typeof digest === 'string' && ( digest.startsWith('NEXT_REDIRECT') || digest.startsWith('NEXT_NOT_FOUND') )) { // Re-throw Next.js navigation errors so they can be handled properly throw error; } } return { hasError: true, error, errorInfo: null, isDev: process.env.NODE_ENV === 'development', }; } componentDidCatch(error: Error, errorInfo: ErrorInfo): void { // Don't catch Next.js navigation errors (redirect, notFound, etc.) if (error && typeof error === 'object' && 'digest' in error) { const digest = (error as any).digest; if (typeof digest === 'string' && ( digest.startsWith('NEXT_REDIRECT') || digest.startsWith('NEXT_NOT_FOUND') )) { // Re-throw Next.js navigation errors so they can be handled properly throw error; } } // Add to React error history const reactErrors = (window as any).__GRIDPILOT_REACT_ERRORS__ || []; reactErrors.push({ error, errorInfo, timestamp: new Date().toISOString(), componentStack: errorInfo.componentStack, }); (window as any).__GRIDPILOT_REACT_ERRORS__ = reactErrors; // Report to global error handler with enhanced context const enhancedContext = { ...this.props.context, source: 'react_error_boundary', componentStack: errorInfo.componentStack, reactVersion: React.version, componentName: this.getComponentName(errorInfo), }; // Use global error handler for maximum transparency this.globalErrorHandler.report(error, enhancedContext); // Call custom error handler if provided if (this.props.onError) { this.props.onError(error, errorInfo); } // Show dev overlay if enabled if (this.props.enableDevOverlay && this.state.isDev) { // The global handler will show the overlay, but we can add additional React-specific info this.showReactDevOverlay(error, errorInfo); } // Log to console with maximum detail if (this.state.isDev) { this.logReactErrorWithMaximumDetail(error, errorInfo); } } componentDidMount(): void { // Initialize global error handler if not already done if (this.props.enableDevOverlay && this.state.isDev) { this.globalErrorHandler.initialize(); } } componentWillUnmount(): void { // Clean up if needed } /** * Extract component name from error info */ private getComponentName(errorInfo: ErrorInfo): string | undefined { try { const stack = errorInfo.componentStack; if (stack) { const match = stack.match(/at (\w+)/); return match ? match[1] : undefined; } } catch { // Ignore } return undefined; } /** * Show React-specific dev overlay */ private showReactDevOverlay(error: Error, errorInfo: ErrorInfo): void { const existingOverlay = document.getElementById('gridpilot-react-overlay'); if (existingOverlay) { this.updateReactDevOverlay(existingOverlay, error, errorInfo); return; } const overlay = document.createElement('div'); overlay.id = 'gridpilot-react-overlay'; overlay.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 90%; max-width: 800px; max-height: 80vh; background: #1a1a1a; color: #fff; font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; font-size: 12px; z-index: 999998; overflow: auto; padding: 20px; border: 3px solid #ff6600; border-radius: 8px; box-shadow: 0 0 40px rgba(255, 102, 0, 0.6); `; this.updateReactDevOverlay(overlay, error, errorInfo); document.body.appendChild(overlay); // Add keyboard shortcut to dismiss const dismissHandler = (e: KeyboardEvent) => { if (e.key === 'Escape') { overlay.remove(); document.removeEventListener('keydown', dismissHandler); } }; document.addEventListener('keydown', dismissHandler); } /** * Update React dev overlay */ private updateReactDevOverlay(overlay: HTMLElement, error: Error, errorInfo: ErrorInfo): void { overlay.innerHTML = `

⚛️ React Component Error

Error Message
${error.message}
Component Stack Trace
${errorInfo.componentStack || 'No component stack available'}
JavaScript Stack Trace
${error.stack || 'No stack trace available'}
React Information
React Version: ${React.version}
Error Boundary: Active
Timestamp: ${new Date().toLocaleTimeString()}
Quick Actions
💡 This React error boundary caught a component rendering error. Check the console for additional details from the global error handler.
`; } /** * Log React error with maximum detail */ private logReactErrorWithMaximumDetail(error: Error, errorInfo: ErrorInfo): void { console.groupCollapsed('%c[REACT ERROR BOUNDARY] Component Rendering Failed', 'color: #ff6600; font-weight: bold; font-size: 14px;' ); console.log('Error Details:', { message: error.message, name: error.name, stack: error.stack, }); console.log('Component Stack:', errorInfo.componentStack); console.log('React Context:', { reactVersion: React.version, component: this.getComponentName(errorInfo), timestamp: new Date().toISOString(), }); console.log('Props:', this.props); console.log('State:', this.state); // Show component hierarchy if available try { const hierarchy = this.getComponentHierarchy(); if (hierarchy) { console.log('Component Hierarchy:', hierarchy); } } catch { // Ignore hierarchy extraction errors } console.groupEnd(); } /** * Attempt to extract component hierarchy (for debugging) */ private getComponentHierarchy(): string | null { // This is a simplified version - in practice, you might want to use React DevTools // or other methods to get the full component tree return null; } resetError = (): void => { this.setState({ hasError: false, error: null, errorInfo: null }); if (this.props.onReset) { this.props.onReset(); } }; render(): ReactNode { if (this.state.hasError && this.state.error) { if (this.props.fallback) { return this.props.fallback; } // Show different UI based on environment if (this.state.isDev) { return ( ); } return ( ); } return this.props.children; } } /** * Hook-based alternative for functional components */ export function useEnhancedErrorBoundary() { const [error, setError] = React.useState(null); const [errorInfo, setErrorInfo] = React.useState(null); const [isDev] = React.useState(process.env.NODE_ENV === 'development'); const handleError = (err: Error, info: ErrorInfo) => { setError(err); setErrorInfo(info); // Report to global handler const globalHandler = getGlobalErrorHandler(); globalHandler.report(err, { source: 'react_hook_boundary', componentStack: info.componentStack, }); }; const reset = () => { setError(null); setErrorInfo(null); }; return { error, errorInfo, isDev, handleError, reset, ErrorBoundary: ({ children, enableDevOverlay }: { children: ReactNode; enableDevOverlay?: boolean }) => ( {children} ), }; } /** * Higher-order component wrapper for easy usage */ export function withEnhancedErrorBoundary

( Component: React.ComponentType

, options: Omit = {} ): React.FC

{ const WrappedComponent = (props: P) => ( ); WrappedComponent.displayName = `withEnhancedErrorBoundary(${Component.displayName || Component.name || 'Component'})`; return WrappedComponent; }