klg-asutk-app/components/ErrorBoundary.tsx
Yuriy 0150aba4f5 Consolidation: KLG ASUTK + PAPA integration
- Unify API: lib/api.ts uses /api/v1, inbox uses /api/inbox (rewrites)
- Remove localhost refs: openapi, inbox page
- Add rewrites: /api/inbox|tmc -> inbox-server, /api/v1 -> FastAPI
- Add stub routes: knowledge/insights, recommendations, search, log-error
- Transfer from PAPA: prompts (inspection, tmc), scripts, supabase, data/tmc-requests
- Fix inbox-server: ORDER BY created_at, package.json
- Remove redundant app/api/inbox/files route (rewrites handle it)
- knowledge/ in gitignore (large PDFs)

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 17:18:31 +03:00

241 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Error Boundary для обработки ошибок React компонентов
*/
'use client';
import React, { Component, ErrorInfo, ReactNode } from 'react';
interface Props {
children: ReactNode;
fallback?: ReactNode;
onError?: (error: Error, errorInfo: ErrorInfo) => void;
}
interface State {
hasError: boolean;
error: Error | null;
errorInfo: ErrorInfo | null;
}
export class ErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
hasError: false,
error: null,
errorInfo: null,
};
}
static getDerivedStateFromError(error: Error): State {
return {
hasError: true,
error,
errorInfo: null,
};
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// Логирование ошибки (только на клиенте)
if (typeof window !== 'undefined') {
// Используем console.error для клиентского логирования
console.error('React Error Boundary caught an error:', error, {
componentStack: errorInfo.componentStack,
errorBoundary: true,
});
// Отправка в Sentry через API (если настроен)
if (process.env.NEXT_PUBLIC_SENTRY_DSN) {
try {
fetch('/api/log-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
}),
}).catch(() => {
// Игнорируем ошибки отправки
});
} catch (e) {
// Игнорируем ошибки
}
}
}
// Отправка в Sentry (если настроен)
if (typeof window !== 'undefined' && (window as any).Sentry) {
(window as any).Sentry.captureException(error, {
contexts: {
react: {
componentStack: errorInfo.componentStack,
},
},
});
}
this.setState({
error,
errorInfo,
});
// Вызов пользовательского обработчика
if (this.props.onError) {
this.props.onError(error, errorInfo);
}
}
handleReset = () => {
this.setState({
hasError: false,
error: null,
errorInfo: null,
});
};
render() {
if (this.state.hasError) {
if (this.props.fallback) {
return this.props.fallback;
}
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
minHeight: '400px',
padding: '40px',
textAlign: 'center',
}}
>
<div
style={{
maxWidth: '600px',
backgroundColor: 'white',
padding: '32px',
borderRadius: '8px',
boxShadow: '0 4px 6px rgba(0,0,0,0.1)',
}}
>
<div
style={{
fontSize: '48px',
marginBottom: '16px',
}}
>
</div>
<h2
style={{
fontSize: '24px',
fontWeight: 'bold',
marginBottom: '12px',
color: '#333',
}}
>
Произошла ошибка
</h2>
<p
style={{
fontSize: '16px',
color: '#666',
marginBottom: '24px',
lineHeight: '1.5',
}}
>
К сожалению, произошла непредвиденная ошибка. Мы уже работаем над её исправлением.
</p>
{process.env.NODE_ENV === 'development' && this.state.error && (
<details
style={{
marginBottom: '24px',
padding: '16px',
backgroundColor: '#f5f5f5',
borderRadius: '4px',
textAlign: 'left',
}}
>
<summary
style={{
cursor: 'pointer',
fontWeight: 'bold',
marginBottom: '8px',
}}
>
Детали ошибки (только в режиме разработки)
</summary>
<pre
style={{
fontSize: '12px',
overflow: 'auto',
whiteSpace: 'pre-wrap',
wordBreak: 'break-word',
}}
>
{this.state.error.toString()}
{this.state.errorInfo?.componentStack}
</pre>
</details>
)}
<div style={{ display: 'flex', gap: '12px', justifyContent: 'center' }}>
<button
onClick={this.handleReset}
style={{
padding: '12px 24px',
backgroundColor: '#1e3a5f',
color: 'white',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '14px',
fontWeight: '500',
}}
>
Попробовать снова
</button>
<button
onClick={() => window.location.href = '/'}
style={{
padding: '12px 24px',
backgroundColor: 'transparent',
color: '#1e3a5f',
border: '1px solid #1e3a5f',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '14px',
}}
>
На главную
</button>
</div>
</div>
</div>
);
}
return this.props.children;
}
}
/**
* HOC для обертки компонентов в Error Boundary
*/
export function withErrorBoundary<P extends object>(
Component: React.ComponentType<P>,
fallback?: ReactNode
) {
return function WrappedComponent(props: P) {
return (
<ErrorBoundary fallback={fallback}>
<Component {...props} />
</ErrorBoundary>
);
};
}