← back to Bertha

react-dash/src/setup/SetupWizard.jsx

151 lines

import React, { useState, useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
import { checkSession, post, api } from '../api';
import Welcome from './steps/Welcome';
import ConnectPoly from './steps/ConnectPoly';
import TestConnection from './steps/TestConnection';
import IngestMarkets from './steps/IngestMarkets';
import FetchWeather from './steps/FetchWeather';
import RunPredictions from './steps/RunPredictions';
import RiskProfile from './steps/RiskProfile';
import GoLive from './steps/GoLive';

const STEPS = [
  { label: 'Welcome', icon: '1' },
  { label: 'Connect', icon: '2' },
  { label: 'Test', icon: '3' },
  { label: 'Ingest', icon: '4' },
  { label: 'Weather', icon: '5' },
  { label: 'Predict', icon: '6' },
  { label: 'Risk', icon: '7' },
  { label: 'Go Live', icon: '8' },
];

export default function SetupWizard() {
  const nav = useNavigate();
  const [authed, setAuthed] = useState(false);
  const [step, setStep] = useState(0);
  const [dir, setDir] = useState('forward');
  const [data, setData] = useState({
    keys: null,
    connectionOk: false,
    marketsIngested: 0,
    weatherFetched: false,
    predictionsRun: false,
    riskProfile: null,
  });
  const contentRef = useRef(null);

  useEffect(() => {
    checkSession().then(ok => {
      if (!ok) { nav('/login'); return; }
      setAuthed(true);
      // Try to restore progress
      api('/setup').then(r => {
        if (r.setup_complete) { nav('/'); return; }
        if (r.setup_progress) {
          setStep(r.setup_progress.step || 0);
          setData(d => ({ ...d, ...r.setup_progress.data }));
        }
      }).catch(() => {});
    });
  }, []);

  useEffect(() => {
    // Save progress on step change
    if (authed && step > 0) {
      post('/setup', { action: 'save_progress', progress: { step, data } }).catch(() => {});
    }
  }, [step]);

  function next() {
    setDir('forward');
    if (step < STEPS.length - 1) setStep(s => s + 1);
  }
  function back() {
    setDir('back');
    if (step > 0) setStep(s => s - 1);
  }
  function goTo(i) {
    if (i <= step) {
      setDir(i < step ? 'back' : 'forward');
      setStep(i);
    }
  }
  function update(partial) {
    setData(d => ({ ...d, ...partial }));
  }
  async function complete() {
    await post('/setup', { action: 'complete', progress: { step: STEPS.length - 1, data } });
    nav('/');
  }

  if (!authed) {
    return <div className="flex items-center justify-center min-h-screen"><div className="animate-spin w-8 h-8 border-2 border-blue-500 border-t-transparent rounded-full" /></div>;
  }

  const StepComponent = [Welcome, ConnectPoly, TestConnection, IngestMarkets, FetchWeather, RunPredictions, RiskProfile, GoLive][step];

  return (
    <div className="min-h-screen flex flex-col" style={{ background: 'var(--bg)' }}>
      {/* Header */}
      <div className="flex items-center gap-3 px-6 py-4 border-b border-gray-800">
        <div className="w-8 h-8 rounded-lg flex items-center justify-center flex-shrink-0" style={{ background: 'linear-gradient(135deg,#3b82f6,#8b5cf6)' }}>
          <span className="text-white font-bold text-sm">P</span>
        </div>
        <span className="font-bold text-sm wizard-gradient">POLY SETUP</span>
      </div>

      {/* Stepper */}
      <div className="px-6 py-5">
        <div className="max-w-3xl mx-auto flex items-center">
          {STEPS.map((s, i) => (
            <React.Fragment key={i}>
              <button
                onClick={() => goTo(i)}
                disabled={i > step}
                className="flex flex-col items-center gap-1.5 group"
                style={{ minWidth: 56 }}
              >
                <div className={`w-8 h-8 rounded-full flex items-center justify-center text-xs font-bold transition-all ${
                  i < step ? 'bg-green-500 text-white' :
                  i === step ? 'bg-blue-500 text-white step-active-pulse' :
                  'bg-gray-800 text-gray-500'
                } ${i <= step ? 'cursor-pointer' : 'cursor-default'}`}>
                  {i < step ? (
                    <svg className="w-4 h-4" fill="none" stroke="currentColor" strokeWidth="2.5" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" /></svg>
                  ) : s.icon}
                </div>
                <span className={`text-[10px] font-medium ${
                  i < step ? 'text-green-400' :
                  i === step ? 'text-blue-400' :
                  'text-gray-600'
                }`}>{s.label}</span>
              </button>
              {i < STEPS.length - 1 && (
                <div className={`stepper-line ${i < step ? 'done' : ''}`} />
              )}
            </React.Fragment>
          ))}
        </div>
      </div>

      {/* Step Content */}
      <div className="flex-1 flex items-start justify-center px-6 pb-10">
        <div className="w-full max-w-2xl" ref={contentRef}>
          <div key={step} className="step-enter step-active">
            <StepComponent
              data={data}
              update={update}
              next={next}
              back={back}
              complete={complete}
              step={step}
            />
          </div>
        </div>
      </div>
    </div>
  );
}