's'
This commit is contained in:
253
app/page.tsx
253
app/page.tsx
@@ -1,21 +1,18 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import PocketBase from "pocketbase";
|
||||
import { getPayEnv, getDeviceFromEnv } from "@/app/lib/payEnv";
|
||||
import { getPaymentConfig } from "@/config/services.config";
|
||||
import { formatRestoreDateShort } from "@/config/promo.config";
|
||||
import { usePayStatusPoll } from "@/app/lib/usePayStatusPoll";
|
||||
import { ThemeToggle } from "@/app/components/ThemeToggle";
|
||||
import styles from "./home.module.css";
|
||||
import { VipLayout } from "@/app/vip/components/VipLayout";
|
||||
import { LandingPage } from "@/app/vip/components/LandingPage";
|
||||
import { DashboardPage } from "@/app/vip/components/DashboardPage";
|
||||
import styles from "@/app/vip/components/vip.module.css";
|
||||
|
||||
const PB_URL =
|
||||
process.env.NEXT_PUBLIC_POCKETBASE_URL || "https://pocketbase.hackrobot.cn";
|
||||
|
||||
/** 临时:预览支付成功后的界面,本地调试用,上线前改回 false */
|
||||
const FORCE_VIP_PREVIEW = false;
|
||||
|
||||
/** 微信内支付渠道:zpay | xorpay */
|
||||
const WECHAT_PAY_PROVIDER: "zpay" | "xorpay" = "xorpay";
|
||||
|
||||
@@ -154,33 +151,6 @@ function removeStorage(key: string): void {
|
||||
localStorage.removeItem(key);
|
||||
}
|
||||
|
||||
const FEATURES = [
|
||||
{
|
||||
icon: "🚀",
|
||||
title: "业务方向",
|
||||
desc: "无人直播、RPA 自动化、Web 开发,找到你的变现路径",
|
||||
},
|
||||
{
|
||||
icon: "⚙️",
|
||||
title: "技术杠杆",
|
||||
desc: "云手机、PVE 虚拟化、服务器集群,低成本放大收益",
|
||||
},
|
||||
{
|
||||
icon: "🌐",
|
||||
title: "海外平台",
|
||||
desc: "YouTube、TikTok、SaaS 运营实战,出海掘金",
|
||||
},
|
||||
];
|
||||
|
||||
const UNLOCK_ITEMS = [
|
||||
{ icon: "📖", title: "电子书", desc: "地理套利·自动化", href: "/ebook" },
|
||||
{ icon: "🎬", title: "视频教程", desc: "云手机·无人直播", href: "/cloudphone" },
|
||||
{ icon: "👥", title: "私密社群", desc: "抱团出海", href: null },
|
||||
{ icon: "🛒", title: "商店", desc: "设备自助", href: null },
|
||||
{ icon: "💬", title: "即时问答", desc: "技术咨询", href: "https://chatwoot.hackrobot.cn/livechat?user_id=hackrobot" },
|
||||
{ icon: "🎪", title: "线下沙龙", desc: "面交·对接", href: null },
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
const [paidType, setPaidType] = useState<string | null>(null);
|
||||
const [userName, setUserName] = useState<string | null>(null);
|
||||
@@ -218,29 +188,32 @@ export default function Home() {
|
||||
return userId;
|
||||
}, []);
|
||||
|
||||
const checkPaymentFromPB = useCallback(async (userId: string) => {
|
||||
try {
|
||||
const pb = new PocketBase(PB_URL);
|
||||
const records = await pb.collection("payments").getList(1, 1, {
|
||||
filter: `user_id = "${userId}"`,
|
||||
sort: "-created",
|
||||
});
|
||||
const checkPaymentFromPB = useCallback(
|
||||
async (userId: string): Promise<boolean> => {
|
||||
try {
|
||||
const pb = new PocketBase(PB_URL);
|
||||
const records = await pb.collection("payments").getList(1, 1, {
|
||||
filter: `user_id = "${userId}"`,
|
||||
sort: "-created",
|
||||
});
|
||||
|
||||
if (records.items.length > 0) {
|
||||
const latestType = records.items[0].type as string;
|
||||
const dbUserId = records.items[0].user_id as string;
|
||||
savePaidType(latestType);
|
||||
saveUserName(dbUserId);
|
||||
return latestType;
|
||||
} else {
|
||||
if (records.items.length > 0) {
|
||||
const latestType = records.items[0].type as string;
|
||||
const dbUserId = records.items[0].user_id as string;
|
||||
setStorage("userId", dbUserId);
|
||||
savePaidType(latestType);
|
||||
saveUserName(dbUserId);
|
||||
return latestType === "vip";
|
||||
}
|
||||
savePaidType(null);
|
||||
return null;
|
||||
return false;
|
||||
} catch (err) {
|
||||
console.error("查询 PocketBase 失败:", err);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("查询 PocketBase 失败:", err);
|
||||
return getStorage("paidType");
|
||||
}
|
||||
}, [savePaidType, saveUserName]);
|
||||
},
|
||||
[savePaidType, saveUserName]
|
||||
);
|
||||
|
||||
const clearAllStorage = useCallback(() => {
|
||||
try {
|
||||
@@ -253,19 +226,6 @@ export default function Home() {
|
||||
/* ignore */
|
||||
}
|
||||
});
|
||||
try {
|
||||
for (let i = localStorage.length - 1; i >= 0; i--) {
|
||||
const key = localStorage.key(i);
|
||||
if (key) localStorage.removeItem(key);
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
sessionStorage.clear();
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
setPaidType(null);
|
||||
setUserName(null);
|
||||
setLoading(false);
|
||||
@@ -282,7 +242,13 @@ export default function Home() {
|
||||
const env = getPayEnv();
|
||||
const device = getDeviceFromEnv(env);
|
||||
const totalFee = getPaymentConfig().defaultAmount ?? 8800;
|
||||
const base = { user_id: userId, return_url: returnUrl, total_fee: totalFee, type: "vip", channel: "wxpay" as const };
|
||||
const base = {
|
||||
user_id: userId,
|
||||
return_url: returnUrl,
|
||||
total_fee: totalFee,
|
||||
type: "vip",
|
||||
channel: "wxpay" as const,
|
||||
};
|
||||
if (device === "wechat" && WECHAT_PAY_PROVIDER === "xorpay") {
|
||||
payWechatXorpay(base);
|
||||
} else if (device === "h5") {
|
||||
@@ -293,13 +259,13 @@ export default function Home() {
|
||||
}
|
||||
}, [getOrCreateUserId]);
|
||||
|
||||
const handleCopyUsername = useCallback(() => {
|
||||
const name = userName || "";
|
||||
if (typeof navigator !== "undefined" && navigator.clipboard) {
|
||||
navigator.clipboard.writeText(name);
|
||||
alert("账号已复制");
|
||||
}
|
||||
}, [userName]);
|
||||
const handleLogin = useCallback(() => {
|
||||
document.getElementById("login")?.scrollIntoView({ behavior: "smooth" });
|
||||
}, []);
|
||||
|
||||
const handleVerifySuccess = useCallback(() => {
|
||||
window.location.reload();
|
||||
}, []);
|
||||
|
||||
const env = getPayEnv();
|
||||
const device = getDeviceFromEnv(env);
|
||||
@@ -348,7 +314,10 @@ export default function Home() {
|
||||
|
||||
const userId = getStorage("userId");
|
||||
if (userId) {
|
||||
await checkPaymentFromPB(userId);
|
||||
const found = await checkPaymentFromPB(userId);
|
||||
if (found) {
|
||||
setUserName(getStorage("userName"));
|
||||
}
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
@@ -379,6 +348,8 @@ export default function Home() {
|
||||
}
|
||||
}, [paidType]);
|
||||
|
||||
const isLoggedIn = paidType === "vip";
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className={styles.loading}>
|
||||
@@ -388,124 +359,18 @@ export default function Home() {
|
||||
);
|
||||
}
|
||||
|
||||
if (FORCE_VIP_PREVIEW || paidType === "vip") {
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<header className={styles.header}>
|
||||
<Link href="https://digital.hackrobot.cn/zh" className={styles.logo} target="_blank" rel="noopener">
|
||||
🌍 异度星球
|
||||
</Link>
|
||||
<nav className={styles.nav}>
|
||||
<ThemeToggle />
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main className={styles.main}>
|
||||
<section className={styles.vipSuccess}>
|
||||
<div className={`${styles.vipSuccessCard} animate__animated animate__zoomIn`} style={{ animationDelay: "0.1s", animationFillMode: "both" }}>
|
||||
<div className={styles.vipSuccessIcon}>✅</div>
|
||||
<h1>欢迎加入异度星球</h1>
|
||||
<p className={styles.vipSuccessDesc}>您已解锁全部 VIP 权益</p>
|
||||
<div className={styles.vipCode}>
|
||||
<div className={styles.vipCodeRow}>
|
||||
<span>星球地址</span>
|
||||
<code>qun.hackrobot.cn</code>
|
||||
</div>
|
||||
<div className={styles.vipCodeRow}>
|
||||
<span>账号</span>
|
||||
<code>{getStorage("userName") || (FORCE_VIP_PREVIEW ? "preview" : "")}</code>
|
||||
<button type="button" onClick={handleCopyUsername}>复制</button>
|
||||
</div>
|
||||
<div className={styles.vipCodeRow}>
|
||||
<span>密码</span>
|
||||
<code>123456</code>
|
||||
</div>
|
||||
<a
|
||||
href="https://qun.hackrobot.cn"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.vipCta}
|
||||
>
|
||||
进入星球 →
|
||||
</a>
|
||||
<p className={styles.vipTip}>打开网页后,请点击「左上角头像」打开侧面栏</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<header className={styles.header}>
|
||||
<Link href="https://digital.hackrobot.cn/zh" className={styles.logo} target="_blank" rel="noopener">
|
||||
🌍 异度星球
|
||||
</Link>
|
||||
<nav className={styles.nav}>
|
||||
<ThemeToggle />
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main className={styles.main}>
|
||||
<section className={styles.hero}>
|
||||
<p className={`${styles.heroBadge} animate__animated animate__fadeInDown`} style={{ animationDelay: "0.1s", animationFillMode: "both" }}>🎒 数字游民进阶社群 · 私密资源</p>
|
||||
<h1 className={`${styles.heroTitle} animate__animated animate__fadeInDown`} style={{ animationDelay: "0.2s", animationFillMode: "both" }}>
|
||||
异度星球
|
||||
</h1>
|
||||
<p className={`${styles.heroSubtitle} animate__animated animate__fadeInDown`} style={{ animationDelay: "0.3s", animationFillMode: "both" }}>
|
||||
解锁电子书、视频教程、即时问答 · 抱团出海,技术杠杆
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section className={`${styles.section} ${styles.sectionTightTop}`}>
|
||||
<div className={styles.features}>
|
||||
{FEATURES.map((f, i) => (
|
||||
<div key={i} className={`${styles.featureCard} animate__animated animate__fadeInUp`} style={{ animationDelay: `${0.3 + i * 0.1}s`, animationFillMode: "both" }}>
|
||||
<span className={styles.featureIcon}>{f.icon}</span>
|
||||
<h3>{f.title}</h3>
|
||||
<p>{f.desc}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={styles.section}>
|
||||
<h2 className="animate__animated animate__fadeInDown" style={{ animationDelay: "0.1s", animationFillMode: "both" }}>🔓 解锁 VIP</h2>
|
||||
<p className={`${styles.sectionDesc} animate__animated animate__fadeInUp`} style={{ animationDelay: "0.2s", animationFillMode: "both" }}>
|
||||
一次付费·全部解锁
|
||||
</p>
|
||||
<div className={styles.unlockGrid}>
|
||||
{UNLOCK_ITEMS.map((item, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className={`${styles.unlockCard} ${styles.unlockCardDisabled} animate__animated animate__fadeInUp`}
|
||||
style={{ animationDelay: `${0.25 + i * 0.06}s`, animationFillMode: "both" }}
|
||||
>
|
||||
<span className={styles.unlockIcon}>{item.icon}</span>
|
||||
<span className={styles.unlockTitle}>{item.title}</span>
|
||||
<span className={styles.unlockDesc}>{item.desc}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className={`${styles.section} ${styles.sectionCta}`}>
|
||||
<div className={`${styles.ctaCard} animate__animated animate__zoomIn`} style={{ animationDelay: "0.2s", animationFillMode: "both" }}>
|
||||
<h2>限时特惠 · {formatRestoreDateShort()}恢复原价</h2>
|
||||
<p>阶梯定价 · 数字交付 · 不支持退款</p>
|
||||
<p className={styles.ctaNote}>🚫 不适合躺赚、现金流紧张</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => { handlePay(); }}
|
||||
className={`${styles.btnPrimary} ${styles.btnLarge}`}
|
||||
>
|
||||
🔒 解锁 VIP
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
<VipLayout isLoggedIn={isLoggedIn} userName={userName}>
|
||||
{isLoggedIn ? (
|
||||
<DashboardPage userName={userName} />
|
||||
) : (
|
||||
<LandingPage
|
||||
onJoin={handlePay}
|
||||
onLogin={handleLogin}
|
||||
onVerify={checkPaymentFromPB}
|
||||
onVerifySuccess={handleVerifySuccess}
|
||||
/>
|
||||
)}
|
||||
</VipLayout>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user