350 lines
14 KiB
TypeScript
350 lines
14 KiB
TypeScript
import { CloseOutlined, ExperimentOutlined } from "@ant-design/icons";
|
||
import { useState } from "react";
|
||
import { betaApplicationClient } from "../api/betaApplicationClient";
|
||
|
||
interface BetaApplicationModalProps {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
}
|
||
|
||
/* ── Form state ── */
|
||
interface BetaFormData {
|
||
name: string;
|
||
email: string;
|
||
phone: string;
|
||
wechat: string;
|
||
industry: string;
|
||
company: string;
|
||
city: string;
|
||
aiTools: string;
|
||
aiDuration: string;
|
||
aiTrack: string;
|
||
aiDirection: string[];
|
||
weeklyUsage: string;
|
||
feedbackWilling: string;
|
||
wantFeature: string[];
|
||
selfStatement: string;
|
||
signature: string;
|
||
applicationDate: string;
|
||
agreeRules: boolean;
|
||
}
|
||
|
||
const INITIAL_FORM: BetaFormData = {
|
||
name: "",
|
||
email: "",
|
||
phone: "",
|
||
wechat: "",
|
||
industry: "",
|
||
company: "",
|
||
city: "",
|
||
aiTools: "",
|
||
aiDuration: "",
|
||
aiTrack: "",
|
||
aiDirection: [],
|
||
weeklyUsage: "",
|
||
feedbackWilling: "",
|
||
wantFeature: [],
|
||
selfStatement: "",
|
||
signature: "",
|
||
applicationDate: "",
|
||
agreeRules: false,
|
||
};
|
||
|
||
/* ── Option groups (from the docx) ── */
|
||
const AI_DURATION_OPTIONS = ["1年以内", "1-3年", "3-5年", "5年以上"];
|
||
const AI_TRACK_OPTIONS = ["是,长期承接相关业务", "业余创作", "新手学习"];
|
||
const AI_DIRECTION_OPTIONS = [
|
||
"AI短剧批量制作", "漫剧剧情生成", "自媒体短视频", "电商图文及视频素材",
|
||
"MCN商业内容", "企业宣传视频", "个人兴趣创作", "其他",
|
||
];
|
||
const WEEKLY_USAGE_OPTIONS = ["7次及以上", "1-3次", "空闲时间使用"];
|
||
const FEEDBACK_OPTIONS = ["全力配合深度反馈", "简单体验留言", "仅使用不反馈"];
|
||
const WANT_FEATURE_OPTIONS = [
|
||
"一站式短剧漫剧完整AIGC工作流", "电商素材自动化创作流程",
|
||
"多模态智能中枢全能创作", "批量自动化创作流程", "全新未公开AI创作玩法",
|
||
];
|
||
|
||
/* ── Helper: single-select radio group ── */
|
||
function RadioGroup({
|
||
name, options, value, onChange,
|
||
}: {
|
||
name: string;
|
||
options: string[];
|
||
value: string;
|
||
onChange: (v: string) => void;
|
||
}) {
|
||
return (
|
||
<div className="beta-radio-group">
|
||
{options.map((opt) => (
|
||
<label key={opt} className="beta-radio">
|
||
<input
|
||
type="radio"
|
||
name={name}
|
||
checked={value === opt}
|
||
onChange={() => onChange(opt)}
|
||
/>
|
||
<span>{opt}</span>
|
||
</label>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ── Helper: multi-select checkbox group ── */
|
||
function CheckboxGroup({
|
||
options, value, onChange,
|
||
}: {
|
||
options: string[];
|
||
value: string[];
|
||
onChange: (v: string[]) => void;
|
||
}) {
|
||
return (
|
||
<div className="beta-checkbox-group">
|
||
{options.map((opt) => (
|
||
<label key={opt} className="beta-checkbox">
|
||
<input
|
||
type="checkbox"
|
||
checked={value.includes(opt)}
|
||
onChange={() => {
|
||
if (value.includes(opt)) {
|
||
onChange(value.filter((item) => item !== opt));
|
||
} else {
|
||
onChange([...value, opt]);
|
||
}
|
||
}}
|
||
/>
|
||
<span>{opt}</span>
|
||
</label>
|
||
))}
|
||
</div>
|
||
);
|
||
}
|
||
|
||
/* ── Helper: text field ── */
|
||
function TextField({
|
||
label, value, onChange, placeholder,
|
||
}: {
|
||
label: string;
|
||
value: string;
|
||
onChange: (v: string) => void;
|
||
placeholder?: string;
|
||
}) {
|
||
return (
|
||
<div className="beta-text-field">
|
||
<span className="beta-text-field__label">{label}</span>
|
||
<input
|
||
type="text"
|
||
className="beta-text-field__input"
|
||
value={value}
|
||
onChange={(e) => onChange(e.target.value)}
|
||
placeholder={placeholder ?? "请填写"}
|
||
/>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
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 (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email.trim())) return "请填写用于接收内测码的有效邮箱";
|
||
if (!form.phone.trim()) return "请填写联系手机号码";
|
||
if (!form.wechat.trim()) return "请填写微信账号";
|
||
if (!form.selfStatement.trim()) return "请填写申请自述";
|
||
if (!form.signature.trim()) return "请填写申请人确认签字";
|
||
if (!form.applicationDate.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(),
|
||
email: form.email.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(),
|
||
applicationDate: form.applicationDate.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={close} aria-label="关闭内测申请弹窗" />
|
||
|
||
<section className="beta-application-modal__panel">
|
||
{/* ── Header ── */}
|
||
<header className="beta-modal-header">
|
||
<div className="beta-modal-header__left">
|
||
<ExperimentOutlined className="beta-modal-header__icon" />
|
||
<div>
|
||
<h2 id="beta-modal-title">OmniAI 内测体验官申请表</h2>
|
||
<p className="beta-modal-header__subtitle">封闭限量内测 · 仅限 <strong>30 人</strong> · 赠送 <strong>500 元等值 50,000 积分</strong></p>
|
||
</div>
|
||
</div>
|
||
<button type="button" className="beta-modal-header__close" onClick={close} aria-label="关闭" disabled={submitting}>
|
||
<CloseOutlined />
|
||
</button>
|
||
</header>
|
||
|
||
{/* ── Body (scrollable document) ── */}
|
||
<div className="beta-modal-body">
|
||
|
||
{/* 一、个人基础信息 */}
|
||
<section className="beta-doc-section">
|
||
<h3 className="beta-doc-section__title">一、个人基础信息</h3>
|
||
<div className="beta-doc-grid">
|
||
<TextField label="姓名 / 常用昵称" value={form.name} onChange={(v) => update("name", v)} />
|
||
<TextField label="接收内测码邮箱" value={form.email} onChange={(v) => update("email", v)} placeholder="审核通过后内测码将发送到此邮箱" />
|
||
<TextField label="联系手机号码" value={form.phone} onChange={(v) => update("phone", v)} />
|
||
<TextField label="微信账号" value={form.wechat} onChange={(v) => update("wechat", v)} />
|
||
<TextField label="所在行业 / 职业" value={form.industry} onChange={(v) => update("industry", v)} />
|
||
<TextField label="所属公司 / 机构" value={form.company} onChange={(v) => update("company", v)} />
|
||
<TextField label="所在城市" value={form.city} onChange={(v) => update("city", v)} />
|
||
</div>
|
||
</section>
|
||
|
||
{/* 二、AI从业与使用经历 */}
|
||
<section className="beta-doc-section">
|
||
<h3 className="beta-doc-section__title">二、AI 从业与使用经历</h3>
|
||
<div className="beta-doc-grid">
|
||
<TextField label="日常常用 AI 创作工具有哪些" value={form.aiTools} onChange={(v) => update("aiTools", v)} placeholder="例如:Midjourney / Stable Diffusion / ChatGPT 等" />
|
||
<div className="beta-form-group">
|
||
<span className="beta-form-group__label">AI 内容创作从业时长</span>
|
||
<RadioGroup name="aiDuration" options={AI_DURATION_OPTIONS} value={form.aiDuration} onChange={(v) => update("aiDuration", v)} />
|
||
</div>
|
||
<div className="beta-form-group">
|
||
<span className="beta-form-group__label">是否深耕 AI 短剧、漫剧、数字视频、电商赛道</span>
|
||
<RadioGroup name="aiTrack" options={AI_TRACK_OPTIONS} value={form.aiTrack} onChange={(v) => update("aiTrack", v)} />
|
||
</div>
|
||
<div className="beta-form-group beta-form-group--full">
|
||
<span className="beta-form-group__label">日常主要创作方向(可多选)</span>
|
||
<CheckboxGroup options={AI_DIRECTION_OPTIONS} value={form.aiDirection} onChange={(v) => update("aiDirection", v)} />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* 三、内测使用意向调研 */}
|
||
<section className="beta-doc-section">
|
||
<h3 className="beta-doc-section__title">三、内测使用意向调研</h3>
|
||
<div className="beta-doc-grid">
|
||
<div className="beta-form-group">
|
||
<span className="beta-form-group__label">每周可稳定登录使用内测平台次数</span>
|
||
<RadioGroup name="weeklyUsage" options={WEEKLY_USAGE_OPTIONS} value={form.weeklyUsage} onChange={(v) => update("weeklyUsage", v)} />
|
||
</div>
|
||
<div className="beta-form-group">
|
||
<span className="beta-form-group__label">是否愿意积极反馈产品 BUG、优化建议、功能需求</span>
|
||
<RadioGroup name="feedback" options={FEEDBACK_OPTIONS} value={form.feedbackWilling} onChange={(v) => update("feedbackWilling", v)} />
|
||
</div>
|
||
<div className="beta-form-group beta-form-group--full">
|
||
<span className="beta-form-group__label">本次最想体验 OmniAI 核心功能(可多选)</span>
|
||
<CheckboxGroup options={WANT_FEATURE_OPTIONS} value={form.wantFeature} onChange={(v) => update("wantFeature", v)} />
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
{/* 四、申请自述 */}
|
||
<section className="beta-doc-section">
|
||
<h3 className="beta-doc-section__title">四、申请自述 <em className="beta-required">(必填)</em></h3>
|
||
<p className="beta-doc-section__desc">请简述自身 AI 创作优势、业务需求,以及加入本次封闭内测的理由:</p>
|
||
<textarea
|
||
className="beta-textarea"
|
||
value={form.selfStatement}
|
||
onChange={(e) => update("selfStatement", e.target.value)}
|
||
placeholder="请在此填写您的申请自述(必填)…"
|
||
rows={6}
|
||
/>
|
||
</section>
|
||
|
||
{/* 五、内测规则知情同意书 */}
|
||
<section className="beta-doc-section">
|
||
<h3 className="beta-doc-section__title">五、内测规则知情同意书</h3>
|
||
<ol className="beta-rules-list">
|
||
<li>本次为封闭限量内测,仅限 <strong>30 人</strong>,按照资质匹配度 + 申请顺序筛选;</li>
|
||
<li>内测赠送 <strong>500 元等值 50,000 积分</strong>,仅限内测期间使用,不可提现、不可转让、不可兑换现金;</li>
|
||
<li>内测版本含未上线测试功能,存在功能不稳定、界面调整、参数优化等情况,申请人自愿理解包容;</li>
|
||
<li>严禁私自泄露内测专属工作流、内部功能、测试接口、未发布技术方案等内部资料;</li>
|
||
<li>审核通过后,官方将在 <strong>48 小时</strong> 内通过预留邮箱发放内测码、登录权限及免费积分;</li>
|
||
<li>正式版上线后,优质内测体验官可享受专属永久优惠权限与平台荣誉称号。</li>
|
||
</ol>
|
||
|
||
<label className="beta-agree-row">
|
||
<input
|
||
type="checkbox"
|
||
checked={form.agreeRules}
|
||
onChange={(e) => update("agreeRules", e.target.checked)}
|
||
/>
|
||
<span>本人已完整阅读并同意以上全部内测规则,自愿遵守内测所有管理要求。</span>
|
||
</label>
|
||
|
||
<div className="beta-doc-grid beta-doc-grid--two">
|
||
<TextField label="申请人确认签字" value={form.signature} onChange={(v) => update("signature", v)} placeholder="请签署姓名" />
|
||
<TextField label="申请填写日期" value={form.applicationDate} onChange={(v) => update("applicationDate", v)} placeholder="例如:2026年6月8日" />
|
||
</div>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
{/* ── Footer ── */}
|
||
<footer className="beta-modal-footer">
|
||
{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={() => void submit()} disabled={submitting}>
|
||
{submitting ? "提交中..." : "提交申请"}
|
||
</button>
|
||
</footer>
|
||
</section>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
export default BetaApplicationModal;
|