Files
gitlab-instance-0a899031_no…/app/vip/components/VipHeader.tsx
2026-03-13 03:43:50 -05:00

98 lines
3.4 KiB
TypeScript

"use client";
import { useState, useEffect, useRef } from "react";
import Link from "next/link";
import { ThemeToggle } from "@/app/components/ThemeToggle";
import { Copyable } from "./Copyable";
import { pbLogout } from "@/app/lib/pocketbase";
import styles from "./vip.module.css";
function getAvatarLetter(email: string): string {
const local = email.split("@")[0];
if (!local) return "?";
return local.slice(0, 1).toUpperCase();
}
type VipHeaderProps = {
isLoggedIn: boolean;
userName?: string | null;
userEmail?: string | null;
onLogout?: () => void;
};
export function VipHeader({ isLoggedIn, userName, userEmail, onLogout }: VipHeaderProps) {
const [open, setOpen] = useState(false);
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false);
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const handleLogout = async () => {
await pbLogout();
setOpen(false);
onLogout?.();
};
const showUser = isLoggedIn || userEmail;
const displayName = userName || userEmail || "会员";
return (
<header className={styles.header}>
<Link
href="https://digital.hackrobot.cn/zh"
className={styles.logo}
target="_blank"
rel="noopener"
>
🌍
</Link>
<nav className={styles.nav}>
<ThemeToggle />
{showUser ? (
<div className={styles.userBadge} style={{ display: "flex", alignItems: "center", gap: "0.5rem" }}>
<div className="relative" ref={ref}>
<button
type="button"
onClick={() => setOpen((o) => !o)}
className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-amber-500 text-sm font-semibold text-white shadow-sm hover:bg-amber-600"
aria-label="用户菜单"
>
{getAvatarLetter(displayName)}
</button>
{isLoggedIn && (
<span
className={styles.vipTag}
title="VIP 会员"
>
VIP
</span>
)}
{open && (
<div className="absolute right-0 top-full z-50 mt-2 min-w-[120px] rounded-lg border border-slate-200 bg-white py-1 shadow-lg dark:border-slate-700 dark:bg-slate-800">
<span className="block px-4 py-2 text-sm text-slate-600 dark:text-slate-400 truncate max-w-[160px]">
<Copyable text={displayName}>{displayName}</Copyable>
</span>
{onLogout && (
<button
onClick={handleLogout}
className="flex w-full items-center gap-2 px-4 py-2 text-left text-sm text-slate-700 hover:bg-slate-50 dark:text-slate-300 dark:hover:bg-slate-700"
>
退
</button>
)}
</div>
)}
</div>
<span className="hidden sm:inline max-w-[120px] truncate text-sm"><Copyable text={displayName}>{displayName}</Copyable></span>
</div>
) : null}
</nav>
</header>
);
}