auth-section-nav.tsx124 lines · main
| 1 | 'use client'; |
| 2 | |
| 3 | import Link from 'next/link'; |
| 4 | import { usePathname, useRouter } from 'next/navigation'; |
| 5 | import { 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 | */ |
| 11 | const 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 | |
| 25 | const AUTH_ACCENT = '#FFFD74'; |
| 26 | const COOKIE = 'briven_auth_dev'; |
| 27 | |
| 28 | function readDevCookie(): boolean { |
| 29 | if (typeof document === 'undefined') return false; |
| 30 | return document.cookie.split('; ').some((c) => c === `${COOKIE}=1`); |
| 31 | } |
| 32 | |
| 33 | export 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 | } |