's'调试支付
This commit is contained in:
@@ -4,18 +4,7 @@ import { useState, useEffect } from "react";
|
||||
import { VideoCard } from "./VideoCard";
|
||||
import { useVideoProgress } from "./useVideoProgress";
|
||||
import AuthModal from "../../components/AuthModal";
|
||||
|
||||
function getStoredUser(): string | null {
|
||||
if (typeof window === "undefined") return null;
|
||||
try {
|
||||
const raw = localStorage.getItem("pb_user");
|
||||
if (!raw) return null;
|
||||
const user = JSON.parse(raw);
|
||||
return user?.email ?? null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
import { getStoredUserEmail, pbLogout } from "@/app/lib/pocketbase";
|
||||
|
||||
/* ─── data ─── */
|
||||
|
||||
@@ -166,12 +155,11 @@ export default function CoursePage() {
|
||||
const { markCompleted, isCompleted, isPartCompleted } = useVideoProgress();
|
||||
|
||||
useEffect(() => {
|
||||
setUserEmail(getStoredUser());
|
||||
setUserEmail(getStoredUserEmail());
|
||||
}, []);
|
||||
|
||||
const handleLogout = () => {
|
||||
localStorage.removeItem("pb_token");
|
||||
localStorage.removeItem("pb_user");
|
||||
pbLogout();
|
||||
setUserEmail(null);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState, useRef, useCallback } from "react";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import type { EbookData } from "../lib/ebook";
|
||||
import type { EbookData } from "@/app/lib/ebook";
|
||||
import "github-markdown-css/github-markdown.css";
|
||||
import styles from "./ebook.module.css";
|
||||
|
||||
|
||||
@@ -2,7 +2,15 @@
|
||||
|
||||
import { useState, useRef, useEffect, type FormEvent, type ChangeEvent } from "react";
|
||||
import { Link } from "@/i18n/navigation";
|
||||
import { getPayEnv, getRecommendedChannel, mustUseWxPay, type PayChannel, type PayEnv } from "../../lib/payEnv";
|
||||
import {
|
||||
getPayEnv,
|
||||
getRecommendedChannel,
|
||||
mustUseWxPay,
|
||||
redirectToPay,
|
||||
getDeviceFromEnv,
|
||||
} from "@/app/lib/payment";
|
||||
import { usePayStatusPoll } from "@/app/lib/payment/usePayStatusPoll";
|
||||
import type { PayChannel, PayEnv } from "@/app/lib/payment";
|
||||
|
||||
const genderOptions = ["男", "女"];
|
||||
const singleOptions = ["单身", "恋爱中", "已婚"];
|
||||
@@ -55,6 +63,9 @@ export default function JoinPage() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
// 微信 H5 支付完成后可能不跳转,后台静默轮询,检测到已支付则显示成功页
|
||||
usePayStatusPoll(() => setSubmitted(true));
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
const env = getPayEnv();
|
||||
@@ -86,33 +97,18 @@ export default function JoinPage() {
|
||||
}
|
||||
setSubmitting(false);
|
||||
setPayRedirecting(true);
|
||||
const returnUrl = `${window.location.origin}${window.location.pathname}?paid=1`;
|
||||
const payForm = document.createElement("form");
|
||||
payForm.method = "POST";
|
||||
payForm.action = "/api/pay";
|
||||
payForm.target = "_self";
|
||||
payForm.style.display = "none";
|
||||
// Z-Pay return_url 不支持带参数,使用 /join/paid 专用路径
|
||||
const returnUrl = `${window.location.origin}${window.location.pathname}/paid`;
|
||||
const env = getPayEnv();
|
||||
const channel = mustUseWxPay(env) ? "wxpay" : payChannel;
|
||||
const device = env === "pc" ? "pc" : "h5"; // PC 跳转 payurl,H5/微信 用 payurl/payurl2
|
||||
const fields: [string, string][] = [
|
||||
["user_id", userId],
|
||||
["total_fee", "80"],
|
||||
["type", "meetup"],
|
||||
["channel", channel],
|
||||
["device", device],
|
||||
["return_url", returnUrl],
|
||||
["html", "1"],
|
||||
];
|
||||
fields.forEach(([k, v]) => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "hidden";
|
||||
input.name = k;
|
||||
input.value = v;
|
||||
payForm.appendChild(input);
|
||||
const device = getDeviceFromEnv(env);
|
||||
redirectToPay({
|
||||
user_id: userId,
|
||||
return_url: returnUrl,
|
||||
channel,
|
||||
device,
|
||||
useJoinConfig: true,
|
||||
});
|
||||
document.body.appendChild(payForm);
|
||||
payForm.submit();
|
||||
return;
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "网络错误,请重试");
|
||||
|
||||
31
app/[locale]/join/paid/page.tsx
Normal file
31
app/[locale]/join/paid/page.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
"use client";
|
||||
|
||||
import { Link } from "@/i18n/navigation";
|
||||
|
||||
/**
|
||||
* 支付完成回跳页
|
||||
* Z-Pay 的 return_url 不支持带查询参数,故使用 /join/paid 作为专用成功页
|
||||
*/
|
||||
export default function JoinPaidPage() {
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center bg-[#f0f4f8] px-4">
|
||||
<div className="w-full max-w-md animate-fade-in rounded-2xl bg-white p-10 text-center shadow-lg">
|
||||
<div className="mx-auto mb-5 flex h-20 w-20 items-center justify-center rounded-full bg-emerald-50 text-5xl">
|
||||
✅
|
||||
</div>
|
||||
<h2 className="text-2xl font-bold text-slate-800">申请已提交</h2>
|
||||
<p className="mt-3 text-slate-500">
|
||||
我们会尽快审核你的信息
|
||||
<br />
|
||||
审核通过后将通过微信邀请你入群
|
||||
</p>
|
||||
<Link
|
||||
href="/"
|
||||
className="mt-8 inline-flex items-center gap-2 rounded-full bg-sky-500 px-8 py-3 font-semibold text-white transition-colors hover:bg-sky-600"
|
||||
>
|
||||
← 返回首页
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -2,14 +2,18 @@ import type { Metadata } from "next";
|
||||
import { notFound } from "next/navigation";
|
||||
import { LocaleProvider } from "../context/LocaleContext";
|
||||
import type { Locale } from "../context/LocaleContext";
|
||||
import { getThemeConfig } from "@/config";
|
||||
import { getThemeMessages } from "@/app/lib/theme-data";
|
||||
|
||||
const LOCALES: Locale[] = ["zh", "en"];
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "数字游民指南 | Digital Nomad Guide",
|
||||
description:
|
||||
"从零开始,7天开启你的数字游民生活。远程工作、地点自由、技能变现 —— 一站式数字游民资源平台。",
|
||||
};
|
||||
export const metadata: Metadata = (() => {
|
||||
const theme = getThemeConfig();
|
||||
return {
|
||||
title: theme.meta.title,
|
||||
description: theme.meta.description,
|
||||
};
|
||||
})();
|
||||
|
||||
export function generateStaticParams() {
|
||||
return LOCALES.map((locale) => ({ locale }));
|
||||
@@ -27,7 +31,7 @@ export default async function LocaleLayout({
|
||||
notFound();
|
||||
}
|
||||
|
||||
const messages = (await import(`../../messages/${locale}.json`)).default;
|
||||
const messages = await getThemeMessages(locale as Locale);
|
||||
|
||||
return (
|
||||
<LocaleProvider locale={locale as Locale} messages={messages}>
|
||||
|
||||
@@ -5,8 +5,11 @@ import Roadmap from "../components/Roadmap";
|
||||
import Tools from "../components/Tools";
|
||||
import Community from "../components/Community";
|
||||
import Footer from "../components/Footer";
|
||||
import { getThemeToolsData } from "../lib/theme-data";
|
||||
|
||||
export default async function HomePage() {
|
||||
const toolsData = await getThemeToolsData();
|
||||
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
@@ -14,7 +17,7 @@ export default function HomePage() {
|
||||
<Hero />
|
||||
<Features />
|
||||
<Roadmap />
|
||||
<Tools />
|
||||
<Tools data={toolsData} />
|
||||
<Community />
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
Reference in New Issue
Block a user