Files
PinPoint-Golf/src/components/Navbar.tsx
T

344 lines
15 KiB
TypeScript

import React, { useState } from 'react';
import {
Flag,
Table,
BarChart3,
Settings,
Plus,
RotateCcw,
Trophy,
Users,
User as UserIcon,
LogOut,
Shield,
Eye,
EyeOff,
ChevronDown,
Lock,
} from 'lucide-react';
import { Round } from '../types/golf';
import { formatToPar } from '../utils/golfStats';
import { useAuth } from '../context/AuthContext';
export type ActiveTab = 'hole' | 'scorecard' | 'analytics' | 'settings';
interface NavbarProps {
activeTab: ActiveTab;
setActiveTab: (tab: ActiveTab) => void;
currentRound?: Round | null;
onNewRound: () => void;
onOpenRounds: () => void;
onOpenAchievements: () => void;
onOpenCommunity: () => void;
onOpenAuth: () => void;
achievementsUnlockedCount: number;
scoreSummary: {
totalGross: number;
toPar: number;
holesPlayedCount: number;
};
}
export const Navbar: React.FC<NavbarProps> = ({
activeTab,
setActiveTab,
currentRound,
onNewRound,
onOpenRounds,
onOpenAchievements,
onOpenCommunity,
onOpenAuth,
achievementsUnlockedCount,
scoreSummary,
}) => {
const { user, logout, updateProfile } = useAuth();
const [profileDropdownOpen, setProfileDropdownOpen] = useState(false);
return (
<>
{/* Top Header - Desktop & Tablet */}
<header className="sticky top-0 z-30 bg-stone-900/95 backdrop-blur-md border-b border-stone-800">
<div className="max-w-7xl mx-auto px-4 sm:px-6 h-16 flex items-center justify-between gap-4 sm:gap-6">
{/* Zone 1: Brand Wordmark */}
<div className="flex items-center gap-3 shrink-0">
<button
onClick={() => setActiveTab('hole')}
className="text-left flex items-center gap-2.5 focus:outline-none group"
>
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-emerald-500 to-emerald-700 flex items-center justify-center text-white shadow-md shadow-emerald-950/40">
<Flag className="w-4 h-4 text-emerald-100" />
</div>
<div>
<span className="text-lg font-extrabold tracking-tight text-white group-hover:text-emerald-400 transition-colors">
PinPoint Golf
</span>
<span className="hidden lg:inline-flex items-center ml-2 text-xs font-medium text-stone-400">
{currentRound ? currentRound.courseName : 'No Active Round'}
{currentRound?.isCompleted && (
<span className="ml-1.5 text-[10px] font-bold text-emerald-400 bg-stone-800 border border-emerald-500/30 px-1.5 py-0.5 rounded flex items-center gap-1">
<Lock className="w-2.5 h-2.5" />
Saved
</span>
)}
</span>
</div>
</button>
</div>
{/* Zone 2: Navigation Links */}
<nav className="hidden md:flex items-center gap-1 sm:gap-1.5 text-sm font-medium">
<button
onClick={() => setActiveTab('hole')}
className={`px-3 py-1.5 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'hole'
? 'bg-stone-800 text-emerald-400 font-semibold shadow-sm'
: 'text-stone-300 hover:text-white hover:bg-stone-800/60'
}`}
>
Hole Entry
</button>
<button
onClick={() => setActiveTab('scorecard')}
className={`px-3 py-1.5 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'scorecard'
? 'bg-stone-800 text-emerald-400 font-semibold shadow-sm'
: 'text-stone-300 hover:text-white hover:bg-stone-800/60'
}`}
>
Scorecard
</button>
<button
onClick={() => setActiveTab('analytics')}
className={`px-3 py-1.5 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'analytics'
? 'bg-stone-800 text-emerald-400 font-semibold shadow-sm'
: 'text-stone-300 hover:text-white hover:bg-stone-800/60'
}`}
>
Analytics
</button>
<button
onClick={() => setActiveTab('settings')}
className={`px-3 py-1.5 rounded-lg transition-colors whitespace-nowrap ${
activeTab === 'settings'
? 'bg-stone-800 text-emerald-400 font-semibold shadow-sm'
: 'text-stone-300 hover:text-white hover:bg-stone-800/60'
}`}
>
Settings
</button>
{/* Community / Leaderboard Link */}
<button
onClick={onOpenCommunity}
className="px-3 py-1.5 rounded-lg text-amber-300/90 hover:text-amber-200 hover:bg-amber-500/10 transition-colors flex items-center gap-1.5"
title={user ? 'Community Leaderboards' : 'Community (Sign-in required)'}
>
<Users className="w-3.5 h-3.5 text-amber-400" />
<span>Community</span>
{!user && <Lock className="w-2.5 h-2.5 text-amber-400/80" />}
</button>
</nav>
{/* Zone 3: Score Quick Pill + Actions + Auth */}
<div className="flex items-center gap-1.5 sm:gap-2.5 shrink-0">
{/* Live Score Tally */}
<div className="flex items-center gap-1.5 sm:gap-2 px-2.5 sm:px-3 py-1 bg-stone-800/90 border border-stone-700/80 rounded-lg text-xs font-mono tabular-nums">
<span className="text-stone-400 hidden xs:inline">Score</span>
<span className="font-bold text-white text-xs sm:text-sm">
{scoreSummary.totalGross > 0 ? scoreSummary.totalGross : '—'}
</span>
<span
className={`font-semibold px-1.5 py-0.5 rounded text-[11px] sm:text-xs ${
scoreSummary.toPar < 0
? 'bg-emerald-500/20 text-emerald-400'
: scoreSummary.toPar > 0
? 'bg-amber-500/20 text-amber-400'
: 'bg-stone-700 text-stone-300'
}`}
>
{formatToPar(scoreSummary.toPar)}
</span>
</div>
{/* Achievements Trophy Button */}
<button
onClick={onOpenAchievements}
className="flex items-center gap-1 px-2 sm:px-2.5 py-1.5 bg-amber-500/10 hover:bg-amber-500/20 text-amber-300 border border-amber-500/30 rounded-lg text-xs font-bold transition-all shadow-sm"
title={user ? 'Career Trophies & Achievements' : 'Trophies (Sign-in required)'}
>
<Trophy className="w-3.5 h-3.5 text-amber-400" />
{user ? (
<span className="font-mono text-xs">{achievementsUnlockedCount}</span>
) : (
<Lock className="w-3 h-3 text-amber-400" />
)}
</button>
{/* Mobile Community Button */}
<button
onClick={onOpenCommunity}
className="md:hidden p-1.5 text-amber-300 hover:bg-amber-500/10 rounded-lg transition relative"
title={user ? 'Community Leaderboard' : 'Community (Sign-in required)'}
>
<Users className="w-4 h-4 text-amber-400" />
{!user && (
<Lock className="w-2.5 h-2.5 text-amber-400 absolute top-1 right-1" />
)}
</button>
{/* Saved Rounds */}
<button
onClick={onOpenRounds}
title="Saved Rounds"
className="p-1.5 sm:p-2 text-stone-300 hover:text-white hover:bg-stone-800 rounded-lg transition-colors"
>
<RotateCcw className="w-4 h-4" />
</button>
{/* New Round */}
<button
onClick={onNewRound}
className="hidden sm:flex items-center gap-1 sm:gap-1.5 px-2.5 sm:px-3 py-1.5 text-xs font-semibold text-white bg-emerald-600 hover:bg-emerald-500 rounded-lg shadow-sm transition-colors whitespace-nowrap"
>
<Plus className="w-3.5 h-3.5" />
<span>New Round</span>
</button>
{/* User Auth Profile Button */}
{user ? (
<div className="relative">
<button
onClick={() => setProfileDropdownOpen(!profileDropdownOpen)}
className="flex items-center gap-2 px-2.5 py-1 bg-stone-800/90 hover:bg-stone-750 border border-stone-700 rounded-xl text-xs text-stone-200 transition"
>
{user.avatarUrl ? (
<img src={user.avatarUrl} alt={user.name} className="w-5 h-5 rounded-full object-cover" />
) : (
<div className="w-5 h-5 rounded-full bg-emerald-600 text-[10px] font-bold text-white flex items-center justify-center">
{user.name.charAt(0).toUpperCase()}
</div>
)}
<div className="hidden sm:flex flex-col text-left leading-tight">
<span className="font-semibold text-[11px] truncate max-w-[90px]">{user.name}</span>
<span className="text-[10px] text-emerald-400 font-mono">Hcp {user.handicapIndex}</span>
</div>
<ChevronDown className="w-3 h-3 text-stone-400" />
</button>
{/* Dropdown Menu */}
{profileDropdownOpen && (
<div className="absolute right-0 mt-2 w-64 bg-stone-900 border border-stone-800 rounded-2xl shadow-2xl p-3 z-50 animate-fadeIn space-y-2">
<div className="px-2 py-1.5 border-b border-stone-800/80">
<p className="font-bold text-xs text-stone-100">{user.name}</p>
<p className="text-[10px] text-stone-400 font-mono">@{user.username}</p>
<p className="text-[10px] text-stone-500 mt-0.5">{user.homeClub}</p>
</div>
<div className="px-2 py-1 flex items-center justify-between text-xs">
<span className="text-stone-400">Handicap Index:</span>
<span className="font-mono font-bold text-emerald-400">{user.handicapIndex}</span>
</div>
<button
onClick={async () => {
await updateProfile({ isPublic: !user.isPublic });
}}
className="w-full px-2 py-1.5 rounded-lg text-left text-xs text-stone-300 hover:bg-stone-800 flex items-center justify-between transition"
>
<span className="flex items-center gap-1.5">
{user.isPublic ? <Eye className="w-3.5 h-3.5 text-emerald-400" /> : <EyeOff className="w-3.5 h-3.5 text-stone-400" />}
Public Stats:
</span>
<span className={`font-semibold text-[11px] ${user.isPublic ? 'text-emerald-400' : 'text-stone-500'}`}>
{user.isPublic ? 'Enabled' : 'Private'}
</span>
</button>
<button
onClick={() => {
setProfileDropdownOpen(false);
onOpenCommunity();
}}
className="w-full px-2 py-1.5 rounded-lg text-left text-xs text-stone-300 hover:bg-stone-800 flex items-center gap-2 transition"
>
<Users className="w-3.5 h-3.5 text-amber-400" />
View Community Leaderboard
</button>
<div className="pt-1 border-t border-stone-800">
<button
onClick={() => {
setProfileDropdownOpen(false);
logout();
}}
className="w-full px-2 py-1.5 rounded-lg text-left text-xs text-rose-400 hover:bg-rose-500/10 flex items-center gap-2 transition"
>
<LogOut className="w-3.5 h-3.5" />
Sign Out
</button>
</div>
</div>
)}
</div>
) : (
<button
onClick={onOpenAuth}
className="flex items-center gap-1.5 px-3 py-1.5 bg-stone-800 hover:bg-stone-700 text-stone-200 border border-stone-700 rounded-xl text-xs font-semibold shadow-sm transition"
>
<UserIcon className="w-3.5 h-3.5 text-emerald-400" />
<span>Sign In</span>
</button>
)}
</div>
</div>
</header>
{/* Mobile Bottom Tab Bar (Thumb Zone Ergonomics) */}
<div className="md:hidden fixed bottom-0 left-0 right-0 z-40 bg-stone-900/95 backdrop-blur-lg border-t border-stone-800">
<div className="grid grid-cols-4 items-center h-16 max-w-lg mx-auto px-2">
<button
onClick={() => setActiveTab('hole')}
className={`flex flex-col items-center justify-center py-1 transition-colors ${
activeTab === 'hole' ? 'text-emerald-400 font-semibold' : 'text-stone-400 hover:text-stone-200'
}`}
>
<Flag className="w-5 h-5" />
<span className="text-[10px] mt-1">Hole Entry</span>
</button>
<button
onClick={() => setActiveTab('scorecard')}
className={`flex flex-col items-center justify-center py-1 transition-colors ${
activeTab === 'scorecard' ? 'text-emerald-400 font-semibold' : 'text-stone-400 hover:text-stone-200'
}`}
>
<Table className="w-5 h-5" />
<span className="text-[10px] mt-1">Scorecard</span>
</button>
<button
onClick={() => setActiveTab('analytics')}
className={`flex flex-col items-center justify-center py-1 transition-colors ${
activeTab === 'analytics' ? 'text-emerald-400 font-semibold' : 'text-stone-400 hover:text-stone-200'
}`}
>
<BarChart3 className="w-5 h-5" />
<span className="text-[10px] mt-1">Analytics</span>
</button>
<button
onClick={() => setActiveTab('settings')}
className={`flex flex-col items-center justify-center py-1 transition-colors ${
activeTab === 'settings' ? 'text-emerald-400 font-semibold' : 'text-stone-400 hover:text-stone-200'
}`}
>
<Settings className="w-5 h-5" />
<span className="text-[10px] mt-1">Settings</span>
</button>
</div>
</div>
</>
);
};