Files
vibn-frontend/components/project/project-tab-bar.tsx
Mark Henderson 69c3a1258c feat(project): Product/Infrastructure/Hosting tab shell with live Gitea preview
Replaces the old two-tile project landing with a tabbed shell anchored
on three sections: Product (codebases), Infrastructure (swappable
services), Hosting (runtime + reachability). Bare project URL
redirects to /product so the founder always lands on the most
actionable surface.

Product tab is the only one wired with real data so far: each
codebase tile is selectable and renders a lazy-loading Gitea file
tree for apps/<codebase>/ in the right column. Both columns share
height + a heading slot so panels stay visually aligned even when
the right side is sparse.

Infrastructure and Hosting are stubs ready for Phase 2 wiring (no
behavioural change vs today). The old (workspace)/infrastructure
route is removed in favour of the new tab; the other 15 sidebar
routes are untouched and still reachable for the migration window.

Made-with: Cursor
2026-04-28 16:37:38 -07:00

77 lines
2.2 KiB
TypeScript

"use client";
/**
* Project tab bar — Product · Infrastructure · Hosting.
*
* Lives at the top of the cream main area, right below the project
* header. The active tab is determined by the URL pathname so back /
* forward / refresh always highlight the right one.
*/
import Link from "next/link";
import { usePathname } from "next/navigation";
import { Box, Cloud, Server } from "lucide-react";
const TABS = [
{ id: "product", label: "Product", icon: Box, blurb: "Custom code, design, and content built for this vision." },
{ id: "infrastructure", label: "Infrastructure", icon: Server, blurb: "Swappable services this product depends on." },
{ id: "hosting", label: "Hosting", icon: Cloud, blurb: "Where it runs and how people reach it." },
] as const;
export function ProjectTabBar({
workspace,
projectId,
}: {
workspace: string;
projectId: string;
}) {
const pathname = usePathname() ?? "";
const activeTab =
TABS.find(t => pathname.includes(`/project/${projectId}/${t.id}`))?.id ??
"product";
return (
<nav style={tabBar} aria-label="Project sections">
{TABS.map(tab => {
const isActive = tab.id === activeTab;
const Icon = tab.icon;
return (
<Link
key={tab.id}
href={`/${workspace}/project/${projectId}/${tab.id}`}
style={{
...tabLink,
color: isActive ? "#1a1a1a" : "#5f5e5a",
borderBottomColor: isActive ? "#1a1a1a" : "transparent",
fontWeight: isActive ? 600 : 500,
}}
title={tab.blurb}
>
<Icon size={14} style={{ opacity: isActive ? 1 : 0.7 }} />
{tab.label}
</Link>
);
})}
</nav>
);
}
const tabBar: React.CSSProperties = {
display: "flex",
gap: 4,
marginTop: 22,
marginBottom: -1,
};
const tabLink: React.CSSProperties = {
display: "inline-flex",
alignItems: "center",
gap: 8,
padding: "10px 14px",
fontSize: "0.82rem",
textDecoration: "none",
borderBottom: "2px solid transparent",
transition: "color 0.15s, border-color 0.15s",
fontFamily: '"Outfit", "Inter", ui-sans-serif, sans-serif',
};