Triviaverse / src /components /layout /Header.tsx
Simeon Garratt
feat: remove Supabase, bundle questions locally, use JSON DB + API
ad111f4
Raw
History Blame Contribute Delete
5.36 kB
"use client";
import { useState, useEffect, useRef, useCallback } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Volume2, VolumeX, Trophy, Star, Award, ShoppingBag } from "lucide-react";
import { useSound } from "@/hooks/useSound";
import { getStoredPlayer } from "@/lib/player-store";
import { getPlayerStarCoins } from "@/lib/supabase/queries";
import { useCosmeticContext } from "@/components/cosmetics/CosmeticProvider";
/**
* Fixed top navigation header for Triviaverse.
* Shows the logo (links to home), optionally displays the player name,
* coin balance, trophy room link, shop link, a Leaderboard nav link,
* and a mute/unmute toggle when showNav is true.
*/
interface HeaderProps {
playerName?: string;
showNav?: boolean;
}
export function Header({ playerName, showNav = true }: HeaderProps) {
const { muted, toggleMute, play } = useSound();
const router = useRouter();
const [coins, setCoins] = useState<number | null>(null);
const { equipped } = useCosmeticContext();
const hasTitleEffect = equipped.title_effect !== null;
// Easter egg: triple-click logo within 1.5s → /admin
const clickCountRef = useRef(0);
const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleLogoClick = useCallback((e: React.MouseEvent) => {
e.preventDefault();
clickCountRef.current += 1;
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
if (clickCountRef.current >= 3) {
clickCountRef.current = 0;
router.push("/admin");
return;
}
clickTimerRef.current = setTimeout(() => {
if (clickCountRef.current === 1) router.push("/");
clickCountRef.current = 0;
}, 400);
}, [router]);
// Easter egg: TRIVIAMASTER name flag
const [isMaster] = useState(
() =>
typeof window !== "undefined" &&
localStorage.getItem("triviamaster_unlocked") === "1",
);
useEffect(() => {
const player = getStoredPlayer();
if (player?.id) {
getPlayerStarCoins(player.id).then(setCoins);
}
}, []);
return (
<header className="fixed top-0 left-0 right-0 z-50 border-b border-white/10 bg-navy/80 backdrop-blur-md shadow-[0_1px_0_rgba(233,30,140,0.2)]">
<div className="mx-auto flex h-16 max-w-6xl items-center justify-between px-4">
{/* Logo — single click goes to /, triple-click goes to /admin */}
<button
onClick={handleLogoClick}
className={`bg-clip-text text-xl font-bold text-transparent ${
hasTitleEffect ? "" : "bg-gradient-to-r from-magenta to-cyan"
}`}
style={hasTitleEffect ? { backgroundImage: "var(--cosmetic-title-gradient)" } : undefined}
>
TRIVIAVERSE
</button>
{/* Right section */}
{showNav && (
<nav className="flex items-center gap-4">
{playerName && (
<span className="text-sm text-white/60">
Playing as{" "}
<span
className="font-semibold"
style={
isMaster
? {
background: "linear-gradient(90deg, #dc2626, #7f1d1d, #dc2626)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
backgroundClip: "text",
}
: { color: "white" }
}
>
{playerName}
</span>
</span>
)}
{/* Coin balance */}
{coins !== null && (
<span className="flex items-center gap-1 text-sm font-semibold text-gold">
<Star size={14} />
{coins.toLocaleString()}
</span>
)}
{/* Trophy room link */}
<Link
href="/trophies"
className="flex items-center justify-center px-2 py-1 rounded text-white/80 hover:bg-white/10 hover:text-white transition-colors"
aria-label="Trophy Room"
>
<Award size={20} />
</Link>
{/* Shop link */}
<Link
href="/shop"
className="flex items-center justify-center px-2 py-1 rounded text-white/80 hover:bg-white/10 hover:text-white transition-colors"
aria-label="Shop"
>
<ShoppingBag size={20} />
</Link>
<Link
href="/leaderboard"
className="flex items-center gap-1.5 rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-sm text-white/80 transition-colors hover:bg-white/10 hover:text-white"
>
<Trophy size={16} />
Leaderboard
</Link>
<button
onClick={() => {
toggleMute();
play("click");
}}
className="flex items-center justify-center px-2 py-1 rounded text-white/80 hover:bg-white/10 hover:text-white transition-colors"
aria-label={muted ? "Unmute" : "Mute"}
>
{muted ? <VolumeX size={20} /> : <Volume2 size={20} />}
</button>
</nav>
)}
</div>
</header>
);
}