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:
133
components/project-main/FreshIdeaMain.tsx
Normal file
133
components/project-main/FreshIdeaMain.tsx
Normal file
@@ -0,0 +1,133 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { AtlasChat } from "@/components/AtlasChat";
|
||||
import { useRouter, useParams } from "next/navigation";
|
||||
|
||||
const DISCOVERY_PHASES = [
|
||||
"big_picture",
|
||||
"users_personas",
|
||||
"features_scope",
|
||||
"business_model",
|
||||
"screens_data",
|
||||
"risks_questions",
|
||||
];
|
||||
|
||||
interface FreshIdeaMainProps {
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
}
|
||||
|
||||
export function FreshIdeaMain({ projectId, projectName }: FreshIdeaMainProps) {
|
||||
const router = useRouter();
|
||||
const params = useParams();
|
||||
const workspace = params?.workspace as string;
|
||||
|
||||
const [savedPhaseIds, setSavedPhaseIds] = useState<Set<string>>(new Set());
|
||||
const [allDone, setAllDone] = useState(false);
|
||||
const [prdLoading, setPrdLoading] = useState(false);
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const poll = () => {
|
||||
fetch(`/api/projects/${projectId}/save-phase`)
|
||||
.then(r => r.json())
|
||||
.then(d => {
|
||||
const ids = new Set<string>((d.phases ?? []).map((p: { phase: string }) => p.phase));
|
||||
setSavedPhaseIds(ids);
|
||||
const done = DISCOVERY_PHASES.every(id => ids.has(id));
|
||||
setAllDone(done);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
poll();
|
||||
const interval = setInterval(poll, 8_000);
|
||||
return () => clearInterval(interval);
|
||||
}, [projectId]);
|
||||
|
||||
const handleGeneratePRD = async () => {
|
||||
if (prdLoading) return;
|
||||
setPrdLoading(true);
|
||||
try {
|
||||
router.push(`/${workspace}/project/${projectId}/prd`);
|
||||
} finally {
|
||||
setPrdLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMVP = () => {
|
||||
router.push(`/${workspace}/project/${projectId}/build`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ height: "100%", display: "flex", flexDirection: "column", position: "relative" }}>
|
||||
{/* Decision banner — shown when all 6 phases are saved */}
|
||||
{allDone && !dismissed && (
|
||||
<div style={{
|
||||
background: "linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)",
|
||||
padding: "18px 28px",
|
||||
display: "flex", alignItems: "center", justifyContent: "space-between",
|
||||
gap: 16, flexShrink: 0, flexWrap: "wrap",
|
||||
borderBottom: "1px solid #333",
|
||||
}}>
|
||||
<div>
|
||||
<div style={{ fontSize: "0.88rem", fontWeight: 700, color: "#fff", fontFamily: "Outfit, sans-serif", marginBottom: 3 }}>
|
||||
✦ Discovery complete — what's next?
|
||||
</div>
|
||||
<div style={{ fontSize: "0.75rem", color: "#a09a90", fontFamily: "Outfit, sans-serif" }}>
|
||||
Atlas has captured all 6 discovery phases. Choose your next step.
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: "flex", gap: 10, flexShrink: 0 }}>
|
||||
<button
|
||||
onClick={handleGeneratePRD}
|
||||
disabled={prdLoading}
|
||||
style={{
|
||||
padding: "10px 20px", borderRadius: 8, border: "none",
|
||||
background: "#fff", color: "#1a1a1a",
|
||||
fontSize: "0.84rem", fontWeight: 700,
|
||||
fontFamily: "Outfit, sans-serif", cursor: "pointer",
|
||||
transition: "opacity 0.12s",
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.opacity = "0.88")}
|
||||
onMouseLeave={e => (e.currentTarget.style.opacity = "1")}
|
||||
>
|
||||
{prdLoading ? "Navigating…" : "Generate PRD →"}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleMVP}
|
||||
style={{
|
||||
padding: "10px 20px", borderRadius: 8,
|
||||
border: "1px solid rgba(255,255,255,0.2)",
|
||||
background: "transparent", color: "#fff",
|
||||
fontSize: "0.84rem", fontWeight: 600,
|
||||
fontFamily: "Outfit, sans-serif", cursor: "pointer",
|
||||
transition: "background 0.12s",
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.background = "rgba(255,255,255,0.08)")}
|
||||
onMouseLeave={e => (e.currentTarget.style.background = "transparent")}
|
||||
>
|
||||
Plan MVP Test →
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setDismissed(true)}
|
||||
style={{
|
||||
background: "none", border: "none", cursor: "pointer",
|
||||
color: "#666", fontSize: "1rem", padding: "4px 6px",
|
||||
fontFamily: "Outfit, sans-serif",
|
||||
}}
|
||||
title="Dismiss"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<AtlasChat
|
||||
projectId={projectId}
|
||||
projectName={projectName}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user