MotorSignal-App/website/components/founder-portal/founder-top-bar.tsx

293 lines
13 KiB
TypeScript

'use client';
import React, { useState, useRef, useEffect } from 'react';
import Link from 'next/link';
import { Club, UserPortalPermissions, ClubRole } from './types';
interface FounderTopBarProps {
club: Club;
permissions: UserPortalPermissions;
activeNav?: string;
onToggleMobileSidebar?: () => void;
onPlanRide?: () => void;
onPostAnnouncement?: () => void;
onSwitchRole?: (role: ClubRole) => void;
onSwitchClub?: () => void;
}
export function FounderTopBar({
club,
permissions,
activeNav,
onToggleMobileSidebar,
onPlanRide,
onPostAnnouncement,
onSwitchRole,
onSwitchClub,
}: FounderTopBarProps) {
const [userMenuOpen, setUserMenuOpen] = useState(false);
const userMenuRef = useRef<HTMLDivElement>(null);
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (userMenuRef.current && !userMenuRef.current.contains(event.target as Node)) {
setUserMenuOpen(false);
}
}
function handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
setUserMenuOpen(false);
}
}
if (userMenuOpen) {
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleKeyDown);
}
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleKeyDown);
};
}, [userMenuOpen]);
return (
<header className="fixed top-0 left-0 lg:left-72 right-0 h-16 bg-surface-container-lowest/95 backdrop-blur-md border-b border-brand-border/60 z-40 px-4 sm:px-6 lg:px-8">
<div className="w-full max-w-[1440px] mx-auto h-full flex items-center justify-between gap-3 sm:gap-4">
{/* Left: Mobile hamburger + Breadcrumbs + Switch Club */}
<div className="flex items-center gap-3 sm:gap-4 min-w-0">
{/* Mobile Hamburger Toggle */}
<button
onClick={onToggleMobileSidebar}
className="lg:hidden text-outline hover:text-on-surface p-1 rounded-lg hover:bg-surface-container transition-colors"
aria-label="Open sidebar"
>
<span className="material-symbols-outlined text-[22px]">menu</span>
</button>
<div className="flex items-center gap-2 font-mono text-xs text-outline shrink-0">
<Link href="/clubs" className="hover:text-on-surface transition-colors">
Clubs
</Link>
<span>/</span>
<Link
href={`/clubs/${club.slug || club.id}/manage`}
className="text-on-surface font-semibold truncate max-w-[120px] sm:max-w-[180px] md:max-w-none hover:text-brand-cyan transition-colors"
>
{club.name}
</Link>
{activeNav === 'profile' && (
<>
<span className="text-outline-variant">/</span>
<span className="text-brand-cyan font-semibold">Club Profile</span>
</>
)}
{activeNav === 'settings' && (
<>
<span className="text-outline-variant">/</span>
<span className="text-brand-cyan font-semibold">Settings</span>
</>
)}
{activeNav === 'applications' && (
<>
<span className="text-outline-variant">/</span>
<span data-breadcrumb="APPLICATIONS" className="text-brand-cyan font-semibold">Applications</span>
</>
)}
</div>
<div className="relative flex items-center shrink-0">
<button
onClick={onSwitchClub}
className="flex items-center gap-1.5 px-2.5 py-1 rounded-lg bg-surface-container border border-brand-border/60 text-slate-200 font-body text-xs hover:bg-surface-container-high transition-colors"
>
<span className="material-symbols-outlined text-[15px] text-brand-cyan">swap_horiz</span>
<span className="hidden sm:inline">Switch Club</span>
<span className="material-symbols-outlined text-[15px] text-outline">expand_more</span>
</button>
</div>
</div>
{/* Middle: Search Input (desktop/tablet only) */}
<div className="flex-1 max-w-xs mx-2 lg:mx-4 min-w-0 hidden md:block">
<div className="relative">
<span className="material-symbols-outlined absolute left-3 top-1/2 -translate-y-1/2 text-outline text-[17px]">
search
</span>
<input
className="w-full h-8 pl-9 pr-3 rounded-lg bg-surface-container-low border border-brand-border/60 text-on-surface placeholder:text-outline/70 font-body text-xs focus:outline-none focus:border-brand-cyan transition-colors"
placeholder="Search members, rides, routes..."
type="text"
aria-label="Search members, rides, routes"
/>
</div>
</div>
{/* Right: Actions + Role Switcher preview pill + Notifications + Profile */}
<div className="flex items-center gap-2 sm:gap-2.5 shrink-0">
{/* Authoritative Role Badge (Production) vs Interactive Role Switcher (Development Only) */}
{process.env.NODE_ENV === 'development' ? (
<div
className="hidden xl:flex items-center gap-1 px-2 py-0.5 rounded-lg bg-surface-container-high border border-brand-border/60 text-[11px] font-mono text-outline"
title="Interactive role preview for testing software permissions (Development Only)"
>
<span className="text-outline-variant">Dev Preview:</span>
{(['owner', 'admin', 'member'] as const).map((r) => (
<button
key={r}
onClick={() => onSwitchRole?.(r)}
className={`px-1.5 py-0.5 rounded capitalize font-semibold transition-colors ${
permissions.role === r
? 'bg-primary-container text-on-primary-container font-bold'
: 'hover:text-on-surface'
}`}
>
{r}
</button>
))}
</div>
) : (
<div
className="hidden sm:flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-surface-container border border-brand-border/60 text-[11px] font-mono text-outline capitalize font-medium"
title="Authoritative software role"
>
<span className="w-1.5 h-1.5 rounded-full bg-brand-cyan" />
<span className="text-on-surface font-semibold">{permissions.role}</span>
</div>
)}
{/* Post Announcement (Owner / Admin only) */}
{permissions.canManageSettings && (
<button
onClick={onPostAnnouncement}
className="hidden xl:flex items-center gap-1.5 px-3 py-1.5 rounded-xl bg-surface-container border border-brand-border/60 text-slate-200 font-body text-xs font-medium hover:bg-surface-container-high transition-colors"
>
<span className="material-symbols-outlined text-[15px] text-brand-cyan">campaign</span>
<span>Post Announcement</span>
</button>
)}
{/* Plan Club Ride (Owner / Admin only) */}
{permissions.canPlanRides && (
<button
onClick={onPlanRide}
className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl bg-primary-container text-on-primary-container font-body text-xs font-bold hover:brightness-110 shadow-[0_0_12px_rgba(0,242,254,0.25)] transition-all shrink-0"
>
<span className="material-symbols-outlined text-[15px]">add_road</span>
<span className="hidden sm:inline">Plan Club Ride</span>
</button>
)}
{/* Notifications */}
<div className="relative ml-1 shrink-0">
<button
className="w-8 h-8 rounded-xl bg-surface-container border border-brand-border/60 flex items-center justify-center text-outline hover:text-on-surface transition-colors"
title="Notifications"
aria-label="Notifications"
>
<span className="material-symbols-outlined text-[18px]">notifications</span>
</button>
<span className="absolute top-1 right-1 w-2 h-2 rounded-full bg-error" />
</div>
{/* Rider Profile Menu Dropdown */}
<div className="relative shrink-0 ml-0.5" ref={userMenuRef}>
<button
type="button"
onClick={() => setUserMenuOpen(!userMenuOpen)}
className={`w-8 h-8 rounded-xl bg-surface-container-low hover:bg-surface-container-high border transition-all flex items-center justify-center cursor-pointer ${
userMenuOpen
? 'border-primary-container ring-2 ring-primary-container/20'
: 'border-brand-border/60 hover:border-brand-cyan/50'
}`}
title="Rider Menu"
aria-label="Open rider menu"
aria-expanded={userMenuOpen}
aria-haspopup="menu"
>
<span className="material-symbols-outlined text-primary-container text-[18px]">
person
</span>
</button>
{userMenuOpen && (
<div
role="menu"
aria-orientation="vertical"
className="absolute right-0 mt-2 w-60 rounded-2xl bg-surface-container-lowest/95 backdrop-blur-2xl border border-brand-border/80 shadow-2xl p-2 z-50 animate-in fade-in slide-in-from-top-2 flex flex-col gap-1"
>
<div className="px-3 py-2 border-b border-brand-border/40 mb-1">
<span className="font-headline text-xs font-semibold text-white block">
{club.founderName || 'Rider Account'}
</span>
<span className="font-mono text-[10px] text-brand-cyan">
{permissions.role === 'owner' ? 'Club Owner' : permissions.role === 'admin' ? 'Club Admin' : 'Club Member'}
</span>
</div>
<Link
href="/account"
onClick={() => setUserMenuOpen(false)}
role="menuitem"
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-slate-200 hover:text-brand-cyan hover:bg-surface-container transition-colors text-xs font-semibold"
>
<span className="material-symbols-outlined text-[18px] text-brand-cyan">
person
</span>
<span>Rider Account</span>
</Link>
<Link
href="/clubs/manage"
onClick={() => setUserMenuOpen(false)}
role="menuitem"
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-slate-200 hover:text-brand-cyan hover:bg-surface-container transition-colors text-xs font-semibold"
>
<span className="material-symbols-outlined text-[18px] text-brand-cyan">
shield
</span>
<span>Founder Portal</span>
</Link>
{(permissions.role === 'owner' || permissions.role === 'admin') && (
<Link
href="/admin"
onClick={() => setUserMenuOpen(false)}
role="menuitem"
className="flex items-center gap-3 px-3 py-2.5 rounded-xl text-slate-200 hover:text-brand-cyan hover:bg-surface-container transition-colors text-xs font-semibold"
>
<span className="material-symbols-outlined text-[18px] text-brand-cyan">
admin_panel_settings
</span>
<span>Operations Admin</span>
</Link>
)}
<div className="border-t border-brand-border/40 my-1" />
<button
type="button"
role="menuitem"
onClick={async () => {
setUserMenuOpen(false);
try {
await fetch('/api/auth/logout', { method: 'POST' });
} catch {}
try {
const { firebaseAuth } = await import('../../lib/firebase-client');
await firebaseAuth.signOut();
} catch {}
window.location.assign('/login');
}}
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-error/80 hover:text-error hover:bg-error-container/10 transition-colors text-xs font-semibold text-left cursor-pointer"
>
<span className="material-symbols-outlined text-[18px]">logout</span>
<span>Sign Out</span>
</button>
</div>
)}
</div>
</div>
</div>
</header>
);
}