's'
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { Copyable } from "../Copyable";
|
||||
import styles from "../vip.module.css";
|
||||
|
||||
type WelcomeBlockProps = {
|
||||
@@ -12,8 +13,174 @@ function getStorage(key: string): string | null {
|
||||
return localStorage.getItem(key);
|
||||
}
|
||||
|
||||
/** 匿名 user_id 格式:user + 数字 */
|
||||
function isAnonymousUserId(val: string | null): boolean {
|
||||
return !!val && /^user\d+$/.test(val);
|
||||
}
|
||||
|
||||
export function WelcomeBlock({ userName }: WelcomeBlockProps) {
|
||||
const displayName = userName || getStorage("userName") || "会员";
|
||||
const userId = getStorage("userId");
|
||||
const needLinkEmail = isAnonymousUserId(userId);
|
||||
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [passwordConfirm, setPasswordConfirm] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [linked, setLinked] = useState(false);
|
||||
|
||||
const handleLinkSubmit = async (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
setLoading(true);
|
||||
try {
|
||||
if (password !== passwordConfirm) {
|
||||
setError("两次密码不一致");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (password.length < 8) {
|
||||
setError("密码至少 8 位");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
const res = await fetch("/api/auth/link-email", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
email: email.trim(),
|
||||
password,
|
||||
anonymousUserId: userId,
|
||||
}),
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!data?.ok) {
|
||||
setError(data?.error || "操作失败");
|
||||
setLoading(false);
|
||||
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 }),
|
||||
credentials: "include",
|
||||
});
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem("userId", data.record.id);
|
||||
localStorage.setItem("userName", data.record.email || data.record.id);
|
||||
}
|
||||
setLinked(true);
|
||||
window.location.reload();
|
||||
} catch {
|
||||
setError("操作失败");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (needLinkEmail && !linked) {
|
||||
return (
|
||||
<section className={styles.section} style={{ paddingTop: "1.5rem" }}>
|
||||
<h1
|
||||
className="animate__animated animate__fadeInDown"
|
||||
style={{
|
||||
fontSize: "clamp(1.5rem, 4vw, 2rem)",
|
||||
fontWeight: 600,
|
||||
margin: 0,
|
||||
animationDelay: "0.1s",
|
||||
animationFillMode: "both",
|
||||
}}
|
||||
>
|
||||
欢迎加入,请补充邮箱
|
||||
</h1>
|
||||
<p
|
||||
className="animate__animated animate__fadeInDown"
|
||||
style={{
|
||||
fontSize: "0.95rem",
|
||||
color: "var(--muted-foreground)",
|
||||
margin: "0.25rem 0 1rem",
|
||||
animationDelay: "0.15s",
|
||||
animationFillMode: "both",
|
||||
}}
|
||||
>
|
||||
补充邮箱后可在各站点通用登录,Memos 账号密码为 12345678
|
||||
</p>
|
||||
<form onSubmit={handleLinkSubmit} className={styles.loginCard} style={{ maxWidth: "400px" }}>
|
||||
<input
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.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",
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
placeholder="至少 8 位"
|
||||
required
|
||||
minLength={8}
|
||||
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",
|
||||
}}
|
||||
/>
|
||||
<input
|
||||
type="password"
|
||||
value={passwordConfirm}
|
||||
onChange={(e) => setPasswordConfirm(e.target.value)}
|
||||
placeholder="再次输入密码"
|
||||
required
|
||||
minLength={8}
|
||||
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 && (
|
||||
<p style={{ fontSize: "0.85rem", color: "#ef4444", marginBottom: "0.75rem" }}>{error}</p>
|
||||
)}
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
className={`${styles.btnPrimary} ${styles.btnLarge}`}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{loading ? "处理中…" : "确认并关联"}
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={styles.section} style={{ paddingTop: "1.5rem" }}>
|
||||
<h1
|
||||
@@ -26,7 +193,7 @@ export function WelcomeBlock({ userName }: WelcomeBlockProps) {
|
||||
animationFillMode: "both",
|
||||
}}
|
||||
>
|
||||
欢迎回来,{displayName}
|
||||
欢迎回来,<Copyable text={displayName}>{displayName}</Copyable>
|
||||
</h1>
|
||||
<p
|
||||
className="animate__animated animate__fadeInDown"
|
||||
@@ -40,23 +207,22 @@ export function WelcomeBlock({ userName }: WelcomeBlockProps) {
|
||||
>
|
||||
以下是您的会员资产与推荐动作
|
||||
</p>
|
||||
<Link
|
||||
href="https://qun.hackrobot.cn"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.card}
|
||||
style={{
|
||||
display: "inline-flex",
|
||||
alignItems: "center",
|
||||
gap: "0.5rem",
|
||||
marginTop: "1rem",
|
||||
textDecoration: "none",
|
||||
color: "var(--accent)",
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
🌐 进入星球 qun.hackrobot.cn →
|
||||
</Link>
|
||||
<div className={styles.loginCard}>
|
||||
<div className={styles.loginCardTitle}>星球登录信息</div>
|
||||
<div className={styles.loginCardRow}>
|
||||
<span className={styles.loginCardLabel}>网址</span>
|
||||
<Copyable text="https://qun.hackrobot.cn">qun.hackrobot.cn</Copyable>
|
||||
<a href="https://qun.hackrobot.cn" target="_blank" rel="noopener noreferrer" className={styles.loginCardLink}>打开</a>
|
||||
</div>
|
||||
<div className={styles.loginCardRow}>
|
||||
<span className={styles.loginCardLabel}>账号</span>
|
||||
<Copyable text={displayName}><span className={styles.loginCardValue}>{displayName}</span></Copyable>
|
||||
</div>
|
||||
<div className={styles.loginCardRow}>
|
||||
<span className={styles.loginCardLabel}>密码</span>
|
||||
<Copyable text="12345678"><span className={styles.loginCardValue}>12345678</span></Copyable>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user