a807c4c85e
Remplace le laurier dessiné à la main par le logo fourni (badge circulaire « L'Agora ») comme identité visuelle principale : favicon (icon.png), icône iOS (apple-icon.png, remplace icon.svg), marque du header, page login, page signup, et grand format en en-tête du classement. - components/agora-logo.tsx : composant partagé, `unoptimized` sur next/image — l'optimiseur d'images de Next.js aplatit systématiquement la transparence de ce fichier (PNG ou WebP) lors du redimensionnement à la volée, donc on sert l'asset statique tel quel. - Asset retravaillé (public/logo-agora.webp) : le fichier fourni n'avait pas de vraie transparence (damier dessiné en dur, y compris dans les zones "vides" de l'illustration) et le cercle était mal centré dans son canevas carré (anneau doré rogné en haut). Recadré, recentré avec une marge uniforme, fond extérieur en transparence réelle, fond intérieur aplati en blanc crème (#F4ECD8, le token "marble" existant) pour se fondre dans les cartes marbre de l'app. - Header : bandeau renommé « LE TRIBUNAL » → « L'AGORA » (à gauche) ; les icônes du menu déroulant restent inchangées (icônes distinctes par page, nécessaires pour s'y retrouver). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
165 lines
5.3 KiB
TypeScript
165 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname, useRouter } from "next/navigation";
|
|
import { useEffect, useRef, useState } from "react";
|
|
import { createClient } from "@/lib/supabase/client";
|
|
import { LaurelWreath } from "@/components/laurel-wreath";
|
|
import { Avatar } from "@/components/avatar";
|
|
import { AgoraLogo } from "@/components/agora-logo";
|
|
import {
|
|
IconWheel,
|
|
IconScroll,
|
|
IconPerson,
|
|
IconColumn,
|
|
IconLogout,
|
|
IconChevronDown,
|
|
IconCalendar,
|
|
} from "@/components/icons";
|
|
|
|
type MenuEntry = {
|
|
href: string;
|
|
label: string;
|
|
icon: React.ReactNode;
|
|
};
|
|
|
|
function MenuLink({
|
|
entry,
|
|
isActive,
|
|
onNavigate,
|
|
}: {
|
|
entry: MenuEntry;
|
|
isActive: boolean;
|
|
onNavigate: () => void;
|
|
}) {
|
|
return (
|
|
<Link
|
|
href={entry.href}
|
|
onClick={onNavigate}
|
|
className={`flex items-center gap-3 px-4 py-2.5 text-sm transition-colors ${
|
|
isActive ? "bg-gold/15 text-ink-2 font-medium" : "text-text-marble hover:bg-gold/10"
|
|
}`}
|
|
>
|
|
<span className={isActive ? "text-gold" : "text-text-mut"}>{entry.icon}</span>
|
|
{entry.label}
|
|
</Link>
|
|
);
|
|
}
|
|
|
|
export function Header({
|
|
pseudo,
|
|
avatarUrl,
|
|
isJudge,
|
|
points,
|
|
}: {
|
|
pseudo: string;
|
|
avatarUrl: string | null;
|
|
isJudge: boolean;
|
|
points: number;
|
|
}) {
|
|
const router = useRouter();
|
|
const pathname = usePathname();
|
|
const [menuOpen, setMenuOpen] = useState(false);
|
|
const [signingOut, setSigningOut] = useState(false);
|
|
const menuRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
function handleClickOutside(event: MouseEvent) {
|
|
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
|
setMenuOpen(false);
|
|
}
|
|
}
|
|
function handleKeyDown(event: KeyboardEvent) {
|
|
if (event.key === "Escape") setMenuOpen(false);
|
|
}
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
document.addEventListener("keydown", handleKeyDown);
|
|
return () => {
|
|
document.removeEventListener("mousedown", handleClickOutside);
|
|
document.removeEventListener("keydown", handleKeyDown);
|
|
};
|
|
}, []);
|
|
|
|
async function handleSignOut() {
|
|
setSigningOut(true);
|
|
const supabase = createClient();
|
|
await supabase.auth.signOut();
|
|
router.push("/login");
|
|
router.refresh();
|
|
}
|
|
|
|
const entries: MenuEntry[] = [
|
|
{ href: "/leaderboard", label: "Le Classement", icon: <LaurelWreath className="h-5 w-5" /> },
|
|
{ href: "/roulette", label: "La Roulette", icon: <IconWheel /> },
|
|
{ href: "/calendrier", label: "Le Calendrier des Dieux", icon: <IconCalendar /> },
|
|
{ href: "/journal", label: "Le Crieur", icon: <IconScroll /> },
|
|
{ href: "/profile", label: "Mon profil", icon: <IconPerson /> },
|
|
];
|
|
if (isJudge) {
|
|
entries.push({
|
|
href: "/admin",
|
|
label: "Le Conseil des Archontes",
|
|
icon: <IconColumn />,
|
|
});
|
|
}
|
|
|
|
return (
|
|
<header className="sticky top-0 z-10 bg-ink-2/95 backdrop-blur">
|
|
<div className="mx-auto flex max-w-4xl items-center justify-between gap-2 px-2 py-2.5 sm:px-4 sm:py-3">
|
|
<Link href="/leaderboard" className="flex min-w-0 items-center gap-1.5 sm:gap-2">
|
|
<AgoraLogo className="h-7 w-7 sm:h-9 sm:w-9" />
|
|
<span className="whitespace-nowrap font-heading text-[0.6rem] tracking-[0.08em] text-gold-bright uppercase sm:text-sm sm:tracking-[0.15em]">
|
|
L'Agora
|
|
</span>
|
|
</Link>
|
|
|
|
<div ref={menuRef} className="relative shrink-0">
|
|
<button
|
|
type="button"
|
|
onClick={() => setMenuOpen((open) => !open)}
|
|
aria-expanded={menuOpen}
|
|
aria-label="Ouvrir le menu"
|
|
className="flex items-center gap-1.5 rounded-full py-1 pr-1 pl-1.5 transition-colors hover:bg-marble/10 sm:gap-2 sm:pr-1.5"
|
|
>
|
|
<Avatar pseudo={pseudo} avatarUrl={avatarUrl} size="xs" />
|
|
<span className="max-w-[4.5rem] truncate text-xs font-medium text-marble sm:max-w-[9rem] sm:text-sm">
|
|
{pseudo}
|
|
</span>
|
|
<span className="shrink-0 rounded-full bg-gold/15 px-1.5 py-0.5 text-[0.65rem] font-semibold text-gold-bright sm:px-2 sm:text-xs">
|
|
{points}
|
|
</span>
|
|
<IconChevronDown
|
|
className={`h-3.5 w-3.5 shrink-0 text-marble/60 transition-transform sm:h-4 sm:w-4 ${menuOpen ? "rotate-180" : ""}`}
|
|
/>
|
|
</button>
|
|
|
|
{menuOpen && (
|
|
<div className="marble-surface absolute right-0 z-20 mt-2 w-64 overflow-hidden rounded-lg border border-gold/40 py-1 shadow-lg">
|
|
{entries.map((entry) => (
|
|
<MenuLink
|
|
key={entry.href}
|
|
entry={entry}
|
|
isActive={pathname.startsWith(entry.href)}
|
|
onNavigate={() => setMenuOpen(false)}
|
|
/>
|
|
))}
|
|
<div className="my-1 border-t border-gold/20" />
|
|
<button
|
|
type="button"
|
|
onClick={handleSignOut}
|
|
disabled={signingOut}
|
|
className="flex w-full items-center gap-3 px-4 py-2.5 text-left text-sm text-oxblood transition-colors hover:bg-oxblood/10 disabled:opacity-50"
|
|
>
|
|
<IconLogout className="h-5 w-5" />
|
|
{signingOut ? "…" : "Se déconnecter"}
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="meander-divider" />
|
|
</header>
|
|
);
|
|
}
|