Merge remote-tracking branch 'origin/main' into cloudflare
This commit is contained in:
commit
e3ac4a0a29
@ -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.
|
||||
|
||||
<PremiumContent>
|
||||
|
||||
## 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!
|
||||
|
||||
</PremiumContent>
|
||||
|
@ -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 的反馈。
|
||||
|
||||
<PremiumContent>
|
||||
|
||||
## 为什么选择 Fumadocs
|
||||
|
||||
Fumadocs 的设计考虑了灵活性。
|
||||
@ -53,4 +56,6 @@ Fumadocs 为 Next.js 提供了额外的工具,包括语法高亮、文档搜
|
||||
|
||||
Fumadocs 由 Fuma 和许多贡献者维护,关注代码库的可维护性。
|
||||
虽然我们不打算提供人们想要的每一项功能,但我们更专注于使基本功能完美且维护良好。
|
||||
您也可以通过贡献来帮助 Fumadocs 变得更加有用!
|
||||
您也可以通过贡献来帮助 Fumadocs 变得更加有用!
|
||||
|
||||
</PremiumContent>
|
||||
|
@ -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": {
|
||||
|
@ -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": {
|
||||
|
@ -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: {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import AllPostsButton from '@/components/blog/all-posts-button';
|
||||
import BlogGrid from '@/components/blog/blog-grid';
|
||||
import { PremiumBadge } from '@/components/blog/premium-badge';
|
||||
import { PremiumGuard } from '@/components/blog/premium-guard';
|
||||
import { getMDXComponents } from '@/components/docs/mdx-components';
|
||||
import { NewsletterCard } from '@/components/newsletter/newsletter-card';
|
||||
import { PremiumBadge } from '@/components/premium/premium-badge';
|
||||
import { PremiumGuard } from '@/components/premium/premium-guard';
|
||||
import { websiteConfig } from '@/config/website';
|
||||
import { LocaleLink } from '@/i18n/navigation';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
|
@ -1,5 +1,7 @@
|
||||
import * as Preview from '@/components/docs';
|
||||
import { getMDXComponents } from '@/components/docs/mdx-components';
|
||||
import { PremiumBadge } from '@/components/premium/premium-badge';
|
||||
import { PremiumGuard } from '@/components/premium/premium-guard';
|
||||
import {
|
||||
HoverCard,
|
||||
HoverCardContent,
|
||||
@ -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) {
|
||||
}}
|
||||
>
|
||||
<DocsTitle>{page.data.title}</DocsTitle>
|
||||
{premium && <PremiumBadge size="sm" className="mt-2" />}
|
||||
<DocsDescription>{page.data.description}</DocsDescription>
|
||||
<DocsBody>
|
||||
{/* Preview Rendered Component */}
|
||||
{preview ? <PreviewRenderer preview={preview} /> : null}
|
||||
|
||||
{/* MDX Content */}
|
||||
<MDX
|
||||
components={getMDXComponents({
|
||||
a: ({ href, ...props }: { href?: string; [key: string]: any }) => {
|
||||
const found = source.getPageByHref(href ?? '', {
|
||||
dir: page.file.dirname,
|
||||
});
|
||||
<PremiumGuard
|
||||
isPremium={!!premium}
|
||||
canAccess={hasPremiumAccess}
|
||||
className="max-w-none"
|
||||
>
|
||||
<MDX
|
||||
components={getMDXComponents({
|
||||
a: ({
|
||||
href,
|
||||
...props
|
||||
}: { href?: string; [key: string]: any }) => {
|
||||
const found = source.getPageByHref(href ?? '', {
|
||||
dir: page.file.dirname,
|
||||
});
|
||||
|
||||
if (!found) return <Link href={href} {...props} />;
|
||||
if (!found) return <Link href={href} {...props} />;
|
||||
|
||||
return (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<Link
|
||||
href={
|
||||
found.hash
|
||||
? `${found.page.url}#${found.hash}`
|
||||
: found.page.url
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="text-sm">
|
||||
<p className="font-medium">{found.page.data.title}</p>
|
||||
<p className="text-fd-muted-foreground">
|
||||
{found.page.data.description}
|
||||
</p>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
return (
|
||||
<HoverCard>
|
||||
<HoverCardTrigger asChild>
|
||||
<Link
|
||||
href={
|
||||
found.hash
|
||||
? `${found.page.url}#${found.hash}`
|
||||
: found.page.url
|
||||
}
|
||||
{...props}
|
||||
/>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent className="text-sm">
|
||||
<p className="font-medium">{found.page.data.title}</p>
|
||||
<p className="text-fd-muted-foreground">
|
||||
{found.page.data.description}
|
||||
</p>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
);
|
||||
},
|
||||
})}
|
||||
/>
|
||||
</PremiumGuard>
|
||||
</DocsBody>
|
||||
</DocsPage>
|
||||
);
|
||||
|
@ -3,8 +3,8 @@ import { LocaleLink } from '@/i18n/navigation';
|
||||
import { formatDate } from '@/lib/formatter';
|
||||
import { type BlogType, authorSource, categorySource } from '@/lib/source';
|
||||
import Image from 'next/image';
|
||||
import { PremiumBadge } from '../premium/premium-badge';
|
||||
import BlogImage from './blog-image';
|
||||
import { PremiumBadge } from './premium-badge';
|
||||
|
||||
interface BlogCardProps {
|
||||
locale: string;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { PremiumContent } from '@/components/blog/premium-content';
|
||||
import { ImageWrapper } from '@/components/docs/image-wrapper';
|
||||
import { Wrapper } from '@/components/docs/wrapper';
|
||||
import { YoutubeVideo } from '@/components/docs/youtube-video';
|
||||
import { PremiumContent } from '@/components/premium/premium-content';
|
||||
import { Accordion, Accordions } from 'fumadocs-ui/components/accordion';
|
||||
import { Callout } from 'fumadocs-ui/components/callout';
|
||||
import { File, Files, Folder } from 'fumadocs-ui/components/files';
|
||||
|
@ -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
|
||||
)}
|
@ -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({
|
||||
</div>
|
||||
|
||||
{/* Enhanced login prompt for server-side blocked content */}
|
||||
<div className="mt-16">
|
||||
<div className="mt-8">
|
||||
<div className="w-full p-12 rounded-lg bg-gradient-to-br from-primary/5 via-primary/10 to-secondary/5 border border-primary/20">
|
||||
<div className="flex flex-col items-center justify-center gap-6 text-center">
|
||||
<div className="p-4 rounded-full bg-primary/10">
|
||||
@ -85,17 +85,17 @@ export function PremiumGuard({
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xl font-semibold">
|
||||
{t('premiumContent.loginRequired')}
|
||||
{t('loginRequired')}
|
||||
</h3>
|
||||
<p className="text-muted-foreground max-w-md">
|
||||
{t('premiumContent.loginDescription')}
|
||||
{t('loginDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<LoginWrapper mode="modal" asChild callbackUrl={pathname}>
|
||||
<Button size="lg" className="min-w-[160px] cursor-pointer">
|
||||
<LockIcon className="mr-2 size-4" />
|
||||
{t('premiumContent.signIn')}
|
||||
{t('signIn')}
|
||||
</Button>
|
||||
</LoginWrapper>
|
||||
</div>
|
||||
@ -115,7 +115,7 @@ export function PremiumGuard({
|
||||
</div>
|
||||
|
||||
{/* Enhanced login prompt */}
|
||||
<div className="mt-16">
|
||||
<div className="mt-8">
|
||||
<div className="w-full p-12 rounded-lg bg-gradient-to-br from-primary/5 via-primary/10 to-secondary/5 border border-primary/20">
|
||||
<div className="flex flex-col items-center justify-center gap-6 text-center">
|
||||
<div className="p-4 rounded-full bg-primary/10">
|
||||
@ -123,18 +123,16 @@ export function PremiumGuard({
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-xl font-semibold">
|
||||
{t('premiumContent.loginRequired')}
|
||||
</h3>
|
||||
<h3 className="text-xl font-semibold">{t('loginRequired')}</h3>
|
||||
<p className="text-muted-foreground max-w-md">
|
||||
{t('premiumContent.loginDescription')}
|
||||
{t('loginDescription')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<LoginWrapper mode="modal" asChild callbackUrl={pathname}>
|
||||
<Button size="lg" className="min-w-[160px] cursor-pointer">
|
||||
<LockIcon className="mr-2 size-4" />
|
||||
{t('premiumContent.signIn')}
|
||||
{t('signIn')}
|
||||
</Button>
|
||||
</LoginWrapper>
|
||||
</div>
|
||||
@ -154,7 +152,7 @@ export function PremiumGuard({
|
||||
{isLoadingPayment && (
|
||||
<div className="mt-8 flex items-center justify-center text-primary font-semibold">
|
||||
<Loader2Icon className="size-5 animate-spin mr-2" />
|
||||
<span>{t('premiumContent.checkingAccess')}</span>
|
||||
<span>{t('checkingAccess')}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@ -170,7 +168,7 @@ export function PremiumGuard({
|
||||
</div>
|
||||
|
||||
{/* Inline subscription banner for logged-in non-members */}
|
||||
<div className="mt-16">
|
||||
<div className="mt-8">
|
||||
<Card className="bg-gradient-to-br from-primary/5 via-primary/10 to-secondary/5 border border-primary/20">
|
||||
<CardContent className="p-12 text-center">
|
||||
<div className="flex justify-center mb-6">
|
||||
@ -179,18 +177,19 @@ export function PremiumGuard({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-xl font-semibold mb-2">
|
||||
{t('premiumContent.title')}
|
||||
</h3>
|
||||
<h3 className="text-xl font-semibold mb-2">{t('title')}</h3>
|
||||
|
||||
<p className="text-muted-foreground mb-6 max-w-md mx-auto">
|
||||
{t('premiumContent.description')}
|
||||
{t('description')}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-3 justify-center items-center">
|
||||
<Button asChild size="lg" className="min-w-[160px]">
|
||||
<LocaleLink href="/pricing">
|
||||
{t('premiumContent.upgradeCta')}
|
||||
<LocaleLink
|
||||
href="/pricing"
|
||||
className="text-white no-underline hover:text-white/90"
|
||||
>
|
||||
{t('upgradeCta')}
|
||||
<ArrowRightIcon className="ml-2 size-4" />
|
||||
</LocaleLink>
|
||||
</Button>
|
||||
@ -199,15 +198,15 @@ export function PremiumGuard({
|
||||
<div className="mt-8 flex items-center justify-center gap-4 text-sm text-muted-foreground">
|
||||
<span className="flex items-center gap-2">
|
||||
<CheckCircleIcon className="size-4 text-primary" />
|
||||
{t('premiumContent.benefit1')}
|
||||
{t('benefit1')}
|
||||
</span>
|
||||
<span className="flex items-center gap-2">
|
||||
<CheckCircleIcon className="size-4 text-primary" />
|
||||
{t('premiumContent.benefit2')}
|
||||
{t('benefit2')}
|
||||
</span>
|
||||
<span className="flex items-center gap-2">
|
||||
<CheckCircleIcon className="size-4 text-primary" />
|
||||
{t('premiumContent.benefit3')}
|
||||
{t('benefit3')}
|
||||
</span>
|
||||
</div>
|
||||
</CardContent>
|
Loading…
Reference in New Issue
Block a user