Files
vibn-frontend/components/project-main/FreshIdeaMain.tsx

307 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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>
);
}