error-boundary.tsx•5.41 kB
'use client'
import React from 'react'
interface ErrorBoundaryState {
hasError: boolean
error?: Error
errorInfo?: React.ErrorInfo
}
interface ErrorBoundaryProps {
children: React.ReactNode
fallback?: React.ComponentType<{
error?: Error
resetError: () => void
goHome: () => void
}>
onError?: (error: Error, errorInfo: React.ErrorInfo) => void
}
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return {
hasError: true,
error
}
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
this.setState({
error,
errorInfo
})
// Call the onError callback if provided
if (this.props.onError) {
this.props.onError(error, errorInfo)
}
// Log error to console in development
if (process.env.NODE_ENV === 'development') {
console.error('Error Boundary caught an error:', error, errorInfo)
}
}
resetError = () => {
this.setState({ hasError: false, error: undefined, errorInfo: undefined })
}
render() {
if (this.state.hasError) {
if (this.props.fallback) {
const FallbackComponent = this.props.fallback
return (
<FallbackComponent
error={this.state.error}
resetError={this.resetError}
goHome={() => window.location.href = '/'}
/>
)
}
// Default error UI
return <DefaultErrorFallback error={this.state.error} resetError={this.resetError} />
}
return this.props.children
}
}
interface ErrorFallbackProps {
error?: Error
resetError: () => void
goHome?: () => void
}
function DefaultErrorFallback({ error, resetError, goHome }: ErrorFallbackProps) {
return (
<div style={{
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: 'linear-gradient(135deg, #fee2e2 0%, #fecaca 100%)',
padding: '20px'
}}>
<div style={{
background: 'white',
borderRadius: '12px',
padding: '48px',
maxWidth: '500px',
width: '100%',
boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.25)',
textAlign: 'center'
}}>
<div style={{
fontSize: '3rem',
marginBottom: '16px'
}}>
😵
</div>
<h1 style={{
fontSize: '1.5rem',
fontWeight: 'bold',
marginBottom: '16px',
color: '#dc2626'
}}>
Oops! Something went wrong
</h1>
<p style={{
fontSize: '1rem',
color: '#6b7280',
marginBottom: '24px',
lineHeight: '1.5'
}}>
We encountered an unexpected error. This has been logged and we'll look into it.
</p>
{process.env.NODE_ENV === 'development' && error && (
<details style={{
marginBottom: '24px',
textAlign: 'left',
background: '#f3f4f6',
padding: '12px',
borderRadius: '6px',
fontSize: '0.875rem'
}}>
<summary style={{ cursor: 'pointer', fontWeight: '600', marginBottom: '8px' }}>
Error Details (Development)
</summary>
<pre style={{
whiteSpace: 'pre-wrap',
color: '#dc2626',
fontSize: '0.75rem'
}}>
{error.message}
{error.stack && '\n\n' + error.stack}
</pre>
</details>
)}
<div style={{
display: 'flex',
gap: '12px',
justifyContent: 'center',
flexWrap: 'wrap'
}}>
<button
onClick={resetError}
style={{
background: '#dc2626',
color: 'white',
padding: '12px 24px',
borderRadius: '8px',
border: 'none',
fontSize: '1rem',
fontWeight: '600',
cursor: 'pointer',
transition: 'background 0.2s'
}}
onMouseOver={(e) => e.currentTarget.style.background = '#b91c1c'}
onMouseOut={(e) => e.currentTarget.style.background = '#dc2626'}
>
Try Again
</button>
{goHome && (
<button
onClick={goHome}
style={{
background: 'transparent',
color: '#dc2626',
padding: '12px 24px',
borderRadius: '8px',
border: '2px solid #dc2626',
fontSize: '1rem',
fontWeight: '600',
cursor: 'pointer',
transition: 'all 0.2s'
}}
onMouseOver={(e) => {
e.currentTarget.style.background = '#dc2626'
e.currentTarget.style.color = 'white'
}}
onMouseOut={(e) => {
e.currentTarget.style.background = 'transparent'
e.currentTarget.style.color = '#dc2626'
}}
>
Go Home
</button>
)}
</div>
</div>
</div>
)
}