s'c1'
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { Link, useLocale, usePathname, useRouter, useTranslation } from "@/i18n/navigation";
|
||||
import { getStoredUserEmail } from "@/app/lib/pocketbase";
|
||||
import { useCrossSiteUrls } from "@/app/lib/useCrossSiteUrls";
|
||||
@@ -18,6 +18,7 @@ export default function Header() {
|
||||
const [scrolled, setScrolled] = useState(false);
|
||||
const [authOpen, setAuthOpen] = useState(false);
|
||||
const [userEmail, setUserEmail] = useState<string | null>(null);
|
||||
const [vip, setVip] = useState(false);
|
||||
const { vipUrl } = useCrossSiteUrls();
|
||||
const navLinks = [
|
||||
{ labelKey: "roadmap" as const, href: "#roadmap" },
|
||||
@@ -26,27 +27,42 @@ export default function Header() {
|
||||
{ labelKey: "resources" as const, href: "#resources" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch("/api/auth/me", { credentials: "include" });
|
||||
const data = await res.json();
|
||||
if (!mounted) return;
|
||||
if (data?.user && data?.token) {
|
||||
const { pbSaveAuth } = await import("@/app/lib/pocketbase");
|
||||
pbSaveAuth(data.token, { id: data.user.id, email: data.user.email });
|
||||
setUserEmail(data.user.email);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
const fetchAuthAndVip = useCallback(async () => {
|
||||
try {
|
||||
const meRes = await fetch("/api/auth/me", { credentials: "include" });
|
||||
const meData = await meRes.json();
|
||||
if (meData?.user && meData?.token) {
|
||||
const { pbSaveAuth } = await import("@/app/lib/pocketbase");
|
||||
pbSaveAuth(meData.token, { id: meData.user.id, email: meData.user.email });
|
||||
setUserEmail(meData.user.email);
|
||||
// 用邮箱查 VIP,比 cookie userId 更可靠
|
||||
const vipRes = await fetch(`/api/vip/check?email=${encodeURIComponent(meData.user.email)}`, { credentials: "include" });
|
||||
const vipData = await vipRes.json();
|
||||
setVip(vipData?.vip === true);
|
||||
} else {
|
||||
setUserEmail(getStoredUserEmail());
|
||||
setVip(false);
|
||||
}
|
||||
if (mounted) setUserEmail(getStoredUserEmail());
|
||||
})();
|
||||
return () => { mounted = false; };
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchAuthAndVip();
|
||||
}, [fetchAuthAndVip]);
|
||||
|
||||
useEffect(() => {
|
||||
const onVipUpdated = () => fetchAuthAndVip();
|
||||
const onAuthSuccess = () => fetchAuthAndVip();
|
||||
window.addEventListener("vip:updated", onVipUpdated);
|
||||
window.addEventListener("auth:success", onAuthSuccess);
|
||||
return () => {
|
||||
window.removeEventListener("vip:updated", onVipUpdated);
|
||||
window.removeEventListener("auth:success", onAuthSuccess);
|
||||
};
|
||||
}, [fetchAuthAndVip]);
|
||||
|
||||
return (
|
||||
<header
|
||||
className={`fixed top-0 left-0 right-0 z-50 w-full max-w-[100vw] transition-all duration-300 ${
|
||||
@@ -78,7 +94,7 @@ export default function Header() {
|
||||
|
||||
<div className="flex shrink-0 items-center gap-1 sm:gap-3">
|
||||
{userEmail ? (
|
||||
<UserAvatar email={userEmail} onLogout={() => setUserEmail(null)} />
|
||||
<UserAvatar email={userEmail} vip={vip} onLogout={() => { setUserEmail(null); setVip(false); router.replace("/"); }} />
|
||||
) : (
|
||||
<button
|
||||
onClick={() => setAuthOpen(true)}
|
||||
@@ -148,7 +164,7 @@ export default function Header() {
|
||||
</button>
|
||||
{userEmail ? (
|
||||
<div className="mt-2 flex items-center gap-3 rounded-lg border border-slate-100 bg-slate-50 px-4 py-2.5 dark:border-slate-700 dark:bg-slate-800">
|
||||
<UserAvatar email={userEmail} onLogout={() => { setUserEmail(null); setMobileOpen(false); }} />
|
||||
<UserAvatar email={userEmail} vip={vip} onLogout={() => { setUserEmail(null); setVip(false); setMobileOpen(false); router.replace("/"); }} />
|
||||
<span className="truncate text-sm text-slate-600 dark:text-slate-400">{userEmail}</span>
|
||||
</div>
|
||||
) : (
|
||||
@@ -176,7 +192,7 @@ export default function Header() {
|
||||
<AuthModal
|
||||
isOpen={authOpen}
|
||||
onClose={() => setAuthOpen(false)}
|
||||
onSuccess={(email) => setUserEmail(email)}
|
||||
onSuccess={() => fetchAuthAndVip()}
|
||||
/>
|
||||
</header>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user