project-tabs.tsx105 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 // Agent access (MCP keys) — always visible: agents are first-class users
17 // of Briven, so the door to them can't hide behind developer mode.
18 { href: '/mcp', label: 'mcp', dev: false },
19 { href: '/members', label: 'members', dev: false },
20 { href: '/functions', label: 'functions', dev: true },
21 { href: '/cron', label: 'cron', dev: true },
22 { href: '/webhooks', label: 'webhooks', dev: true },
23 { href: '/logs', label: 'logs', dev: true },
24 { href: '/deployments', label: 'deployments', dev: true },
25 { href: '/connect', label: 'connect', dev: true },
26 { href: '/env', label: 'env', dev: true },
27 { href: '/keys', label: 'api keys', dev: true },
28 { href: '/ai-schema', label: 'ai', match: '/ai-', dev: true },
29 { href: '/settings', label: 'settings', dev: false },
30] as const;
31
32export function ProjectTabs({
33 projectId,
34 developerMode,
35}: {
36 projectId: string;
37 developerMode: boolean;
38}) {
39 const pathname = usePathname();
40 const router = useRouter();
41 const base = `/dashboard/projects/${projectId}`;
42 const visible = TABS.filter((tab) => developerMode || !tab.dev);
43
44 function toggleDev() {
45 const on = document.cookie.split('; ').some((c) => c === 'briven_dev=1');
46 document.cookie = `briven_dev=${on ? '0' : '1'}; path=/; max-age=31536000; samesite=lax`;
47 router.refresh();
48 }
49
50 return (
51 <div className="flex items-center gap-2 border-b border-[var(--color-border-subtle)]">
52 <nav
53 aria-label="project sections"
54 className="flex flex-1 gap-1 overflow-x-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
55 >
56 {visible.map((tab) => {
57 const href = `${base}${tab.href}`;
58 const matchPrefix = 'match' in tab && tab.match ? `${base}${tab.match}` : href;
59 const active = tab.href === '' ? pathname === base : pathname.startsWith(matchPrefix);
60 return (
61 <Link
62 key={tab.href}
63 href={href}
64 className={`shrink-0 whitespace-nowrap px-3 py-2 font-mono text-sm transition outline-none focus:outline-none focus-visible:outline-none ${
65 active
66 ? 'font-medium text-[var(--color-text)]'
67 : 'text-[var(--color-text-muted)] hover:text-[var(--color-text)]'
68 }`}
69 >
70 {tab.label}
71 </Link>
72 );
73 })}
74 </nav>
75 <button
76 type="button"
77 onClick={toggleDev}
78 title={
79 developerMode
80 ? 'Hide developer tools — show the simple view'
81 : 'Show developer tools (SQL editor, functions, api keys, webhooks…)'
82 }
83 className={`flex shrink-0 items-center gap-1 whitespace-nowrap rounded-md px-2.5 py-1 font-mono text-xs transition ${
84 developerMode
85 ? 'bg-[var(--color-primary)] text-white'
86 : 'text-[color-mix(in_srgb,var(--color-primary)_60%,transparent)] hover:text-[var(--color-primary)]'
87 }`}
88 >
89 <svg
90 aria-hidden
91 viewBox="0 0 24 24"
92 fill="none"
93 stroke="currentColor"
94 strokeWidth="2"
95 strokeLinecap="round"
96 strokeLinejoin="round"
97 className="size-3.5"
98 >
99 <path d="M7 17 17 7M7 7h10v10" />
100 </svg>
101 developer mode
102 </button>
103 </div>
104 );
105}