135 lines
4.2 KiB
TypeScript
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
|
|
);
|
|
}
|