'use client'
import React, { Component, ReactNode } from 'react'
import { ExclamationTriangleIcon, ArrowPathIcon } from '@heroicons/react/24/outline'
interface Props {
children: ReactNode
fallback?: ReactNode
onError?: (error: Error, errorInfo: React.ErrorInfo) => void
}
interface State {
hasError: boolean
error: Error | null
errorInfo: React.ErrorInfo | null
}
export default class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
hasError: false,
error: null,
errorInfo: null
}
}
static getDerivedStateFromError(error: Error): State {
return {
hasError: true,
error,
errorInfo: null
}
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
this.setState({
error,
errorInfo
})
// Log error to console in development
if (process.env.NODE_ENV === 'development') {
console.error('Error caught by boundary:', error)
console.error('Error info:', errorInfo)
}
// Call optional error handler
this.props.onError?.(error, errorInfo)
// In production, you might want to send error to logging service
if (process.env.NODE_ENV === 'production') {
// Send to error tracking service like Sentry
// logErrorToService(error, errorInfo)
}
}
handleRetry = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
})
}
handleReload = () => {
window.location.reload()
}
render() {
if (this.state.hasError) {
// Custom fallback UI if provided
if (this.props.fallback) {
return this.props.fallback
}
// Default error UI
return (
<div className="min-h-[400px] flex items-center justify-center p-6">
<div className="max-w-md w-full bg-background-elevated border border-error rounded-lg p-6 text-center">
<div className="flex justify-center mb-4">
<ExclamationTriangleIcon className="w-12 h-12 text-error" />
</div>
<h2 className="text-heading text-foreground mb-2">
Something went wrong
</h2>
<p className="text-body text-foreground-secondary mb-6">
An unexpected error occurred. This is likely a temporary issue.
</p>
{/* Error details in development */}
{process.env.NODE_ENV === 'development' && this.state.error && (
<details className="mb-6 text-left">
<summary className="cursor-pointer text-body font-medium text-foreground-secondary hover:text-foreground mb-2">
Error Details (Development)
</summary>
<div className="bg-background-tertiary rounded p-3 text-xs font-mono text-foreground-secondary overflow-auto max-h-40">
<div className="mb-2">
<strong>Error:</strong> {this.state.error.message}
</div>
<div className="mb-2">
<strong>Stack:</strong>
<pre className="whitespace-pre-wrap mt-1">
{this.state.error.stack}
</pre>
</div>
{this.state.errorInfo && (
<div>
<strong>Component Stack:</strong>
<pre className="whitespace-pre-wrap mt-1">
{this.state.errorInfo.componentStack}
</pre>
</div>
)}
</div>
</details>
)}
<div className="flex gap-3 justify-center">
<button
onClick={this.handleRetry}
className="btn-primary"
>
<ArrowPathIcon className="w-4 h-4" />
Try Again
</button>
<button
onClick={this.handleReload}
className="btn-secondary"
>
Reload Page
</button>
</div>
{/* Help text */}
<p className="text-caption text-foreground-tertiary mt-4">
If this problem persists, try refreshing the page or clearing your browser cache.
</p>
</div>
</div>
)
}
return this.props.children
}
}
// Higher-order component for easier usage
export function withErrorBoundary<P extends object>(
Component: React.ComponentType<P>,
errorBoundaryProps?: Omit<Props, 'children'>
) {
const WrappedComponent = (props: P) => (
<ErrorBoundary {...errorBoundaryProps}>
<Component {...props} />
</ErrorBoundary>
)
WrappedComponent.displayName = `withErrorBoundary(${Component.displayName || Component.name})`
return WrappedComponent
}
// Hook for error reporting in functional components
export function useErrorHandler() {
return (error: Error, errorInfo?: string) => {
// In development, throw the error to trigger error boundary
if (process.env.NODE_ENV === 'development') {
throw error
}
// In production, log the error
console.error('Error caught by useErrorHandler:', error)
if (errorInfo) {
console.error('Additional info:', errorInfo)
}
// Send to error tracking service
// logErrorToService(error, { additionalInfo: errorInfo })
}
}