Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | import { useState, useEffect, lazy, Suspense, Component } from 'react';
import type { ReactNode, ErrorInfo } from 'react';
import { setup as setupApi } from './lib/api';
import type { SetupStatus } from './types/generated';
const SetupWizard = lazy(() => import('./pages/SetupWizard').then(m => ({ default: m.SetupWizard })));
const Dashboard = lazy(() => import('./pages/Dashboard').then(m => ({ default: m.Dashboard })));
class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
state: { error: Error | null } = { error: null };
static getDerivedStateFromError(error: Error) { return { error }; }
componentDidCatch(error: Error, info: ErrorInfo) { console.error('Uncaught error:', error, info); }
render() {
if (this.state.error) {
return (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', height: '100vh', flexDirection: 'column', gap: 16 }}>
<span style={{ color: '#ff4444', fontSize: 15, fontFamily: 'JetBrains Mono, monospace' }}>
Something went wrong.
</span>
<pre style={{ color: 'rgba(255,255,255,0.5)', fontSize: 12, maxWidth: '80vw', overflow: 'auto' }}>
{this.state.error.message}
</pre>
<button onClick={() => window.location.reload()} style={{ padding: '8px 16px', cursor: 'pointer', background: '#c8ff00', color: '#000', border: 'none', borderRadius: 4, fontFamily: 'JetBrains Mono, monospace' }}>
Reload
</button>
</div>
);
}
return this.props.children;
}
}
export function App() {
const [setupStatus, setSetupStatus] = useState<SetupStatus | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
setupApi.getStatus()
.then(setSetupStatus)
.catch(() => {
// If API is down, show setup anyway
setSetupStatus(null);
})
.finally(() => setLoading(false));
}, []);
if (loading) {
return <LoadingScreen />;
}
// First run or setup incomplete → show wizard
if (!setupStatus || setupStatus.is_first_run || setupStatus.current_step !== 'Complete') {
return (
<ErrorBoundary>
<Suspense fallback={<LoadingScreen />}>
<SetupWizard
initialStatus={setupStatus}
onComplete={() => {
// Re-fetch status to get fresh state with is_first_run=false
setupApi.getStatus().then(setSetupStatus).catch(() => {});
}}
/>
</Suspense>
</ErrorBoundary>
);
}
// Setup complete → show dashboard
return (
<ErrorBoundary>
<Suspense fallback={<LoadingScreen />}>
<Dashboard onReset={() => {
setupApi.reset().then(() => {
setSetupStatus(null);
setLoading(true);
setupApi.getStatus().then(setSetupStatus).finally(() => setLoading(false));
}).catch(() => {});
}} />
</Suspense>
</ErrorBoundary>
);
}
function LoadingScreen() {
return (
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
flexDirection: 'column',
gap: 16,
}}>
<div style={{
width: 32, height: 32,
border: '3px solid rgba(200,255,0,0.2)',
borderTopColor: '#c8ff00',
borderRadius: '50%',
animation: 'spin 0.8s linear infinite',
}} />
<span style={{ color: 'rgba(255,255,255,0.4)', fontSize: 13, fontFamily: 'JetBrains Mono, monospace' }}>
Entering the grid...
</span>
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
</div>
);
}
|