124 lines
3.4 KiB
TypeScript
124 lines
3.4 KiB
TypeScript
import React, { Component, ReactNode } from 'react';
|
||
|
||
interface Props {
|
||
children: ReactNode;
|
||
fallback?: ReactNode;
|
||
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
|
||
}
|
||
|
||
interface State {
|
||
hasError: boolean;
|
||
error: Error | null;
|
||
}
|
||
|
||
/**
|
||
* Error Boundary component to catch and handle React errors
|
||
* Prevents entire app from crashing when a component throws
|
||
*
|
||
* @example
|
||
* <ErrorBoundary fallback={<ErrorFallback />}>
|
||
* <MyComponent />
|
||
* </ErrorBoundary>
|
||
*/
|
||
export class ErrorBoundary extends Component<Props, State> {
|
||
constructor(props: Props) {
|
||
super(props);
|
||
this.state = {
|
||
hasError: false,
|
||
error: null,
|
||
};
|
||
}
|
||
|
||
static getDerivedStateFromError(error: Error): State {
|
||
return {
|
||
hasError: true,
|
||
error,
|
||
};
|
||
}
|
||
|
||
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||
// Log error to console in development
|
||
console.error('ErrorBoundary caught an error:', error, errorInfo);
|
||
|
||
// Call optional error handler
|
||
this.props.onError?.(error, errorInfo);
|
||
}
|
||
|
||
handleReset = () => {
|
||
this.setState({
|
||
hasError: false,
|
||
error: null,
|
||
});
|
||
};
|
||
|
||
render() {
|
||
if (this.state.hasError) {
|
||
// Render custom fallback or default error UI
|
||
if (this.props.fallback) {
|
||
return this.props.fallback;
|
||
}
|
||
|
||
return (
|
||
<div className="flex flex-col items-center justify-center p-8 bg-red-50 border border-red-200 rounded-lg">
|
||
<div className="text-red-600 font-semibold mb-2">⚠️ Что-то пошло не так</div>
|
||
<div className="text-sm text-red-500 mb-4">
|
||
{this.state.error?.message || 'Произошла неизвестная ошибка'}
|
||
</div>
|
||
<button
|
||
onClick={this.handleReset}
|
||
className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 transition-colors"
|
||
>
|
||
Попробовать снова
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return this.props.children;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Specific Error Boundary for Builder components
|
||
*/
|
||
export function BuilderErrorBoundary({ children }: { children: ReactNode }) {
|
||
return (
|
||
<ErrorBoundary
|
||
fallback={
|
||
<div className="flex flex-col items-center justify-center p-8 bg-yellow-50 border border-yellow-200 rounded-lg">
|
||
<div className="text-yellow-700 font-semibold mb-2">⚠️ Ошибка в билдере</div>
|
||
<div className="text-sm text-yellow-600 mb-4">
|
||
Не удалось загрузить компонент. Попробуйте перезагрузить страницу.
|
||
</div>
|
||
</div>
|
||
}
|
||
onError={(error) => {
|
||
// Could send to error tracking service here
|
||
console.error('[Builder Error]:', error);
|
||
}}
|
||
>
|
||
{children}
|
||
</ErrorBoundary>
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Specific Error Boundary for Preview component
|
||
*/
|
||
export function PreviewErrorBoundary({ children }: { children: ReactNode }) {
|
||
return (
|
||
<ErrorBoundary
|
||
fallback={
|
||
<div className="flex flex-col items-center justify-center h-full p-8 bg-gray-50">
|
||
<div className="text-gray-600 font-semibold mb-2">⚠️ Ошибка превью</div>
|
||
<div className="text-sm text-gray-500">
|
||
Не удалось отобразить превью экрана
|
||
</div>
|
||
</div>
|
||
}
|
||
>
|
||
{children}
|
||
</ErrorBoundary>
|
||
);
|
||
}
|