Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | 66x 66x 33x 16x 16x 16x 16x 1x 1x 112x 48x 6x 42x 64x 4x | // src/components/ErrorBoundary.tsx
/**
* React Error Boundary with Sentry integration.
* Implements ADR-015: Application Performance Monitoring and Error Tracking.
*
* This component catches JavaScript errors anywhere in the child component tree,
* logs them to Sentry/Bugsink, and displays a fallback UI instead of crashing.
*/
import { Component, ReactNode } from 'react';
import { Sentry, captureException, isSentryConfigured } from '../services/sentry.client';
interface ErrorBoundaryProps {
/** Child components to render */
children: ReactNode;
/** Optional custom fallback UI. If not provided, uses default error message. */
fallback?: ReactNode;
/** Optional callback when an error is caught */
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
eventId: string | null;
}
/**
* Error Boundary component that catches React component errors
* and reports them to Sentry/Bugsink.
*
* @example
* ```tsx
* <ErrorBoundary fallback={<p>Something went wrong.</p>}>
* <MyComponent />
* </ErrorBoundary>
* ```
*/
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = {
hasError: false,
error: null,
eventId: null,
};
}
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
// Log to console in development
console.error('ErrorBoundary caught an error:', error, errorInfo);
// Report to Sentry with component stack
const eventId = captureException(error, {
componentStack: errorInfo.componentStack,
});
this.setState({ eventId: eventId ?? null });
// Call optional onError callback
this.props.onError?.(error, errorInfo);
}
handleReload = (): void => {
window.location.reload();
};
handleReportFeedback = (): void => {
if (isSentryConfigured && this.state.eventId) {
// Open Sentry feedback dialog if available
Sentry.showReportDialog({ eventId: this.state.eventId });
}
};
render(): ReactNode {
if (this.state.hasError) {
// Custom fallback UI if provided
if (this.props.fallback) {
return this.props.fallback;
}
// Default fallback UI
return (
<div className="flex min-h-screen items-center justify-center bg-gray-50 dark:bg-gray-900 p-4">
<div className="max-w-md w-full bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 text-center">
<div className="text-red-500 dark:text-red-400 mb-4">
<svg
className="w-16 h-16 mx-auto"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
aria-hidden="true"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
/>
</svg>
</div>
<h1 className="text-xl font-semibold text-gray-900 dark:text-white mb-2">
Something went wrong
</h1>
<p className="text-gray-600 dark:text-gray-400 mb-6">
We're sorry, but an unexpected error occurred. Our team has been notified.
</p>
<div className="flex flex-col sm:flex-row gap-3 justify-center">
<button
onClick={this.handleReload}
className="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition-colors"
>
Reload Page
</button>
{isSentryConfigured && this.state.eventId && (
<button
onClick={this.handleReportFeedback}
className="px-4 py-2 bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 rounded-md hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors"
>
Report Feedback
</button>
)}
</div>
{this.state.error && process.env.NODE_ENV === 'development' && (
<details className="mt-6 text-left">
<summary className="cursor-pointer text-sm text-gray-500 dark:text-gray-400">
Error Details (Development Only)
</summary>
<pre className="mt-2 p-3 bg-gray-100 dark:bg-gray-900 rounded text-xs overflow-auto max-h-48 text-red-600 dark:text-red-400">
{this.state.error.message}
{'\n\n'}
{this.state.error.stack}
</pre>
</details>
)}
</div>
</div>
);
}
return this.props.children;
}
}
/**
* Pre-configured Sentry ErrorBoundary from @sentry/react.
* Use this for simpler integration when you don't need custom UI.
*/
export const SentryErrorBoundary = Sentry.ErrorBoundary;
|