'use client'; import React, { Component, ReactNode, ErrorInfo, useState, version } from 'react'; import { ApiError } from '@/lib/api/base/ApiError'; import { getGlobalErrorHandler } from '@/lib/infrastructure/GlobalErrorHandler'; import { DevErrorPanel } from '@/ui/DevErrorPanel'; import { ErrorDisplay } from '@/ui/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; } interface GridPilotWindow extends Window { __GRIDPILOT_REACT_ERRORS__?: Array<{ error: Error; errorInfo: ErrorInfo; timestamp: string; componentStack?: string; }>; } /** * 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 Record).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 Record).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 if (typeof window !== 'undefined') { const gpWindow = window as unknown as GridPilotWindow; const reactErrors = gpWindow.__GRIDPILOT_REACT_ERRORS__ || []; gpWindow.__GRIDPILOT_REACT_ERRORS__ = [ ...reactErrors, { error, errorInfo, timestamp: new Date().toISOString(), componentStack: errorInfo.componentStack || undefined, } ]; } // Report to global error handler with enhanced context const enhancedContext = { ...this.props.context, source: 'react_error_boundary', componentStack: errorInfo.componentStack, reactVersion: 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); } // 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(); } } /** * 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; } /** * 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: version, component: this.getComponentName(errorInfo), timestamp: new Date().toISOString(), }); console.log('Props:', this.props); console.log('State:', this.state); console.groupEnd(); } 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] = useState(null); const [errorInfo, setErrorInfo] = useState(null); const [isDev] = 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; }