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:
@@ -11,6 +11,7 @@ interface ProjectData {
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
featureCount?: number;
|
||||
creationMode?: "fresh" | "chat-import" | "code-import" | "migration";
|
||||
}
|
||||
|
||||
async function getProjectData(projectId: string): Promise<ProjectData> {
|
||||
@@ -31,6 +32,7 @@ async function getProjectData(projectId: string): Promise<ProjectData> {
|
||||
createdAt: created_at,
|
||||
updatedAt: updated_at,
|
||||
featureCount: Array.isArray(data?.features) ? data.features.length : (data?.featureCount ?? 0),
|
||||
creationMode: data?.creationMode ?? "fresh",
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -62,6 +64,7 @@ export default async function ProjectLayout({
|
||||
createdAt={project.createdAt}
|
||||
updatedAt={project.updatedAt}
|
||||
featureCount={project.featureCount}
|
||||
creationMode={project.creationMode}
|
||||
>
|
||||
{children}
|
||||
</ProjectShell>
|
||||
|
||||
@@ -3,71 +3,33 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { AtlasChat } from "@/components/AtlasChat";
|
||||
import { OrchestratorChat } from "@/components/OrchestratorChat";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
function MobileQRButton({ projectId, workspace }: { projectId: string; workspace: string }) {
|
||||
const [show, setShow] = useState(false);
|
||||
const url = typeof window !== "undefined"
|
||||
? `${window.location.origin}/${workspace}/project/${projectId}/overview`
|
||||
: "";
|
||||
const qrSrc = `https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=${encodeURIComponent(url)}&bgcolor=f6f4f0&color=1a1a1a&margin=2`;
|
||||
|
||||
return (
|
||||
<div style={{ position: "relative" }}>
|
||||
<button
|
||||
onClick={() => setShow(s => !s)}
|
||||
title="Open on your phone"
|
||||
style={{
|
||||
display: "flex", alignItems: "center", gap: 6,
|
||||
padding: "6px 12px", borderRadius: 7,
|
||||
background: "none", border: "1px solid #e0dcd4",
|
||||
fontSize: "0.72rem", fontFamily: "Outfit, sans-serif",
|
||||
color: "#8a8478", cursor: "pointer",
|
||||
transition: "border-color 0.12s",
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.borderColor = "#b5b0a6")}
|
||||
onMouseLeave={e => (e.currentTarget.style.borderColor = "#e0dcd4")}
|
||||
>
|
||||
📱 Open on phone
|
||||
</button>
|
||||
{show && (
|
||||
<div style={{
|
||||
position: "absolute", top: "calc(100% + 8px)", right: 0,
|
||||
background: "#fff", borderRadius: 12,
|
||||
border: "1px solid #e8e4dc",
|
||||
boxShadow: "0 8px 24px #1a1a1a12",
|
||||
padding: "16px", zIndex: 50,
|
||||
display: "flex", flexDirection: "column", alignItems: "center", gap: 10,
|
||||
minWidth: 220,
|
||||
}}>
|
||||
<img src={qrSrc} alt="QR code" width={180} height={180} style={{ borderRadius: 8 }} />
|
||||
<p style={{ fontSize: "0.72rem", color: "#8a8478", textAlign: "center", margin: 0, fontFamily: "Outfit, sans-serif" }}>
|
||||
Scan to open Atlas on your phone
|
||||
</p>
|
||||
<p style={{ fontSize: "0.65rem", color: "#b5b0a6", textAlign: "center", margin: 0, fontFamily: "IBM Plex Mono, monospace", wordBreak: "break-all" }}>
|
||||
{url}
|
||||
</p>
|
||||
<button onClick={() => setShow(false)} style={{ fontSize: "0.68rem", color: "#a09a90", background: "none", border: "none", cursor: "pointer" }}>
|
||||
Close
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { FreshIdeaMain } from "@/components/project-main/FreshIdeaMain";
|
||||
import { ChatImportMain } from "@/components/project-main/ChatImportMain";
|
||||
import { CodeImportMain } from "@/components/project-main/CodeImportMain";
|
||||
import { MigrateMain } from "@/components/project-main/MigrateMain";
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
productName: string;
|
||||
name?: string;
|
||||
stage?: "discovery" | "architecture" | "building" | "active";
|
||||
creationMode?: "fresh" | "chat-import" | "code-import" | "migration";
|
||||
creationStage?: string;
|
||||
sourceData?: {
|
||||
chatText?: string;
|
||||
repoUrl?: string;
|
||||
liveUrl?: string;
|
||||
hosting?: string;
|
||||
description?: string;
|
||||
};
|
||||
analysisResult?: Record<string, unknown>;
|
||||
migrationPlan?: string;
|
||||
}
|
||||
|
||||
export default function ProjectOverviewPage() {
|
||||
const params = useParams();
|
||||
const projectId = params.projectId as string;
|
||||
const workspace = params.workspace as string;
|
||||
const { status: authStatus } = useSession();
|
||||
const [project, setProject] = useState<Project | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -78,8 +40,8 @@ export default function ProjectOverviewPage() {
|
||||
return;
|
||||
}
|
||||
fetch(`/api/projects/${projectId}`)
|
||||
.then((r) => r.json())
|
||||
.then((d) => setProject(d.project))
|
||||
.then(r => r.json())
|
||||
.then(d => setProject(d.project))
|
||||
.catch(() => {})
|
||||
.finally(() => setLoading(false));
|
||||
}, [authStatus, projectId]);
|
||||
@@ -100,21 +62,50 @@ export default function ProjectOverviewPage() {
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ height: "100%", display: "flex", flexDirection: "column" }}>
|
||||
{/* Desktop-only: Open on phone button */}
|
||||
<style>{`@media (max-width: 768px) { .vibn-phone-btn { display: none !important; } }`}</style>
|
||||
<div className="vibn-phone-btn" style={{
|
||||
position: "absolute", top: 14, right: 248,
|
||||
zIndex: 20,
|
||||
}}>
|
||||
<MobileQRButton projectId={projectId} workspace={workspace} />
|
||||
</div>
|
||||
const projectName = project.productName || project.name || "Untitled";
|
||||
const mode = project.creationMode ?? "fresh";
|
||||
|
||||
<AtlasChat
|
||||
if (mode === "chat-import") {
|
||||
return (
|
||||
<ChatImportMain
|
||||
projectId={projectId}
|
||||
projectName={project.productName}
|
||||
projectName={projectName}
|
||||
sourceData={project.sourceData}
|
||||
analysisResult={project.analysisResult as Parameters<typeof ChatImportMain>[0]["analysisResult"]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === "code-import") {
|
||||
return (
|
||||
<CodeImportMain
|
||||
projectId={projectId}
|
||||
projectName={projectName}
|
||||
sourceData={project.sourceData}
|
||||
analysisResult={project.analysisResult}
|
||||
creationStage={project.creationStage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (mode === "migration") {
|
||||
return (
|
||||
<MigrateMain
|
||||
projectId={projectId}
|
||||
projectName={projectName}
|
||||
sourceData={project.sourceData}
|
||||
analysisResult={project.analysisResult}
|
||||
migrationPlan={project.migrationPlan}
|
||||
creationStage={project.creationStage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// Default: "fresh" — wraps AtlasChat with decision banner
|
||||
return (
|
||||
<FreshIdeaMain
|
||||
projectId={projectId}
|
||||
projectName={projectName}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user