project-tabs.tsx107 lines · main
1'use client';
2
3import Link from 'next/link';
4import { usePathname, useRouter } from 'next/navigation';
5
6// Non-coders see a clean set of tabs by default. Developer-only surfaces
7// (SQL editor, functions, webhooks, api keys, env, deployments, etc.) are
8// hidden behind a "developer mode" toggle so the default experience stays
9// simple. The toggle persists via a cookie read server-side in the layout.
10const TABS = [
11 { href: '', label: 'overview', dev: false },
12 { href: '/studio', label: 'studio', dev: false },
13 { href: '/snapshots', label: 'snapshots', dev: false },
14 // Auth is no longer a project tab — independent yellow Briven Auth section.
15 { href: '/storage', label: 'S3 bucket', dev: false },
16 // Service badges — one pass per product (db / s3 / auth), always visible.
17 { href: '/badges', label: 'badges', dev: false },
18 // Agent access (MCP keys) — always visible: agents are first-class users
19 // of Briven, so the door to them can't hide behind developer mode.
20 { href: '/mcp', label: 'mcp', dev: false },
21 { href: '/members', label: 'members', dev: false },
22 { href: '/functions', label: 'functions', dev: true },
23 { href: '/cron', label: 'cron', dev: true },
24 { href: '/webhooks', label: 'webhooks', dev: true },
25 { href: '/logs', label: 'logs', dev: true },
26 { href: '/deployments', label: 'deployments', dev: true },
27 { href: '/connect', label: 'connect', dev: true },
28 { href: '/env', label: 'env', dev: true },
29 { href: '/keys', label: 'api keys', dev: true },
30 { href: '/ai-schema', label: 'ai', match: '/ai-', dev: true },
31 { href: '/settings', label: 'settings', dev: false },
32] as const;
33
34export function ProjectTabs({
35 projectId,
36 developerMode,
37}: {
38 projectId: string;
39 developerMode: boolean;
40}) {
41 const pathname = usePathname();
42 const router = useRouter();
43 const base = `/dashboard/projects/${projectId}`;
44 const visible = TABS.filter((tab) => developerMode || !tab.dev);
45
46 function toggleDev() {
47 const on = document.cookie.split('; ').some((c) => c === 'briven_dev=1');
48 document.cookie = `briven_dev=${on ? '0' : '1'}; path=/; max-age=31536000; samesite=lax`;
49 router.refresh();
50 }
51
52 return (
53 <div className="flex items-center gap-2 border-b border-[var(--color-border-subtle)]">
54 <nav
55 aria-label="project sections"
56 className="flex flex-1 gap-1 overflow-x-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
57 >
58 {visible.map((tab) => {
59 const href = `${base}${tab.href}`;
60 const matchPrefix = 'match' in tab && tab.match ? `${base}${tab.match}` : href;
61 const active = tab.href === '' ? pathname === base : pathname.startsWith(matchPrefix);
62 return (
63 <Link
64 key={tab.href}
65 href={href}
66 className={`shrink-0 whitespace-nowrap px-3 py-2 font-mono text-sm transition outline-none focus:outline-none focus-visible:outline-none ${
67 active
68 ? 'font-medium text-[var(--color-text)]'
69 : 'text-[var(--color-text-muted)] hover:text-[var(--color-text)]'
70 }`}
71 >
72 {tab.label}
73 </Link>
74 );
75 })}
76 </nav>
77 <button
78 type="button"
79 onClick={toggleDev}
80 title={
81 developerMode
82 ? 'Hide developer tools — show the simple view'
83 : 'Show developer tools (SQL editor, functions, api keys, webhooks…)'
84 }
85 className={`flex shrink-0 items-center gap-1 whitespace-nowrap rounded-md px-2.5 py-1 font-mono text-xs transition ${
86 developerMode
87 ? 'bg-[var(--color-primary)] text-white'
88 : 'text-[color-mix(in_srgb,var(--color-primary)_60%,transparent)] hover:text-[var(--color-primary)]'
89 }`}
90 >
91 <svg
92 aria-hidden
93 viewBox="0 0 24 24"
94 fill="none"
95 stroke="currentColor"
96 strokeWidth="2"
97 strokeLinecap="round"
98 strokeLinejoin="round"
99 className="size-3.5"
100 >
101 <path d="M7 17 17 7M7 7h10v10" />
102 </svg>
103 developer mode
104 </button>
105 </div>
106 );
107}