From 66f0d4c280b1e69e2a6969a2405464e191e888fb Mon Sep 17 00:00:00 2001 From: Paul Grey Date: Tue, 19 May 2026 10:07:57 +1200 Subject: [PATCH] =?UTF-8?q?feat(nav):=20restructure=20top=20menu=20?= =?UTF-8?q?=E2=80=94=20promote=20Get=20Started,=20drop=20"Network"=20dropd?= =?UTF-8?q?own=20ambiguity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous nav had three problems an audit didn't catch but the operator-funnel did: 1. "Network" dropdown contained "How It Works" alongside Validators and Arbitrators — a docs page mixed in with entity-type pages. Semantically inconsistent and hard to scan. 2. The word "Network" was doing double duty — there's a `[mainnet]` badge right next to it labeled Network (the chain badge). Two different concepts using the same word. 3. "Get Started" was buried in the user-avatar dropdown. The page literally for first-time visitors was only reachable AFTER they connected a wallet. The home hero CTA linked to it but a visitor scrolling the page couldn't see it in the nav. ## What changes Primary nav (visible to logged-out visitors) now reflects the conversion funnel directly: Logo · [mainnet] Agents · Jobs · Get Started · How It Works · More ▾ Connect / Avatar ▾ - "Discover" → "Agents" (shorter, clearer label in the nav; the home page's "Discover Agents" H2 stays unchanged — pairs cleanly) - "Get Started" promoted to top-level — matches the home hero CTA - "How It Works" promoted to top-level — answers "what is this?" - "Network" dropdown removed - New "More" dropdown collects the secondary pages: Leaderboard, Validators, Arbitrators (entity directories only, no docs) - Avatar dropdown: Dashboard + Logout (Get Started moves out) ## Mobile Flat list, no nested dropdown for "More" — vertical space isn't tight on mobile, and the previous expand-and-collapse pattern was extra interaction for no benefit. - Primary: Agents, Jobs, Get Started, How It Works - "More" label + Leaderboard, Validators, Arbitrators (flat) - Dashboard (when logged in) - Connect / Logout ## What's unchanged - The `Page` type and `activePage` values stay byte-identical. All existing pages keep working without any caller changes. - The NetworkBadge ([mainnet]/[testnet] toggle pill) keeps its spot next to the logo. That's now the only thing on the page using the word "Network" — disambiguated. - "Discover Agents" stays as the home page's H2 (action verb on the page) and the footer's link label. The nav's shorter "Agents" is the scannable abbreviation; both read naturally. ## Verification - Frontend builds clean - All 9 pages that pass `activePage` continue to highlight the correct nav item (the `MORE_PAGES` array tracks active state for the dropdown trigger like `NETWORK_PAGES` did before) --- frontend/src/components/Header.tsx | 129 ++++++++++++++--------------- 1 file changed, 63 insertions(+), 66 deletions(-) diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index d1cabe2..5c2de71 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -5,26 +5,32 @@ import { getSelectedNetwork, switchNetwork, type NetworkId } from '@/lib/network export type Page = 'discover' | 'jobs' | 'leaderboard' | 'validators' | 'arbitrators' | 'how-it-works' | 'get-started' | 'dashboard'; -const NETWORK_PAGES: Page[] = ['validators', 'arbitrators', 'how-it-works']; -const USER_PAGES: Page[] = ['dashboard', 'get-started']; +// Pages collapsed under the "More" dropdown — secondary content the +// average first-time visitor doesn't need surfaced. +const MORE_PAGES: Page[] = ['leaderboard', 'validators', 'arbitrators']; interface NavItem { href: string; label: string; page: Page } +// Primary nav — the four things a new visitor actually wants to do: +// 1. browse Agents (the registry) +// 2. browse Jobs (the marketplace) +// 3. deploy their own agent (Get Started — has the video walkthrough) +// 4. understand the system (How It Works) const MAIN_NAV: NavItem[] = [ - { href: '/', label: 'Discover', page: 'discover' }, + { href: '/', label: 'Agents', page: 'discover' }, { href: '/jobs', label: 'Jobs', page: 'jobs' }, - { href: '/leaderboard', label: 'Leaderboard', page: 'leaderboard' }, + { href: '/get-started', label: 'Get Started', page: 'get-started' }, + { href: '/how-it-works', label: 'How It Works', page: 'how-it-works' }, ]; -const NETWORK_ITEMS: NavItem[] = [ +// Secondary — power-user / network-participant content. Previously the +// "Network" dropdown mixed entity types (Validators, Arbitrators) with +// a docs page (How It Works) and clashed semantically with the +// [mainnet] badge. Now: a clean "More" with only directories. +const MORE_ITEMS: NavItem[] = [ + { href: '/leaderboard', label: 'Leaderboard', page: 'leaderboard' }, { href: '/validators', label: 'Validators', page: 'validators' }, { href: '/arbitrators', label: 'Arbitrators', page: 'arbitrators' }, - { href: '/how-it-works', label: 'How It Works', page: 'how-it-works' }, -]; - -const USER_MENU_ITEMS: NavItem[] = [ - { href: '/dashboard', label: 'Dashboard', page: 'dashboard' }, - { href: '/get-started', label: 'Get Started', page: 'get-started' }, ]; function NetworkBadge() { @@ -58,19 +64,18 @@ function NetworkBadge() { export function Header({ activePage }: { activePage?: Page }) { const { session, loading, login, logout } = useProton(); const [menuOpen, setMenuOpen] = useState(false); - const [networkOpen, setNetworkOpen] = useState(false); + const [moreOpen, setMoreOpen] = useState(false); const [userOpen, setUserOpen] = useState(false); - const [mobileNetworkOpen, setMobileNetworkOpen] = useState(false); - const networkRef = useRef(null); + const moreRef = useRef(null); const userRef = useRef(null); - const isNetworkActive = NETWORK_PAGES.includes(activePage as Page); - const isUserActive = USER_PAGES.includes(activePage as Page); + const isMoreActive = MORE_PAGES.includes(activePage as Page); + const isUserActive = activePage === 'dashboard'; // Close dropdowns on outside click useEffect(() => { function handleClick(e: MouseEvent) { - if (networkRef.current && !networkRef.current.contains(e.target as Node)) setNetworkOpen(false); + if (moreRef.current && !moreRef.current.contains(e.target as Node)) setMoreOpen(false); if (userRef.current && !userRef.current.contains(e.target as Node)) setUserOpen(false); } document.addEventListener('mousedown', handleClick); @@ -101,32 +106,32 @@ export function Header({ activePage }: { activePage?: Page }) { - {/* Desktop nav — center links */} + {/* Desktop nav — primary links + "More" dropdown */}