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