feat: add beta application review flow

This commit is contained in:
2026-06-08 15:23:13 +08:00
parent 33723d00f0
commit 3a1bc0241e
8 changed files with 949 additions and 5 deletions
+20
View File
@@ -64,6 +64,12 @@ function formatBalance(cents: number): string {
return `${value.toFixed(2)} 积分`;
}
function canReviewBetaApplications(session: WebUserSession | null): boolean {
const role = String(session?.user.role || "").trim().toLowerCase();
const username = String(session?.user.username || "").trim().toLowerCase();
return role === "admin" || username === "xqy1912";
}
function AppShell({
activeView,
navItems,
@@ -249,6 +255,7 @@ function AppShell({
const displayedBalanceLabel = session ? formatBalance(displayedBalanceCents) : "0 积分";
const showCommunityReview = canReviewCommunity(session);
const showCommunityCaseAdd = canManageCommunityCases(session);
const showBetaApplicationReview = canReviewBetaApplications(session);
return (
<div
@@ -486,6 +493,19 @@ function AppShell({
</button>
</>
) : null}
{showBetaApplicationReview ? (
<button
type="button"
className="profile-popover__review-btn"
onClick={() => {
setProfileOpen(false);
onSelectView("betaApplications");
}}
>
<ShellIcon name="check-circle" />
</button>
) : null}
{showCommunityCaseAdd ? (
<>
<button
+65 -5
View File
@@ -1,5 +1,6 @@
import { CloseOutlined, ExperimentOutlined } from "@ant-design/icons";
import { useState } from "react";
import { betaApplicationClient } from "../api/betaApplicationClient";
interface BetaApplicationModalProps {
open: boolean;
@@ -140,16 +141,70 @@ function TextField({
const BetaApplicationModal = ({ open, onClose }: BetaApplicationModalProps) => {
const [form, setForm] = useState<BetaFormData>(INITIAL_FORM);
const [submitting, setSubmitting] = useState(false);
const [message, setMessage] = useState<{ tone: "success" | "error"; text: string } | null>(null);
const update = <K extends keyof BetaFormData>(key: K, value: BetaFormData[K]) => {
setForm((prev) => ({ ...prev, [key]: value }));
setMessage(null);
};
const close = () => {
if (submitting) return;
onClose();
};
const validate = () => {
if (!form.name.trim()) return "请填写姓名 / 常用昵称";
if (!form.phone.trim()) return "请填写联系手机号码";
if (!form.wechat.trim()) return "请填写微信账号";
if (!form.selfStatement.trim()) return "请填写申请自述";
if (!form.signature.trim()) return "请填写申请人确认签字";
if (!form.agreeRules) return "请先阅读并同意内测规则";
return null;
};
const submit = async () => {
if (submitting) return;
const validationError = validate();
if (validationError) {
setMessage({ tone: "error", text: validationError });
return;
}
setSubmitting(true);
setMessage(null);
try {
await betaApplicationClient.submit({
...form,
name: form.name.trim(),
phone: form.phone.trim(),
wechat: form.wechat.trim(),
industry: form.industry.trim(),
company: form.company.trim(),
city: form.city.trim(),
aiTools: form.aiTools.trim(),
aiDuration: form.aiDuration.trim(),
aiTrack: form.aiTrack.trim(),
weeklyUsage: form.weeklyUsage.trim(),
feedbackWilling: form.feedbackWilling.trim(),
selfStatement: form.selfStatement.trim(),
signature: form.signature.trim(),
});
setForm(INITIAL_FORM);
setMessage({ tone: "success", text: "申请已提交,请留意站内通知。" });
} catch (error) {
setMessage({ tone: "error", text: error instanceof Error ? error.message : "提交内测申请失败" });
} finally {
setSubmitting(false);
}
};
if (!open) return null;
return (
<div className="beta-application-modal" role="dialog" aria-modal="true" aria-labelledby="beta-modal-title">
<button type="button" className="beta-application-modal__backdrop" onClick={onClose} aria-label="关闭内测申请弹窗" />
<button type="button" className="beta-application-modal__backdrop" onClick={close} aria-label="关闭内测申请弹窗" />
<section className="beta-application-modal__panel">
{/* ── Header ── */}
@@ -161,7 +216,7 @@ const BetaApplicationModal = ({ open, onClose }: BetaApplicationModalProps) => {
<p className="beta-modal-header__subtitle"> · <strong>30 </strong> · <strong>500 </strong> </p>
</div>
</div>
<button type="button" className="beta-modal-header__close" onClick={onClose} aria-label="关闭">
<button type="button" className="beta-modal-header__close" onClick={close} aria-label="关闭" disabled={submitting}>
<CloseOutlined />
</button>
</header>
@@ -268,11 +323,16 @@ const BetaApplicationModal = ({ open, onClose }: BetaApplicationModalProps) => {
{/* ── Footer ── */}
<footer className="beta-modal-footer">
<button type="button" className="beta-modal-footer__btn beta-modal-footer__btn--secondary" onClick={onClose}>
{message ? (
<p className={`beta-modal-footer__message beta-modal-footer__message--${message.tone}`} role="status">
{message.text}
</p>
) : null}
<button type="button" className="beta-modal-footer__btn beta-modal-footer__btn--secondary" onClick={close} disabled={submitting}>
</button>
<button type="button" className="beta-modal-footer__btn beta-modal-footer__btn--primary" onClick={onClose}>
<button type="button" className="beta-modal-footer__btn beta-modal-footer__btn--primary" onClick={() => void submit()} disabled={submitting}>
{submitting ? "提交中..." : "提交申请"}
</button>
</footer>
</section>