feat: add 'Generate architecture' CTA banner on PRD page when arch not yet generated
Made-with: Cursor
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
|
|
||||||
// Maps each PRD section to the discovery phase that populates it
|
// Maps each PRD section to the discovery phase that populates it
|
||||||
const PRD_SECTIONS = [
|
const PRD_SECTIONS = [
|
||||||
@@ -184,11 +184,14 @@ function ArchitectureView({ arch }: { arch: Architecture }) {
|
|||||||
export default function PRDPage() {
|
export default function PRDPage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const projectId = params.projectId as string;
|
const projectId = params.projectId as string;
|
||||||
|
const workspace = params.workspace as string;
|
||||||
const [prd, setPrd] = useState<string | null>(null);
|
const [prd, setPrd] = useState<string | null>(null);
|
||||||
const [architecture, setArchitecture] = useState<Architecture | null>(null);
|
const [architecture, setArchitecture] = useState<Architecture | null>(null);
|
||||||
const [savedPhases, setSavedPhases] = useState<SavedPhase[]>([]);
|
const [savedPhases, setSavedPhases] = useState<SavedPhase[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [activeTab, setActiveTab] = useState<"prd" | "architecture">("prd");
|
const [activeTab, setActiveTab] = useState<"prd" | "architecture">("prd");
|
||||||
|
const [archGenerating, setArchGenerating] = useState(false);
|
||||||
|
const [archError, setArchError] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([
|
Promise.all([
|
||||||
@@ -202,6 +205,24 @@ export default function PRDPage() {
|
|||||||
});
|
});
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const handleGenerateArchitecture = async () => {
|
||||||
|
setArchGenerating(true);
|
||||||
|
setArchError(null);
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/projects/${projectId}/architecture`, { method: "POST" });
|
||||||
|
const data = await res.json();
|
||||||
|
if (!res.ok) throw new Error(data.error ?? "Generation failed");
|
||||||
|
setArchitecture(data.architecture);
|
||||||
|
setActiveTab("architecture");
|
||||||
|
} catch (e) {
|
||||||
|
setArchError(e instanceof Error ? e.message : "Something went wrong");
|
||||||
|
} finally {
|
||||||
|
setArchGenerating(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const phaseMap = new Map(savedPhases.map(p => [p.phase, p]));
|
const phaseMap = new Map(savedPhases.map(p => [p.phase, p]));
|
||||||
const savedPhaseIds = new Set(savedPhases.map(p => p.phase));
|
const savedPhaseIds = new Set(savedPhases.map(p => p.phase));
|
||||||
|
|
||||||
@@ -257,6 +278,51 @@ export default function PRDPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Next step banner — PRD done but no architecture yet */}
|
||||||
|
{prd && !architecture && activeTab === "prd" && (
|
||||||
|
<div style={{
|
||||||
|
marginBottom: 24, padding: "18px 22px",
|
||||||
|
background: "#1a1a1a", borderRadius: 10,
|
||||||
|
display: "flex", alignItems: "center", justifyContent: "space-between",
|
||||||
|
gap: 16, flexWrap: "wrap",
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<div style={{ fontSize: "0.88rem", fontWeight: 700, color: "#fff", marginBottom: 4 }}>
|
||||||
|
Next: Generate technical architecture
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: "0.76rem", color: "#a09a90", lineHeight: 1.5 }}>
|
||||||
|
The AI will read your PRD and recommend the apps, services, and infrastructure your product needs. Takes ~30 seconds.
|
||||||
|
</div>
|
||||||
|
{archError && (
|
||||||
|
<div style={{ fontSize: "0.74rem", color: "#f87171", marginTop: 6 }}>⚠ {archError}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleGenerateArchitecture}
|
||||||
|
disabled={archGenerating}
|
||||||
|
style={{
|
||||||
|
padding: "10px 20px", borderRadius: 8, border: "none",
|
||||||
|
background: archGenerating ? "#4a4640" : "#fff",
|
||||||
|
color: archGenerating ? "#a09a90" : "#1a1a1a",
|
||||||
|
fontSize: "0.82rem", fontWeight: 700,
|
||||||
|
fontFamily: "Outfit, sans-serif",
|
||||||
|
cursor: archGenerating ? "default" : "pointer",
|
||||||
|
flexShrink: 0, display: "flex", alignItems: "center", gap: 8,
|
||||||
|
transition: "opacity 0.15s",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{archGenerating && (
|
||||||
|
<span style={{
|
||||||
|
width: 12, height: 12, borderRadius: "50%",
|
||||||
|
border: "2px solid #60606040", borderTopColor: "#a09a90",
|
||||||
|
animation: "spin 0.7s linear infinite", display: "inline-block",
|
||||||
|
}} />
|
||||||
|
)}
|
||||||
|
{archGenerating ? "Analysing PRD…" : "Generate architecture →"}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Architecture tab */}
|
{/* Architecture tab */}
|
||||||
{activeTab === "architecture" && architecture && (
|
{activeTab === "architecture" && architecture && (
|
||||||
<ArchitectureView arch={architecture} />
|
<ArchitectureView arch={architecture} />
|
||||||
|
|||||||
Reference in New Issue
Block a user