29853/frontend/src/components/ErrorBoundary.tsx
2025-03-13 08:53:25 +00:00

208 lines
5.9 KiB
TypeScript

import React, { Component, ErrorInfo, ReactNode } from 'react';
import { mdiAlertCircle } from '@mdi/js';
import BaseIcon from './BaseIcon';
// Define the props and state interfaces
interface ErrorBoundaryProps {
children: ReactNode;
}
interface ErrorBoundaryState {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
showStack: boolean;
}
// Class-based ErrorBoundary Component
class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
// Define state variables
this.state = {
hasError: false,
error: null,
errorInfo: null,
showStack: false,
};
}
static getDerivedStateFromError(error: Error): Partial<ErrorBoundaryState> {
// Update state so the next render will show the fallback UI
return {
hasError: true,
error: error,
};
}
componentDidUpdate(
prevProps: Readonly<ErrorBoundaryProps>,
prevState: Readonly<ErrorBoundaryState>,
snapshot?: any,
) {
console.log('componentDidUpdate');
}
async componentWillUnmount() {
console.log('componentWillUnmount');
const response = await fetch('/api/logError', {
method: 'DELETE',
});
const data = await response.json();
console.log('Error logs cleared:', data);
}
async componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.log('Error caught in boundary:', error, errorInfo);
// Update state with error details
this.setState({
errorInfo: errorInfo,
});
// Function to log errors to the server
const logErrorToServer = async () => {
try {
const response = await fetch('/api/logError', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: error.message,
stack: errorInfo.componentStack,
}),
});
const data = await response.json();
console.log('Error logged:', data);
} catch (err) {
console.error('Failed to log error:', err);
}
};
// Function to fetch logged errors (optional)
const fetchLoggedErrors = async () => {
try {
const response = await fetch('/api/logError');
const data = await response.json();
console.log('Fetched logs:', data);
} catch (err) {
console.error('Failed to fetch logs:', err);
}
};
await logErrorToServer();
await fetchLoggedErrors();
}
toggleStack = () => {
this.setState((prevState) => ({
showStack: !prevState.showStack,
}));
};
resetError = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null,
showStack: false,
});
};
tryAgain = async () => {
try {
const response = await fetch('/api/logError', {
method: 'DELETE',
});
const data = await response.json();
console.log('Error logs cleared:', data);
} catch (e) {
console.error('Failed to clear error logs:', e);
}
this.setState({ hasError: false });
};
render() {
if (this.state.hasError) {
// Extract error details
const { error, errorInfo, showStack } = this.state;
const errorMessage = error?.message || 'An unexpected error occurred';
const stackTrace =
errorInfo?.componentStack || error?.stack || 'No stack trace available';
return (
<div className='flex items-center justify-center min-h-screen bg-pavitra-300'>
<div className='max-w-lg w-full p-8 bg-white rounded-lg shadow-sm'>
<div className='flex flex-col items-center text-center space-y-6'>
<div className='p-3 bg-pavitra-500 rounded-full flex items-center justify-center'>
<BaseIcon
path={mdiAlertCircle}
size={32}
className='text-pavitra-red'
/>
</div>
<div className='space-y-2'>
<h2 className='text-xl font-semibold text-pavitra-900'>
Something went wrong
</h2>
<p className='text-pavitra-800'>
We&apos;re sorry, but we encountered an unexpected error.
</p>
</div>
<div className='w-full text-left p-4 bg-pavitra-400 rounded-md overflow-hidden'>
<p className='font-mono text-sm text-pavitra-red break-words'>
{errorMessage}
</p>
<div className='mt-4'>
<button
onClick={this.toggleStack}
className='text-xs text-pavitra-800 flex items-center gap-1'
>
<span>{showStack ? 'Hide' : 'Show'} stack trace</span>
<span className='text-xs'>{showStack ? '▲' : '▼'}</span>
</button>
{showStack && (
<pre className='mt-2 p-3 bg-pavitra-500 rounded text-xs font-mono text-pavitra-900 overflow-x-auto max-h-64'>
{stackTrace}
</pre>
)}
</div>
</div>
<div className='space-y-4 w-full'>
<button
className='w-full py-2 px-4 bg-pavitra-blue hover:bg-pavitra-900 text-white rounded-md transition-colors'
onClick={this.tryAgain}
>
Try Again
</button>
<button
className='w-full py-2 px-4 border border-pavitra-600 text-pavitra-800 hover:bg-pavitra-400 rounded-md transition-colors'
onClick={this.resetError}
>
Go Back
</button>
</div>
</div>
</div>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;