diff --git a/app/cloudphone/components/ChatFloatButton.tsx b/app/cloudphone/components/ChatFloatButton.tsx index 2f844e2..67cd238 100644 --- a/app/cloudphone/components/ChatFloatButton.tsx +++ b/app/cloudphone/components/ChatFloatButton.tsx @@ -1,13 +1,14 @@ "use client"; import { useState, useEffect } from "react"; -import Link from "next/link"; import { isPaid } from "../lib/payment"; +import { InstantQaVipModal } from "@/app/components/InstantQaVipModal"; const CHAT_URL = "https://chatwoot.hackrobot.cn/livechat?user_id=hackrobot"; export function ChatFloatButton() { const [paid, setPaid] = useState(false); + const [modalOpen, setModalOpen] = useState(false); useEffect(() => { setPaid(isPaid()); @@ -39,16 +40,20 @@ export function ChatFloatButton() { } return ( - - - - - + <> + + setModalOpen(false)} /> + ); } diff --git a/app/cloudphone/components/Footer.tsx b/app/cloudphone/components/Footer.tsx index d79a01d..b39f3d0 100644 --- a/app/cloudphone/components/Footer.tsx +++ b/app/cloudphone/components/Footer.tsx @@ -4,11 +4,13 @@ import { useState, useEffect } from "react"; import Link from "next/link"; import { SITE_CONFIG } from "../config/course"; import { isPaid } from "../lib/payment"; +import { InstantQaVipModal } from "@/app/components/InstantQaVipModal"; const CHAT_URL = "https://chatwoot.hackrobot.cn/livechat?user_id=hackrobot"; export function Footer() { const [paid, setPaid] = useState(false); + const [modalOpen, setModalOpen] = useState(false); useEffect(() => { setPaid(isPaid()); @@ -40,22 +42,23 @@ export function Footer() { 即时问答 ) : ( - setModalOpen(true)} className="inline-flex items-center gap-1.5 rounded-full bg-[var(--accent)]/15 px-4 py-2 text-sm font-medium text-[var(--accent)] transition hover:bg-[var(--accent)]/25 hover:text-[var(--accent)]" - title="开通VIP解锁即时问答" > 即时问答 - + )}
{SITE_CONFIG.footer}
+ setModalOpen(false)} /> ); } diff --git a/app/cloudphone/components/Header.tsx b/app/cloudphone/components/Header.tsx index 11f3efd..071335b 100644 --- a/app/cloudphone/components/Header.tsx +++ b/app/cloudphone/components/Header.tsx @@ -6,9 +6,11 @@ import { SITE_CONFIG } from "../config/course"; import { isPaid } from "../lib/payment"; import { triggerPay } from "@/app/lib/triggerPay"; import { ThemeToggle } from "@/app/components/ThemeToggle"; +import { InstantQaVipModal } from "@/app/components/InstantQaVipModal"; export function Header() { const [paid, setPaid] = useState(false); + const [qaModalOpen, setQaModalOpen] = useState(false); useEffect(() => { setPaid(isPaid()); const onPayUpdate = () => setPaid(isPaid()); @@ -17,6 +19,7 @@ export function Header() { }, []); return ( + <>
@@ -37,16 +40,16 @@ export function Header() { 即时问答 ) : ( - setQaModalOpen(true)} className="hidden items-center gap-1.5 rounded-full border border-[var(--accent)]/40 bg-[var(--accent)]/10 px-3 py-1.5 text-xs font-medium text-[var(--accent)] transition hover:bg-[var(--accent)]/20 sm:inline-flex sm:px-4 sm:py-2 sm:text-sm" - title="开通VIP解锁即时问答" > 即时问答 - + )} 首页 @@ -92,5 +95,7 @@ export function Header() {
+ setQaModalOpen(false)} /> + ); } diff --git a/app/cloudphone/components/sections/CurriculumSection.tsx b/app/cloudphone/components/sections/CurriculumSection.tsx index 9aedf54..4a38c5e 100644 --- a/app/cloudphone/components/sections/CurriculumSection.tsx +++ b/app/cloudphone/components/sections/CurriculumSection.tsx @@ -78,7 +78,7 @@ export function CurriculumSection() { {isOpen && (
    {c.lessons.map((l) => { - const free = l.moduleIndex === 0 && l.lessonIndex < 2; + const free = false; /* 暂时全部加锁 */ const unlocked = canWatchLesson(l.moduleIndex, l.lessonIndex); const key = `${l.moduleIndex}-${l.lessonIndex}`; const bookmarked = isBookmarked(l.moduleIndex, l.lessonIndex); diff --git a/app/cloudphone/course/[moduleIndex]/[lessonIndex]/page.tsx b/app/cloudphone/course/[moduleIndex]/[lessonIndex]/page.tsx index 9805a88..95e8328 100644 --- a/app/cloudphone/course/[moduleIndex]/[lessonIndex]/page.tsx +++ b/app/cloudphone/course/[moduleIndex]/[lessonIndex]/page.tsx @@ -24,7 +24,7 @@ export default function LessonPage() { const lesson = getLesson(moduleIndex, lessonIndex); const unlocked = canWatchLesson(moduleIndex, lessonIndex); - const isFreeTrial = moduleIndex === 0 && lessonIndex < 2; + const isFreeTrial = false; /* 暂时全部加锁,仅 VIP 可看 */ const [completed, setCompleted] = useState(() => getCompletedLessons().size); const { prev: prevLesson, next: nextLesson } = getPrevNextLesson( CURRICULUM_CONFIG.modules, diff --git a/app/cloudphone/lib/payment.ts b/app/cloudphone/lib/payment.ts index 84355e0..b3b4b51 100644 --- a/app/cloudphone/lib/payment.ts +++ b/app/cloudphone/lib/payment.ts @@ -10,9 +10,8 @@ export function isPaid(): boolean { return localStorage.getItem("paidType") === "vip"; } -/** 前 2 节为试看,无需支付 */ -export function isFreeTrial(moduleIndex: number, lessonIndex: number): boolean { - if (moduleIndex === 0 && lessonIndex < 2) return true; +/** 暂时全部加锁,仅 VIP 可看(原前 2 节试看已关闭) */ +export function isFreeTrial(_moduleIndex: number, _lessonIndex: number): boolean { return false; } diff --git a/app/components/InstantQaVipModal.tsx b/app/components/InstantQaVipModal.tsx new file mode 100644 index 0000000..64b6ff2 --- /dev/null +++ b/app/components/InstantQaVipModal.tsx @@ -0,0 +1,80 @@ +"use client"; + +import { useEffect } from "react"; +import { triggerPay } from "@/app/lib/triggerPay"; +import styles from "@/app/vip/components/vip.module.css"; + +type InstantQaVipModalProps = { + open: boolean; + onClose: () => void; +}; + +export function InstantQaVipModal({ open, onClose }: InstantQaVipModalProps) { + useEffect(() => { + if (open) document.body.style.overflow = "hidden"; + else document.body.style.overflow = ""; + return () => { document.body.style.overflow = ""; }; + }, [open]); + + useEffect(() => { + if (!open) return; + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }, [open, onClose]); + + if (!open) return null; + + const handleUnlock = () => { + onClose(); + triggerPay(); + }; + + return ( +
    +
    +
    e.stopPropagation()} + > +
    + + 💬 + +

    + 即时问答 +

    +
    +

    + 即时问答是会员专享的贴心服务,开通后即可获得一对一技术咨询支持,随时解答你的疑问~ +

    +
    + + +
    +
    +
    + ); +} diff --git a/app/topic/components/TopicCurriculum.tsx b/app/topic/components/TopicCurriculum.tsx index 864aee7..f280e6d 100644 --- a/app/topic/components/TopicCurriculum.tsx +++ b/app/topic/components/TopicCurriculum.tsx @@ -87,7 +87,7 @@ export function TopicCurriculum({ topicId }: TopicCurriculumProps) {
      {module.lessons.map((lesson, li) => { const origMod = module.originalModuleIndex; - const free = origMod === 0 && li < 2; + const free = false; /* 暂时全部加锁 */ const unlocked = canWatchLesson(origMod, li); const key = `${origMod}-${li}`; const bookmarked = isBookmarked(origMod, li); diff --git a/app/topic/components/TopicFooter.tsx b/app/topic/components/TopicFooter.tsx index ce77086..7171b8e 100644 --- a/app/topic/components/TopicFooter.tsx +++ b/app/topic/components/TopicFooter.tsx @@ -4,6 +4,7 @@ import { useState, useEffect } from "react"; import Link from "next/link"; import { getTopicConfig } from "../config"; import { isPaid } from "@/app/cloudphone/lib/payment"; +import { InstantQaVipModal } from "@/app/components/InstantQaVipModal"; type TopicFooterProps = { topicId: string; @@ -14,6 +15,7 @@ const CHAT_URL = "https://chatwoot.hackrobot.cn/livechat?user_id=hackrobot"; export function TopicFooter({ topicId }: TopicFooterProps) { const config = getTopicConfig(topicId); const [paid, setPaid] = useState(false); + const [modalOpen, setModalOpen] = useState(false); useEffect(() => { setPaid(isPaid()); @@ -48,22 +50,23 @@ export function TopicFooter({ topicId }: TopicFooterProps) { 即时问答 ) : ( - setModalOpen(true)} className="inline-flex items-center gap-1.5 rounded-full bg-[var(--accent)]/15 px-4 py-2 text-sm font-medium text-[var(--accent)] transition hover:bg-[var(--accent)]/25" - title="开通VIP解锁即时问答" > 即时问答 - + )}
    {config ? `${config.icon} ${config.title} · 异度星球` : "异度星球 · 数字游民社群"}
    + setModalOpen(false)} /> ); } diff --git a/app/topic/components/TopicHeader.tsx b/app/topic/components/TopicHeader.tsx index 9b07bed..c93add8 100644 --- a/app/topic/components/TopicHeader.tsx +++ b/app/topic/components/TopicHeader.tsx @@ -6,6 +6,7 @@ import { ThemeToggle } from "@/app/components/ThemeToggle"; import { getTopicConfig } from "../config"; import { isPaid } from "@/app/cloudphone/lib/payment"; import { triggerPay } from "@/app/lib/triggerPay"; +import { InstantQaVipModal } from "@/app/components/InstantQaVipModal"; type TopicHeaderProps = { topicId: string; @@ -13,6 +14,7 @@ type TopicHeaderProps = { export function TopicHeader({ topicId }: TopicHeaderProps) { const [paid, setPaid] = useState(false); + const [qaModalOpen, setQaModalOpen] = useState(false); const config = getTopicConfig(topicId); useEffect(() => { @@ -25,6 +27,7 @@ export function TopicHeader({ topicId }: TopicHeaderProps) { const isEnrollCta = !config?.externalUrl && !paid; return ( + <>
    ) : ( - setQaModalOpen(true)} className="hidden items-center gap-1.5 rounded-full border border-[var(--accent)]/40 bg-[var(--accent)]/10 px-3 py-1.5 text-xs font-medium text-[var(--accent)] transition hover:bg-[var(--accent)]/20 sm:inline-flex sm:px-4 sm:py-2 sm:text-sm" - title="开通VIP解锁即时问答" > 即时问答 - + )} {config?.externalUrl ? (
    + setQaModalOpen(false)} /> + ); } diff --git a/config/content.config.ts b/config/content.config.ts index 462cfd2..99f7965 100644 --- a/config/content.config.ts +++ b/config/content.config.ts @@ -24,7 +24,7 @@ export const CLOUDPHONE_COURSE_CONFIG: CourseConfig = { courseId: "cloudphone", courseHomeHref: "/cloudphone", payHref: "/", - freeTrial: (m, l) => m === 0 && l < 2, + freeTrial: () => false, /* 暂时全部加锁,仅 VIP 可看 */ videoStoragePrefix: "lms-cloudphone-video-", learningStoragePrefix: "lms-cloudphone-", };