完善初始化更新
This commit is contained in:
696
sproutgate-frontend/src/components/UserPortal.jsx
Normal file
696
sproutgate-frontend/src/components/UserPortal.jsx
Normal file
@@ -0,0 +1,696 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
API_BASE,
|
||||
authClientFetchHeaders,
|
||||
buildAuthCallbackUrl,
|
||||
clearAuthClientContext,
|
||||
fetchClientVisitMeta,
|
||||
formatAuthBanMessage,
|
||||
persistAuthClientFromFlow
|
||||
} from "../config";
|
||||
import UserPortalAuthSection from "./userPortal/UserPortalAuthSection";
|
||||
import UserPortalProfileSection from "./userPortal/UserPortalProfileSection";
|
||||
|
||||
export default function UserPortal({ onReady, authFlow, onPreviewImage }) {
|
||||
const [account, setAccount] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [user, setUser] = useState(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
const [mode, setMode] = useState("login");
|
||||
|
||||
const [registerForm, setRegisterForm] = useState({
|
||||
account: "", password: "", username: "", email: "", code: "", inviteCode: ""
|
||||
});
|
||||
const [registrationRequireInvite, setRegistrationRequireInvite] = useState(false);
|
||||
const [registerSent, setRegisterSent] = useState(false);
|
||||
const [registerExpiresAt, setRegisterExpiresAt] = useState("");
|
||||
const [registerLoading, setRegisterLoading] = useState(false);
|
||||
const [registerError, setRegisterError] = useState("");
|
||||
const [registerMessage, setRegisterMessage] = useState("");
|
||||
|
||||
const [resetForm, setResetForm] = useState({
|
||||
account: "", email: "", code: "", newPassword: ""
|
||||
});
|
||||
const [resetSent, setResetSent] = useState(false);
|
||||
const [resetExpiresAt, setResetExpiresAt] = useState("");
|
||||
const [resetLoading, setResetLoading] = useState(false);
|
||||
const [resetError, setResetError] = useState("");
|
||||
const [resetMessage, setResetMessage] = useState("");
|
||||
|
||||
const [secondaryForm, setSecondaryForm] = useState({ email: "", code: "" });
|
||||
const [secondarySent, setSecondarySent] = useState(false);
|
||||
const [secondaryExpiresAt, setSecondaryExpiresAt] = useState("");
|
||||
const [secondaryLoading, setSecondaryLoading] = useState(false);
|
||||
const [secondaryError, setSecondaryError] = useState("");
|
||||
const [secondaryMessage, setSecondaryMessage] = useState("");
|
||||
|
||||
const [profileForm, setProfileForm] = useState({
|
||||
username: "", phone: "", avatarUrl: "", websiteUrl: "", bio: "", password: ""
|
||||
});
|
||||
const [profileLoading, setProfileLoading] = useState(false);
|
||||
const [profileError, setProfileError] = useState("");
|
||||
const [profileMessage, setProfileMessage] = useState("");
|
||||
const [profileEditorOpen, setProfileEditorOpen] = useState(false);
|
||||
const [profileEditorFocus, setProfileEditorFocus] = useState("");
|
||||
const [secondaryEditorOpen, setSecondaryEditorOpen] = useState(false);
|
||||
|
||||
const [checkInReward, setCheckInReward] = useState(1);
|
||||
const [checkInToday, setCheckInToday] = useState(false);
|
||||
const [checkInLoading, setCheckInLoading] = useState(false);
|
||||
const [checkInError, setCheckInError] = useState("");
|
||||
const [checkInMessage, setCheckInMessage] = useState("");
|
||||
|
||||
const isAuthFlow = Boolean(authFlow?.redirectUri);
|
||||
|
||||
const redirectToAuthCallback = (tokenValue, userData, expiresAt = "") => {
|
||||
if (!isAuthFlow || !authFlow?.redirectUri || !tokenValue) return;
|
||||
const callbackUrl = buildAuthCallbackUrl(authFlow.redirectUri, {
|
||||
token: tokenValue,
|
||||
account: userData?.account || "",
|
||||
username: userData?.username || "",
|
||||
expiresAt,
|
||||
state: authFlow.state || ""
|
||||
});
|
||||
window.location.replace(callbackUrl);
|
||||
};
|
||||
|
||||
const clearTokenAndUser = () => {
|
||||
localStorage.removeItem("sproutgate_token");
|
||||
localStorage.removeItem("sproutgate_token_expires_at");
|
||||
setUser(null);
|
||||
};
|
||||
|
||||
const bearerHeaders = (tokenValue) => ({
|
||||
...authClientFetchHeaders(),
|
||||
Authorization: `Bearer ${tokenValue}`
|
||||
});
|
||||
|
||||
const syncCurrentUser = async (tokenValue) => {
|
||||
const baseHeaders = bearerHeaders(tokenValue);
|
||||
const res = await fetch(`${API_BASE}/api/auth/me`, { headers: baseHeaders });
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
if (res.status === 403) {
|
||||
clearTokenAndUser();
|
||||
const msg = formatAuthBanMessage(data);
|
||||
setError(msg);
|
||||
throw new Error(msg);
|
||||
}
|
||||
throw new Error(data.error || "加载用户失败");
|
||||
}
|
||||
if (data.user) setUser(data.user);
|
||||
const checkIn = data.checkIn || {};
|
||||
setCheckInReward(Number(checkIn.rewardCoins) || 1);
|
||||
setCheckInToday(Boolean(checkIn.checkedInToday));
|
||||
|
||||
fetchClientVisitMeta()
|
||||
.then((meta) => {
|
||||
if (!meta.ip && !meta.displayLocation) return null;
|
||||
const h = { ...baseHeaders };
|
||||
if (meta.ip) h["X-Visit-Ip"] = meta.ip;
|
||||
if (meta.displayLocation) h["X-Visit-Location"] = meta.displayLocation;
|
||||
return fetch(`${API_BASE}/api/auth/me`, { headers: h });
|
||||
})
|
||||
.then(async (r) => {
|
||||
if (!r) return null;
|
||||
const d = await r.json().catch(() => ({}));
|
||||
if (r.status === 403) {
|
||||
clearTokenAndUser();
|
||||
setError(formatAuthBanMessage(d));
|
||||
return null;
|
||||
}
|
||||
return r.ok ? d : null;
|
||||
})
|
||||
.then((d) => {
|
||||
if (!d) return;
|
||||
if (d.user) setUser(d.user);
|
||||
const c = d.checkIn;
|
||||
if (c) {
|
||||
setCheckInReward(Number(c.rewardCoins) || 1);
|
||||
setCheckInToday(Boolean(c.checkedInToday));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const done = () => { if (!cancelled && onReady) onReady(); };
|
||||
const token = localStorage.getItem("sproutgate_token");
|
||||
if (token) {
|
||||
syncCurrentUser(token).catch(() => {}).finally(() => done());
|
||||
} else {
|
||||
done();
|
||||
}
|
||||
return () => { cancelled = true; };
|
||||
}, [onReady]);
|
||||
|
||||
useEffect(() => {
|
||||
persistAuthClientFromFlow(authFlow);
|
||||
}, [authFlow]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/public/registration-policy`);
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!cancelled && res.ok) {
|
||||
setRegistrationRequireInvite(Boolean(data.requireInviteCode));
|
||||
}
|
||||
} catch {
|
||||
/* 忽略,默认不要求邀请码 */
|
||||
}
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setProfileForm({
|
||||
username: user.username || "",
|
||||
phone: user.phone || "",
|
||||
avatarUrl: user.avatarUrl || "",
|
||||
websiteUrl: user.websiteUrl || "",
|
||||
bio: user.bio || "",
|
||||
password: ""
|
||||
});
|
||||
}
|
||||
}, [user]);
|
||||
|
||||
const handleLogin = async (event) => {
|
||||
event.preventDefault();
|
||||
setLoading(true);
|
||||
setError("");
|
||||
try {
|
||||
const loginPayload = { account, password };
|
||||
const cid = (authFlow?.clientId || "").trim();
|
||||
const cname = (authFlow?.clientName || "").trim();
|
||||
if (cid) {
|
||||
loginPayload.clientId = cid;
|
||||
if (cname) loginPayload.clientName = cname;
|
||||
} else {
|
||||
const h = authClientFetchHeaders();
|
||||
if (h["X-Auth-Client"]) {
|
||||
loginPayload.clientId = h["X-Auth-Client"];
|
||||
if (h["X-Auth-Client-Name"]) loginPayload.clientName = h["X-Auth-Client-Name"];
|
||||
}
|
||||
}
|
||||
const res = await fetch(`${API_BASE}/api/auth/login`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(loginPayload)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
res.status === 403 ? formatAuthBanMessage(data) : (data.error || "登录失败")
|
||||
);
|
||||
}
|
||||
localStorage.setItem("sproutgate_token", data.token);
|
||||
localStorage.setItem("sproutgate_token_expires_at", data.expiresAt || "");
|
||||
setUser(data.user);
|
||||
syncCurrentUser(data.token).catch(() => {});
|
||||
setAccount("");
|
||||
setPassword("");
|
||||
if (isAuthFlow) {
|
||||
redirectToAuthCallback(data.token, data.user, data.expiresAt || "");
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("sproutgate_token");
|
||||
localStorage.removeItem("sproutgate_token_expires_at");
|
||||
clearAuthClientContext();
|
||||
setUser(null);
|
||||
setProfileEditorOpen(false);
|
||||
setProfileEditorFocus("");
|
||||
setSecondaryEditorOpen(false);
|
||||
setCheckInReward(1);
|
||||
setCheckInToday(false);
|
||||
setCheckInError("");
|
||||
setCheckInMessage("");
|
||||
};
|
||||
|
||||
const handleContinueAuth = () => {
|
||||
const tokenValue = localStorage.getItem("sproutgate_token") || "";
|
||||
if (!tokenValue) {
|
||||
setError("当前没有可用的登录会话,请先重新登录");
|
||||
return;
|
||||
}
|
||||
redirectToAuthCallback(
|
||||
tokenValue, user,
|
||||
localStorage.getItem("sproutgate_token_expires_at") || ""
|
||||
);
|
||||
};
|
||||
|
||||
const handleSwitchAuthAccount = () => {
|
||||
handleLogout();
|
||||
setMode("login");
|
||||
setAccount("");
|
||||
setPassword("");
|
||||
setError("");
|
||||
};
|
||||
|
||||
const handleRegisterChange = (field, value) => {
|
||||
setRegisterForm((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const resetRegisterFlow = () => {
|
||||
setRegisterSent(false);
|
||||
setRegisterExpiresAt("");
|
||||
setRegisterForm({ account: "", password: "", username: "", email: "", code: "", inviteCode: "" });
|
||||
};
|
||||
|
||||
const handleSendCode = async (event) => {
|
||||
event.preventDefault();
|
||||
setRegisterLoading(true);
|
||||
setRegisterError("");
|
||||
setRegisterMessage("");
|
||||
if (!registerForm.account || !registerForm.password || !registerForm.email) {
|
||||
setRegisterError("请填写账户、密码和邮箱");
|
||||
setRegisterLoading(false);
|
||||
return;
|
||||
}
|
||||
if (registrationRequireInvite && !String(registerForm.inviteCode || "").trim()) {
|
||||
setRegisterError("请输入邀请码");
|
||||
setRegisterLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const payload = {
|
||||
account: registerForm.account,
|
||||
password: registerForm.password,
|
||||
username: registerForm.username,
|
||||
email: registerForm.email
|
||||
};
|
||||
const inv = String(registerForm.inviteCode || "").trim();
|
||||
if (inv) payload.inviteCode = inv;
|
||||
const res = await fetch(`${API_BASE}/api/auth/register`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || "发送验证码失败");
|
||||
setRegisterSent(true);
|
||||
setRegisterExpiresAt(data.expiresAt || "");
|
||||
setRegisterMessage("验证码已发送,请检查邮箱");
|
||||
} catch (err) {
|
||||
setRegisterError(err.message);
|
||||
} finally {
|
||||
setRegisterLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifyRegister = async (event) => {
|
||||
event.preventDefault();
|
||||
setRegisterLoading(true);
|
||||
setRegisterError("");
|
||||
setRegisterMessage("");
|
||||
if (!registerForm.account || !registerForm.code) {
|
||||
setRegisterError("请输入账户与验证码");
|
||||
setRegisterLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/auth/verify-email`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ account: registerForm.account, code: registerForm.code })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) throw new Error(data.error || "验证失败");
|
||||
setRegisterMessage("注册成功,请使用账号登录");
|
||||
setRegisterSent(false);
|
||||
setRegisterForm({ account: "", password: "", username: "", email: "", code: "", inviteCode: "" });
|
||||
setMode("login");
|
||||
} catch (err) {
|
||||
setRegisterError(err.message);
|
||||
} finally {
|
||||
setRegisterLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetChange = (field, value) => {
|
||||
setResetForm((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleSendReset = async (event) => {
|
||||
event.preventDefault();
|
||||
setResetLoading(true);
|
||||
setResetError("");
|
||||
setResetMessage("");
|
||||
if (!resetForm.account || !resetForm.email) {
|
||||
setResetError("请填写账户与邮箱");
|
||||
setResetLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/auth/forgot-password`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ account: resetForm.account, email: resetForm.email })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
res.status === 403 ? formatAuthBanMessage(data) : (data.error || "发送重置邮件失败")
|
||||
);
|
||||
}
|
||||
setResetSent(true);
|
||||
setResetExpiresAt(data.expiresAt || "");
|
||||
setResetMessage("重置验证码已发送,请检查邮箱");
|
||||
} catch (err) {
|
||||
setResetError(err.message);
|
||||
} finally {
|
||||
setResetLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleResetPassword = async (event) => {
|
||||
event.preventDefault();
|
||||
setResetLoading(true);
|
||||
setResetError("");
|
||||
setResetMessage("");
|
||||
if (!resetForm.account || !resetForm.code || !resetForm.newPassword) {
|
||||
setResetError("请填写账户、验证码与新密码");
|
||||
setResetLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/auth/reset-password`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
account: resetForm.account,
|
||||
code: resetForm.code,
|
||||
newPassword: resetForm.newPassword
|
||||
})
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
throw new Error(
|
||||
res.status === 403 ? formatAuthBanMessage(data) : (data.error || "重置失败")
|
||||
);
|
||||
}
|
||||
setResetMessage("密码已重置,请使用新密码登录");
|
||||
setResetSent(false);
|
||||
setResetForm({ account: "", email: "", code: "", newPassword: "" });
|
||||
setMode("login");
|
||||
} catch (err) {
|
||||
setResetError(err.message);
|
||||
} finally {
|
||||
setResetLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSecondaryChange = (field, value) => {
|
||||
setSecondaryForm((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleSendSecondary = async (event) => {
|
||||
event.preventDefault();
|
||||
setSecondaryLoading(true);
|
||||
setSecondaryError("");
|
||||
setSecondaryMessage("");
|
||||
const token = localStorage.getItem("sproutgate_token");
|
||||
if (!token) { setSecondaryError("请先登录"); setSecondaryLoading(false); return; }
|
||||
if (!secondaryForm.email) { setSecondaryError("请输入辅助邮箱"); setSecondaryLoading(false); return; }
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/auth/secondary-email/request`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...bearerHeaders(token) },
|
||||
body: JSON.stringify({ email: secondaryForm.email })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
if (res.status === 403) {
|
||||
clearTokenAndUser();
|
||||
setSecondaryError(formatAuthBanMessage(data));
|
||||
} else {
|
||||
throw new Error(data.error || "发送验证码失败");
|
||||
}
|
||||
return;
|
||||
}
|
||||
setSecondarySent(true);
|
||||
setSecondaryExpiresAt(data.expiresAt || "");
|
||||
setSecondaryMessage("验证码已发送,请检查邮箱");
|
||||
} catch (err) {
|
||||
setSecondaryError(err.message);
|
||||
} finally {
|
||||
setSecondaryLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleVerifySecondary = async (event) => {
|
||||
event.preventDefault();
|
||||
setSecondaryLoading(true);
|
||||
setSecondaryError("");
|
||||
setSecondaryMessage("");
|
||||
const token = localStorage.getItem("sproutgate_token");
|
||||
if (!token) { setSecondaryError("请先登录"); setSecondaryLoading(false); return; }
|
||||
if (!secondaryForm.email || !secondaryForm.code) {
|
||||
setSecondaryError("请输入邮箱与验证码");
|
||||
setSecondaryLoading(false);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/auth/secondary-email/verify`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", ...bearerHeaders(token) },
|
||||
body: JSON.stringify({ email: secondaryForm.email, code: secondaryForm.code })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
if (res.status === 403) {
|
||||
clearTokenAndUser();
|
||||
setSecondaryError(formatAuthBanMessage(data));
|
||||
} else {
|
||||
throw new Error(data.error || "验证失败");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.user) setUser(data.user);
|
||||
setSecondaryMessage("辅助邮箱验证成功");
|
||||
setSecondarySent(false);
|
||||
setSecondaryForm({ email: "", code: "" });
|
||||
setSecondaryEditorOpen(false);
|
||||
} catch (err) {
|
||||
setSecondaryError(err.message);
|
||||
} finally {
|
||||
setSecondaryLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleProfileChange = (field, value) => {
|
||||
setProfileForm((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const handleProfileSave = async (event) => {
|
||||
event.preventDefault();
|
||||
setProfileLoading(true);
|
||||
setProfileError("");
|
||||
setProfileMessage("");
|
||||
const token = localStorage.getItem("sproutgate_token");
|
||||
if (!token) { setProfileError("请先登录"); setProfileLoading(false); return; }
|
||||
const payload = {
|
||||
username: profileForm.username,
|
||||
phone: profileForm.phone,
|
||||
avatarUrl: profileForm.avatarUrl,
|
||||
websiteUrl: profileForm.websiteUrl,
|
||||
bio: profileForm.bio
|
||||
};
|
||||
if (profileForm.password) payload.password = profileForm.password;
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/auth/profile`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json", ...bearerHeaders(token) },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
if (res.status === 403) {
|
||||
clearTokenAndUser();
|
||||
setProfileError(formatAuthBanMessage(data));
|
||||
} else {
|
||||
throw new Error(data.error || "保存失败");
|
||||
}
|
||||
return;
|
||||
}
|
||||
setUser(data.user);
|
||||
setProfileMessage("保存成功");
|
||||
setProfileForm((prev) => ({ ...prev, password: "" }));
|
||||
setProfileEditorOpen(false);
|
||||
setProfileEditorFocus("");
|
||||
} catch (err) {
|
||||
setProfileError(err.message);
|
||||
} finally {
|
||||
setProfileLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const openProfileEditor = (field = "") => {
|
||||
setProfileError("");
|
||||
setProfileMessage("");
|
||||
setProfileEditorFocus(field);
|
||||
setProfileEditorOpen(true);
|
||||
};
|
||||
|
||||
const openSecondaryEditor = () => {
|
||||
setSecondaryError("");
|
||||
setSecondaryMessage("");
|
||||
setSecondarySent(false);
|
||||
setSecondaryForm({ email: "", code: "" });
|
||||
setSecondaryEditorOpen(true);
|
||||
};
|
||||
|
||||
const closeProfileEditor = () => {
|
||||
setProfileEditorOpen(false);
|
||||
setProfileEditorFocus("");
|
||||
};
|
||||
|
||||
const closeSecondaryEditor = () => {
|
||||
setSecondaryEditorOpen(false);
|
||||
setSecondarySent(false);
|
||||
setSecondaryForm({ email: "", code: "" });
|
||||
setSecondaryError("");
|
||||
setSecondaryMessage("");
|
||||
};
|
||||
|
||||
const profileFocusLabels = {
|
||||
phone: "手机号",
|
||||
avatarUrl: "头像",
|
||||
websiteUrl: "个人主页",
|
||||
bio: "简介",
|
||||
username: "用户名"
|
||||
};
|
||||
const profileModalTitle = profileEditorFocus && profileFocusLabels[profileEditorFocus]
|
||||
? `修改${profileFocusLabels[profileEditorFocus]}`
|
||||
: "修改资料";
|
||||
|
||||
const handleCheckIn = async () => {
|
||||
const token = localStorage.getItem("sproutgate_token");
|
||||
if (!token) { setCheckInError("请先登录"); return; }
|
||||
setCheckInLoading(true);
|
||||
setCheckInError("");
|
||||
setCheckInMessage("");
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/auth/check-in`, {
|
||||
method: "POST",
|
||||
headers: bearerHeaders(token)
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!res.ok) {
|
||||
if (res.status === 403) {
|
||||
clearTokenAndUser();
|
||||
setCheckInError(formatAuthBanMessage(data));
|
||||
} else {
|
||||
throw new Error(data.error || "签到失败");
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.user) setUser(data.user);
|
||||
const checkIn = data.checkIn || {};
|
||||
setCheckInReward(Number(checkIn.rewardCoins) || 1);
|
||||
setCheckInToday(Boolean(checkIn.checkedInToday));
|
||||
setCheckInMessage(
|
||||
data.message || (data.alreadyCheckedIn ? "今日已签到" : `签到成功,获得 ${data.awardedCoins ?? data.rewardCoins ?? 0} 萌芽币`)
|
||||
);
|
||||
} catch (err) {
|
||||
setCheckInError(err.message);
|
||||
} finally {
|
||||
setCheckInLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="panel">
|
||||
<UserPortalAuthSection
|
||||
isAuthFlow={isAuthFlow}
|
||||
user={user}
|
||||
onPreviewImage={onPreviewImage}
|
||||
handleContinueAuth={handleContinueAuth}
|
||||
handleSwitchAuthAccount={handleSwitchAuthAccount}
|
||||
mode={mode}
|
||||
setMode={setMode}
|
||||
account={account}
|
||||
setAccount={setAccount}
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
loading={loading}
|
||||
error={error}
|
||||
setError={setError}
|
||||
handleLogin={handleLogin}
|
||||
registerForm={registerForm}
|
||||
handleRegisterChange={handleRegisterChange}
|
||||
registerSent={registerSent}
|
||||
registerExpiresAt={registerExpiresAt}
|
||||
registerLoading={registerLoading}
|
||||
registerError={registerError}
|
||||
registerMessage={registerMessage}
|
||||
handleSendCode={handleSendCode}
|
||||
handleVerifyRegister={handleVerifyRegister}
|
||||
setRegisterError={setRegisterError}
|
||||
setRegisterMessage={setRegisterMessage}
|
||||
registrationRequireInvite={registrationRequireInvite}
|
||||
resetRegisterFlow={resetRegisterFlow}
|
||||
resetForm={resetForm}
|
||||
handleResetChange={handleResetChange}
|
||||
resetSent={resetSent}
|
||||
resetExpiresAt={resetExpiresAt}
|
||||
resetLoading={resetLoading}
|
||||
resetError={resetError}
|
||||
resetMessage={resetMessage}
|
||||
handleSendReset={handleSendReset}
|
||||
handleResetPassword={handleResetPassword}
|
||||
setResetError={setResetError}
|
||||
setResetMessage={setResetMessage}
|
||||
/>
|
||||
{user && (
|
||||
<UserPortalProfileSection
|
||||
user={user}
|
||||
onPreviewImage={onPreviewImage}
|
||||
handleLogout={handleLogout}
|
||||
openProfileEditor={openProfileEditor}
|
||||
openSecondaryEditor={openSecondaryEditor}
|
||||
profileEditorOpen={profileEditorOpen}
|
||||
closeProfileEditor={closeProfileEditor}
|
||||
handleProfileSave={handleProfileSave}
|
||||
profileForm={profileForm}
|
||||
handleProfileChange={handleProfileChange}
|
||||
profileLoading={profileLoading}
|
||||
profileError={profileError}
|
||||
profileMessage={profileMessage}
|
||||
profileModalTitle={profileModalTitle}
|
||||
secondaryEditorOpen={secondaryEditorOpen}
|
||||
closeSecondaryEditor={closeSecondaryEditor}
|
||||
handleSendSecondary={handleSendSecondary}
|
||||
handleVerifySecondary={handleVerifySecondary}
|
||||
secondaryForm={secondaryForm}
|
||||
handleSecondaryChange={handleSecondaryChange}
|
||||
secondarySent={secondarySent}
|
||||
secondaryLoading={secondaryLoading}
|
||||
secondaryError={secondaryError}
|
||||
secondaryMessage={secondaryMessage}
|
||||
secondaryExpiresAt={secondaryExpiresAt}
|
||||
checkInReward={checkInReward}
|
||||
checkInToday={checkInToday}
|
||||
checkInLoading={checkInLoading}
|
||||
checkInError={checkInError}
|
||||
checkInMessage={checkInMessage}
|
||||
handleCheckIn={handleCheckIn}
|
||||
/>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user