diff --git a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/layout.tsx b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/layout.tsx index 0f19b631..b58157f6 100644 --- a/vibn-frontend/app/[workspace]/project/[projectId]/(home)/layout.tsx +++ b/vibn-frontend/app/[workspace]/project/[projectId]/(home)/layout.tsx @@ -7,6 +7,7 @@ import { ReactNode } from "react"; import { Toaster } from "sonner"; import { ChatPanel } from "@/components/vibn-chat/chat-panel"; import { ProjectStreamHandler } from "@/components/project/project-stream-handler"; +import { DashboardSidebar } from "@/components/project/dashboard-sidebar"; export default async function ProjectShell({ children, @@ -21,7 +22,14 @@ export default async function ProjectShell({ <>
- + + {children} + + } + />
diff --git a/vibn-frontend/components/project/project-icon-rail.tsx b/vibn-frontend/components/project/project-icon-rail.tsx index 619d6fc8..2375eebd 100644 --- a/vibn-frontend/components/project/project-icon-rail.tsx +++ b/vibn-frontend/components/project/project-icon-rail.tsx @@ -1,101 +1,89 @@ "use client"; -/** - * Horizontal project nav bar — sits in the unified top row beside chat. - * - * Primary section icons sit on the trailing (right) side; Settings stays - * at the far right. The parent row owns the bottom border. - */ import Link from "next/link"; -import { usePathname } from "next/navigation"; -import { - Eye, - Code2, - ClipboardList, - Palette, - Globe, - Database, - Settings, - CreditCard, - PlaneTakeoff, -} from "lucide-react"; +import { usePathname, useParams } from "next/navigation"; +import { Monitor, Smartphone, RotateCw, ExternalLink } from "lucide-react"; +import { usePreviewToolbarStore } from "./preview-toolbar/preview-toolbar-state"; +import { useAnatomy } from "@/components/project/use-anatomy"; interface Props { workspace: string; projectId: string; + actions?: React.ReactNode; } -interface RailItem { - segment: string; - label: string; - Icon: typeof Eye; - aliases?: string[]; -} - -const PRIMARY_ITEMS: RailItem[] = [ - { segment: "preview", label: "Preview", Icon: Eye }, - { segment: "plan", label: "Plan", Icon: ClipboardList }, - { segment: "market", label: "Market", Icon: PlaneTakeoff }, - { segment: "product", label: "Code", Icon: Code2, aliases: ["code"] }, - { segment: "hosting", label: "Hosting", Icon: Globe }, - { segment: "infrastructure", label: "Infra", Icon: Database }, - { segment: "billing", label: "Billing", Icon: CreditCard }, -]; - -export function ProjectIconRail({ workspace, projectId }: Props) { +export function ProjectIconRail({ workspace, projectId, actions }: Props) { const pathname = usePathname() ?? ""; const projectBase = `/${workspace}/project/${projectId}`; const isPreviewActive = pathname === `${projectBase}/preview` || pathname.startsWith(`${projectBase}/preview/`); - const isActive = (item: RailItem) => { - const segments = [item.segment, ...(item.aliases ?? [])]; - return segments.some( - (s) => - pathname === `${projectBase}/${s}` || - pathname.startsWith(`${projectBase}/${s}/`), - ); - }; - return ( ); } -import { Monitor, Smartphone, RotateCw, ExternalLink } from "lucide-react"; -import { usePreviewToolbarStore } from "./preview-toolbar/preview-toolbar-state"; -import { useAnatomy } from "@/components/project/use-anatomy"; -import { useParams } from "next/navigation"; - function PreviewDeviceToggles() { const deviceMode = usePreviewToolbarStore((s) => s.deviceMode); const setDeviceMode = usePreviewToolbarStore((s) => s.setDeviceMode); @@ -110,7 +98,7 @@ function PreviewDeviceToggles() { const displayUrl = running?.url ?? ""; return ( -
+
- - {active && {label}} - - ); -} - const bar: React.CSSProperties = { display: "flex", flexDirection: "row", @@ -294,10 +243,11 @@ const bar: React.CSSProperties = { flex: 1, minWidth: 0, height: "100%", - padding: "0 12px", - gap: 6, + padding: "0 16px", + gap: 12, boxSizing: "border-box", - background: "#faf8f5", + background: "#fafafa", + borderBottom: "1px solid #e4e4e7", fontFamily: '"Outfit", "Inter", ui-sans-serif, sans-serif', }; @@ -305,18 +255,9 @@ const primaryGroup: React.CSSProperties = { display: "flex", flexDirection: "row", alignItems: "center", - gap: 4, -}; - -const linkBase: React.CSSProperties = { - display: "flex", - alignItems: "center", - justifyContent: "center", - width: 36, - height: 36, - borderRadius: 6, - border: "1px solid", - textDecoration: "none", - transition: "background 0.15s, color 0.15s, border-color 0.15s", - flexShrink: 0, + gap: 2, + background: "#f4f4f5", + padding: 4, + borderRadius: 8, + border: "1px solid #e4e4e7", }; diff --git a/vibn-frontend/components/vibn-chat/chat-panel.tsx b/vibn-frontend/components/vibn-chat/chat-panel.tsx index 3e57752a..fe1218ff 100644 --- a/vibn-frontend/components/vibn-chat/chat-panel.tsx +++ b/vibn-frontend/components/vibn-chat/chat-panel.tsx @@ -2472,7 +2472,39 @@ export function ChatPanel({ alignItems: "stretch", }} > - + { + if (activeThread && !sending) { + sendMessage("ship it"); + } + }} + disabled={!activeThread || sending} + style={{ + background: "#18181b", + border: "none", + cursor: + activeThread && !sending ? "pointer" : "not-allowed", + padding: "6px 14px", + borderRadius: 6, + color: "#fff", + display: "flex", + alignItems: "center", + fontSize: "0.75rem", + fontWeight: 600, + opacity: activeThread && !sending ? 1 : 0.5, + transition: "opacity 0.15s ease", + }} + title="Ship to production" + > + Publish + + } + />
@@ -2724,34 +2756,6 @@ export function ChatPanel({ )}
- {/* Top-Level Publish Button */} - -