2026-07-25 09:19:32 +02:00
|
|
|
|
import React from 'react';
|
2026-07-26 23:45:59 +02:00
|
|
|
|
import { logError } from '@/utils/errorLogger';
|
2026-07-25 09:19:32 +02:00
|
|
|
|
|
|
|
|
|
|
interface ErrorBoundaryProps {
|
|
|
|
|
|
children: React.ReactNode;
|
|
|
|
|
|
fallback?: React.ReactNode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface ErrorBoundaryState {
|
|
|
|
|
|
hasError: boolean;
|
|
|
|
|
|
error: Error | null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* App-wide ErrorBoundary that catches React rendering errors
|
|
|
|
|
|
* and displays a fallback UI with a reload button.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
|
|
|
|
|
constructor(props: ErrorBoundaryProps) {
|
|
|
|
|
|
super(props);
|
|
|
|
|
|
this.state = { hasError: false, error: null };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
|
|
|
|
return { hasError: true, error };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
|
|
|
|
|
|
console.error('[ErrorBoundary] Uncaught error:', error, errorInfo);
|
2026-07-26 23:45:59 +02:00
|
|
|
|
logError(error, { componentStack: errorInfo.componentStack, source: 'ErrorBoundary' });
|
2026-07-25 09:19:32 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
handleReload = (): void => {
|
|
|
|
|
|
window.location.reload();
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
render(): React.ReactNode {
|
|
|
|
|
|
if (this.state.hasError) {
|
|
|
|
|
|
if (this.props.fallback) {
|
|
|
|
|
|
return this.props.fallback;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
className="flex items-center justify-center min-h-screen bg-secondary-50 p-8"
|
|
|
|
|
|
role="alert"
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="max-w-md w-full bg-white rounded-lg shadow-lg p-8 text-center">
|
|
|
|
|
|
<div className="text-red-500 text-6xl mb-4" aria-hidden="true">
|
|
|
|
|
|
⚠️
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h1 className="text-2xl font-bold text-secondary-900 mb-2">
|
|
|
|
|
|
Ein Fehler ist aufgetreten
|
|
|
|
|
|
</h1>
|
|
|
|
|
|
<p className="text-secondary-600 mb-6">
|
|
|
|
|
|
Die Anwendung konnte nicht geladen werden. Bitte versuchen Sie es erneut.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
{this.state.error && (
|
|
|
|
|
|
<details className="mb-6 text-left">
|
|
|
|
|
|
<summary className="cursor-pointer text-sm text-secondary-500 hover:text-secondary-700">
|
|
|
|
|
|
Fehlerdetails
|
|
|
|
|
|
</summary>
|
|
|
|
|
|
<pre className="mt-2 p-3 bg-secondary-100 rounded text-xs text-secondary-700 overflow-auto max-h-32">
|
|
|
|
|
|
{this.state.error.message}
|
|
|
|
|
|
{this.state.error.stack && `\n\n${this.state.error.stack}`}
|
|
|
|
|
|
</pre>
|
|
|
|
|
|
</details>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={this.handleReload}
|
|
|
|
|
|
className="inline-flex items-center px-6 py-3 bg-primary-600 text-white font-medium rounded-lg hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 transition-colors"
|
|
|
|
|
|
>
|
|
|
|
|
|
Neu laden
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return this.props.children;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default ErrorBoundary;
|