From 5d5eb82013565e45f2873875b1c08788d1271d93 Mon Sep 17 00:00:00 2001 From: javayhu Date: Wed, 3 Sep 2025 01:09:01 +0800 Subject: [PATCH] feat: docs support premium content --- content/docs/what-is-fumadocs.mdx | 5 ++ content/docs/what-is-fumadocs.zh.mdx | 7 +- messages/en.json | 31 ++++---- messages/zh.json | 31 ++++---- source.config.ts | 1 + src/app/[locale]/docs/[[...slug]]/page.tsx | 82 ++++++++++++++-------- src/components/blog/premium-badge.tsx | 3 +- src/components/blog/premium-guard.tsx | 43 ++++++------ 8 files changed, 116 insertions(+), 87 deletions(-) diff --git a/content/docs/what-is-fumadocs.mdx b/content/docs/what-is-fumadocs.mdx index 729a072..311262e 100644 --- a/content/docs/what-is-fumadocs.mdx +++ b/content/docs/what-is-fumadocs.mdx @@ -2,6 +2,7 @@ title: What is Fumadocs description: Introducing Fumadocs, a docs framework that you can break. icon: CircleHelp +premium: true --- Fumadocs was created because I wanted a more customisable experience for building docs, to be a docs framework that is not opinionated, **a "framework" that you can break**. @@ -18,6 +19,8 @@ You are still using features of Next.js App Router, like **Static Site Generatio **Opinionated on UI:** The only thing Fumadocs UI (the default theme) offers is **User Interface**. The UI is opinionated for bringing better mobile responsiveness and user experience. Instead, we use a much more flexible approach inspired by Shadcn UI — [Fumadocs CLI](/docs/cli), so we can iterate our design quick, and welcome for more feedback about the UI. + + ## Why Fumadocs Fumadocs is designed with flexibility in mind. @@ -56,3 +59,5 @@ docs easier, with less boilerplate. Fumadocs is maintained by Fuma and many contributors, with care on the maintainability of codebase. While we don't aim to offer every functionality people wanted, we're more focused on making basic features perfect and well-maintained. You can also help Fumadocs to be more useful by contributing! + + diff --git a/content/docs/what-is-fumadocs.zh.mdx b/content/docs/what-is-fumadocs.zh.mdx index ff975fe..b1d0a34 100644 --- a/content/docs/what-is-fumadocs.zh.mdx +++ b/content/docs/what-is-fumadocs.zh.mdx @@ -2,6 +2,7 @@ title: 什么是 Fumadocs description: 介绍 Fumadocs,一个可以打破常规的文档框架 icon: CircleHelp +premium: true --- Fumadocs 的创建是因为我想要一种更加可定制化的文档构建体验,一个不固执己见的文档框架,**一个你可以"打破"的"框架"**。 @@ -18,6 +19,8 @@ Fumadocs 的创建是因为我想要一种更加可定制化的文档构建体 **对 UI 有自己的看法:** Fumadocs UI(默认主题)提供的唯一东西是**用户界面**。UI 的设计理念是提供更好的移动响应性和用户体验。 相反,我们使用受 Shadcn UI 启发的更灵活的方法 — [Fumadocs CLI](/docs/cli),这样我们可以快速迭代设计,并欢迎更多关于 UI 的反馈。 + + ## 为什么选择 Fumadocs Fumadocs 的设计考虑了灵活性。 @@ -53,4 +56,6 @@ Fumadocs 为 Next.js 提供了额外的工具,包括语法高亮、文档搜 Fumadocs 由 Fuma 和许多贡献者维护,关注代码库的可维护性。 虽然我们不打算提供人们想要的每一项功能,但我们更专注于使基本功能完美且维护良好。 -您也可以通过贡献来帮助 Fumadocs 变得更加有用! \ No newline at end of file +您也可以通过贡献来帮助 Fumadocs 变得更加有用! + + diff --git a/messages/en.json b/messages/en.json index f0487de..6de8349 100644 --- a/messages/en.json +++ b/messages/en.json @@ -282,20 +282,7 @@ "all": "All", "noPostsFound": "No posts found", "allPosts": "All Posts", - "morePosts": "More Posts", - "premiumContent": { - "title": "Unlock Premium Content", - "description": "Subscribe to our Pro plan to access all premium articles and exclusive content.", - "upgradeCta": "Upgrade Now", - "benefit1": "All premium articles", - "benefit2": "Exclusive content", - "benefit3": "Cancel anytime", - "signIn": "Sign In", - "loginRequired": "Sign in to continue reading", - "loginDescription": "This is a premium article. Sign in to your account to access the full content.", - "checkingAccess": "Checking access...", - "loadingContent": "Loading full content..." - } + "morePosts": "More Posts" }, "DocsPage": { "toc": "Table of Contents", @@ -306,8 +293,20 @@ "nextPage": "Next", "chooseLanguage": "Select language", "title": "MkSaaS Docs", - "homepage": "Homepage", - "blog": "Blog" + "homepage": "Homepage" + }, + "PremiumContent": { + "title": "Unlock Premium Content", + "description": "Subscribe to our Pro plan to access all premium content and exclusive content.", + "upgradeCta": "Upgrade Now", + "benefit1": "All premium content", + "benefit2": "Exclusive content", + "benefit3": "Cancel anytime", + "signIn": "Sign In", + "loginRequired": "Sign in to continue reading", + "loginDescription": "This is premium content. Sign in to your account to access the full content.", + "checkingAccess": "Checking access...", + "loadingContent": "Loading full content..." }, "Marketing": { "navbar": { diff --git a/messages/zh.json b/messages/zh.json index ee59280..b222833 100644 --- a/messages/zh.json +++ b/messages/zh.json @@ -282,20 +282,7 @@ "all": "全部", "noPostsFound": "没有找到文章", "allPosts": "全部文章", - "morePosts": "更多文章", - "premiumContent": { - "title": "解锁付费内容", - "description": "订阅我们的付费计划,访问所有付费文章和独家内容。", - "upgradeCta": "立即升级", - "benefit1": "所有文章", - "benefit2": "独家内容", - "benefit3": "随时取消", - "signIn": "登录", - "loginRequired": "登录以继续阅读", - "loginDescription": "这是一篇付费文章,请登录您的账户以访问完整内容。", - "checkingAccess": "检查阅读权限...", - "loadingContent": "加载完整内容..." - } + "morePosts": "更多文章" }, "DocsPage": { "toc": "目录", @@ -306,8 +293,20 @@ "nextPage": "下一页", "chooseLanguage": "选择语言", "title": "MkSaaS文档", - "homepage": "首页", - "blog": "博客" + "homepage": "首页" + }, + "PremiumContent": { + "title": "解锁付费内容", + "description": "订阅我们的付费计划,访问所有付费内容和独家内容。", + "upgradeCta": "立即升级", + "benefit1": "所有内容", + "benefit2": "独家内容", + "benefit3": "随时取消", + "signIn": "登录", + "loginRequired": "登录以继续阅读", + "loginDescription": "这是一篇付费内容,请登录您的账户以访问完整内容。", + "checkingAccess": "检查阅读权限...", + "loadingContent": "加载完整内容..." }, "Marketing": { "navbar": { diff --git a/source.config.ts b/source.config.ts index 1243d2d..548f9d6 100644 --- a/source.config.ts +++ b/source.config.ts @@ -15,6 +15,7 @@ export const docs = defineDocs({ schema: frontmatterSchema.extend({ preview: z.string().optional(), index: z.boolean().default(false), + premium: z.boolean().optional(), }), }, meta: { diff --git a/src/app/[locale]/docs/[[...slug]]/page.tsx b/src/app/[locale]/docs/[[...slug]]/page.tsx index 50f1fdf..626732f 100644 --- a/src/app/[locale]/docs/[[...slug]]/page.tsx +++ b/src/app/[locale]/docs/[[...slug]]/page.tsx @@ -1,3 +1,5 @@ +import { PremiumBadge } from '@/components/blog/premium-badge'; +import { PremiumGuard } from '@/components/blog/premium-guard'; import * as Preview from '@/components/docs'; import { getMDXComponents } from '@/components/docs/mdx-components'; import { @@ -7,6 +9,8 @@ import { } from '@/components/ui/hover-card'; import { LOCALES } from '@/i18n/routing'; import { constructMetadata } from '@/lib/metadata'; +import { checkPremiumAccess } from '@/lib/premium-access'; +import { getSession } from '@/lib/server'; import { source } from '@/lib/source'; import { getUrlWithLocale } from '@/lib/urls/urls'; import Link from 'fumadocs-core/link'; @@ -86,6 +90,14 @@ export default async function DocPage({ params }: DocPageProps) { } const preview = page.data.preview; + const { premium } = page.data; + + // Check premium access for premium docs + const session = await getSession(); + const hasPremiumAccess = + premium && session?.user?.id + ? await checkPremiumAccess(session.user.id) + : !premium; // Non-premium docs are always accessible const MDX = page.data.body; @@ -98,44 +110,54 @@ export default async function DocPage({ params }: DocPageProps) { }} > {page.data.title} + {premium && } {page.data.description} {/* Preview Rendered Component */} {preview ? : null} {/* MDX Content */} - { - const found = source.getPageByHref(href ?? '', { - dir: page.file.dirname, - }); + + { + const found = source.getPageByHref(href ?? '', { + dir: page.file.dirname, + }); - if (!found) return ; + if (!found) return ; - return ( - - - - - -

{found.page.data.title}

-

- {found.page.data.description} -

-
-
- ); - }, - })} - /> + return ( + + + + + +

{found.page.data.title}

+

+ {found.page.data.description} +

+
+
+ ); + }, + })} + /> +
); diff --git a/src/components/blog/premium-badge.tsx b/src/components/blog/premium-badge.tsx index cff5f75..93dee51 100644 --- a/src/components/blog/premium-badge.tsx +++ b/src/components/blog/premium-badge.tsx @@ -35,8 +35,7 @@ export function PremiumBadge({ variant={variant} className={cn( 'inline-flex items-center gap-1 font-medium', - 'bg-gradient-to-r from-amber-500 to-orange-500', - 'text-white border-0 hover:from-amber-600 hover:to-orange-600', + 'bg-orange-400 text-white border-0', sizeClasses[size], className )} diff --git a/src/components/blog/premium-guard.tsx b/src/components/blog/premium-guard.tsx index 9462dcc..84a0cd1 100644 --- a/src/components/blog/premium-guard.tsx +++ b/src/components/blog/premium-guard.tsx @@ -30,7 +30,7 @@ export function PremiumGuard({ className, }: PremiumGuardProps) { // All hooks must be called unconditionally at the top - const t = useTranslations('BlogPage'); + const t = useTranslations('PremiumContent'); const pathname = useLocalePathname(); const currentUser = useCurrentUser(); const { data: paymentData, isLoading: isLoadingPayment } = useCurrentPlan( @@ -76,7 +76,7 @@ export function PremiumGuard({ {/* Enhanced login prompt for server-side blocked content */} -
+
@@ -85,17 +85,17 @@ export function PremiumGuard({

- {t('premiumContent.loginRequired')} + {t('loginRequired')}

- {t('premiumContent.loginDescription')} + {t('loginDescription')}

@@ -115,7 +115,7 @@ export function PremiumGuard({
{/* Enhanced login prompt */} -
+
@@ -123,18 +123,16 @@ export function PremiumGuard({
-

- {t('premiumContent.loginRequired')} -

+

{t('loginRequired')}

- {t('premiumContent.loginDescription')} + {t('loginDescription')}

@@ -154,7 +152,7 @@ export function PremiumGuard({ {isLoadingPayment && (
- {t('premiumContent.checkingAccess')} + {t('checkingAccess')}
)}
@@ -170,7 +168,7 @@ export function PremiumGuard({
{/* Inline subscription banner for logged-in non-members */} -
+
@@ -179,18 +177,19 @@ export function PremiumGuard({
-

- {t('premiumContent.title')} -

+

{t('title')}

- {t('premiumContent.description')} + {t('description')}

@@ -199,15 +198,15 @@ export function PremiumGuard({
- {t('premiumContent.benefit1')} + {t('benefit1')} - {t('premiumContent.benefit2')} + {t('benefit2')} - {t('premiumContent.benefit3')} + {t('benefit3')}