mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-23 12:11:13 -05:00
feat: Implement all 7 phases
This commit is contained in:
159
src/components/error/ModerationErrorBoundary.tsx
Normal file
159
src/components/error/ModerationErrorBoundary.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||
import { AlertCircle, RefreshCw } from 'lucide-react';
|
||||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
interface ModerationErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
submissionId?: string;
|
||||
fallback?: ReactNode;
|
||||
onError?: (error: Error, errorInfo: ErrorInfo) => void;
|
||||
}
|
||||
|
||||
interface ModerationErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
errorInfo: ErrorInfo | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Error Boundary for Moderation Queue Components
|
||||
*
|
||||
* Prevents individual queue item render errors from crashing the entire queue.
|
||||
* Shows user-friendly error UI with retry functionality.
|
||||
*
|
||||
* Usage:
|
||||
* ```tsx
|
||||
* <ModerationErrorBoundary submissionId={item.id}>
|
||||
* <QueueItem item={item} />
|
||||
* </ModerationErrorBoundary>
|
||||
* ```
|
||||
*/
|
||||
export class ModerationErrorBoundary extends Component<
|
||||
ModerationErrorBoundaryProps,
|
||||
ModerationErrorBoundaryState
|
||||
> {
|
||||
constructor(props: ModerationErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
hasError: false,
|
||||
error: null,
|
||||
errorInfo: null,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): Partial<ModerationErrorBoundaryState> {
|
||||
return {
|
||||
hasError: true,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
// Log error to monitoring system
|
||||
logger.error('Moderation component error caught by boundary', {
|
||||
action: 'error_boundary_catch',
|
||||
submissionId: this.props.submissionId,
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
componentStack: errorInfo.componentStack,
|
||||
});
|
||||
|
||||
// Update state with error info
|
||||
this.setState({
|
||||
errorInfo,
|
||||
});
|
||||
|
||||
// Call optional error handler
|
||||
this.props.onError?.(error, errorInfo);
|
||||
}
|
||||
|
||||
handleRetry = () => {
|
||||
this.setState({
|
||||
hasError: false,
|
||||
error: null,
|
||||
errorInfo: null,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
// Custom fallback if provided
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback;
|
||||
}
|
||||
|
||||
// Default error UI
|
||||
return (
|
||||
<Card className="border-red-200 dark:border-red-800 bg-red-50/50 dark:bg-red-900/10">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="flex items-center gap-2 text-red-700 dark:text-red-300">
|
||||
<AlertCircle className="w-5 h-5" />
|
||||
Queue Item Error
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<Alert variant="destructive">
|
||||
<AlertCircle className="h-4 w-4" />
|
||||
<AlertTitle>Failed to render submission</AlertTitle>
|
||||
<AlertDescription>
|
||||
<div className="mt-2 space-y-2">
|
||||
<p className="text-sm">
|
||||
{this.state.error?.message || 'An unexpected error occurred'}
|
||||
</p>
|
||||
{this.props.submissionId && (
|
||||
<p className="text-xs text-muted-foreground font-mono">
|
||||
Submission ID: {this.props.submissionId}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={this.handleRetry}
|
||||
className="gap-2"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
Retry
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
JSON.stringify({
|
||||
error: this.state.error?.message,
|
||||
stack: this.state.error?.stack,
|
||||
submissionId: this.props.submissionId,
|
||||
}, null, 2)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Copy Error Details
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{process.env.NODE_ENV === 'development' && this.state.errorInfo && (
|
||||
<details className="text-xs">
|
||||
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
|
||||
Show Component Stack
|
||||
</summary>
|
||||
<pre className="mt-2 overflow-auto p-2 bg-muted rounded text-xs">
|
||||
{this.state.errorInfo.componentStack}
|
||||
</pre>
|
||||
</details>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user