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

188 lines
6.4 KiB
TypeScript

import React, { Component, ErrorInfo, ReactNode } from 'react';
import { AlertCircle, ArrowLeft, RefreshCw, Shield } 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 AdminErrorBoundaryProps {
children: ReactNode;
fallback?: ReactNode;
section?: string; // e.g., "Moderation", "Users", "Settings"
}
type ErrorWithId = Error & { errorId: string };
interface AdminErrorBoundaryState {
hasError: boolean;
error: ErrorWithId | null;
errorInfo: ErrorInfo | null;
}
/**
* Admin Error Boundary Component (P0 #5)
*
* Specialized error boundary for admin sections.
* Prevents admin panel errors from affecting the entire app.
*
* Usage:
* ```tsx
* <AdminErrorBoundary section="User Management">
* <UserManagement />
* </AdminErrorBoundary>
* ```
*/
export class AdminErrorBoundary extends Component<AdminErrorBoundaryProps, AdminErrorBoundaryState> {
constructor(props: AdminErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
static getDerivedStateFromError(error: Error): Partial<AdminErrorBoundaryState> {
return {
hasError: true,
error: error as ErrorWithId,
};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Log to database and get error ID for user reference
const errorId = handleError(error, {
action: `Admin panel error in ${this.props.section || 'unknown section'}`,
metadata: {
section: this.props.section,
componentStack: errorInfo.componentStack,
severity: 'high',
},
});
this.setState({ errorInfo, error: { ...error, errorId } as ErrorWithId });
}
handleRetry = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null,
});
};
handleBackToDashboard = () => {
window.location.href = '/admin';
};
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div className="min-h-[500px] flex items-center justify-center p-6">
<Card className="max-w-3xl w-full border-destructive/50 bg-destructive/5">
<CardHeader className="pb-4">
<div className="flex items-center gap-3">
<div className="p-2 rounded-lg bg-destructive/10">
<Shield className="w-6 h-6 text-destructive" />
</div>
<div>
<CardTitle className="text-destructive flex items-center gap-2">
<AlertCircle className="w-5 h-5" />
Admin Panel Error
</CardTitle>
<CardDescription className="mt-1">
{this.props.section
? `An error occurred in ${this.props.section}`
: 'An error occurred in the admin panel'}
</CardDescription>
</div>
</div>
</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 in the admin panel'}
</p>
{(this.state.error as any)?.errorId && (
<p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded">
Reference ID: {((this.state.error as any).errorId as string).slice(0, 8)}
</p>
)}
<p className="text-xs text-muted-foreground">
This error has been logged. If the problem persists, please contact support.
</p>
</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" />
Retry
</Button>
<Button
variant="outline"
size="sm"
onClick={this.handleBackToDashboard}
className="gap-2"
>
<ArrowLeft className="w-4 h-4" />
Back to Dashboard
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => {
navigator.clipboard.writeText(
JSON.stringify({
section: this.props.section,
error: this.state.error?.message,
stack: this.state.error?.stack,
timestamp: new Date().toISOString(),
url: window.location.href,
}, null, 2)
);
}}
>
Copy Error Report
</Button>
</div>
{import.meta.env.DEV && this.state.errorInfo && (
<details className="text-xs">
<summary className="cursor-pointer text-muted-foreground hover:text-foreground font-medium">
Show Stack Trace (Development Only)
</summary>
<div className="mt-2 space-y-2">
<pre className="overflow-auto p-3 bg-muted rounded text-xs max-h-[200px]">
{this.state.error?.stack}
</pre>
<pre className="overflow-auto p-3 bg-muted rounded text-xs max-h-[200px]">
{this.state.errorInfo.componentStack}
</pre>
</div>
</details>
)}
</CardContent>
</Card>
</div>
);
}
return this.props.children;
}
}