fix(app): add ErrorBoundary to show actual crash errors instead of white screen
This commit is contained in:
71
src/App.tsx
71
src/App.tsx
@@ -3,7 +3,8 @@
|
|||||||
* Handles routing and global providers
|
* Handles routing and global providers
|
||||||
*/
|
*/
|
||||||
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
import { Routes, Route, useNavigate, useLocation } from 'react-router-dom';
|
||||||
import { useEffect } from 'react';
|
import { Component, useEffect } from 'react';
|
||||||
|
import type { ErrorInfo, ReactNode } from 'react';
|
||||||
import { Toaster } from 'sonner';
|
import { Toaster } from 'sonner';
|
||||||
import { MainLayout } from './components/layout/MainLayout';
|
import { MainLayout } from './components/layout/MainLayout';
|
||||||
import { Dashboard } from './pages/Dashboard';
|
import { Dashboard } from './pages/Dashboard';
|
||||||
@@ -16,6 +17,70 @@ import { Setup } from './pages/Setup';
|
|||||||
import { useSettingsStore } from './stores/settings';
|
import { useSettingsStore } from './stores/settings';
|
||||||
import { useGatewayStore } from './stores/gateway';
|
import { useGatewayStore } from './stores/gateway';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error Boundary to catch and display React rendering errors
|
||||||
|
*/
|
||||||
|
class ErrorBoundary extends Component<
|
||||||
|
{ children: ReactNode },
|
||||||
|
{ hasError: boolean; error: Error | null }
|
||||||
|
> {
|
||||||
|
constructor(props: { children: ReactNode }) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false, error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error) {
|
||||||
|
return { hasError: true, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, info: ErrorInfo) {
|
||||||
|
console.error('React Error Boundary caught error:', error, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return (
|
||||||
|
<div style={{
|
||||||
|
padding: '40px',
|
||||||
|
color: '#f87171',
|
||||||
|
background: '#0f172a',
|
||||||
|
minHeight: '100vh',
|
||||||
|
fontFamily: 'monospace'
|
||||||
|
}}>
|
||||||
|
<h1 style={{ fontSize: '24px', marginBottom: '16px' }}>Something went wrong</h1>
|
||||||
|
<pre style={{
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
background: '#1e293b',
|
||||||
|
padding: '16px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
fontSize: '14px'
|
||||||
|
}}>
|
||||||
|
{this.state.error?.message}
|
||||||
|
{'\n\n'}
|
||||||
|
{this.state.error?.stack}
|
||||||
|
</pre>
|
||||||
|
<button
|
||||||
|
onClick={() => { this.setState({ hasError: false, error: null }); window.location.reload(); }}
|
||||||
|
style={{
|
||||||
|
marginTop: '16px',
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: '#3b82f6',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '6px',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Reload
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -69,7 +134,7 @@ function App() {
|
|||||||
}, [theme]);
|
}, [theme]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<ErrorBoundary>
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Setup wizard (shown on first launch) */}
|
{/* Setup wizard (shown on first launch) */}
|
||||||
<Route path="/setup/*" element={<Setup />} />
|
<Route path="/setup/*" element={<Setup />} />
|
||||||
@@ -91,7 +156,7 @@ function App() {
|
|||||||
richColors
|
richColors
|
||||||
closeButton
|
closeButton
|
||||||
/>
|
/>
|
||||||
</>
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user