import React, { useState, useEffect, useRef, useMemo, useCallback } from "react"; import { WizardTop, WizardBody, WizardQ, LANE_LABELS } from "./onboarding-primitives"; // Build + Ready screens. The build screen shows the terminal stream + a live // preview stencil; ready is a quiet confirmation page with the workspace URL. // ── Per-path build plans ─────────────────────────────────────────────────── function buildPlanFor(path, data) { const common = [ { line: "vibn init — reading brief", ms: 600 }, { line: "↳ provisioning workspace", ms: 700 }, { line: "↳ wiring auth (email + Google)", ms: 800 }, { line: "↳ minting database & seed schema", ms: 700 }, ]; if (path === "entrepreneur") { return [ ...common, { line: `↳ generating landing page · vibe "${data.vibe || "warm"}"`, ms: 900 }, { line: `↳ writing copy aimed at "${(data.audience || "your audience").slice(0, 40)}"`, ms: 800 }, { line: "↳ wiring email capture + Stripe payment link", ms: 700 }, { line: "↳ scaffolding admin: subscribers, sales, comments", ms: 800 }, { line: `↳ tuning launch plan for goal: ${data.goal || "first_customer"}`, ms: 700 }, { line: "↳ publishing preview → " + workspaceUrlFor(path, data), ms: 900 }, { line: "ready.", ms: 400, ok: true }, ]; } if (path === "owner") { return [ ...common, { line: `↳ modelling ${data.biz || "small business"} for "${data.bizName || "your business"}"`, ms: 800 }, { line: `↳ importing your stack (${(data.tools || []).length} tools)`, ms: 800 }, { line: `↳ building module: ${labelFor(data.firstThing)}`, ms: 1000 }, { line: "↳ generating customer + job records (10 sample)", ms: 700 }, { line: "↳ scheduling daily ops view + weekly report", ms: 700 }, { line: `↳ wiring savings tracker · est. $${(data.spend || 0)}/mo replaced`, ms: 800 }, { line: "↳ publishing preview → " + workspaceUrlFor(path, data), ms: 900 }, { line: "ready.", ms: 400, ok: true }, ]; } return [ ...common, { line: `↳ branding workspace for "${data.clientName || "client"}"`, ms: 800 }, { line: `↳ scaffolding scope (${(data.scope || []).length} modules)`, ms: 1000 }, ...(data.scope || []).slice(0, 4).map((s) => ({ line: ` • ${s}`, ms: 350 })), { line: "↳ generating handoff document + invoice template", ms: 700 }, { line: `↳ setting deploy target: ${data.handoff || "subdomain"}`, ms: 700 }, { line: "↳ publishing preview → " + workspaceUrlFor(path, data), ms: 900 }, { line: "ready.", ms: 400, ok: true }, ]; } function labelFor(id) { const map = { booking: "Bookings & scheduling", invoicing: "Quotes & invoices", customers: "Customer portal", inventory: "Inventory & orders", team: "Team & dispatch", marketing: "Marketing site", }; return map[id] || "your first workflow"; } function workspaceUrlFor(path, data) { const seed = (path === "owner" && data.bizName) || (path === "consultant" && data.clientName) || (path === "entrepreneur" && (data.audience || data.idea)) || "your-workspace"; const slug = String(seed).toLowerCase() .replace(/[^a-z0-9\s-]/g, "") .trim() .split(/\s+/) .slice(0, 3) .join("-") .slice(0, 28) || "your-workspace"; return `${slug}.vibn.app`; } const BUILD_BIZ_LABEL = { service: "Trades / services", retail: "Retail", food: "Food & drink", appointments: "Appointments", events: "Events / hospitality", other: "Small business", }; // ── Build screen ─────────────────────────────────────────────────────────── export function BuildScreen({ path, data, onBack, onClose, onOpen }) { const plan = React.useMemo(() => buildPlanFor(path, data), [path, data]); const [lineIdx, setLineIdx] = React.useState(0); const [done, setDone] = React.useState(false); const logRef = React.useRef(null); React.useEffect(() => { if (lineIdx >= plan.length) { setDone(true); return undefined; } const t = setTimeout(() => setLineIdx(lineIdx + 1), plan[lineIdx].ms); return () => clearTimeout(t); }, [lineIdx, plan]); React.useEffect(() => { if (logRef.current) logRef.current.scrollTop = logRef.current.scrollHeight; }, [lineIdx]); const url = workspaceUrlFor(path, data); const lane = LANE_LABELS[path]; const previewTitle = path === "owner" ? (data.bizName || "Your business") : path === "consultant" ? (data.clientName || "Your client") : "Your launch page"; const previewSub = path === "owner" ? `${BUILD_BIZ_LABEL[data.biz] || "Small business"} · ${labelFor(data.firstThing)}` : path === "consultant" ? (data.industry || "Project") : (data.audience || "An idea worth building").slice(0, 64); const pct = done ? 1 : lineIdx / plan.length; return ( <> {/* terminal */} vibn build — {url} {!done && live} {plan.slice(0, lineIdx + 1).map((p, i) => { const isCurrent = i === lineIdx && !done; const isOk = p.ok && i <= lineIdx; const cls = isOk ? "l-ok" : isCurrent ? "l-current" : "l-done"; return {p.line}; })} {!done && } {/* preview */} https://{url} {previewTitle} {previewSub} {done ? "build complete" : <>Building {Math.min(lineIdx, plan.length)}/{plan.length}>} {done && ( Open my workspace )} > ); } // ── Ready screen ─────────────────────────────────────────────────────────── export function ReadyScreen({ path, data, onClose, onOpenChat }) { const url = workspaceUrlFor(path, data); const summary = summaryFor(path, data); return ( <> Workspace URL {url} {summary.map((row, i) => ( {row.label} {row.value} ))} Back to home Open the build chat > ); } function summaryFor(path, data) { if (path === "owner") { return [ { label: "Business", value: `${data.bizName || "Untitled"} · ${BUILD_BIZ_LABEL[data.biz] || "Small business"}` }, { label: "Replacing", value: `${(data.tools || []).length} tools · ~$${data.spend || 0}/mo` }, { label: "First fix", value: labelFor(data.firstThing) }, { label: "Team", value: `${data.team || 1} · ${(data.customers || 0).toLocaleString()} cust/mo` }, ]; } if (path === "consultant") { return [ { label: "Client", value: `${data.clientName || "Untitled"} · ${data.industry || ""}` }, { label: "Scope", value: `${(data.scope || []).length} modules` }, { label: "Brief", value: (data.brief || "").slice(0, 60) + ((data.brief || "").length > 60 ? "…" : "") }, { label: "Handoff", value: data.handoff || "subdomain" }, ]; } return [ { label: "Building", value: (data.idea || "").slice(0, 64) + ((data.idea || "").length > 64 ? "…" : "") }, { label: "Audience", value: (data.audience || "").slice(0, 64) }, { label: "Goal", value: data.goal || "first_customer" }, { label: "Vibe", value: data.vibe || "warm" }, ]; }