's''
This commit is contained in:
@@ -5,7 +5,7 @@ export default function Footer() {
|
||||
<footer className="border-t border-stone-200 dark:border-stone-800 mt-12 py-8">
|
||||
<div className="max-w-[900px] mx-auto px-5 sm:px-8 text-center text-sm text-stone-500 dark:text-stone-400">
|
||||
<Link
|
||||
href="/join"
|
||||
href="/join?volunteer=1"
|
||||
className="inline-flex items-center gap-1.5 mb-4 text-amber-600 dark:text-amber-400 hover:text-amber-700 dark:hover:text-amber-300 font-medium"
|
||||
>
|
||||
<span aria-hidden>🙋</span> 招募志愿者
|
||||
|
||||
@@ -10,12 +10,36 @@ import Footer from "./Footer";
|
||||
import Header from "./Header";
|
||||
import { getSessionsWithDates, HOSTS } from "@/config/home.config";
|
||||
|
||||
const WECHAT_STORAGE_KEY = "salon_join_wechat";
|
||||
|
||||
interface HomeContentProps {
|
||||
initialVolunteer: { nickname: string; avatar?: string; xiaohongshu_url?: string } | null;
|
||||
}
|
||||
|
||||
const VOLUNTEER_BIO = "签到、茶歇准备、拍照摄像。";
|
||||
|
||||
function getPendingOrderId(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
const cookie = document.cookie.split(";").find((c) => c.trim().startsWith("join_pay_order="));
|
||||
const raw = cookie?.split("=")[1]?.trim();
|
||||
const decoded = raw ? decodeURIComponent(raw) : "";
|
||||
const fromStorage = localStorage.getItem("join_pay_order") || "";
|
||||
const value = decoded || fromStorage;
|
||||
const orderId = value.split("|")[0]?.trim() || "";
|
||||
return orderId || null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function clearPendingOrder(): void {
|
||||
try {
|
||||
localStorage.removeItem("join_pay_order");
|
||||
document.cookie = "join_pay_order=; path=/; max-age=0";
|
||||
} catch {}
|
||||
}
|
||||
|
||||
export default function HomeContent({ initialVolunteer }: HomeContentProps) {
|
||||
const [volunteer, setVolunteer] = useState(initialVolunteer);
|
||||
|
||||
@@ -26,6 +50,55 @@ export default function HomeContent({ initialVolunteer }: HomeContentProps) {
|
||||
.catch(() => {});
|
||||
}, []);
|
||||
|
||||
// 支付完成后再次打开首页:后台完成订单并刷新用户信息(手机浏览器唤醒微信支付后,用户可能直接回首页,notify 可能未到,需轮询)
|
||||
useEffect(() => {
|
||||
const orderId = getPendingOrderId();
|
||||
if (!orderId) return;
|
||||
|
||||
let cancelled = false;
|
||||
const tryComplete = async (): Promise<boolean> => {
|
||||
const r = await fetch("/api/salon/complete-order", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ order_id: orderId }),
|
||||
credentials: "include",
|
||||
});
|
||||
const d = await r.json();
|
||||
if (!d?.ok) return false;
|
||||
if (d?.token && d?.record) {
|
||||
fetch("/api/auth/sync-session", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ token: d.token, record: d.record }),
|
||||
credentials: "include",
|
||||
}).catch(() => {});
|
||||
import("@/app/lib/pocketbase").then(({ pbSaveAuth }) => pbSaveAuth(d.token, d.record));
|
||||
if (d.record?.wechatId) {
|
||||
try {
|
||||
localStorage.setItem(WECHAT_STORAGE_KEY, d.record.wechatId);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
clearPendingOrder();
|
||||
window.dispatchEvent(new CustomEvent("auth:updated"));
|
||||
window.dispatchEvent(new CustomEvent("vip:updated"));
|
||||
return true;
|
||||
};
|
||||
|
||||
const run = async () => {
|
||||
const retryDelays = [1500, 2500, 3500, 4500, 5500];
|
||||
for (let i = 0; i <= retryDelays.length; i++) {
|
||||
if (cancelled) return;
|
||||
if (await tryComplete()) return;
|
||||
if (i < retryDelays.length && !cancelled) {
|
||||
await new Promise((r) => setTimeout(r, retryDelays[i]));
|
||||
}
|
||||
}
|
||||
};
|
||||
void run();
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-[#faf8f5] dark:bg-stone-950 pb-20 sm:pb-12">
|
||||
<Header />
|
||||
@@ -138,13 +211,18 @@ export default function HomeContent({ initialVolunteer }: HomeContentProps) {
|
||||
{HOSTS.map((host) => {
|
||||
const isHost2 = host.id === "host-2";
|
||||
const displayHost = isHost2 && volunteer
|
||||
? { ...host, name: volunteer.nickname, avatar: volunteer.avatar || host.avatar, bio: VOLUNTEER_BIO }
|
||||
? {
|
||||
...host,
|
||||
name: volunteer.nickname,
|
||||
avatar: volunteer.avatar || host.avatar,
|
||||
bio: VOLUNTEER_BIO,
|
||||
link: volunteer.xiaohongshu_url || host.link,
|
||||
}
|
||||
: host;
|
||||
return (
|
||||
<HostLink
|
||||
key={host.id}
|
||||
host={displayHost}
|
||||
noLink
|
||||
{...(isHost2 && !volunteer && {
|
||||
ctaText: "立即申请",
|
||||
showFreeBadge: true,
|
||||
|
||||
@@ -5,6 +5,8 @@ import { createPortal } from "react-dom";
|
||||
import {
|
||||
getPayEnv,
|
||||
redirectToPay,
|
||||
redirectToPayH5,
|
||||
payWechatXorpaySync,
|
||||
getDeviceFromEnv,
|
||||
} from "@/app/lib/payment";
|
||||
import type { PayChannel } from "@/app/lib/payment";
|
||||
@@ -70,15 +72,20 @@ export default function JoinModal({ isOpen, onClose, initialEmail = "", vipOnly
|
||||
return;
|
||||
}
|
||||
const returnUrl = typeof window !== "undefined" ? `${window.location.origin}/join/paid` : "/join/paid";
|
||||
const env = getPayEnv();
|
||||
const channel: PayChannel = "wxpay";
|
||||
redirectToPay({
|
||||
const device = getDeviceFromEnv(getPayEnv());
|
||||
const base = {
|
||||
user_id: data.user_id,
|
||||
return_url: returnUrl,
|
||||
channel,
|
||||
device: getDeviceFromEnv(env),
|
||||
channel: "wxpay" as PayChannel,
|
||||
useJoinConfig: true,
|
||||
});
|
||||
};
|
||||
if (device === "wechat") {
|
||||
payWechatXorpaySync(base);
|
||||
} else if (device === "h5") {
|
||||
await redirectToPayH5(base);
|
||||
} else {
|
||||
redirectToPay({ ...base, device });
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "操作失败");
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user