Files
thrilltrack-explorer/src-old/components/error/EntityErrorBoundary.tsx

187 lines
6.1 KiB
TypeScript

import React, { Component, ErrorInfo, ReactNode } from 'react';
import { AlertCircle, ArrowLeft, Home, RefreshCw } from 'lucide-react';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { handleError } from '@/lib/errorHandler';
interface EntityErrorBoundaryProps {
children: ReactNode;
entityType: 'park' | 'ride' | 'manufacturer' | 'designer' | 'operator' | 'owner';
entitySlug?: string;
fallback?: ReactNode;
}
interface EntityErrorBoundaryState {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
}
type ErrorWithId = Error & { errorId: string };
export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, EntityErrorBoundaryState> {
constructor(props: EntityErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
static getDerivedStateFromError(error: Error): Partial<EntityErrorBoundaryState> {
return {
hasError: true,
error,
};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Log to database and get error ID for user reference
const errorId = handleError(error, {
action: `${this.props.entityType} page error`,
metadata: {
entityType: this.props.entityType,
entitySlug: this.props.entitySlug,
componentStack: errorInfo.componentStack,
},
});
this.setState({ errorInfo, error: { ...error, errorId } as ErrorWithId });
}
handleRetry = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null,
});
};
handleBackToList = () => {
const listPages: Record<string, string> = {
park: '/parks',
ride: '/rides',
manufacturer: '/manufacturers',
designer: '/designers',
operator: '/operators',
owner: '/owners',
};
window.location.href = listPages[this.props.entityType] || '/';
};
handleGoHome = () => {
window.location.href = '/';
};
getEntityLabel() {
const labels: Record<string, string> = {
park: 'Park',
ride: 'Ride',
manufacturer: 'Manufacturer',
designer: 'Designer',
operator: 'Operator',
owner: 'Property Owner',
};
return labels[this.props.entityType] || 'Entity';
}
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
const entityLabel = this.getEntityLabel();
return (
<div className="container mx-auto px-4 py-12">
<Card className="max-w-2xl mx-auto border-destructive/50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-destructive">
<AlertCircle className="w-5 h-5" />
Failed to Load {entityLabel}
</CardTitle>
<CardDescription>
{this.props.entitySlug
? `Unable to display ${entityLabel.toLowerCase()}: ${this.props.entitySlug}`
: `Unable to display this ${entityLabel.toLowerCase()}`}
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<Alert variant="destructive">
<AlertCircle className="h-4 w-4" />
<AlertTitle>Error Details</AlertTitle>
<AlertDescription>
<div className="mt-2 space-y-2">
<p className="text-sm">
{this.state.error?.message || `An unexpected error occurred while loading this ${entityLabel.toLowerCase()}`}
</p>
{(this.state.error as ErrorWithId)?.errorId && (
<p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded">
Reference ID: {(this.state.error as ErrorWithId).errorId.slice(0, 8)}
</p>
)}
<p className="text-xs text-muted-foreground">
This might be due to:
</p>
<ul className="text-xs text-muted-foreground list-disc list-inside space-y-1">
<li>The {entityLabel.toLowerCase()} no longer exists</li>
<li>Temporary data loading issues</li>
<li>Network connectivity problems</li>
</ul>
</div>
</AlertDescription>
</Alert>
<div className="flex items-center gap-2 flex-wrap">
<Button
variant="default"
size="sm"
onClick={this.handleRetry}
className="gap-2"
>
<RefreshCw className="w-4 h-4" />
Try Again
</Button>
<Button
variant="outline"
size="sm"
onClick={this.handleBackToList}
className="gap-2"
>
<ArrowLeft className="w-4 h-4" />
Back to {entityLabel}s
</Button>
<Button
variant="ghost"
size="sm"
onClick={this.handleGoHome}
className="gap-2"
>
<Home className="w-4 h-4" />
Home
</Button>
</div>
{import.meta.env.DEV && this.state.errorInfo && (
<details className="text-xs">
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
Show Debug Info (Development Only)
</summary>
<pre className="mt-2 overflow-auto p-3 bg-muted rounded text-xs max-h-[300px]">
{this.state.errorInfo.componentStack}
</pre>
</details>
)}
</CardContent>
</Card>
</div>
);
}
return this.props.children;
}
}