mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 23:31:11 -05:00
feat: Add error boundaries
This commit is contained in:
119
src/components/error/RouteErrorBoundary.tsx
Normal file
119
src/components/error/RouteErrorBoundary.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||
import { AlertTriangle, Home, RefreshCw } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { logger } from '@/lib/logger';
|
||||
|
||||
interface RouteErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
interface RouteErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Route Error Boundary Component (P0 #5)
|
||||
*
|
||||
* Top-level error boundary that wraps all routes.
|
||||
* Last line of defense to prevent complete app crashes.
|
||||
*
|
||||
* Usage: Wrap Routes component in App.tsx
|
||||
* ```tsx
|
||||
* <RouteErrorBoundary>
|
||||
* <Routes>...</Routes>
|
||||
* </RouteErrorBoundary>
|
||||
* ```
|
||||
*/
|
||||
export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, RouteErrorBoundaryState> {
|
||||
constructor(props: RouteErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
hasError: false,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): Partial<RouteErrorBoundaryState> {
|
||||
return {
|
||||
hasError: true,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
|
||||
// Critical: Route-level error - highest priority logging
|
||||
logger.error('Route-level error caught by boundary', {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
componentStack: errorInfo.componentStack,
|
||||
url: window.location.href,
|
||||
severity: 'critical',
|
||||
});
|
||||
}
|
||||
|
||||
handleReload = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
handleGoHome = () => {
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="min-h-screen flex items-center justify-center p-4 bg-background">
|
||||
<Card className="max-w-lg w-full shadow-lg">
|
||||
<CardHeader className="text-center pb-4">
|
||||
<div className="mx-auto w-16 h-16 bg-destructive/10 rounded-full flex items-center justify-center mb-4">
|
||||
<AlertTriangle className="w-8 h-8 text-destructive" />
|
||||
</div>
|
||||
<CardTitle className="text-2xl">
|
||||
Something Went Wrong
|
||||
</CardTitle>
|
||||
<CardDescription className="mt-2">
|
||||
We encountered an unexpected error. This has been logged and we'll look into it.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{import.meta.env.DEV && this.state.error && (
|
||||
<div className="p-3 bg-muted rounded-lg">
|
||||
<p className="text-xs font-mono text-muted-foreground">
|
||||
{this.state.error.message}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-2">
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={this.handleReload}
|
||||
className="flex-1 gap-2"
|
||||
>
|
||||
<RefreshCw className="w-4 h-4" />
|
||||
Reload Page
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={this.handleGoHome}
|
||||
className="flex-1 gap-2"
|
||||
>
|
||||
<Home className="w-4 h-4" />
|
||||
Go Home
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<p className="text-xs text-center text-muted-foreground">
|
||||
If this problem persists, please contact support
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user