MotorSignal-App/website/components/site-header-client.tsx

182 lines
9.0 KiB
TypeScript

'use client';
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
interface SiteHeaderProps {
signedIn: boolean;
role: string;
}
export function SiteHeaderClient({ signedIn, role }: SiteHeaderProps) {
const pathname = usePathname();
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
useEffect(() => {
setMobileMenuOpen(false);
}, [pathname]);
if (pathname?.includes('/manage') || pathname?.startsWith('/admin') || pathname?.startsWith('/ops')) return null;
const navItems = [
{ label: 'Live Cockpit', href: '/' },
{ label: 'Features', href: '/features' },
{ label: 'Group Rides', href: '/group-rides' },
{ label: 'Pricing', href: '/pricing' },
{ label: 'Hardware & App', href: '/pricing#hardware' },
];
return (
<header className="fixed top-0 w-full z-50 bg-surface-container-lowest/80 backdrop-blur-xl border-b border-outline-variant/30 shadow-[0_1px_8px_rgba(0,0,0,0.04)]">
<div className="h-20 max-w-container-max mx-auto px-4 sm:px-6 lg:px-8 flex items-center justify-between gap-space-sm sm:gap-space-md">
{/* Left: Brand + Navigation */}
<div className="flex items-center gap-space-sm sm:gap-space-lg min-w-0">
<Link className="flex items-center gap-2 sm:gap-space-sm group shrink-0" href="/" aria-label="MotorSignal home">
<div className="w-9 h-9 sm:w-10 sm:h-10 rounded-xl bg-gradient-to-tr from-primary-container via-surface-tint to-tertiary-container flex items-center justify-center shadow-[0_0_16px_rgba(0,242,254,0.35)] shrink-0">
<span className="material-symbols-outlined text-on-primary-container text-[20px] sm:text-[22px] font-bold">show_chart</span>
</div>
<div className="flex flex-col min-w-0">
<span className="font-headline-sm text-sm sm:text-headline-sm text-on-surface tracking-tight uppercase group-hover:text-primary-container transition-colors truncate">MotorSignal</span>
<span className="font-label-caps text-[10px] sm:text-label-caps text-primary-fixed-dim uppercase tracking-widest hidden xs:block">PRO TELEMETRY</span>
</div>
</Link>
<nav className="hidden xl:flex items-center gap-space-lg whitespace-nowrap" aria-label="Primary navigation">
{navItems.map((item) => {
const isActive =
item.href === '/' ? pathname === '/' : pathname.startsWith(item.href);
return (
<Link
key={item.label}
href={item.href}
className={`transition-colors whitespace-nowrap ${
isActive
? 'text-primary-container font-headline-sm border-b-2 border-primary-container pb-0.5'
: 'font-label-md text-label-md text-on-surface-variant hover:text-on-surface'
}`}
>
{item.label}
</Link>
);
})}
</nav>
</div>
{/* Right: Telemetry Grid pill + Auth actions + Mobile Hamburger */}
<div className="flex items-center gap-1.5 sm:gap-space-md whitespace-nowrap shrink-0">
{/* Telemetry Grid: Online Badge */}
<div className="hidden md:flex items-center gap-space-xs px-space-sm py-1 rounded-full bg-surface-container-low border border-outline-variant/40">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-secondary-fixed opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-secondary-fixed-dim"></span>
</span>
<span className="font-label-caps text-label-caps text-on-surface-variant uppercase">
Telemetry Grid: Online
</span>
</div>
{signedIn ? (
<div className="flex items-center gap-space-xs">
<Link
className="w-9 h-9 sm:w-11 sm:h-11 rounded-full bg-surface-container-high border border-outline-variant/40 flex items-center justify-center hover:ring-2 hover:ring-primary-container transition-all shrink-0"
href="/account"
title="Rider Account"
>
<span className="material-symbols-outlined text-primary-container text-[20px] sm:text-[22px]">
person
</span>
</Link>
</div>
) : (
<div className="flex items-center gap-1.5 sm:gap-space-sm">
<Link
className="hidden sm:inline-flex items-center justify-center px-space-md py-2.5 rounded-xl bg-surface-container-low hover:bg-surface-container-high border border-outline-variant/40 text-on-surface hover:text-primary transition-all font-label-md text-label-md min-h-[44px]"
href="/login"
>
Sign In
</Link>
<Link
className="inline-flex items-center justify-center px-2.5 sm:px-space-lg py-2 sm:py-2.5 rounded-xl bg-gradient-to-r from-primary-container to-primary-fixed text-on-primary-container font-headline-sm text-xs sm:text-headline-sm hover:shadow-[0_0_24px_rgba(0,242,254,0.45)] transition-all whitespace-nowrap min-h-[38px] sm:min-h-[44px]"
href="/register"
>
<span className="hidden sm:inline">Get Started Free</span>
<span className="sm:hidden">Get Started</span>
</Link>
<Link
className="sm:hidden w-9 h-9 rounded-full bg-primary flex items-center justify-center hover:ring-2 hover:ring-primary-container transition-all shrink-0"
href="/login"
title="Account"
aria-label="Sign in"
>
<span className="material-symbols-outlined text-on-primary text-[18px]">
person
</span>
</Link>
</div>
)}
{/* Mobile Menu Hamburger Button */}
<button
type="button"
className="xl:hidden w-9 h-9 sm:w-11 sm:h-11 rounded-xl bg-surface-container-low hover:bg-surface-container-high border border-outline-variant/40 flex items-center justify-center text-on-surface hover:text-primary transition-colors cursor-pointer shrink-0"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
aria-label={mobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'}
aria-expanded={mobileMenuOpen}
>
<span className="material-symbols-outlined text-[20px] sm:text-[24px]">
{mobileMenuOpen ? 'close' : 'menu'}
</span>
</button>
</div>
</div>
{/* Mobile Menu Drawer */}
{mobileMenuOpen && (
<div className="xl:hidden bg-surface-container-lowest/95 backdrop-blur-2xl border-b border-outline-variant/40 px-4 sm:px-gutter-desktop py-space-md shadow-2xl flex flex-col gap-space-sm animate-in slide-in-from-top-2">
<nav className="flex flex-col gap-1" aria-label="Mobile navigation">
{navItems.map((item) => {
const isActive =
item.href === '/' ? pathname === '/' : pathname.startsWith(item.href);
return (
<Link
key={item.label}
href={item.href}
onClick={() => setMobileMenuOpen(false)}
className={`px-space-md py-3 rounded-xl font-label-md text-label-md transition-colors min-h-[44px] flex items-center ${
isActive
? 'bg-surface-container-high text-primary-container font-semibold border-l-4 border-primary-container'
: 'text-on-surface-variant hover:text-on-surface hover:bg-surface-container'
}`}
>
{item.label}
</Link>
);
})}
</nav>
<div className="pt-space-sm border-t border-outline-variant/30 flex items-center justify-between text-xs">
<div className="flex items-center gap-space-xs">
<span className="relative flex h-2 w-2">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-secondary-fixed opacity-75"></span>
<span className="relative inline-flex rounded-full h-2 w-2 bg-secondary-fixed-dim"></span>
</span>
<span className="font-label-caps text-label-caps text-on-surface-variant uppercase">
Telemetry Grid: Online
</span>
</div>
{!signedIn && (
<Link
href="/login"
onClick={() => setMobileMenuOpen(false)}
className="text-primary font-label-md text-label-md hover:underline py-1.5 px-2"
>
Sign In →
</Link>
)}
</div>
</div>
)}
</header>
);
}