's'
This commit is contained in:
@@ -5,16 +5,7 @@ import Link from "next/link";
|
||||
import ThemeToggle from "../components/ThemeToggle";
|
||||
import { DIGITAL_NOMAD_WECHAT_QR } from "@/config/home.config";
|
||||
|
||||
/** 各学历对应的典型毕业年龄,用于推算年龄 */
|
||||
const EDUCATION_GRAD_AGE: Record<string, number> = {
|
||||
"高中及以下": 18,
|
||||
"大专": 21,
|
||||
"本科": 22,
|
||||
"硕士": 25,
|
||||
"博士": 28,
|
||||
};
|
||||
const educationOptions = ["高中及以下", "大专", "本科", "硕士", "博士"];
|
||||
const graduationYears = Array.from({ length: 41 }, (_, i) => String(2026 - i));
|
||||
const ageRangeOptions = ["18-22", "23-27", "28-32", "33-37", "38+"];
|
||||
|
||||
function getDisplayCount(text?: string, value?: number | string, suffix = ""): string | null {
|
||||
if (typeof text === "string" && text.trim()) {
|
||||
@@ -36,10 +27,8 @@ export default function JoinPage() {
|
||||
const [xiaohongshuUrl, setXiaohongshuUrl] = useState("");
|
||||
const [profession, setProfession] = useState("");
|
||||
const [wechat, setWechat] = useState("");
|
||||
const [phone, setPhone] = useState("");
|
||||
const [intro, setIntro] = useState("");
|
||||
const [education, setEducation] = useState("");
|
||||
const [graduationYear, setGraduationYear] = useState("");
|
||||
const [gender, setGender] = useState("");
|
||||
const [ageRange, setAgeRange] = useState("");
|
||||
const [agreeRules, setAgreeRules] = useState(false);
|
||||
const [urlError, setUrlError] = useState<string | null>(null);
|
||||
const [profile, setProfile] = useState<{
|
||||
@@ -129,28 +118,24 @@ export default function JoinPage() {
|
||||
e.preventDefault();
|
||||
setError(null);
|
||||
|
||||
if (!xiaohongshuUrl.trim()) {
|
||||
setError("请填写小红书主页链接");
|
||||
return;
|
||||
}
|
||||
if (urlError) {
|
||||
if (xiaohongshuUrl.trim() && urlError) {
|
||||
setError("请先修正小红书链接");
|
||||
return;
|
||||
}
|
||||
if (!education || !graduationYear) {
|
||||
setError("请选择学历和毕业时间");
|
||||
return;
|
||||
}
|
||||
if (!profession.trim()) {
|
||||
setError("请填写职业");
|
||||
return;
|
||||
}
|
||||
if (!wechat.trim() && !phone.trim()) {
|
||||
setError("请填写微信号或手机号");
|
||||
if (!wechat.trim()) {
|
||||
setError("请填写微信号");
|
||||
return;
|
||||
}
|
||||
if (!intro.trim()) {
|
||||
setError("请填写自我介绍");
|
||||
if (!gender) {
|
||||
setError("请选择性别");
|
||||
return;
|
||||
}
|
||||
if (!ageRange) {
|
||||
setError("请选择年龄区间");
|
||||
return;
|
||||
}
|
||||
if (!agreeRules) {
|
||||
@@ -160,29 +145,26 @@ export default function JoinPage() {
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const gradAge = EDUCATION_GRAD_AGE[education] ?? 22;
|
||||
const estimatedAge = new Date().getFullYear() - parseInt(graduationYear, 10) + gradAge;
|
||||
const ageOk = estimatedAge < 28;
|
||||
const isFemale = profile?.gender === "female";
|
||||
const genderLabel = getGenderLabel(profile?.gender);
|
||||
const postCountOk = (profile?.postCount ?? 0) > 5;
|
||||
const postCountLabel = getDisplayCount(profile?.postCountText, profile?.postCount) ?? "0";
|
||||
const submitXiaohongshuUrl = profile?.resolvedUrl || xiaohongshuUrl.trim();
|
||||
const formGenderFemale = gender === "女" || gender === "female";
|
||||
const xiaohongshuGenderFemale = profile?.gender === "female";
|
||||
const postCountOk = (profile?.postCount ?? 0) > 10;
|
||||
const ipInGuangdong = profile?.ipLocation?.includes("广东") ?? false;
|
||||
const qualify =
|
||||
formGenderFemale &&
|
||||
xiaohongshuGenderFemale &&
|
||||
postCountOk &&
|
||||
ipInGuangdong;
|
||||
|
||||
const reasonParts = [
|
||||
intro,
|
||||
`学历:${education}`,
|
||||
`毕业:${graduationYear}`,
|
||||
profile?.gender && `性别:${profile.gender}`,
|
||||
`发帖:${profile?.postCount ?? 0}`,
|
||||
].filter(Boolean);
|
||||
const genderLabel = gender === "女" ? "女" : gender === "男" ? "男" : "无";
|
||||
const postCountLabel = getDisplayCount(profile?.postCountText, profile?.postCount) ?? "0";
|
||||
const submitXiaohongshuUrl = profile?.resolvedUrl || xiaohongshuUrl.trim() || "";
|
||||
|
||||
const submitReasonParts = [
|
||||
reasonParts[0],
|
||||
`\u5b66\u5386:${education}`,
|
||||
`\u6bd5\u4e1a:${graduationYear}`,
|
||||
`\u6027\u522b:${genderLabel}`,
|
||||
`\u53d1\u5e16:${postCountLabel}`,
|
||||
`职业:${profession.trim()}`,
|
||||
`性别:${genderLabel}`,
|
||||
`年龄:${ageRange}`,
|
||||
profile && `发帖:${postCountLabel}`,
|
||||
profile?.ipLocation && `IP:${profile.ipLocation}`,
|
||||
].filter(Boolean);
|
||||
|
||||
const res = await fetch("/api/join", {
|
||||
@@ -191,16 +173,17 @@ export default function JoinPage() {
|
||||
body: JSON.stringify({
|
||||
nickname: profile?.nickname || "匿名",
|
||||
wechat: wechat.trim(),
|
||||
phone: phone.trim(),
|
||||
phone: "",
|
||||
profession: profession.trim(),
|
||||
xiaohongshu_url: submitXiaohongshuUrl,
|
||||
xiaohongshu_avatar_url: profile?.avatarUrl || "",
|
||||
gender: genderLabel,
|
||||
intro: submitReasonParts.join(" | "),
|
||||
age_range: ageRange,
|
||||
city: "深圳",
|
||||
session: "digital-nomad",
|
||||
education,
|
||||
graduation_year: graduationYear,
|
||||
education: "",
|
||||
graduation_year: "",
|
||||
agree_rules: true,
|
||||
first_time: "",
|
||||
status: "",
|
||||
@@ -216,7 +199,7 @@ export default function JoinPage() {
|
||||
throw new Error(data?.error || "提交失败,请稍后重试");
|
||||
}
|
||||
|
||||
setQualify(isFemale && ageOk && postCountOk);
|
||||
setQualify(qualify);
|
||||
setSubmitted(true);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "网络错误,请重试");
|
||||
@@ -229,7 +212,6 @@ export default function JoinPage() {
|
||||
const followersDisplay = getDisplayCount(profile?.followersText, profile?.followers);
|
||||
const likesDisplay = getDisplayCount(profile?.likesAndCollectsText, profile?.likesAndCollects);
|
||||
const safePostCountDisplay = getDisplayCount(profile?.postCountText, profile?.postCount, " \u7bc7");
|
||||
const postCountDisplay = getDisplayCount(profile?.postCountText, profile?.postCount, " 篇");
|
||||
|
||||
const profileGenderLabel = getGenderLabel(profile?.gender);
|
||||
|
||||
@@ -240,13 +222,13 @@ export default function JoinPage() {
|
||||
{qualify ? (
|
||||
<>
|
||||
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-emerald-50 dark:bg-emerald-900/30 text-4xl">
|
||||
✅
|
||||
🎉
|
||||
</div>
|
||||
<h2 className="text-xl sm:text-2xl font-bold text-stone-800 dark:text-stone-100">
|
||||
恭喜,符合条件
|
||||
审核通过
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">
|
||||
扫码添加微信,加入活动群
|
||||
扫码添加好友,加入活动群
|
||||
</p>
|
||||
<div className="mt-6 flex justify-center">
|
||||
<img
|
||||
@@ -256,7 +238,7 @@ export default function JoinPage() {
|
||||
/>
|
||||
</div>
|
||||
<p className="mt-4 text-xs text-stone-400 dark:text-stone-500">
|
||||
添加时请备注:活动报名
|
||||
📝 添加时请备注:活动报名
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
@@ -268,15 +250,19 @@ export default function JoinPage() {
|
||||
资料审核中
|
||||
</h2>
|
||||
<p className="mt-2 text-sm text-stone-500 dark:text-stone-400">
|
||||
我们会在 1-3 个工作日内审核您的资料,通过后将通过小红书私信联系您。
|
||||
⏰ 通常会在 24 小时内完成审核,通过后用微信联系你。
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<p className="mt-6 text-xs text-stone-400 dark:text-stone-500 leading-relaxed">
|
||||
💰 本场收取少量场地分摊费29/人,用于覆盖民宿/场地基础成本。<br />
|
||||
通过审核后再确认名额,未确认无需支付。
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="mt-6 sm:mt-8 inline-flex items-center gap-2 rounded-xl bg-amber-600 px-6 sm:px-8 py-2.5 sm:py-3 font-medium text-white text-sm sm:text-base transition-colors hover:bg-amber-700"
|
||||
>
|
||||
← 返回首页
|
||||
🏠 返回首页
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -306,8 +292,11 @@ export default function JoinPage() {
|
||||
<form onSubmit={handleSubmit} className="space-y-5">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">
|
||||
小红书主页链接 <span className="text-red-500">*</span>
|
||||
📱 小红书主页链接
|
||||
</label>
|
||||
<p className="text-xs text-stone-500 dark:text-stone-400 mb-1.5">
|
||||
💡 建议填写,可加快审核
|
||||
</p>
|
||||
<input
|
||||
type="url"
|
||||
value={xiaohongshuUrl}
|
||||
@@ -317,18 +306,17 @@ export default function JoinPage() {
|
||||
}}
|
||||
onBlur={handleUrlBlur}
|
||||
placeholder="https://www.xiaohongshu.com/user/profile/xxx"
|
||||
required
|
||||
className="form-input"
|
||||
/>
|
||||
{validating && (
|
||||
<p className="mt-1.5 text-xs text-amber-600 dark:text-amber-400">校验中…</p>
|
||||
<p className="mt-1.5 text-xs text-amber-600 dark:text-amber-400">🔄 校验中…</p>
|
||||
)}
|
||||
{urlError && !validating && (
|
||||
<p className="mt-1.5 text-xs text-red-600 dark:text-red-400">{urlError}</p>
|
||||
<p className="mt-1.5 text-xs text-red-600 dark:text-red-400">⚠️ {urlError}</p>
|
||||
)}
|
||||
{profile && !urlError && (
|
||||
<p className="mt-1.5 text-xs text-emerald-600 dark:text-emerald-400">
|
||||
{"\u2713 \u5df2\u83b7\u53d6\uff1a"}{profile.nickname}
|
||||
✅ 已获取:{profile.nickname}
|
||||
{profile.redbookId && ` \u00b7 @${profile.redbookId}`}
|
||||
{profile.ipLocation && ` \u00b7 ${profile.ipLocation}`}
|
||||
{profile.personalLink && ` \u00b7 ${profile.personalLink}`}
|
||||
@@ -339,24 +327,11 @@ export default function JoinPage() {
|
||||
{` \u00b7 ${profileGenderLabel}`}
|
||||
</p>
|
||||
)}
|
||||
{((profile) => false ? (
|
||||
<p className="mt-1.5 text-xs text-emerald-600 dark:text-emerald-400">
|
||||
✓ 已获取:{profile.nickname}
|
||||
{profile.redbookId && ` · @${profile.redbookId}`}
|
||||
{profile.ipLocation && ` · ${profile.ipLocation}`}
|
||||
{profile.personalLink && ` · ${profile.personalLink}`}
|
||||
{profile.following != null && ` · 关注 ${profile.following}`}
|
||||
{profile.followers != null && ` · 粉丝 ${profile.followers}`}
|
||||
{profile.likesAndCollects != null && ` · 获赞与收藏 ${profile.likesAndCollects}`}
|
||||
{profile.postCount != null && ` · 发帖 ${profile.postCount} 篇`}
|
||||
{profile.gender && ` · ${profile.gender === "female" ? "女" : "男"}`}
|
||||
</p>
|
||||
) : null)(profile as NonNullable<typeof profile>)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">
|
||||
职业 <span className="text-red-500">*</span>
|
||||
💼 职业 <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
@@ -369,94 +344,54 @@ export default function JoinPage() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">
|
||||
微信号
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
maxLength={140}
|
||||
placeholder="用于拉群"
|
||||
value={wechat}
|
||||
onChange={(e) => setWechat(e.target.value)}
|
||||
className="form-input"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">
|
||||
手机号
|
||||
</label>
|
||||
<input
|
||||
type="tel"
|
||||
maxLength={11}
|
||||
pattern="^\d{11}$"
|
||||
placeholder="请输入11位手机号"
|
||||
value={phone}
|
||||
onChange={(e) => setPhone(e.target.value.replace(/\D/g, "").slice(0, 11))}
|
||||
className="form-input"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-stone-500 dark:text-stone-400 -mt-2">
|
||||
微信号和手机号至少填写一项
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">
|
||||
自我介绍 <span className="text-red-500">*</span>
|
||||
💬 微信号 <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<textarea
|
||||
maxLength={500}
|
||||
rows={5}
|
||||
placeholder="分享您的经历、兴趣或加入原因,越详细越好哦~"
|
||||
value={intro}
|
||||
onChange={(e) => setIntro(e.target.value)}
|
||||
<input
|
||||
type="text"
|
||||
maxLength={140}
|
||||
placeholder="用于拉群"
|
||||
value={wechat}
|
||||
onChange={(e) => setWechat(e.target.value)}
|
||||
required
|
||||
className="form-input min-h-[120px] resize-none"
|
||||
className="form-input"
|
||||
/>
|
||||
<span className="text-xs text-stone-400 dark:text-stone-500">
|
||||
{intro.length}/500
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">
|
||||
学历 <span className="text-red-500">*</span>
|
||||
👤 性别 <span className="text-red-500">*</span>
|
||||
</label>
|
||||
<select
|
||||
value={education}
|
||||
onChange={(e) => setEducation(e.target.value)}
|
||||
value={gender}
|
||||
onChange={(e) => setGender(e.target.value)}
|
||||
required
|
||||
className={`form-input appearance-none ${education ? "text-stone-800 dark:text-stone-100" : "text-stone-400 dark:text-stone-500"}`}
|
||||
className={`form-input appearance-none ${gender ? "text-stone-800 dark:text-stone-100" : "text-stone-400 dark:text-stone-500"}`}
|
||||
>
|
||||
<option value="">请选择</option>
|
||||
{educationOptions.map((o) => (
|
||||
<option value="女">女</option>
|
||||
<option value="男">男</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={ageRange}
|
||||
onChange={(e) => setAgeRange(e.target.value)}
|
||||
required
|
||||
className={`form-input appearance-none ${ageRange ? "text-stone-800 dark:text-stone-100" : "text-stone-400 dark:text-stone-500"}`}
|
||||
>
|
||||
<option value="">请选择</option>
|
||||
{ageRangeOptions.map((o) => (
|
||||
<option key={o} value={o}>{o}</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={graduationYear}
|
||||
onChange={(e) => setGraduationYear(e.target.value)}
|
||||
required
|
||||
className={`form-input appearance-none ${graduationYear ? "text-stone-800 dark:text-stone-100" : "text-stone-400 dark:text-stone-500"}`}
|
||||
>
|
||||
<option value="">请选择</option>
|
||||
{graduationYears.map((y) => (
|
||||
<option key={y} value={y}>{y}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-stone-500 dark:text-stone-400 -mt-2">
|
||||
年龄将根据学历和毕业时间推算
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<label className="flex items-start gap-3 cursor-pointer">
|
||||
@@ -468,31 +403,31 @@ export default function JoinPage() {
|
||||
className="mt-1 rounded border-stone-300 text-amber-600 focus:ring-amber-500"
|
||||
/>
|
||||
<span className="text-xs sm:text-sm text-stone-600 dark:text-stone-400">
|
||||
我已阅读并同意社区规则
|
||||
📋 我已阅读并同意社区规则
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<p className="rounded-lg bg-red-50 dark:bg-red-900/30 px-4 py-3 text-sm text-red-600 dark:text-red-400">
|
||||
{error}
|
||||
<p className="rounded-lg bg-red-50 dark:bg-red-900/30 px-4 py-3 text-sm text-red-600 dark:text-red-400 flex items-center gap-2">
|
||||
⚠️ {error}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={submitting || !!urlError || validating}
|
||||
className="w-full rounded-xl bg-amber-600 py-3.5 text-base font-medium text-white transition-colors hover:bg-amber-700 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50"
|
||||
className="w-full rounded-xl bg-amber-600 py-3.5 text-base font-medium text-white transition-colors hover:bg-amber-700 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 flex items-center justify-center gap-2"
|
||||
>
|
||||
{submitting ? "提交中…" : "提交报名"}
|
||||
{submitting ? "🔄 提交中…" : "📤 提交报名"}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<Link href="/" className="text-xs sm:text-sm text-stone-400 dark:text-stone-500 hover:text-amber-600">
|
||||
← 返回首页
|
||||
<Link href="/" className="text-xs sm:text-sm text-stone-400 dark:text-stone-500 hover:text-amber-600 inline-flex items-center gap-1">
|
||||
🏠 返回首页
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user