- Map Justine tokens to shadcn CSS variables (--vibn-* aliases) - Switch fonts to Inter + Lora via next/font (IBM Plex Mono for code) - Base typography: body Inter, h1–h3 Lora; marketing hero + wordmark serif - Project shell and global chrome use semantic colors - Replace Outfit/Newsreader references across TSX inline styles Made-with: Cursor
107 lines
3.4 KiB
TypeScript
107 lines
3.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { createPortal } from "react-dom";
|
|
import { TypeSelector } from "./TypeSelector";
|
|
import { FreshIdeaSetup } from "./FreshIdeaSetup";
|
|
import { ChatImportSetup } from "./ChatImportSetup";
|
|
import { CodeImportSetup } from "./CodeImportSetup";
|
|
import { MigrateSetup } from "./MigrateSetup";
|
|
|
|
export type CreationMode = "fresh" | "chat-import" | "code-import" | "migration";
|
|
|
|
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>("setup");
|
|
const [mode, setMode] = useState<CreationMode | null>("fresh");
|
|
|
|
useEffect(() => {
|
|
if (open) {
|
|
setStep("setup");
|
|
setMode("fresh");
|
|
}
|
|
}, [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");
|
|
};
|
|
|
|
const handleBack = () => {
|
|
setStep("select-type");
|
|
setMode(null);
|
|
};
|
|
|
|
const setupProps = { workspace, onClose: () => onOpenChange(false), onBack: handleBack };
|
|
|
|
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>
|
|
|
|
{/* Backdrop */}
|
|
<div
|
|
onClick={() => onOpenChange(false)}
|
|
style={{
|
|
position: "fixed", inset: 0, zIndex: 50,
|
|
background: "rgba(26,26,26,0.38)",
|
|
animation: "vibn-fadeIn 0.15s ease",
|
|
}}
|
|
/>
|
|
|
|
{/* Modal container */}
|
|
<div style={{
|
|
position: "fixed", inset: 0, zIndex: 51,
|
|
display: "flex", alignItems: "center", justifyContent: "center",
|
|
padding: 24, pointerEvents: "none",
|
|
}}>
|
|
<div
|
|
onClick={e => e.stopPropagation()}
|
|
style={{
|
|
background: "#fff", borderRadius: 16,
|
|
boxShadow: "0 12px 48px rgba(26,26,26,0.16)",
|
|
width: "100%",
|
|
maxWidth: 520,
|
|
fontFamily: "var(--font-inter), ui-sans-serif, sans-serif",
|
|
pointerEvents: "all",
|
|
animation: "vibn-slideUp 0.18s cubic-bezier(0.4,0,0.2,1)",
|
|
transition: "max-width 0.2s ease",
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
{step === "select-type" && (
|
|
<TypeSelector
|
|
onSelect={handleSelectType}
|
|
onClose={() => onOpenChange(false)}
|
|
/>
|
|
)}
|
|
{step === "setup" && mode === "fresh" && <FreshIdeaSetup {...setupProps} />}
|
|
{step === "setup" && mode === "chat-import" && <ChatImportSetup {...setupProps} />}
|
|
{step === "setup" && mode === "code-import" && <CodeImportSetup {...setupProps} />}
|
|
{step === "setup" && mode === "migration" && <MigrateSetup {...setupProps} />}
|
|
</div>
|
|
</div>
|
|
</>,
|
|
document.body
|
|
);
|
|
}
|