This commit is contained in:
eric
2026-03-12 03:54:22 -05:00
parent 421f979e10
commit e7dde61e31
23 changed files with 876 additions and 125 deletions

View File

@@ -1,31 +1,20 @@
/**
* 课程支付状态 - localStorage来自 nomadlms
* 与 join 报名支付分离,课程解锁用此模块
* 课程解锁 - 校验登录态 + VIP/api/vip/check
* 与 join 报名支付分离,课程解锁用 VIP 状态
*/
const PAY_STORAGE_KEY = "lms-pay";
/** 是否已支付(课程已解锁) */
export function isPaid(): boolean {
if (typeof window === "undefined") return false;
return localStorage.getItem(PAY_STORAGE_KEY) === "ok";
}
/** 设置已支付(支付成功后调用) */
export function setPaid(): void {
if (typeof window === "undefined") return;
localStorage.setItem(PAY_STORAGE_KEY, "ok");
window.dispatchEvent(new CustomEvent("pay:updated"));
}
/** 前 2 章为试看,无需支付 */
export function isFreeTrial(moduleIndex: number, lessonIndex: number): boolean {
if (moduleIndex === 0 && lessonIndex < 2) return true;
return false;
}
/** 章节是否可观看 */
export function canWatchLesson(moduleIndex: number, lessonIndex: number): boolean {
/** 章节是否可观看(需传入 vip 状态,来自 useAuthAndVip 或 /api/vip/check */
export function canWatchLesson(
moduleIndex: number,
lessonIndex: number,
vip: boolean
): boolean {
if (isFreeTrial(moduleIndex, lessonIndex)) return true;
return isPaid();
return vip;
}

View File

@@ -1,19 +1,17 @@
/**
* 环境配置:区分本地调试与生产部署
* 生产https://api.hackrobot.cnpayjsapi
* 域名更换:设置 ROOT_DOMAIN如 nomadro.com可推导默认值
*/
const isDev = process.env.NODE_ENV === "development";
import { DEFAULT_PAYMENT_API_URL, DEFAULT_SITE_URL } from "@/config/domain.config";
/** 支付 API 地址payjsapi */
export const PAYMENT_API_URL =
process.env.PAYMENT_API_URL ||
(isDev ? "http://127.0.0.1:8700" : "https://api.hackrobot.cn");
process.env.PAYMENT_API_URL || DEFAULT_PAYMENT_API_URL;
/** 站点根地址(用于 return_url 等回跳,生产需配置) */
export const SITE_URL =
process.env.NEXT_PUBLIC_SITE_URL ||
(isDev ? "http://localhost:3001" : "https://hackrobot.cn");
process.env.NEXT_PUBLIC_SITE_URL || DEFAULT_SITE_URL;
/** 是否为本地开发 */
export const IS_DEV = isDev;
export const IS_DEV = process.env.NODE_ENV === "development";

View File

@@ -67,11 +67,16 @@ export function pbSaveAuth(token: string, record: AuthUser): void {
localStorage.setItem(PB_STORAGE_KEYS.user, JSON.stringify(record));
}
/** 登出 - 清除本地存储 */
export function pbLogout(): void {
/** 登出 - 清除本地存储和根域 Cookie */
export async function pbLogout(): Promise<void> {
if (typeof window === "undefined") return;
localStorage.removeItem(PB_STORAGE_KEYS.token);
localStorage.removeItem(PB_STORAGE_KEYS.user);
try {
await fetch("/api/auth/logout", { method: "POST" });
} catch {
/* ignore */
}
}
/** 获取当前存储的用户邮箱 */

45
app/lib/useAuthAndVip.ts Normal file
View File

@@ -0,0 +1,45 @@
"use client";
import { useState, useEffect, useCallback } from "react";
export interface AuthAndVipState {
user: { id: string; email: string } | null;
vip: boolean;
loading: boolean;
refetch: () => Promise<{ user: { id: string; email: string } | null; vip: boolean }>;
}
export function useAuthAndVip(): AuthAndVipState {
const [user, setUser] = useState<{ id: string; email: string } | null>(null);
const [vip, setVip] = useState(false);
const [loading, setLoading] = useState(true);
const refetch = useCallback(async (): Promise<{ user: typeof user; vip: boolean }> => {
setLoading(true);
try {
const [meRes, vipRes] = await Promise.all([
fetch("/api/auth/me", { credentials: "include" }),
fetch("/api/vip/check", { credentials: "include" }),
]);
const meData = await meRes.json();
const vipData = await vipRes.json();
const newUser = meData?.user ?? null;
const newVip = vipData?.vip === true;
setUser(newUser);
setVip(newVip);
return { user: newUser, vip: newVip };
} catch {
setUser(null);
setVip(false);
return { user: null, vip: false };
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
refetch();
}, [refetch]);
return { user, vip, loading, refetch };
}