208 lines
5.9 KiB
TypeScript
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'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;
|