"use client"; import { useEffect, useState } from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; import { signOut, useSession } from "next-auth/react"; interface VIBNSidebarProps { workspace: string; } interface ProjectData { id: string; productName?: string; name?: string; status?: string; giteaRepo?: string; giteaRepoUrl?: string; surfaces?: string[]; surfaceThemes?: Record; apps?: Array<{ name: string; path: string; coolifyServiceUuid?: string | null; domain?: string | null }>; } interface AppEntry { name: string; path: string; } // ── Section helpers ───────────────────────────────────────────────────────── function SectionHeading({ label, collapsed }: { label: string; collapsed: boolean }) { if (collapsed) return null; return (
{label}
); } function SectionRow({ icon, label, href, dim, collapsed, }: { icon: string; label: string; href?: string; dim?: boolean; collapsed: boolean; }) { const style: React.CSSProperties = { display: "flex", alignItems: "center", justifyContent: collapsed ? "center" : "flex-start", gap: 8, padding: collapsed ? "7px 0" : "5px 12px", borderRadius: 5, textDecoration: "none", color: dim ? "#c5c0b8" : "#4a4640", fontSize: "0.78rem", fontWeight: 450, transition: "background 0.1s", width: "100%", boxSizing: "border-box" as const, }; const inner = ( <> {icon} {!collapsed && ( {label} )} ); if (href) { return ( { if (!dim) (e.currentTarget as HTMLElement).style.background = "#f6f4f0"; }} onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = "transparent"; }} > {inner} ); } return (
{inner}
); } function SectionDivider() { return
; } // ── Surface label map ──────────────────────────────────────────────────────── const SURFACE_LABELS: Record = { "marketing": "Marketing site", "web-app": "Web app", "admin": "Admin panel", "api": "API layer", }; const SURFACE_ICONS: Record = { "marketing": "◎", "web-app": "⬡", "admin": "◫", "api": "⌁", }; // ── Main sidebar ───────────────────────────────────────────────────────────── const COLLAPSED_KEY = "vibn_sidebar_collapsed"; const COLLAPSED_W = 52; const EXPANDED_W = 216; export function VIBNSidebar({ workspace }: VIBNSidebarProps) { const pathname = usePathname(); const { data: session } = useSession(); const [collapsed, setCollapsed] = useState(false); const [mounted, setMounted] = useState(false); // Project-specific data const [project, setProject] = useState(null); const [apps, setApps] = useState([]); // Global projects list (used when NOT inside a project) const [projects, setProjects] = useState>([]); const activeProjectId = pathname?.match(/\/project\/([^/]+)/)?.[1] ?? null; const activeTab = pathname?.match(/\/project\/[^/]+\/([^/]+)/)?.[1] ?? null; // Restore collapse state useEffect(() => { const stored = localStorage.getItem(COLLAPSED_KEY); if (stored === "1") setCollapsed(true); setMounted(true); }, []); const toggle = () => { setCollapsed(prev => { localStorage.setItem(COLLAPSED_KEY, prev ? "0" : "1"); return !prev; }); }; // Fetch global projects list (for non-project pages) useEffect(() => { if (activeProjectId) return; fetch("/api/projects") .then(r => r.json()) .then(d => setProjects(d.projects ?? [])) .catch(() => {}); }, [activeProjectId]); // Fetch project-specific data when inside a project useEffect(() => { if (!activeProjectId) { setProject(null); setApps([]); return; } fetch(`/api/projects/${activeProjectId}`) .then(r => r.json()) .then(d => setProject(d.project ?? null)) .catch(() => {}); fetch(`/api/projects/${activeProjectId}/apps`) .then(r => r.json()) .then(d => setApps(d.apps ?? [])) .catch(() => {}); }, [activeProjectId]); const isProjects = !activeProjectId && (pathname?.includes("/projects") || pathname?.includes("/project")); const isActivity = !activeProjectId && pathname?.includes("/activity"); const isSettings = !activeProjectId && pathname?.includes("/settings"); const topNavItems = [ { id: "projects", label: "Projects", icon: "⌗", href: `/${workspace}/projects` }, { id: "activity", label: "Activity", icon: "↗", href: `/${workspace}/activity` }, { id: "settings", label: "Settings", icon: "⚙", href: `/${workspace}/settings` }, ]; const userInitial = session?.user?.name?.[0]?.toUpperCase() ?? session?.user?.email?.[0]?.toUpperCase() ?? "?"; const w = collapsed ? COLLAPSED_W : EXPANDED_W; const transition = mounted ? "width 0.2s cubic-bezier(0.4,0,0.2,1)" : "none"; const base = `/${workspace}/project/${activeProjectId}`; // Surfaces locked in on design page const surfaces = project?.surfaces ?? []; // Coolify/monorepo apps const infraApps = project?.apps ?? []; return ( ); }