veza/apps/web/src/components/ErrorBoundary.tsx

124 lines
3.5 KiB
TypeScript

import { Component, type ErrorInfo, type ReactNode } from 'react';
import * as Sentry from '@sentry/react';
import { logger, getLogContext } from '@/utils/logger';
import { ErrorDisplay } from '@/components/ui/ErrorDisplay';
interface Props {
children: ReactNode;
fallback?: ReactNode;
}
interface State {
hasError: boolean;
error?: Error;
errorInfo?: ErrorInfo;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error };
}
override componentDidCatch(error: Error, errorInfo: ErrorInfo) {
this.setState({
error,
errorInfo,
});
// Action 3.3.1.4: Enhanced error boundary logging for monitoring
const logContext = getLogContext();
// Gather additional context for monitoring
const monitoringContext = {
...logContext,
component: 'ErrorBoundary',
errorType: error.name || 'Error',
errorMessage: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
url: typeof window !== 'undefined' ? window.location.href : undefined,
userAgent:
typeof navigator !== 'undefined' ? navigator.userAgent : undefined,
timestamp: new Date().toISOString(),
};
// Log to structured logger
logger.error('[ErrorBoundary] React error caught', monitoringContext);
// Send to Sentry with enriched context
if (typeof window !== 'undefined') {
Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack,
},
application: {
...logContext,
url: window.location.href,
userAgent: navigator.userAgent,
},
},
tags: {
error_boundary: true,
error_type: error.name || 'Error',
...(logContext.request_id
? { request_id: String(logContext.request_id) }
: {}),
},
level: 'error',
});
}
}
handleReset = () => {
this.setState({ hasError: false, error: undefined, errorInfo: undefined });
};
override render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
// Action 3.3.1.3: Use ErrorDisplay component for consistent error presentation
return (
<div className="min-h-screen flex items-center justify-center bg-kodo-void dark:bg-kodo-ink p-4">
<div className="w-full max-w-md">
<ErrorDisplay
error={
this.state.error ||
new Error("Une erreur inattendue s'est produite")
}
variant="card"
severity="error"
size="lg"
showDetails={import.meta.env.DEV}
context={{
component: 'ErrorBoundary',
action: 'rendering component',
componentStack: this.state.errorInfo?.componentStack,
}}
onRetry={this.handleReset}
actions={[
{
label: "Retour à l'accueil",
onClick: () => {
window.location.href = '/';
},
variant: 'outline',
},
]}
/>
</div>
</div>
);
}
return this.props.children;
}
}