182 lines
9.0 KiB
TypeScript
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>
|
|
);
|
|
}
|