# All files / src App.tsx

0% Statements 0/31
0% Branches 0/9
0% Functions 0/19
0% Lines 0/26

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>
      );
    }