"use client"; import { useEffect, useState, type FormEvent } from "react"; import { Copyable } from "../Copyable"; import styles from "../vip.module.css"; type WelcomeBlockProps = { userName: string | null; userEmail?: string | null; bindingRequired?: boolean; }; const DEFAULT_PASSWORD = "12345678"; function getStorage(key: string): string | null { if (typeof window === "undefined") return null; return localStorage.getItem(key); } function isAnonymousUserId(value: string | null): value is string { return !!value && /^user\d+$/.test(value); } export function WelcomeBlock({ userName, userEmail, bindingRequired = false, }: WelcomeBlockProps) { const userId = getStorage("userId"); const storedUserName = getStorage("userName"); const storedUserEmail = getStorage("userEmail"); const emailLinked = getStorage("emailLinked"); const [dbEmail, setDbEmail] = useState(null); const [backendLinkedEmail, setBackendLinkedEmail] = useState(null); const [backendMemosAccount, setBackendMemosAccount] = useState(null); const [email, setEmail] = useState(""); const [checkingLink, setCheckingLink] = useState(true); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const displayName = storedUserEmail || userEmail || userName || storedUserName || "会员"; const storedLinkedEmail = storedUserEmail || (storedUserName?.includes("@") ? storedUserName : null); const memosAccount = getStorage("memosAccount") || backendMemosAccount || (isAnonymousUserId(userId) ? userId : null); const pbEmail = (() => { try { const raw = getStorage("pb_user"); if (!raw) return null; const parsed = JSON.parse(raw) as { email?: string }; return parsed.email ?? null; } catch { return null; } })(); const alreadyLinked = emailLinked === "1" || !!storedLinkedEmail || !!backendLinkedEmail || !!pbEmail; const needLinkEmail = bindingRequired && isAnonymousUserId(userId) && !alreadyLinked && !checkingLink; useEffect(() => { if (!userId || (alreadyLinked && memosAccount)) { setCheckingLink(false); return; } fetch(`/api/vip/linked-email?user_id=${encodeURIComponent(userId)}`, { cache: "no-store", }) .then((res) => res.json()) .then((data) => { const linkedEmail = data?.email; const linkedAnonymousUserId = typeof data?.anonymousUserId === "string" && /^user\d+$/.test(data.anonymousUserId) ? data.anonymousUserId : null; if (linkedAnonymousUserId && typeof window !== "undefined") { setBackendMemosAccount(linkedAnonymousUserId); localStorage.setItem("memosAccount", linkedAnonymousUserId); } if (!linkedEmail || typeof window === "undefined") return; setBackendLinkedEmail(linkedEmail); localStorage.setItem("emailLinked", "1"); localStorage.setItem("userEmail", linkedEmail); localStorage.setItem("userName", linkedEmail); if (bindingRequired) { window.location.reload(); } }) .catch(() => {}) .finally(() => setCheckingLink(false)); }, [alreadyLinked, bindingRequired, memosAccount, userId]); useEffect(() => { if (storedLinkedEmail || pbEmail || backendLinkedEmail) return; if (userEmail) { setDbEmail(userEmail); return; } const applyEmail = (nextEmail: string | null) => { if (!nextEmail) return; setDbEmail(nextEmail); if (typeof window !== "undefined" && emailLinked === "1") { localStorage.setItem("userEmail", nextEmail); } }; fetch("/api/auth/email", { credentials: "include", cache: "no-store" }) .then((res) => res.json()) .then((data) => { if (data?.email) { applyEmail(data.email); return; } return fetch("/api/auth/me", { credentials: "include", cache: "no-store", }) .then((res) => res.json()) .then((meData) => applyEmail(meData?.user?.email ?? null)); }) .catch(() => {}); }, [backendLinkedEmail, emailLinked, pbEmail, storedLinkedEmail, userEmail]); const handleLinkSubmit = async (event: FormEvent) => { event.preventDefault(); if (!userId || !isAnonymousUserId(userId)) return; setLoading(true); setError(null); try { const normalizedEmail = email.trim().toLowerCase(); const res = await fetch("/api/auth/link-email", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: normalizedEmail, password: DEFAULT_PASSWORD, anonymousUserId: userId, }), }); const data = await res.json().catch(() => ({})); if (!res.ok || !data?.ok || !data?.token || !data?.record?.id) { setError(data?.error || "绑定邮箱失败"); return; } const { pbSaveAuth } = await import("@/app/lib/pocketbase"); pbSaveAuth(data.token, { id: data.record.id, email: data.record.email, }); await fetch("/api/auth/sync-session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ token: data.token, record: data.record, anonymousUserId: userId, }), credentials: "include", }); localStorage.setItem("memosAccount", userId); localStorage.setItem("userId", data.record.id); localStorage.setItem("userEmail", data.record.email || normalizedEmail); localStorage.setItem("userName", data.record.email || normalizedEmail); localStorage.setItem("emailLinked", "1"); localStorage.setItem("paidType", "vip"); window.location.href = "/"; } catch { setError("绑定邮箱失败"); } finally { setLoading(false); } }; const displayEmail = storedLinkedEmail || pbEmail || backendLinkedEmail || dbEmail || userEmail || null; return ( <> {needLinkEmail ? (
先绑定邮箱,再进入 VIP

当前会员还在匿名账号下。绑定后会迁移到统一邮箱账号,默认密码为 {" "} {DEFAULT_PASSWORD} 。

setEmail(event.target.value)} placeholder="your@email.com" required disabled={loading} style={{ width: "100%", padding: "0.75rem 1rem", fontSize: "1rem", border: "1px solid var(--border)", borderRadius: "8px", background: "var(--background)", color: "var(--foreground)", marginBottom: "0.75rem", }} /> {error ? (

{error}

) : null}
) : null}

欢迎回来,{displayName}

以下是当前会员账号和登录信息

会员登录信息
网址 /community 打开
账号 {memosAccount || "-"}
密码 {DEFAULT_PASSWORD}
邮箱 {displayEmail || "-"}
); }