fix: improve error handling and stability - no logout on transient errors, add ErrorBoundary, global error logging
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||
import { logError } from '@/utils/errorLogger';
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: ReactNode;
|
||||
/** Optional fallback render function; receives the error and a retry callback */
|
||||
fallback?: (error: Error, retry: () => void) => ReactNode;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* React Error Boundary that catches JavaScript errors in the component tree.
|
||||
* Shows a friendly error message with a Retry button.
|
||||
* Logs errors via the global error logger.
|
||||
* Only the affected subtree is broken — the rest of the app continues.
|
||||
*/
|
||||
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
|
||||
// Log to console + backend + sessionStorage buffer
|
||||
logError(error, { componentStack: errorInfo.componentStack });
|
||||
}
|
||||
|
||||
handleRetry = (): void => {
|
||||
this.setState({ hasError: false, error: null });
|
||||
};
|
||||
|
||||
render(): ReactNode {
|
||||
if (this.state.hasError && this.state.error) {
|
||||
// Custom fallback if provided
|
||||
if (this.props.fallback) {
|
||||
return this.props.fallback(this.state.error, this.handleRetry);
|
||||
}
|
||||
|
||||
// Default fallback UI
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
minHeight: '300px',
|
||||
padding: '2rem',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
>
|
||||
<h2 style={{ marginBottom: '0.5rem', color: '#dc2626' }}>
|
||||
Something went wrong
|
||||
</h2>
|
||||
<p style={{ marginBottom: '1rem', color: '#6b7280', maxWidth: '400px' }}>
|
||||
An unexpected error occurred. You can try again or refresh the page.
|
||||
</p>
|
||||
<details style={{ marginBottom: '1rem', maxWidth: '600px', color: '#9ca3af' }}>
|
||||
<summary style={{ cursor: 'pointer', fontSize: '0.875rem' }}>
|
||||
Error details
|
||||
</summary>
|
||||
<pre
|
||||
style={{
|
||||
marginTop: '0.5rem',
|
||||
fontSize: '0.75rem',
|
||||
overflow: 'auto',
|
||||
textAlign: 'left',
|
||||
}}
|
||||
>
|
||||
{this.state.error.message}
|
||||
{this.state.error.stack ? `\n\n${this.state.error.stack}` : ''}
|
||||
</pre>
|
||||
</details>
|
||||
<button
|
||||
onClick={this.handleRetry}
|
||||
style={{
|
||||
padding: '0.5rem 1.5rem',
|
||||
backgroundColor: '#2563eb',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '0.375rem',
|
||||
cursor: 'pointer',
|
||||
fontSize: '0.875rem',
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default ErrorBoundary;
|
||||
Reference in New Issue
Block a user