"use client"; import React from "react"; import { WizardTop, WizardBody, WizardQ, WizardFooter, Field, } from "./onboarding-primitives"; import { cityLabel, extractTools, searchCities, } from "./onboarding-agency-mock"; import { type AgencyOnboardingResult, type AgencyProfile, type CityRef, } from "./onboarding-agency-types"; // Contractor-first onboarding — "Set up your AI agency". // Vibn builds CUSTOM TOOLS for local businesses. We capture who the consultant // is and what they love building, then drop them into their dashboard — where // the local-business targeting recommendations live as an ongoing feature. // Steps: identity → presence → expertise → (dashboard). // Built to the Vibn design concept: dark wizard surface, coral accent, used sparingly. // Fully interactive against mock data; swap the mock calls for the endpoints // documented in onboarding-agency-types.ts. const STEPS = ["identity", "presence", "expertise"] as const; type Step = (typeof STEPS)[number]; export interface AgencyOnboardingProps { /** Fired with the assembled result; wire to POST /api/agency then route to the dashboard. */ onComplete: (result: AgencyOnboardingResult) => void; /** Save & exit. */ onExit: () => void; /** Back to the front-door fork. */ onBack: () => void; } export function AgencyOnboarding({ onComplete, onExit, onBack, }: AgencyOnboardingProps) { const [stepIdx, setStepIdx] = React.useState(0); const step: Step = STEPS[stepIdx]; const [profile, setProfile] = React.useState({ name: "", city: undefined, }); const [expertise, setExpertise] = React.useState(""); const detectedTools = React.useMemo( () => extractTools(expertise), [expertise], ); const goNext = () => setStepIdx((i) => Math.min(STEPS.length - 1, i + 1)); const goPrev = () => (stepIdx === 0 ? onBack() : setStepIdx((i) => i - 1)); const finish = () => { onComplete({ profile, expertise, tools: detectedTools, }); }; const stepLabel = ( { identity: "Your agency", presence: "Your presence", expertise: "Ideal customer", } as Record )[step]; return ( <> {step === "identity" && ( )} {step === "presence" && ( )} {step === "expertise" && ( )} ); } // ── Step 2 · Identity ──────────────────────────────────────────────────────── function IdentityStep({ profile, onChange, onNext, }: { profile: AgencyProfile; onChange: (p: AgencyProfile) => void; onNext: () => void; }) { return ( onChange({ ...profile, name: e.target.value })} /> onChange({ ...profile, city: c })} /> 1 && !!profile.city} nextLabel="Continue" hint={ profile.name && profile.city ? "Press ⌘↵" : "Name + city to continue" } /> ); } // ── City lookup (typeahead) ────────────────────────────────────────────────── // Hits Places API (New) Autocomplete via GET /api/agency/cities; falls back to // the seed list only when that endpoint isn't reachable (offline dev). export function CityLookup({ value, onChange, }: { value?: CityRef; onChange: (c: CityRef) => void; }) { const [query, setQuery] = React.useState(value ? cityLabel(value) : ""); const [open, setOpen] = React.useState(false); const [results, setResults] = React.useState(() => searchCities(""), ); const listboxId = React.useId(); React.useEffect(() => { let cancelled = false; const handle = setTimeout(async () => { let next: CityRef[] | null = null; try { const res = await fetch( `/api/agency/cities?q=${encodeURIComponent(query)}`, ); if (res.ok) { const data = await res.json(); if (Array.isArray(data)) next = data as CityRef[]; } } catch { /* offline / endpoint missing — use the seed fallback */ } if (cancelled) return; setResults(next && next.length ? next : searchCities(query)); }, 180); return () => { cancelled = true; clearTimeout(handle); }; }, [query]); const select = (c: CityRef) => { onChange(c); setQuery(cityLabel(c)); setOpen(false); }; return (
{ setQuery(e.target.value); setOpen(true); }} onFocus={() => setOpen(true)} onBlur={() => setTimeout(() => setOpen(false), 120)} /> {open && results.length > 0 && (
{results.map((c) => { const active = value?.id === c.id; return ( ); })}
)}
); } // ── Step 3 · Online presence ───────────────────────────────────────────────── function PresenceStep({ profile, onChange, onNext, }: { profile: AgencyProfile; onChange: (p: AgencyProfile) => void; onNext: () => void; }) { return (
onChange({ ...profile, hasWebsite: !profile.hasWebsite }) } /> {profile.hasWebsite && ( onChange({ ...profile, websiteUrl: e.target.value }) } /> )} onChange({ ...profile, hasSocials: !profile.hasSocials }) } /> onChange({ ...profile, hasBlog: !profile.hasBlog })} /> onChange({ ...profile, hasCustomDomain: !profile.hasCustomDomain }) } /> onChange({ ...profile, hasExistingClients: !profile.hasExistingClients, }) } />
); } function ToggleRow({ label, on, onToggle, }: { label: string; on: boolean; onToggle: () => void; }) { return ( ); } // ── Step 4 · Your ideal customer (final step → dashboard) ──────────────────── function IdealCustomerStep({ value, onChange, onNext, }: { value: string; onChange: (s: string) => void; onNext: () => void; }) { const ready = value.trim().length >= 8; return (