diff --git a/app/[locale]/course/page.tsx b/app/[locale]/course/page.tsx index 16551d5..9b551b2 100644 --- a/app/[locale]/course/page.tsx +++ b/app/[locale]/course/page.tsx @@ -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); }; diff --git a/app/[locale]/ebook/EbookClient.tsx b/app/[locale]/ebook/EbookClient.tsx index d6119b7..6c22e16 100644 --- a/app/[locale]/ebook/EbookClient.tsx +++ b/app/[locale]/ebook/EbookClient.tsx @@ -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"; diff --git a/app/[locale]/join/page.tsx b/app/[locale]/join/page.tsx index b475ee6..d9fe8a6 100644 --- a/app/[locale]/join/page.tsx +++ b/app/[locale]/join/page.tsx @@ -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 : "网络错误,请重试"); diff --git a/app/[locale]/join/paid/page.tsx b/app/[locale]/join/paid/page.tsx new file mode 100644 index 0000000..4cab2ea --- /dev/null +++ b/app/[locale]/join/paid/page.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { Link } from "@/i18n/navigation"; + +/** + * 支付完成回跳页 + * Z-Pay 的 return_url 不支持带查询参数,故使用 /join/paid 作为专用成功页 + */ +export default function JoinPaidPage() { + return ( +
+
+
+ ✅ +
+

申请已提交

+

+ 我们会尽快审核你的信息 +
+ 审核通过后将通过微信邀请你入群 +

+ + ← 返回首页 + +
+
+ ); +} diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx index 42a44b8..6e28e81 100644 --- a/app/[locale]/layout.tsx +++ b/app/[locale]/layout.tsx @@ -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 ( diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 0cbb240..1610e14 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -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 ( <>
@@ -14,7 +17,7 @@ export default function HomePage() { - +