This commit is contained in:
eric
2026-03-18 02:01:48 -05:00
parent 742c293e14
commit bf10b5d7e4
11 changed files with 415 additions and 138 deletions

View File

@@ -5,7 +5,7 @@ import { usePathname } from "next/navigation";
import { useCompleteOrderPolling } from "@/app/hooks/useCompleteOrderPolling";
import Link from "next/link";
import ThemeToggle from "../components/ThemeToggle";
import { DIGITAL_NOMAD_WECHAT_QR } from "@/config/home.config";
import { DIGITAL_NOMAD_WECHAT_QR, getSessionsWithDates } from "@/config/home.config";
import {
redirectToPay,
redirectToPayH5,
@@ -16,6 +16,15 @@ import {
const ageRangeOptions = ["18-22", "23-27", "28-32", "33-37", "38+"];
/** 活动场次选项:周六/周日,用于 meetupdate 下拉 */
function getMeetupdateOptions(): { value: string; label: string; type: string }[] {
return getSessionsWithDates().map((s) => ({
value: `${s.date} ${s.time}`,
label: `${s.date} ${s.time} · ${s.title}`,
type: s.id,
}));
}
/** 微信号仅允许英文字母、数字、下划线、连字符 */
const WECHAT_REGEX = /^[a-zA-Z0-9_-]*$/;
@@ -137,6 +146,11 @@ export default function JoinPage() {
const [profession, setProfession] = useState("");
const [gender, setGender] = useState("");
const [ageRange, setAgeRange] = useState("");
const meetupdateOptions = getMeetupdateOptions();
const [meetupdate, setMeetupdate] = useState(() => {
const opts = getMeetupdateOptions();
return opts[0]?.value ?? "";
});
const [agreeRules, setAgreeRules] = useState(true);
const [urlError, setUrlError] = useState<string | null>(null);
const [profile, setProfile] = useState<{
@@ -319,6 +333,10 @@ export default function JoinPage() {
setError("请选择性别");
return;
}
if (!meetupdate) {
setError("请选择要参加的活动场次");
return;
}
submittingRef.current = true;
lastSubmitTime.current = now;
@@ -340,6 +358,8 @@ export default function JoinPage() {
const qualify = formGenderFemale && postCountOk;
const genderLabel = gender === "女" ? "女" : gender === "男" ? "男" : "无";
const submitXiaohongshuUrl = profile?.resolvedUrl || extractXiaohongshuUrl(xiaohongshuUrl) || "";
const selectedOption = meetupdateOptions.find((o) => o.value === meetupdate);
const sessionType = selectedOption?.type ?? "session-1";
const res = await fetch("/api/join", {
method: "POST",
@@ -375,7 +395,8 @@ export default function JoinPage() {
: "",
age_range: ageRange,
city: "深圳",
session: fromVolunteer ? "volunteer" : "digital-nomad",
session: sessionType,
meetupdate: meetupdate.trim(),
education: "",
graduation_year: "",
agree_rules: true,
@@ -435,6 +456,7 @@ export default function JoinPage() {
setProfile(null);
setValidating(false);
setSubmitted(false);
setMeetupdate(meetupdateOptions[0]?.value ?? "");
setSubmitting(false);
setQualify(false);
setError(null);
@@ -482,6 +504,12 @@ export default function JoinPage() {
const params = new URLSearchParams(window.location.search);
setFromPaid(params.get("paid") === "1");
setFromVolunteer(params.get("volunteer") === "1");
const sessionParam = params.get("session");
if (sessionParam && (sessionParam === "session-1" || sessionParam === "session-2")) {
const opts = getMeetupdateOptions();
const match = opts.find((o) => o.type === sessionParam);
if (match) setMeetupdate(match.value);
}
const pendingId = getPendingOrderId();
const stored = (localStorage.getItem(WECHAT_STORAGE_KEY) || "").trim();
@@ -941,6 +969,15 @@ export default function JoinPage() {
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">💼 <span className="text-red-500">*</span></label>
<input type="text" maxLength={140} placeholder="您的职业或专业" value={profession} onChange={(e) => setProfession(e.target.value)} required className="form-input" />
</div>
<div>
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">📅 <span className="text-red-500">*</span></label>
<select value={meetupdate} onChange={(e) => setMeetupdate(e.target.value)} required className={`form-input appearance-none ${meetupdate ? "text-stone-800 dark:text-stone-100" : "text-stone-400 dark:text-stone-500"}`}>
<option value=""></option>
{meetupdateOptions.map((o) => (
<option key={o.type} value={o.value}>{o.label}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">👤 <span className="text-red-500">*</span></label>
<select value={gender} onChange={(e) => setGender(e.target.value)} required className={`form-input appearance-none ${gender ? "text-stone-800 dark:text-stone-100" : "text-stone-400 dark:text-stone-500"}`}>