"use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { ReactNode, useEffect, useState } from "react"; import { VIBNSidebar } from "./vibn-sidebar"; import { Toaster } from "sonner"; interface ProjectShellProps { children: ReactNode; workspace: string; projectId: string; projectName: string; projectDescription?: string; projectStatus?: string; projectProgress?: number; discoveryPhase?: number; capturedData?: Record; createdAt?: string; updatedAt?: string; featureCount?: number; } const TABS = [ { id: "overview", label: "Atlas", path: "overview" }, { id: "prd", label: "PRD", path: "prd" }, { id: "design", label: "Design", path: "design" }, { id: "build", label: "Build", path: "build" }, { id: "deployment", label: "Launch", path: "deployment" }, { id: "grow", label: "Grow", path: "grow" }, { id: "insights", label: "Insights", path: "insights" }, { id: "settings", label: "Settings", path: "settings" }, ]; const DISCOVERY_PHASES = [ { id: "big_picture", label: "Big Picture" }, { id: "users_personas", label: "Users & Personas" }, { id: "features_scope", label: "Features" }, { id: "business_model", label: "Business Model" }, { id: "screens_data", label: "Screens" }, { id: "risks_questions", label: "Risks" }, ]; interface SavedPhase { phase: string; title: string; summary: string; data: Record; saved_at: string; } function timeAgo(dateStr?: string): string { if (!dateStr) return "—"; const date = new Date(dateStr); if (isNaN(date.getTime())) return "—"; const diff = (Date.now() - date.getTime()) / 1000; if (diff < 60) return "just now"; if (diff < 3600) return `${Math.floor(diff / 60)}m ago`; if (diff < 86400) return `${Math.floor(diff / 3600)}h ago`; const days = Math.floor(diff / 86400); if (days === 1) return "Yesterday"; if (days < 7) return `${days}d ago`; return new Date(dateStr).toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); } function SectionLabel({ children }: { children: ReactNode }) { return (
{children}
); } function StatusTag({ status }: { status?: string }) { const label = status === "live" ? "Live" : status === "building" ? "Building" : "Defining"; const color = status === "live" ? "#2e7d32" : status === "building" ? "#3d5afe" : "#9a7b3a"; const bg = status === "live" ? "#2e7d3210" : status === "building" ? "#3d5afe10" : "#d4a04a12"; return ( {label} ); } export function ProjectShell({ children, workspace, projectId, projectName, projectDescription, projectStatus, projectProgress, createdAt, updatedAt, featureCount = 0, }: ProjectShellProps) { const pathname = usePathname(); const activeTab = TABS.find((t) => pathname?.includes(`/${t.path}`))?.id ?? "overview"; const progress = projectProgress ?? 0; const [savedPhases, setSavedPhases] = useState([]); useEffect(() => { fetch(`/api/projects/${projectId}/save-phase`) .then(r => r.json()) .then(d => setSavedPhases(d.phases ?? [])) .catch(() => {}); // Refresh every 10s while the user is chatting with Atlas const interval = setInterval(() => { fetch(`/api/projects/${projectId}/save-phase`) .then(r => r.json()) .then(d => setSavedPhases(d.phases ?? [])) .catch(() => {}); }, 10_000); return () => clearInterval(interval); }, [projectId]); const savedPhaseIds = new Set(savedPhases.map(p => p.phase)); const firstUnsavedIdx = DISCOVERY_PHASES.findIndex(p => !savedPhaseIds.has(p.id)); return ( <>
{/* Left sidebar */}
{/* Main column */}
{/* Project header */}
{projectName[0]?.toUpperCase() ?? "P"}

{projectName}

{projectDescription && (

{projectDescription}

)}
{progress}%
{/* Tab bar */}
{TABS.map((t) => ( {t.label} ))}
{/* Page content */}
{children}
{/* Right panel — hidden on design tab (design page has its own right panel) */}
{/* Discovery phases */} Discovery {DISCOVERY_PHASES.map((phase, i) => { const isDone = savedPhaseIds.has(phase.id); const isActive = !isDone && i === firstUnsavedIdx; return (
{isDone ? "✓" : isActive ? "→" : i + 1}
{phase.label}
); })}
{/* Captured data — summaries from saved phases */} Captured {savedPhases.length > 0 ? ( savedPhases.map((p) => (
{p.title}
{p.summary}
)) ) : (

Atlas will capture key details here as you chat.

)}
{/* Project info */} Project Info {[ { k: "Created", v: timeAgo(createdAt) }, { k: "Last active", v: timeAgo(updatedAt) }, { k: "Features", v: featureCount > 0 ? `${featureCount} defined` : "None yet" }, ].map((item, i) => (
{item.k}
{item.v}
))}
); }