188 lines
5.5 KiB
TypeScript
188 lines
5.5 KiB
TypeScript
'use client'
|
|
|
|
import React from 'react'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Button } from '@/components/ui/button'
|
|
import { AlertTriangle, RefreshCw } from 'lucide-react'
|
|
|
|
interface ErrorBoundaryState {
|
|
hasError: boolean
|
|
error?: Error
|
|
errorInfo?: React.ErrorInfo
|
|
}
|
|
|
|
interface ErrorBoundaryProps {
|
|
children: React.ReactNode
|
|
fallback?: React.ComponentType<{ error: Error; retry: () => 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) {
|
|
console.error('Error caught by boundary:', error, errorInfo)
|
|
|
|
// Log error to monitoring service in production
|
|
if (process.env.NODE_ENV === 'production') {
|
|
// You can integrate with services like Sentry here
|
|
console.error('Production error:', {
|
|
error: error.message,
|
|
stack: error.stack,
|
|
componentStack: errorInfo.componentStack,
|
|
})
|
|
}
|
|
|
|
this.setState({
|
|
hasError: true,
|
|
error,
|
|
errorInfo,
|
|
})
|
|
}
|
|
|
|
handleRetry = () => {
|
|
this.setState({ hasError: false, error: undefined, errorInfo: undefined })
|
|
}
|
|
|
|
render() {
|
|
if (this.state.hasError) {
|
|
const { fallback: Fallback } = this.props
|
|
|
|
if (Fallback && this.state.error) {
|
|
return <Fallback error={this.state.error} retry={this.handleRetry} />
|
|
}
|
|
|
|
return (
|
|
<Card className="max-w-lg mx-auto mt-8">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-destructive">
|
|
<AlertTriangle className="h-5 w-5" />
|
|
Something went wrong
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<p className="text-sm text-muted-foreground">
|
|
An unexpected error occurred. Please try refreshing the page or contact support if the problem persists.
|
|
</p>
|
|
|
|
{process.env.NODE_ENV === 'development' && this.state.error && (
|
|
<details className="text-xs bg-muted p-3 rounded">
|
|
<summary className="cursor-pointer font-medium">Error Details</summary>
|
|
<pre className="mt-2 whitespace-pre-wrap">
|
|
{this.state.error.message}
|
|
{'\n\n'}
|
|
{this.state.error.stack}
|
|
</pre>
|
|
</details>
|
|
)}
|
|
|
|
<div className="flex gap-2">
|
|
<Button onClick={this.handleRetry} variant="outline" size="sm">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
Try Again
|
|
</Button>
|
|
<Button
|
|
onClick={() => window.location.reload()}
|
|
size="sm"
|
|
>
|
|
Refresh Page
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|
|
|
|
return this.props.children
|
|
}
|
|
}
|
|
|
|
// Hook version for functional components
|
|
export function useErrorHandler() {
|
|
const [error, setError] = React.useState<Error | null>(null)
|
|
|
|
const resetError = React.useCallback(() => {
|
|
setError(null)
|
|
}, [])
|
|
|
|
const captureError = React.useCallback((error: Error) => {
|
|
console.error('Error captured:', error)
|
|
setError(error)
|
|
}, [])
|
|
|
|
React.useEffect(() => {
|
|
if (error) {
|
|
// Log to monitoring service
|
|
console.error('Error in component:', error)
|
|
}
|
|
}, [error])
|
|
|
|
return { error, resetError, captureError }
|
|
}
|
|
|
|
// Specific error fallback components
|
|
export function AdminErrorFallback({ error, retry }: { error: Error; retry: () => void }) {
|
|
return (
|
|
<div className="min-h-[400px] flex items-center justify-center">
|
|
<Card className="max-w-md">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-destructive">
|
|
<AlertTriangle className="h-5 w-5" />
|
|
Admin Panel Error
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<p className="text-sm text-muted-foreground">
|
|
There was an error loading the admin panel. This might be due to a network issue or server problem.
|
|
</p>
|
|
|
|
<div className="flex gap-2">
|
|
<Button onClick={retry} variant="outline" size="sm">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
Retry
|
|
</Button>
|
|
<Button
|
|
onClick={() => window.location.href = '/admin'}
|
|
size="sm"
|
|
>
|
|
Go to Dashboard
|
|
</Button>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export function CalendarErrorFallback({ error, retry }: { error: Error; retry: () => void }) {
|
|
return (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2 text-destructive">
|
|
<AlertTriangle className="h-5 w-5" />
|
|
Calendar Loading Error
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<p className="text-sm text-muted-foreground">
|
|
Unable to load the appointment calendar. Please check your connection and try again.
|
|
</p>
|
|
|
|
<Button onClick={retry} variant="outline" size="sm">
|
|
<RefreshCw className="h-4 w-4 mr-2" />
|
|
Reload Calendar
|
|
</Button>
|
|
</CardContent>
|
|
</Card>
|
|
)
|
|
}
|