auth-project-nav.tsx119 lines · main
1'use client';
2
3import Link from 'next/link';
4import { usePathname, useRouter } from 'next/navigation';
5
6/**
7 * Tabs for one Auth project — same pattern as project-tabs:
8 * clean set by default; advanced tools behind “developer mode”.
9 */
10const TABS = [
11 { href: '', label: 'overview', exact: true, dev: false },
12 { href: '/users', label: 'users', dev: false },
13 { href: '/sessions', label: 'sessions', dev: false },
14 { href: '/security', label: 'security', dev: false },
15 { href: '/keys', label: 'keys', dev: false },
16 { href: '/providers', label: 'providers', dev: false },
17 { href: '/branding', label: 'branding', dev: false },
18 // Advanced / later tools
19 { href: '/idp', label: 'IdP', dev: true },
20 { href: '/migration', label: 'import', dev: true },
21 { href: '/ai', label: 'AI', dev: true },
22 { href: '/enterprise', label: 'enterprise', dev: true },
23] as const;
24
25const AUTH_ACCENT = '#FFFD74';
26const COOKIE = 'briven_auth_project_dev';
27
28export function AuthProjectNav({
29 projectId,
30 developerMode,
31}: {
32 projectId: string;
33 developerMode: boolean;
34}) {
35 const pathname = usePathname();
36 const router = useRouter();
37 const base = `/dashboard/auth/${projectId}`;
38 const visible = TABS.filter((tab) => developerMode || !tab.dev);
39
40 // If user lands on a hidden dev tab while mode is off, still show that tab
41 // so they aren’t stranded.
42 const forceShow = TABS.filter(
43 (tab) =>
44 tab.dev &&
45 !visible.includes(tab) &&
46 (pathname === `${base}${tab.href}` ||
47 pathname.startsWith(`${base}${tab.href}/`)),
48 );
49 const tabs = [...visible, ...forceShow];
50
51 function toggleDev() {
52 const on = document.cookie
53 .split('; ')
54 .some((c) => c === `${COOKIE}=1`);
55 document.cookie = `${COOKIE}=${on ? '0' : '1'}; path=/; max-age=31536000; samesite=lax`;
56 router.refresh();
57 }
58
59 return (
60 <div className="flex items-center gap-2 border-b border-[var(--color-border-subtle)]">
61 <nav
62 aria-label="Auth project sections"
63 className="flex flex-1 gap-1 overflow-x-auto [-ms-overflow-style:none] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
64 >
65 {tabs.map((tab) => {
66 const href = `${base}${tab.href}`;
67 const exact = 'exact' in tab && tab.exact === true;
68 const active = exact
69 ? pathname === base || pathname === `${base}/`
70 : pathname === href || pathname.startsWith(`${href}/`);
71 return (
72 <Link
73 key={tab.href || 'overview'}
74 href={href}
75 className={`shrink-0 whitespace-nowrap px-3 py-2 font-mono text-sm transition outline-none focus:outline-none focus-visible:outline-none ${
76 active
77 ? 'font-medium text-[var(--color-text)]'
78 : 'text-[var(--color-text-muted)] hover:text-[var(--color-text)]'
79 }`}
80 >
81 {tab.label}
82 </Link>
83 );
84 })}
85 </nav>
86 <button
87 type="button"
88 onClick={toggleDev}
89 title={
90 developerMode
91 ? 'Hide advanced Auth tools'
92 : 'Show advanced tools (IdP, import, AI, enterprise…)'
93 }
94 className="flex shrink-0 items-center gap-1 whitespace-nowrap rounded-md px-2.5 py-1 font-mono text-xs transition"
95 style={
96 developerMode
97 ? { background: AUTH_ACCENT, color: '#111' }
98 : {
99 color: `color-mix(in srgb, ${AUTH_ACCENT} 65%, transparent)`,
100 }
101 }
102 >
103 <svg
104 aria-hidden
105 viewBox="0 0 24 24"
106 fill="none"
107 stroke="currentColor"
108 strokeWidth="2"
109 strokeLinecap="round"
110 strokeLinejoin="round"
111 className="size-3.5"
112 >
113 <path d="M7 17 17 7M7 7h10v10" />
114 </svg>
115 developer mode
116 </button>
117 </div>
118 );
119}