'use client' import type { ErrorInfo, ReactNode } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react' import { RiAlertLine, RiBugLine } from '@remixicon/react' import Button from '@/app/components/base/button' import cn from '@/utils/classnames' type ErrorBoundaryState = { hasError: boolean error: Error | null errorInfo: ErrorInfo | null errorCount: number } type ErrorBoundaryProps = { children: ReactNode fallback?: ReactNode | ((error: Error, reset: () => void) => ReactNode) onError?: (error: Error, errorInfo: ErrorInfo) => void onReset?: () => void showDetails?: boolean className?: string resetKeys?: Array resetOnPropsChange?: boolean isolate?: boolean enableRecovery?: boolean customTitle?: string customMessage?: string } // Internal class component for error catching class ErrorBoundaryInner extends React.Component< ErrorBoundaryProps & { resetErrorBoundary: () => void onResetKeysChange: (prevResetKeys?: Array) => void }, ErrorBoundaryState > { constructor(props: any) { super(props) this.state = { hasError: false, error: null, errorInfo: null, errorCount: 0, } } static getDerivedStateFromError(error: Error): Partial { return { hasError: true, error, } } componentDidCatch(error: Error, errorInfo: ErrorInfo) { if (process.env.NODE_ENV === 'development') { console.error('ErrorBoundary caught an error:', error) console.error('Error Info:', errorInfo) } this.setState(prevState => ({ errorInfo, errorCount: prevState.errorCount + 1, })) if (this.props.onError) this.props.onError(error, errorInfo) } componentDidUpdate(prevProps: any) { const { resetKeys, resetOnPropsChange } = this.props const { hasError } = this.state if (hasError && prevProps.resetKeys !== resetKeys) { if (resetKeys?.some((key, idx) => key !== prevProps.resetKeys?.[idx])) this.props.resetErrorBoundary() } if (hasError && resetOnPropsChange && prevProps.children !== this.props.children) this.props.resetErrorBoundary() if (prevProps.resetKeys !== resetKeys) this.props.onResetKeysChange(prevProps.resetKeys) } render() { const { hasError, error, errorInfo, errorCount } = this.state const { fallback, children, showDetails = false, className, isolate = true, enableRecovery = true, customTitle, customMessage, resetErrorBoundary, } = this.props if (hasError && error) { if (fallback) { if (typeof fallback === 'function') return fallback(error, resetErrorBoundary) return fallback } return (

{customTitle || 'Something went wrong'}

{customMessage || 'An unexpected error occurred while rendering this component.'}

{showDetails && errorInfo && (
Error Details (Development Only)
Error:
                    {error.toString()}
                  
{errorInfo && (
Component Stack:
                      {errorInfo.componentStack}
                    
)} {errorCount > 1 && (
This error has occurred {errorCount} times
)}
)} {enableRecovery && (
)}
) } return children } } // Main functional component wrapper const ErrorBoundary: React.FC = (props) => { const [errorBoundaryKey, setErrorBoundaryKey] = useState(0) const resetKeysRef = useRef(props.resetKeys) const prevResetKeysRef = useRef | undefined>(undefined) const resetErrorBoundary = useCallback(() => { setErrorBoundaryKey(prev => prev + 1) props.onReset?.() }, [props]) const onResetKeysChange = useCallback((prevResetKeys?: Array) => { prevResetKeysRef.current = prevResetKeys }, []) useEffect(() => { if (prevResetKeysRef.current !== props.resetKeys) resetKeysRef.current = props.resetKeys }, [props.resetKeys]) return ( ) } // Hook for imperative error handling export function useErrorHandler() { const [error, setError] = useState(null) useEffect(() => { if (error) throw error }, [error]) return setError } // Hook for catching async errors export function useAsyncError() { const [, setError] = useState() return useCallback( (error: Error) => { setError(() => { throw error }) }, [setError], ) } // HOC for wrapping components with error boundary export function withErrorBoundary

( Component: React.ComponentType

, errorBoundaryProps?: Omit, ): React.ComponentType

{ const WrappedComponent = (props: P) => ( ) WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name || 'Component'})` return WrappedComponent } // Simple error fallback component export const ErrorFallback: React.FC<{ error: Error resetErrorBoundary: () => void }> = ({ error, resetErrorBoundary }) => { return (

Oops! Something went wrong

{error.message}

) } export default ErrorBoundary