307 lines
11 KiB
TypeScript
307 lines
11 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useState } from "react";
|
||
import { AtlasChat } from "@/components/AtlasChat";
|
||
import { useRouter, useParams } from "next/navigation";
|
||
import Link from "next/link";
|
||
|
||
const DISCOVERY_PHASES = [
|
||
"big_picture",
|
||
"users_personas",
|
||
"features_scope",
|
||
"business_model",
|
||
"screens_data",
|
||
"risks_questions",
|
||
];
|
||
|
||
// Maps discovery phases → the PRD sections they populate
|
||
const PRD_SECTIONS: { label: string; phase: string | null }[] = [
|
||
{ label: "Executive Summary", phase: "big_picture" },
|
||
{ label: "Problem Statement", phase: "big_picture" },
|
||
{ label: "Vision & Success Metrics", phase: "big_picture" },
|
||
{ label: "Users & Personas", phase: "users_personas" },
|
||
{ label: "User Flows", phase: "users_personas" },
|
||
{ label: "Feature Requirements", phase: "features_scope" },
|
||
{ label: "Screen Specs", phase: "features_scope" },
|
||
{ label: "Business Model", phase: "business_model" },
|
||
{ label: "Integrations & Dependencies", phase: "screens_data" },
|
||
{ label: "Non-Functional Reqs", phase: null }, // generated at PRD finalization
|
||
{ label: "Risks & Mitigations", phase: "risks_questions" },
|
||
{ label: "Open Questions", phase: "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);
|
||
const [hasPrd, setHasPrd] = useState(false);
|
||
|
||
useEffect(() => {
|
||
// Check if PRD already exists on the project
|
||
fetch(`/api/projects/${projectId}`)
|
||
.then(r => r.json())
|
||
.then(d => { if (d.project?.prd) setHasPrd(true); })
|
||
.catch(() => {});
|
||
|
||
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`);
|
||
};
|
||
|
||
// Once the PRD exists, show a clean "done" state in the main panel.
|
||
// The Atlas conversation history lives in the left CooChat sidebar.
|
||
if (hasPrd) {
|
||
return (
|
||
<div style={{
|
||
height: "100%", display: "flex", flexDirection: "column",
|
||
alignItems: "center", justifyContent: "center",
|
||
fontFamily: "Outfit, sans-serif", padding: "32px",
|
||
}}>
|
||
<div style={{
|
||
maxWidth: 420, textAlign: "center",
|
||
display: "flex", flexDirection: "column", alignItems: "center", gap: 20,
|
||
}}>
|
||
<div style={{
|
||
width: 48, height: 48, borderRadius: 14, background: "#1a1a1a",
|
||
display: "flex", alignItems: "center", justifyContent: "center",
|
||
fontSize: "1.2rem", color: "#fff",
|
||
}}>✦</div>
|
||
<div>
|
||
<div style={{ fontSize: "1.05rem", fontWeight: 700, color: "#1a1a1a", marginBottom: 6 }}>
|
||
Discovery complete
|
||
</div>
|
||
<div style={{ fontSize: "0.82rem", color: "#6b6560", lineHeight: 1.65 }}>
|
||
Your PRD is saved. The full discovery conversation is in the left panel — talk to your COO to plan what to build next.
|
||
</div>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 10 }}>
|
||
<Link
|
||
href={`/${workspace}/project/${projectId}/prd`}
|
||
style={{
|
||
padding: "10px 20px", borderRadius: 8, border: "none",
|
||
background: "#1a1a1a", color: "#fff",
|
||
fontSize: "0.82rem", fontWeight: 600,
|
||
textDecoration: "none", display: "inline-block",
|
||
}}
|
||
>
|
||
View PRD →
|
||
</Link>
|
||
<Link
|
||
href={`/${workspace}/project/${projectId}/build`}
|
||
style={{
|
||
padding: "10px 20px", borderRadius: 8,
|
||
border: "1px solid #e8e4dc",
|
||
background: "#fff", color: "#1a1a1a",
|
||
fontSize: "0.82rem", fontWeight: 500,
|
||
textDecoration: "none", display: "inline-block",
|
||
}}
|
||
>
|
||
Go to Build
|
||
</Link>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
const completedSections = PRD_SECTIONS.filter(({ phase }) =>
|
||
phase === null ? allDone : savedPhaseIds.has(phase)
|
||
).length;
|
||
const totalSections = PRD_SECTIONS.length;
|
||
|
||
return (
|
||
<div style={{ height: "100%", display: "flex", flexDirection: "row", overflow: "hidden" }}>
|
||
|
||
{/* ── Left: Atlas chat ── */}
|
||
<div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden", minWidth: 0 }}>
|
||
{/* Decision banner — shown when all 6 phases are saved */}
|
||
{allDone && !dismissed && (
|
||
<div style={{
|
||
background: "linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%)",
|
||
padding: "14px 20px",
|
||
display: "flex", alignItems: "center", justifyContent: "space-between",
|
||
gap: 16, flexShrink: 0, flexWrap: "wrap",
|
||
borderBottom: "1px solid #333",
|
||
}}>
|
||
<div>
|
||
<div style={{ fontSize: "0.84rem", fontWeight: 700, color: "#fff", fontFamily: "Outfit, sans-serif", marginBottom: 2 }}>
|
||
✦ Discovery complete — what's next?
|
||
</div>
|
||
<div style={{ fontSize: "0.72rem", color: "#a09a90", fontFamily: "Outfit, sans-serif" }}>
|
||
All 6 phases captured. Generate your PRD or jump into Build.
|
||
</div>
|
||
</div>
|
||
<div style={{ display: "flex", gap: 8, flexShrink: 0 }}>
|
||
<button
|
||
onClick={handleGeneratePRD}
|
||
disabled={prdLoading}
|
||
style={{
|
||
padding: "8px 16px", borderRadius: 7, border: "none",
|
||
background: "#fff", color: "#1a1a1a",
|
||
fontSize: "0.8rem", 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: "8px 16px", borderRadius: 7,
|
||
border: "1px solid rgba(255,255,255,0.2)",
|
||
background: "transparent", color: "#fff",
|
||
fontSize: "0.8rem", fontWeight: 600,
|
||
fontFamily: "Outfit, sans-serif", cursor: "pointer",
|
||
}}
|
||
>
|
||
Plan MVP →
|
||
</button>
|
||
<button
|
||
onClick={() => setDismissed(true)}
|
||
style={{
|
||
background: "none", border: "none", cursor: "pointer",
|
||
color: "#888", fontSize: "1rem", padding: "4px 6px",
|
||
}}
|
||
title="Dismiss"
|
||
>×</button>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<AtlasChat projectId={projectId} projectName={projectName} />
|
||
</div>
|
||
|
||
{/* ── Right: PRD section tracker ── */}
|
||
<div style={{
|
||
width: 240, flexShrink: 0,
|
||
background: "#faf8f5",
|
||
borderLeft: "1px solid #e8e4dc",
|
||
display: "flex", flexDirection: "column",
|
||
overflow: "hidden",
|
||
}}>
|
||
{/* Header */}
|
||
<div style={{
|
||
padding: "14px 16px 10px",
|
||
borderBottom: "1px solid #e8e4dc",
|
||
flexShrink: 0,
|
||
}}>
|
||
<div style={{ fontSize: "0.72rem", fontWeight: 700, color: "#1a1a1a", letterSpacing: "0.06em", textTransform: "uppercase", marginBottom: 6 }}>
|
||
PRD Sections
|
||
</div>
|
||
{/* Progress bar */}
|
||
<div style={{ height: 3, background: "#e8e4dc", borderRadius: 99, overflow: "hidden" }}>
|
||
<div style={{
|
||
height: "100%", borderRadius: 99,
|
||
background: "#1a1a1a",
|
||
width: `${Math.round((completedSections / totalSections) * 100)}%`,
|
||
transition: "width 0.4s ease",
|
||
}} />
|
||
</div>
|
||
<div style={{ fontSize: "0.68rem", color: "#a09a90", marginTop: 5 }}>
|
||
{completedSections} of {totalSections} sections complete
|
||
</div>
|
||
</div>
|
||
|
||
{/* Section list */}
|
||
<div style={{ flex: 1, overflowY: "auto", padding: "8px 0" }}>
|
||
{PRD_SECTIONS.map(({ label, phase }) => {
|
||
const isDone = phase === null
|
||
? allDone // non-functional reqs generated when all done
|
||
: savedPhaseIds.has(phase);
|
||
return (
|
||
<div
|
||
key={label}
|
||
style={{
|
||
padding: "8px 16px",
|
||
display: "flex", alignItems: "flex-start", gap: 10,
|
||
}}
|
||
>
|
||
{/* Status dot */}
|
||
<div style={{
|
||
width: 8, height: 8, borderRadius: "50%", flexShrink: 0, marginTop: 4,
|
||
background: isDone ? "#1a1a1a" : "transparent",
|
||
border: isDone ? "none" : "1.5px solid #c8c4bc",
|
||
transition: "all 0.3s",
|
||
}} />
|
||
<div style={{ minWidth: 0 }}>
|
||
<div style={{
|
||
fontSize: "0.78rem", fontWeight: isDone ? 600 : 400,
|
||
color: isDone ? "#1a1a1a" : "#6b6560",
|
||
lineHeight: 1.3,
|
||
}}>
|
||
{label}
|
||
</div>
|
||
{!isDone && (
|
||
<div style={{ fontSize: "0.65rem", color: "#a09a90", marginTop: 2, lineHeight: 1.3 }}>
|
||
{phase === null
|
||
? "Generated when PRD is finalized"
|
||
: "Complete this phase in Atlas"
|
||
}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
|
||
{/* Footer CTA */}
|
||
{allDone && (
|
||
<div style={{ padding: "12px 16px", borderTop: "1px solid #e8e4dc", flexShrink: 0 }}>
|
||
<Link
|
||
href={`/${workspace}/project/${projectId}/prd`}
|
||
style={{
|
||
display: "block", textAlign: "center",
|
||
padding: "9px 0", borderRadius: 7,
|
||
background: "#1a1a1a", color: "#fff",
|
||
fontSize: "0.78rem", fontWeight: 600,
|
||
textDecoration: "none",
|
||
}}
|
||
>
|
||
Generate PRD →
|
||
</Link>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|