feat: implement 4 project type flows with unique AI experiences
- New multi-step CreateProjectFlow replaces 2-step modal with TypeSelector and 4 setup components (Fresh Idea, Chat Import, Code Import, Migrate) - overview/page.tsx routes to unique main component per creationMode - FreshIdeaMain: wraps AtlasChat with post-discovery decision banner (Generate PRD vs Plan MVP Test) - ChatImportMain: 3-stage flow (intake → extracting → review) with editable insight buckets (decisions, ideas, questions, architecture, users) - CodeImportMain: 4-stage flow (input → cloning → mapping → surfaces) with architecture map and surface selection - MigrateMain: 5-stage flow with audit, review, planning, and migration plan doc with checkbox-tracked tasks and non-destructive warning banner - New API routes: analyze-chats, analyze-repo, analysis-status, generate-migration-plan (all using Gemini) - ProjectShell: accepts creationMode prop, filters/renames tabs per type (code-import hides PRD, migration hides PRD/Grow/Insights, renames Atlas tab) - Right panel adapts content based on creationMode Made-with: Cursor
This commit is contained in:
106
components/project-creation/CreateProjectFlow.tsx
Normal file
106
components/project-creation/CreateProjectFlow.tsx
Normal file
@@ -0,0 +1,106 @@
|
||||
"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>("select-type");
|
||||
const [mode, setMode] = useState<CreationMode | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setStep("select-type");
|
||||
setMode(null);
|
||||
}
|
||||
}, [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: step === "select-type" ? 620 : 520,
|
||||
fontFamily: "Outfit, 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
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user