Files
vibn-frontend/components/project-creation/CreateProjectFlow.tsx

135 lines
4.2 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import { createPortal } from "react-dom";
import { Plus_Jakarta_Sans } from "next/font/google";
import { TypeSelector } from "./TypeSelector";
import { BuildSetup } from "./BuildSetup";
import { OssSetup } from "./OssSetup";
import { ImportSetup } from "./ImportSetup";
import { JM } from "./modal-theme";
const modalFont = Plus_Jakarta_Sans({
subsets: ["latin"],
weight: ["400", "500", "600", "700", "800"],
variable: "--font-justine-jakarta",
display: "swap",
});
/**
* Three project-creation paths surfaced to the user:
* - "build" — describe an idea, AI scaffolds and builds it.
* - "oss" — install an open-source tool (link OR describe-it-and-find).
* - "import" — bring a public GitHub repo in.
*
* Each path is its own setup screen with internal step dots (basics → describe).
* The TypeSelector is the entry point; ESC closes; backdrop click closes.
*/
export type CreationMode = "build" | "oss" | "import";
interface CreateProjectFlowProps {
open: boolean;
onOpenChange: (open: boolean) => void;
workspace: string;
}
type Step = "select-type" | "setup";
export function CreateProjectFlow({ open, onOpenChange, workspace }: CreateProjectFlowProps) {
const [step, setStep] = useState<Step>("select-type");
const [mode, setMode] = useState<CreationMode | null>(null);
const [buildWizardStep, setBuildWizardStep] = useState(0);
useEffect(() => {
if (open) {
setStep("select-type");
setMode(null);
setBuildWizardStep(0);
}
}, [open]);
useEffect(() => {
if (!open) return;
const handler = (e: KeyboardEvent) => { if (e.key === "Escape") onOpenChange(false); };
window.addEventListener("keydown", handler);
return () => window.removeEventListener("keydown", handler);
}, [open, onOpenChange]);
if (!open) return null;
const handleSelectType = (selected: CreationMode) => {
setMode(selected);
setStep("setup");
if (selected !== "build") setBuildWizardStep(0);
};
const handleBack = () => {
setStep("select-type");
setMode(null);
setBuildWizardStep(0);
};
const setupProps = { workspace, onClose: () => onOpenChange(false), onBack: handleBack };
const modalMaxWidth =
step === "setup" && mode === "build" && buildWizardStep === 1
? JM.cardMaxWidthDesignKit
: JM.cardMaxWidth;
return createPortal(
<>
<style>{`
@keyframes vibn-fadeIn { from { opacity:0; } to { opacity:1; } }
@keyframes vibn-slideUp { from { opacity:0; transform:translateY(14px); } to { opacity:1; transform:translateY(0); } }
@keyframes vibn-spin { to { transform:rotate(360deg); } }
`}</style>
<div
onClick={() => onOpenChange(false)}
style={{
position: "fixed", inset: 0, zIndex: 200,
background: JM.overlay,
backdropFilter: "blur(2px)",
WebkitBackdropFilter: "blur(2px)",
animation: "vibn-fadeIn 0.15s ease",
}}
/>
<div style={{
position: "fixed", inset: 0, zIndex: 201,
display: "flex", alignItems: "center", justifyContent: "center",
padding: 24, pointerEvents: "none",
}}>
<div
onClick={e => e.stopPropagation()}
className={modalFont.variable}
style={{
background: "#fff", borderRadius: 16,
boxShadow: JM.cardShadow,
width: "100%",
maxWidth: modalMaxWidth,
transition: "max-width 0.22s cubic-bezier(0.4, 0, 0.2, 1)",
fontFamily: JM.fontSans,
pointerEvents: "all",
animation: "vibn-slideUp 0.18s cubic-bezier(0.4,0,0.2,1)",
overflow: "hidden",
}}
>
{step === "select-type" && (
<TypeSelector
onSelect={handleSelectType}
onClose={() => onOpenChange(false)}
/>
)}
{step === "setup" && mode === "build" && (
<BuildSetup {...setupProps} onWizardStepChange={setBuildWizardStep} />
)}
{step === "setup" && mode === "oss" && <OssSetup {...setupProps} />}
{step === "setup" && mode === "import" && <ImportSetup {...setupProps} />}
</div>
</div>
</>,
document.body
);
}