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