Files
router-lists-ui/frontend/src/components/ErrorBoundary.jsx
T

160 lines
5.1 KiB
React

import { Component } from 'react';
import { IconAlertTriangle, IconRefresh, IconHome } from '@tabler/icons-react';
/**
* ErrorBoundary - глобальный обработчик ошибок React
* Ловит ошибки рендеринга и показывает fallback UI
*/
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
errorCount: 0
};
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// Логируем ошибку
console.error('ErrorBoundary caught an error:', error, errorInfo);
// Отправляем в мониторинг (если настроен)
this.logErrorToService(error, errorInfo);
this.setState(prevState => ({
error,
errorInfo,
errorCount: prevState.errorCount + 1
}));
}
logErrorToService(error, errorInfo) {
// Можно интегрировать с Sentry, LogRocket и т.д.
try {
if (typeof window !== 'undefined' && window.notify?.error) {
window.notify.error('Произошла критическая ошибка приложения', {
error: error.toString(),
componentStack: errorInfo?.componentStack
});
}
} catch (e) {
console.error('Failed to log error:', e);
}
}
handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null
});
// Очищаем localStorage если ошибка повторяется
if (this.state.errorCount > 2) {
try {
localStorage.clear();
sessionStorage.clear();
} catch (e) {
console.error('Failed to clear storage:', e);
}
}
// Перезагружаем страницу если много ошибок
if (this.state.errorCount > 3) {
window.location.href = '/';
}
};
handleReload = () => {
window.location.reload();
};
handleGoHome = () => {
window.location.href = '/';
};
render() {
if (this.state.hasError) {
return (
<div className="page page-center">
<div className="container-tight py-4">
<div className="empty">
<div className="empty-icon">
<IconAlertTriangle size={64} className="text-danger" />
</div>
<p className="empty-title">Произошла ошибка</p>
<p className="empty-subtitle text-muted">
{this.state.error?.message || 'Что-то пошло не так. Попробуйте обновить страницу.'}
</p>
{process.env.NODE_ENV === 'development' && this.state.errorInfo && (
<div className="card mt-3">
<div className="card-body">
<h3 className="card-title">Детали ошибки (только в dev режиме)</h3>
<pre className="text-start" style={{
fontSize: '0.75rem',
maxHeight: '300px',
overflow: 'auto',
whiteSpace: 'pre-wrap'
}}>
{this.state.error?.toString()}
{'\n\n'}
{this.state.errorInfo?.componentStack}
</pre>
</div>
</div>
)}
<div className="empty-action">
<div className="btn-list justify-content-center">
<button
className="btn btn-primary"
onClick={this.handleReset}
>
<IconRefresh className="icon" />
Попробовать снова
</button>
<button
className="btn btn-outline-primary"
onClick={this.handleReload}
>
Перезагрузить страницу
</button>
<button
className="btn btn-outline-secondary"
onClick={this.handleGoHome}
>
<IconHome className="icon" />
На главную
</button>
</div>
</div>
{this.state.errorCount > 1 && (
<div className="alert alert-warning mt-3">
<p className="mb-0">
Ошибка повторяется ({this.state.errorCount} раз).
{this.state.errorCount > 2 && ' При следующей попытке кэш будет очищен.'}
{this.state.errorCount > 3 && ' Следующая попытка приведёт к полной перезагрузке.'}
</p>
</div>
)}
</div>
</div>
</div>
);
}
return this.props.children;
}
}
export default ErrorBoundary;