"use client";
import { useCallback, useEffect, useRef, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { JM, JV } from "@/components/project-creation/modal-theme";
const STEPS = [
{ l: "Creating Gitea repository", d: "Setting up version control for your project" },
{ l: "Scaffolding the app", d: "Next.js · TypeScript · Tailwind CSS" },
{ l: "Setting up your database", d: "PostgreSQL + schema based on your product plan" },
{ l: "Building sign up & login", d: "Email + Google + GitHub OAuth" },
{ l: "Wiring payments", d: "Stripe checkout, webhooks, billing portal" },
{ l: "Generating app pages", d: "Dashboard, settings, onboarding, invite flow" },
{ l: "Applying your design", d: "Theme applied across all pages" },
{ l: "Building marketing website", d: "SEO-ready marketing surface" },
{ l: "Setting up email", d: "Welcome, password reset, and marketing templates" },
{ l: "Pushing to Gitea", d: "Full codebase committed and pushed" },
{ l: "Deploying via Coolify", d: "Building Docker image, deploying to your servers" },
{ l: "Running health checks", d: "Verifying pages, auth, and payments are live" },
] as const;
type PhaseRowProps = {
done: boolean;
active: boolean;
label: string;
sub?: string;
onClick?: () => void;
};
function PhaseRow({ done, active, label, sub, onClick }: PhaseRowProps) {
return (
{
if (onClick && !active) (e.currentTarget as HTMLElement).style.background = "#F5F3FF";
}}
onMouseLeave={e => {
if (!active) (e.currentTarget as HTMLElement).style.background = "transparent";
}}
>
{done ? "✓" : active ? "▲" : ""}
);
}
function Confetti() {
const colors = ["#6366F1", "#818CF8", "#4338CA", "#A5B4FC", "#C7D2FE", "#FCD34D", "#34D399", "#60A5FA"];
const pieces = Array.from({ length: 90 }, (_, i) => ({
i,
color: colors[i % colors.length],
left: Math.random() * 100,
delay: Math.random() * 1.2,
dur: Math.random() * 2.5 + 2,
size: Math.random() * 9 + 4,
xDrift: (Math.random() - 0.5) * 200,
rot: Math.random() * 360,
br: ["50%", "3px", "0"][Math.floor(Math.random() * 3)],
}));
return (
);
}
export interface BuildMvpJustineV2Props {
workspace: string;
projectId: string;
projectName: string;
giteaRepo?: string;
/** First webapp surface label + theme if any */
designFeel?: string;
designStructure?: string;
accentLabel?: string;
accentHex?: string;
websiteVoice?: string;
websiteStyle?: string;
topicsLine?: string;
pageColumns?: { title: string; pages: string[] }[];
onSwitchToPreview: () => void;
}
export function BuildMvpJustineV2({
workspace,
projectId,
projectName,
giteaRepo,
designFeel = "Friendly",
designStructure = "Clean",
accentLabel = "Indigo",
accentHex = "#6366F1",
websiteVoice = "Friendly · Balanced · Warm",
websiteStyle = "Editorial",
topicsLine = "The problem · Who it's for · Why now",
pageColumns = [
{ title: "Public", pages: ["Landing page", "Pricing", "About", "Blog"] },
{ title: "Auth", pages: ["Sign up", "Log in", "Forgot password"] },
{ title: "App", pages: ["Dashboard", "Onboarding", "Settings"] },
{ title: "Payments", pages: ["Checkout", "Success", "Manage subscription"] },
],
onSwitchToPreview,
}: BuildMvpJustineV2Props) {
const router = useRouter();
const [uiPhase, setUiPhase] = useState<"review" | "progress" | "done">("review");
const [curStep, setCurStep] = useState(0);
const [building, setBuilding] = useState(false);
const [showConfetti, setShowConfetti] = useState(false);
const intervalRef = useRef | null>(null);
const giteaWebBase = process.env.NEXT_PUBLIC_GITEA_WEB_URL ?? "https://git.vibnai.com";
const giteaHref = giteaRepo ? `${giteaWebBase}/${giteaRepo}` : giteaWebBase;
const clearBuildInterval = useCallback(() => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
}, []);
useEffect(() => () => clearBuildInterval(), [clearBuildInterval]);
const startBuild = () => {
if (building) return;
setBuilding(true);
setTimeout(() => {
setUiPhase("progress");
setCurStep(0);
intervalRef.current = setInterval(() => {
setCurStep(c => {
const next = c + 1;
if (next >= STEPS.length) {
clearBuildInterval();
setUiPhase("done");
setBuilding(false);
setShowConfetti(true);
setTimeout(() => setShowConfetti(false), 5000);
return STEPS.length;
}
return next;
});
}, 700);
}, 400);
};
const renderStepRows = () =>
STEPS.map((s, i) => {
const done = i < curStep;
const active = i === curStep && uiPhase === "progress";
return (
{done && ✓ }
{active && (
◎
)}
{s.l}
{(done || active) && (
{s.d}
)}
);
});
return (
<>
{showConfetti && }
MVP Setup
router.push(`/${workspace}/project/${projectId}/overview`)}
/>
router.push(`/${workspace}/project/${projectId}/tasks`)}
/>
router.push(`/${workspace}/project/${projectId}/design`)}
/>
router.push(`/${workspace}/project/${projectId}/growth`)}
/>
Save & go to dashboard
{uiPhase === "review" && (
Ready to build
Review everything below. Once you hit Build, AI codes your full product and deploys it.
What's being built
{(
[
{ icon: "⛓", k: "Sign up & login", v: "Email + social login", br: true, bb: true },
{ icon: "$", k: "Payments", v: "Subscription billing", br: false, bb: true },
{ icon: "✉", k: "Email", v: "Transactional + marketing", br: true, bb: true },
{ icon: "◧", k: "Product style", v: "Clean & focused", br: false, bb: true },
{ icon: "◉", k: "Website style", v: websiteStyle, br: true, bb: false },
{ icon: "≡", k: "Campaign topics", v: topicsLine, br: false, bb: false },
] as const
).map(cell => (
))}
Pages
{pageColumns.reduce((n, c) => n + c.pages.length, 0)} pages total
{pageColumns.map((col, ci) => (
{col.title}
{col.pages.map(p => (
{p}
))}
))}
Your website
⬡
Website style
{websiteStyle}
You're ready to build your product
Your app will be generated, your backend configured, and everything deployed to your infrastructure —
fully automated, no code needed.
What happens next
{[
{ icon: "✦", t: "Generate UI & all pages", est: "~30s" },
{ icon: "⛁", t: "Set up database & backend", est: "~45s" },
{ icon: "⛓", t: "Connect auth, payments & email", est: "~30s" },
{ icon: "▲", t: "Deploy your app live", est: "~20s" },
].map(row => (
{row.icon}
{row.t}
{row.est}
))}
Takes ~2–4 minutes · All steps run in parallel
{building ? "Starting…" : "Build my product"}
No code needed · You can edit everything after
router.push(`/${workspace}/project/${projectId}/design`)}
style={{
background: "none",
border: "none",
fontFamily: JM.fontSans,
fontSize: 12.5,
color: JM.muted,
cursor: "pointer",
padding: "6px 0",
}}
>
← Go back and tweak choices
)}
{(uiPhase === "progress" || uiPhase === "done") && (
{uiPhase === "done" ? (
<>
🚀
Your MVP is live
Deployed to Coolify · Pushed to Gitea · Ready to share
>
) : (
<>
Building your product…
Step {curStep} of {STEPS.length}
>
)}
{renderStepRows()}
{uiPhase === "done" && (
Your next 3 actions
{[
{
n: "1",
t: "Open your live app",
d: "Share the URL with 5 real people today.",
},
{
n: "2",
t: "Sign up as a user",
d: "Go through your own onboarding. Fix anything confusing.",
},
{
n: "3",
t: "Post your first topic",
d: "AI has drafted your first content batch. Publish one today.",
},
].map((a, i, arr) => (
))}
)}
)}
>
);
}