diff --git a/.ralph/fix_plan.md b/.ralph/fix_plan.md index 9526f3e..c31605a 100644 --- a/.ralph/fix_plan.md +++ b/.ralph/fix_plan.md @@ -396,12 +396,12 @@ Spec: `.ralph/specs/phase-18-cli-cicd.md` --- -## Phase 24: Onboarding + First-Run [PENDIENTE] +## Phase 24: Onboarding + First-Run [COMPLETO] -- [ ] 24.1: Detectar first-run en frontend (GET /api/auth/setup-required) -- [ ] 24.2: Wizard multi-step: paso 1 crear admin, paso 2 nombre org, paso 3 "Start your first exploration" con URL input -- [ ] 24.3: Empty states: ilustraciones/mensajes en tablas vacías ("No findings yet. Start an exploration!") -- [ ] 24.4: Commit: `fase(24): onboarding and first-run experience` +- [x] 24.1: Detectar first-run en frontend (GET /api/auth/setup-required) +- [x] 24.2: Wizard multi-step: paso 1 crear admin, paso 2 nombre org, paso 3 "Start your first exploration" con URL input +- [x] 24.3: Empty states: ilustraciones/mensajes en tablas vacías ("No findings yet. Start an exploration!") +- [x] 24.4: Commit: `fase(24): onboarding and first-run experience` --- diff --git a/frontend/src/pages/Setup.tsx b/frontend/src/pages/Setup.tsx index 10a38ab..d707f38 100644 --- a/frontend/src/pages/Setup.tsx +++ b/frontend/src/pages/Setup.tsx @@ -7,6 +7,8 @@ import { Label } from '@/components/ui/label' import { apiFetch } from '@/lib/api' import { queryClient } from '@/lib/queryClient' +const TOTAL_STEPS = 3 + export function Setup() { const navigate = useNavigate() const [step, setStep] = useState(1) @@ -14,15 +16,12 @@ export function Setup() { const [email, setEmail] = useState('') const [password, setPassword] = useState('') const [orgName, setOrgName] = useState('') + const [firstUrl, setFirstUrl] = useState('') const [error, setError] = useState(null) const [loading, setLoading] = useState(false) - async function handleSubmit(e: React.FormEvent) { + async function handleCreateAccount(e: React.FormEvent) { e.preventDefault() - if (step === 1) { - setStep(2) - return - } setError(null) setLoading(true) try { @@ -31,7 +30,7 @@ export function Setup() { body: JSON.stringify({ name, email, password, organizationName: orgName }), }) await queryClient.invalidateQueries({ queryKey: ['auth'] }) - navigate('/') + setStep(3) } catch (err) { setError(err instanceof Error ? err.message : 'Setup failed') } finally { @@ -39,98 +38,142 @@ export function Setup() { } } + function handleStep1(e: React.FormEvent) { + e.preventDefault() + setStep(2) + } + + async function handleStartExploration() { + if (!firstUrl.trim()) { + navigate('/') + return + } + try { + const session = await apiFetch('/api/sessions', { + method: 'POST', + body: JSON.stringify({ url: firstUrl.trim() }), + }) as { id: string } + navigate(`/sessions/${session.id}`) + } catch { + navigate('/') + } + } + + const stepTitle: Record = { + 1: 'Create your admin account', + 2: 'Name your organization', + 3: 'Start your first exploration', + } + return (
Welcome to ABE - - {step === 1 ? 'Create your admin account' : 'Name your organization'} - + {stepTitle[step]}
- {[1, 2].map(s => ( + {Array.from({ length: TOTAL_STEPS }).map((_, i) => (
))}
-
void handleSubmit(e)} className="space-y-4"> - {step === 1 ? ( - <> -
- - setName(e.target.value)} - placeholder="John Doe" - required - autoFocus - /> -
-
- - setEmail(e.target.value)} - placeholder="admin@example.com" - required - /> -
-
- - setPassword(e.target.value)} - minLength={8} - required - /> -
- +
+ )} + + {step === 2 && ( +
void handleCreateAccount(e)} className="space-y-4"> +
+ + setOrgName(e.target.value)} + placeholder="Acme Security" + required + autoFocus + /> +
+ {error &&

{error}

} +
+ - - ) : ( - <> -
- - setOrgName(e.target.value)} - placeholder="Acme Security" - required - autoFocus - /> -
- {error && ( -

{error}

- )} -
- - -
- - )} - + +
+ + )} + + {step === 3 && ( +
+

+ ABE is ready. Enter a URL to start your first autonomous exploration, or skip to the dashboard. +

+
+ + setFirstUrl(e.target.value)} + placeholder="https://example.com" + autoFocus + /> +
+
+ + +
+
+ )}