's'
This commit is contained in:
@@ -13,29 +13,10 @@ const empty = {
|
|||||||
vip: false,
|
vip: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 按 wechatId 查询 solanRed 记录,优先 salonapi */
|
async function getPocketBaseJoinByWechat(wechatId: string) {
|
||||||
export async function GET(request: NextRequest) {
|
|
||||||
const wechatId = request.nextUrl.searchParams.get("wechat_id")?.trim();
|
|
||||||
if (!wechatId) {
|
|
||||||
return NextResponse.json(empty);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { status, data } = await getSalonApi(
|
|
||||||
request,
|
|
||||||
`/api/salon/join/by-wechat?wechat_id=${encodeURIComponent(wechatId)}`,
|
|
||||||
{ timeoutMs: 8000 }
|
|
||||||
);
|
|
||||||
if (status === 200 && data && typeof data === "object") {
|
|
||||||
return NextResponse.json(data);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// salonapi 不可用,回退到直连 PocketBase
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = await getAdminToken();
|
const token = await getAdminToken();
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return NextResponse.json(empty);
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pb = getPocketBaseConfig();
|
const pb = getPocketBaseConfig();
|
||||||
@@ -45,7 +26,7 @@ export async function GET(request: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), 8000);
|
const timeoutId = setTimeout(() => controller.abort(), 4000);
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${base}/api/collections/${pb.joinCollection}/records?filter=${filter}&sort=-created&perPage=1`,
|
`${base}/api/collections/${pb.joinCollection}/records?filter=${filter}&sort=-created&perPage=1`,
|
||||||
{
|
{
|
||||||
@@ -54,7 +35,7 @@ export async function GET(request: NextRequest) {
|
|||||||
signal: controller.signal,
|
signal: controller.signal,
|
||||||
}
|
}
|
||||||
).finally(() => clearTimeout(timeoutId));
|
).finally(() => clearTimeout(timeoutId));
|
||||||
if (!res.ok) return NextResponse.json(empty);
|
if (!res.ok) return null;
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
const items = Array.isArray(data?.items) ? data.items : [];
|
const items = Array.isArray(data?.items) ? data.items : [];
|
||||||
const rec = items[0] ?? null;
|
const rec = items[0] ?? null;
|
||||||
@@ -66,7 +47,7 @@ export async function GET(request: NextRequest) {
|
|||||||
const isRejected = !!rec?.is_rejected;
|
const isRejected = !!rec?.is_rejected;
|
||||||
const vip = !!rec?.vip;
|
const vip = !!rec?.vip;
|
||||||
|
|
||||||
return NextResponse.json({
|
return {
|
||||||
hasRecord,
|
hasRecord,
|
||||||
record: rec,
|
record: rec,
|
||||||
isComplete,
|
isComplete,
|
||||||
@@ -74,8 +55,36 @@ export async function GET(request: NextRequest) {
|
|||||||
isApproved,
|
isApproved,
|
||||||
isRejected,
|
isRejected,
|
||||||
vip,
|
vip,
|
||||||
});
|
};
|
||||||
} catch {
|
} catch {
|
||||||
return NextResponse.json(empty);
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 按 wechatId 查询 solanRed 记录。优先 salonapi(记录由其创建,线上部署时更可靠),失败时回退直连 PocketBase */
|
||||||
|
export async function GET(request: NextRequest) {
|
||||||
|
const wechatId = request.nextUrl.searchParams.get("wechat_id")?.trim();
|
||||||
|
if (!wechatId) {
|
||||||
|
return NextResponse.json(empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { status, data } = await getSalonApi(
|
||||||
|
request,
|
||||||
|
`/api/salon/join/by-wechat?wechat_id=${encodeURIComponent(wechatId)}`,
|
||||||
|
{ timeoutMs: 6000 }
|
||||||
|
);
|
||||||
|
if (status === 200 && data && typeof data === "object") {
|
||||||
|
return NextResponse.json(data);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
/* salonapi 不可用,回退直连 PocketBase */
|
||||||
|
}
|
||||||
|
|
||||||
|
const pbData = await getPocketBaseJoinByWechat(wechatId);
|
||||||
|
if (pbData) {
|
||||||
|
return NextResponse.json(pbData);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json(empty);
|
||||||
|
}
|
||||||
|
|||||||
@@ -77,6 +77,28 @@ interface UserRecord {
|
|||||||
vip: boolean;
|
vip: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EMPTY_USER_RECORD: UserRecord = {
|
||||||
|
hasRecord: false,
|
||||||
|
record: null,
|
||||||
|
isComplete: false,
|
||||||
|
isWechatFriend: false,
|
||||||
|
isApproved: false,
|
||||||
|
isRejected: false,
|
||||||
|
vip: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
function toUserRecord(data: Partial<UserRecord> & { record?: UserRecord["record"] }): UserRecord {
|
||||||
|
return {
|
||||||
|
hasRecord: !!data.hasRecord,
|
||||||
|
record: data.record ?? null,
|
||||||
|
isComplete: !!data.isComplete,
|
||||||
|
isWechatFriend: !!data.isWechatFriend,
|
||||||
|
isApproved: !!data.isApproved,
|
||||||
|
isRejected: !!data.isRejected,
|
||||||
|
vip: !!data.vip,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function UserProfileCard({ record, showVolunteerTag }: { record: UserRecord["record"]; showVolunteerTag?: boolean }) {
|
function UserProfileCard({ record, showVolunteerTag }: { record: UserRecord["record"]; showVolunteerTag?: boolean }) {
|
||||||
if (!record || (!record.nickname && !record.media && !record.wechatId)) return null;
|
if (!record || (!record.nickname && !record.media && !record.wechatId)) return null;
|
||||||
const isVolunteer = showVolunteerTag && record.is_volunteer;
|
const isVolunteer = showVolunteerTag && record.is_volunteer;
|
||||||
@@ -146,23 +168,20 @@ export default function JoinPage() {
|
|||||||
const SUBMIT_THROTTLE_MS = 3000;
|
const SUBMIT_THROTTLE_MS = 3000;
|
||||||
|
|
||||||
const fetchByWechat = async (w: string) => {
|
const fetchByWechat = async (w: string) => {
|
||||||
if (!w.trim()) return;
|
if (!w.trim()) return null;
|
||||||
setError(null);
|
setError(null);
|
||||||
setCheckingWechat(true);
|
setCheckingWechat(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/join/by-wechat?wechat_id=${encodeURIComponent(w.trim())}`);
|
const res = await fetch(`/api/join/by-wechat?wechat_id=${encodeURIComponent(w.trim())}`, {
|
||||||
const data = await res.json();
|
cache: "no-store",
|
||||||
setUserRecord({
|
|
||||||
hasRecord: !!data.hasRecord,
|
|
||||||
record: data.record ?? null,
|
|
||||||
isComplete: !!data.isComplete,
|
|
||||||
isWechatFriend: !!data.isWechatFriend,
|
|
||||||
isApproved: !!data.isApproved,
|
|
||||||
isRejected: !!data.isRejected,
|
|
||||||
vip: !!data.vip,
|
|
||||||
});
|
});
|
||||||
|
const data = await res.json();
|
||||||
|
const nextRecord = toUserRecord(data);
|
||||||
|
setUserRecord(nextRecord);
|
||||||
|
return nextRecord;
|
||||||
} catch {
|
} catch {
|
||||||
setUserRecord({ hasRecord: false, record: null, isComplete: false, isWechatFriend: false, isApproved: false, isRejected: false, vip: false });
|
setUserRecord(EMPTY_USER_RECORD);
|
||||||
|
return null;
|
||||||
} finally {
|
} finally {
|
||||||
setCheckingWechat(false);
|
setCheckingWechat(false);
|
||||||
}
|
}
|
||||||
@@ -331,13 +350,23 @@ export default function JoinPage() {
|
|||||||
setQualify(qualify);
|
setQualify(qualify);
|
||||||
setSubmitted(true);
|
setSubmitted(true);
|
||||||
try {
|
try {
|
||||||
await fetchByWechat(wechat.trim());
|
localStorage.setItem(WECHAT_STORAGE_KEY, wechat.trim());
|
||||||
} catch {
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
let result = await fetchByWechat(wechat.trim());
|
||||||
|
const maxRetries = 5;
|
||||||
|
const retryDelayMs = 1500;
|
||||||
|
for (let i = 0; i < maxRetries && !result?.hasRecord; i++) {
|
||||||
|
await new Promise((r) => setTimeout(r, retryDelayMs));
|
||||||
|
result = await fetchByWechat(wechat.trim());
|
||||||
|
}
|
||||||
|
if (!result?.hasRecord) {
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(WECHAT_STORAGE_KEY, wechat.trim());
|
window.location.replace("/join");
|
||||||
} catch {}
|
} catch {
|
||||||
window.location.replace("/join");
|
/* ignore */
|
||||||
return;
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : "网络错误,请重试");
|
setError(err instanceof Error ? err.message : "网络错误,请重试");
|
||||||
@@ -618,6 +647,12 @@ export default function JoinPage() {
|
|||||||
用于防止临时爽约
|
用于防止临时爽约
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{paying && (
|
||||||
|
<div className="mt-6 flex items-center justify-center gap-2 rounded-xl bg-amber-50 dark:bg-amber-900/20 py-3 px-4 text-amber-700 dark:text-amber-400 text-sm">
|
||||||
|
<span className="inline-block h-4 w-4 animate-spin rounded-full border-2 border-amber-500 border-t-transparent" />
|
||||||
|
正在跳转支付,请稍候…
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={handlePayNow}
|
onClick={handlePayNow}
|
||||||
@@ -648,7 +683,7 @@ export default function JoinPage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 微信号输入区:失焦时查询,查询中不展示其他元素
|
// 微信号输入区:失焦时查询,查询中不展示其他元素
|
||||||
const wechatOnlyView = !userRecord || checkingWechat;
|
const wechatOnlyView = !userRecord;
|
||||||
|
|
||||||
if (wechatOnlyView) {
|
if (wechatOnlyView) {
|
||||||
return (
|
return (
|
||||||
@@ -733,7 +768,7 @@ export default function JoinPage() {
|
|||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<form onSubmit={handleSubmit} className="space-y-5">
|
<form onSubmit={handleSubmit} action="javascript:void(0)" className="space-y-5">
|
||||||
<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>
|
<label className="block text-sm font-medium text-stone-700 dark:text-stone-300 mb-2">💬 微信号 <span className="text-red-500">*</span></label>
|
||||||
<input
|
<input
|
||||||
@@ -797,6 +832,9 @@ export default function JoinPage() {
|
|||||||
{error && (
|
{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>
|
<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>
|
||||||
)}
|
)}
|
||||||
|
{submitted && checkingWechat && (
|
||||||
|
<p className="text-sm text-amber-600 dark:text-amber-400">提交成功,正在刷新状态…</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">
|
<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">
|
||||||
{submitting ? "🔄 提交中…" : fromVolunteer ? "📤 提交志愿者申请" : "📤 提交报名"}
|
{submitting ? "🔄 提交中…" : fromVolunteer ? "📤 提交志愿者申请" : "📤 提交报名"}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user