mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-20 13:31:12 -05:00
Approve database migration
This commit is contained in:
173
src/components/admin/ErrorDetailsModal.tsx
Normal file
173
src/components/admin/ErrorDetailsModal.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Copy, ExternalLink } from 'lucide-react';
|
||||
import { format } from 'date-fns';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
interface ErrorDetailsModalProps {
|
||||
error: any;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function ErrorDetailsModal({ error, onClose }: ErrorDetailsModalProps) {
|
||||
const copyErrorId = () => {
|
||||
navigator.clipboard.writeText(error.request_id);
|
||||
toast.success('Error ID copied to clipboard');
|
||||
};
|
||||
|
||||
const copyErrorReport = () => {
|
||||
const report = `
|
||||
Error Report
|
||||
============
|
||||
Request ID: ${error.request_id}
|
||||
Timestamp: ${format(new Date(error.created_at), 'PPpp')}
|
||||
Type: ${error.error_type}
|
||||
Endpoint: ${error.endpoint}
|
||||
Method: ${error.method}
|
||||
Status: ${error.status_code}
|
||||
Duration: ${error.duration_ms}ms
|
||||
|
||||
Error Message:
|
||||
${error.error_message}
|
||||
|
||||
${error.error_stack ? `Stack Trace:\n${error.error_stack}` : ''}
|
||||
`.trim();
|
||||
|
||||
navigator.clipboard.writeText(report);
|
||||
toast.success('Error report copied to clipboard');
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={onClose}>
|
||||
<DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2">
|
||||
Error Details
|
||||
<Badge variant="destructive">{error.error_type}</Badge>
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
<Tabs defaultValue="overview" className="w-full">
|
||||
<TabsList>
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="stack">Stack Trace</TabsTrigger>
|
||||
<TabsTrigger value="breadcrumbs">Breadcrumbs</TabsTrigger>
|
||||
<TabsTrigger value="environment">Environment</TabsTrigger>
|
||||
</TabsList>
|
||||
|
||||
<TabsContent value="overview" className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="text-sm font-medium">Request ID</label>
|
||||
<div className="flex items-center gap-2">
|
||||
<code className="text-sm bg-muted px-2 py-1 rounded">
|
||||
{error.request_id}
|
||||
</code>
|
||||
<Button size="sm" variant="ghost" onClick={copyErrorId}>
|
||||
<Copy className="w-4 h-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium">Timestamp</label>
|
||||
<p className="text-sm">{format(new Date(error.created_at), 'PPpp')}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium">Endpoint</label>
|
||||
<p className="text-sm font-mono">{error.endpoint}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium">Method</label>
|
||||
<Badge variant="outline">{error.method}</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium">Status Code</label>
|
||||
<p className="text-sm">{error.status_code}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium">Duration</label>
|
||||
<p className="text-sm">{error.duration_ms}ms</p>
|
||||
</div>
|
||||
{error.user_id && (
|
||||
<div>
|
||||
<label className="text-sm font-medium">User ID</label>
|
||||
<a
|
||||
href={`/admin/users?search=${error.user_id}`}
|
||||
className="text-sm text-primary hover:underline flex items-center gap-1"
|
||||
>
|
||||
{error.user_id.slice(0, 8)}...
|
||||
<ExternalLink className="w-3 h-3" />
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="text-sm font-medium">Error Message</label>
|
||||
<div className="bg-muted p-4 rounded-lg mt-2">
|
||||
<p className="text-sm font-mono">{error.error_message}</p>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="stack">
|
||||
{error.error_stack ? (
|
||||
<pre className="bg-muted p-4 rounded-lg overflow-x-auto text-xs">
|
||||
{error.error_stack}
|
||||
</pre>
|
||||
) : (
|
||||
<p className="text-muted-foreground">No stack trace available</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="breadcrumbs">
|
||||
{error.breadcrumbs && error.breadcrumbs.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{error.breadcrumbs.map((crumb: any, index: number) => (
|
||||
<div key={index} className="border-l-2 border-primary pl-4 py-2">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Badge variant="outline" className="text-xs">
|
||||
{crumb.category}
|
||||
</Badge>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{format(new Date(crumb.timestamp), 'HH:mm:ss.SSS')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-sm">{crumb.message}</p>
|
||||
{crumb.data && (
|
||||
<pre className="text-xs text-muted-foreground mt-1">
|
||||
{JSON.stringify(crumb.data, null, 2)}
|
||||
</pre>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-muted-foreground">No breadcrumbs recorded</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent value="environment">
|
||||
{error.environment_context ? (
|
||||
<pre className="bg-muted p-4 rounded-lg overflow-x-auto text-xs">
|
||||
{JSON.stringify(error.environment_context, null, 2)}
|
||||
</pre>
|
||||
) : (
|
||||
<p className="text-muted-foreground">No environment context available</p>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button variant="outline" onClick={copyErrorReport}>
|
||||
<Copy className="w-4 h-4 mr-2" />
|
||||
Copy Report
|
||||
</Button>
|
||||
<Button onClick={onClose}>Close</Button>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user