project-tabs.tsx105 lines · main
| 1 | 'use client'; |
| 2 | |
| 3 | import Link from 'next/link'; |
| 4 | import { 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. |
| 10 | const 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 | |
| 32 | export 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 | } |