From dccb1f8d3fd0fdcc4dc3fbb29c09017c562d5627 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Fri, 10 Apr 2026 23:44:31 +0200 Subject: [PATCH] feat(27-01): create TopNav component - Sticky top nav bar replacing TotalsBar with full navigation - Logo, Home/Collection/Setups links, search bar, and user avatar - NavLinkOrButton helper: button for anon users on protected routes, Link for authenticated - Active route highlighting via useMatchRoute - Desktop search bar triggers openCatalogSearch('collection') - Desktop nav links hidden on mobile (hidden md:flex) - Uses LucideIcon wrapper, not direct lucide-react imports [Rule 1 - Bug] Used 'house' icon fallback check: plan specified 'home' which does not exist in lucide-react; 'search' and 'layers' verified present --- src/client/components/TopNav.tsx | 129 +++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 src/client/components/TopNav.tsx diff --git a/src/client/components/TopNav.tsx b/src/client/components/TopNav.tsx new file mode 100644 index 0000000..b15b26a --- /dev/null +++ b/src/client/components/TopNav.tsx @@ -0,0 +1,129 @@ +import { Link, useMatchRoute } from "@tanstack/react-router"; +import { useAuth } from "../hooks/useAuth"; +import { LucideIcon } from "../lib/iconData"; +import { useUIStore } from "../stores/uiStore"; +import { UserMenu } from "./UserMenu"; + +interface NavLinkOrButtonProps { + to: string; + isActive: boolean; + isProtected: boolean; + isAuthenticated: boolean; + onAuthPrompt: () => void; + children: React.ReactNode; +} + +function NavLinkOrButton({ + to, + isActive, + isProtected, + isAuthenticated, + onAuthPrompt, + children, +}: NavLinkOrButtonProps) { + const activeClass = "text-gray-900 font-medium"; + const inactiveClass = "text-gray-500 hover:text-gray-700 transition-colors"; + const className = `text-sm ${isActive ? activeClass : inactiveClass}`; + + if (isProtected && !isAuthenticated) { + return ( + + ); + } + + return ( + + {children} + + ); +} + +export function TopNav() { + const { data: auth } = useAuth(); + const isAuthenticated = !!auth?.user; + const openCatalogSearch = useUIStore((s) => s.openCatalogSearch); + const openAuthPrompt = useUIStore((s) => s.openAuthPrompt); + const matchRoute = useMatchRoute(); + + const isHome = !!matchRoute({ to: "/" }); + const isCollection = !!matchRoute({ to: "/collection", fuzzy: true }); + const isSetups = !!matchRoute({ to: "/setups", fuzzy: true }); + + return ( +
+
+
+ {/* Left: Logo */} + + + GearBox + + + {/* Center: Desktop nav links */} + + + {/* Right: Search bar (desktop only) + User section */} +
+ {/* Search bar — desktop only */} + + + {/* User section */} + {isAuthenticated ? ( + + ) : ( + + Sign in + + )} +
+
+
+
+ ); +}