Files
root 9de2465793 s
2026-06-07 15:57:47 +08:00

99 lines
4.0 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect, useRef } from "react";
import { Link, useTranslation } from "@/i18n/navigation";
import { pbLogout } from "@/app/lib/pocketbase";
function getAvatarLetter(email: string): string {
const local = email.split("@")[0];
if (!local) return "?";
return local.slice(0, 1).toUpperCase();
}
interface UserAvatarProps {
email: string;
isVip?: boolean;
onLogout?: () => void;
}
/** 圆形头像 + 账户菜单 */
export default function UserAvatar({ email, isVip = false, onLogout }: UserAvatarProps) {
const { t } = useTranslation("menu");
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?.();
};
return (
<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-[#ff4d4f] text-sm font-semibold text-white shadow-sm ring-2 ring-white transition hover:bg-[#ff7a45] dark:ring-gray-900"
aria-label={t("yourProfile")}
aria-expanded={open}
>
{getAvatarLetter(email)}
</button>
{isVip && (
<span className="absolute -top-1 -right-1 flex h-4 min-w-4 items-center justify-center rounded-full bg-amber-500 px-1 text-[10px] font-bold text-white shadow-sm">
VIP
</span>
)}
{open && (
<div className="absolute right-0 top-full z-50 mt-2 w-[260px] overflow-hidden rounded-xl border border-gray-200 bg-white shadow-xl dark:border-gray-700 dark:bg-gray-800">
<div className="border-b border-gray-100 px-4 py-3 dark:border-gray-700">
<p className="truncate text-sm font-semibold text-gray-900 dark:text-white">{email}</p>
<p className="mt-0.5 text-xs text-gray-500 dark:text-gray-400">{isVip ? "VIP" : t("yourProfile")}</p>
</div>
<div className="px-2 py-2">
<Link
href="/digital/profile"
onClick={() => setOpen(false)}
className="flex w-full items-center gap-2 rounded-lg px-3 py-2.5 text-left text-sm font-medium text-gray-700 transition hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-700"
>
<span className="w-5 text-center">👤</span>
<span>{t("yourProfile")}</span>
</Link>
<Link
href="/settings/notifications"
onClick={() => setOpen(false)}
className="flex w-full items-center gap-2 rounded-lg px-3 py-2.5 text-left text-sm font-medium text-gray-700 transition hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-700"
>
<span className="w-5 text-center"></span>
<span>{t("settings")}</span>
</Link>
<Link
href="/pricing"
onClick={() => setOpen(false)}
className="flex w-full items-center gap-2 rounded-lg px-3 py-2.5 text-left text-sm font-medium text-gray-700 transition hover:bg-gray-50 dark:text-gray-300 dark:hover:bg-gray-700"
>
<span className="w-5 text-center">👑</span>
<span>{t("pricing")}</span>
</Link>
</div>
<button
onClick={handleLogout}
className="flex w-full items-center gap-2 border-t border-gray-100 px-5 py-3 text-left text-sm font-semibold text-red-600 transition hover:bg-red-50 dark:border-gray-700 dark:text-red-300 dark:hover:bg-red-950/30"
>
<span className="w-5 text-center">🚪</span>
{t("logout")}
</button>
</div>
)}
</div>
);
}