Skip to content

Commit 4a81a38

Browse files
feat: add legal docs section with routing, navigation and content structure
1 parent bc5e869 commit 4a81a38

File tree

9 files changed

+162
-9
lines changed

9 files changed

+162
-9
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { legalDocsSource } from '@/lib/source';
2+
import { DocsBody, DocsDescription, DocsTitle, } from 'fumadocs-ui/page';
3+
import { notFound } from 'next/navigation';
4+
import { getMDXComponents } from '@/mdx-components';
5+
import MdxLink from "@/components/mdx/MdxLink";
6+
import { AnchorProvider } from 'fumadocs-core/toc';
7+
import { TOCProvider, TOCScrollArea } from 'fumadocs-ui/components/layout/toc';
8+
import { PageTOC } from 'fumadocs-ui/layouts/docs/page'
9+
import ClerkTOCItems from 'fumadocs-ui/components/layout/toc-clerk';
10+
import { getPageTreePeers } from 'fumadocs-core/server';
11+
import { Cards, Card } from 'fumadocs-ui/components/card';
12+
import TOCMobile from '@/components/layout/TOCMobile';
13+
14+
export default async function Page(props: {
15+
params: Promise<{ slug?: string[] }>;
16+
}) {
17+
const params = await props.params;
18+
const page = legalDocsSource.getPage(params.slug);
19+
if (!page) notFound();
20+
21+
const MDXContent = page.data.body;
22+
23+
return (
24+
<TOCProvider toc={page.data.toc}>
25+
<AnchorProvider toc={page.data.toc}>
26+
<PageTOC className='hidden xl:sticky xl:block !top-[120px] w-68 max-h-[calc(100vh-120px)] order-last shrink-0 overflow-auto'>
27+
<TOCScrollArea className='w-64'>
28+
<ClerkTOCItems />
29+
</TOCScrollArea>
30+
</PageTOC>
31+
<TOCMobile />
32+
<div className='flex flex-col flex-1 gap-4 mx-auto container overflow-y-auto shrink-1 max-w-179 relative p-4'>
33+
<DocsTitle>{page.data.title}</DocsTitle>
34+
<DocsDescription className="mb-0">{page.data.description}</DocsDescription>
35+
<DocsBody>
36+
<MDXContent
37+
components={getMDXComponents({
38+
a: MdxLink
39+
})}
40+
/>
41+
</DocsBody>
42+
<Cards>
43+
{getPageTreePeers(legalDocsSource.pageTree, page.url).slice(0, 2).map((peer) => (
44+
<Card key={peer.url} title={peer.name} href={peer.url}>
45+
{peer.description}
46+
</Card>
47+
))}
48+
</Cards>
49+
<div className="py-8">
50+
</div>
51+
</div>
52+
</AnchorProvider>
53+
</TOCProvider>
54+
);
55+
}
56+
57+
export async function generateStaticParams() {
58+
return legalDocsSource.generateParams();
59+
}
60+
61+
export async function generateMetadata(props: {
62+
params: Promise<{ slug?: string[] }>;
63+
}) {
64+
const params = await props.params;
65+
const page = legalDocsSource.getPage(params.slug);
66+
if (!page) notFound();
67+
68+
return {
69+
title: page.data.title,
70+
description: page.data.description,
71+
};
72+
}

app/docs/legal/layout.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import Sidebar from "@/components/layout/Sidebar";
2+
import { legalDocsSource } from "@/lib/source";
3+
import { TreeContextProvider } from '@/provider/TreeContextProvider';
4+
import { ReactNode } from "react";
5+
import MobileSidebar from "@/app/docs/MobileSidebar";
6+
7+
export default function Page({children}: {children: ReactNode}) {
8+
return (
9+
<div className='relative'>
10+
<TreeContextProvider tree={legalDocsSource.pageTree}>
11+
<div className="max-w-screen-xl flex flex-col xl:flex-row mt-30 mx-auto">
12+
<Sidebar />
13+
{children}
14+
</div>
15+
<MobileSidebar/>
16+
</TreeContextProvider>
17+
</div>
18+
)
19+
}

app/docs/legal/not-found.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import NotFoundPage from '@/components/NotFoundPage'
2+
3+
export default async function NotFound() {
4+
return <NotFoundPage url="/docs/legal" />
5+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {NextResponse} from 'next/server';
2+
import {legalDocsSource} from '@/lib/source';
3+
import {DocumentRecord} from "@/utils/search/typesense";
4+
5+
export const revalidate = false;
6+
7+
export function GET() {
8+
const results = [] as DocumentRecord[];
9+
10+
11+
for (const page of legalDocsSource.getPages()) {
12+
results.push({
13+
_id: page.url,
14+
structured: page.data.structuredData,
15+
url: page.url,
16+
title: page.data.title,
17+
description: page.data.description,
18+
});
19+
}
20+
21+
return NextResponse.json(results);
22+
}

components/layout/TopBarNaigation.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import {useEffect, useRef, useState} from "react"
44
import {clsx} from "clsx";
55
import Link from "next/link";
6+
import {usePathname} from "next/navigation";
67

78
const tabs = [{
89
title: 'Product Docs',
@@ -18,15 +19,28 @@ const tabs = [{
1819
{
1920
title: 'Changelog (EE)',
2021
href: '/docs/changelog',
22+
},
23+
{
24+
title: 'Legal',
25+
href: '/docs/legal',
2126
}
2227
]
2328

2429
export default function TopBarNaigation() {
30+
const pathname = usePathname()
2531
const [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
2632
const [activeIndex, setActiveIndex] = useState(-1)
2733
const [hoverStyle, setHoverStyle] = useState({})
2834
const [activeStyle, setActiveStyle] = useState({left: "0px", width: "0px"})
2935
const tabRefs = useRef<(HTMLDivElement | null)[]>([])
36+
37+
// Filter tabs based on current path - only show Legal if we're on /docs/legal
38+
const visibleTabs = tabs.filter(tab => {
39+
if (tab.href === '/docs/legal') {
40+
return pathname === '/docs/legal' || pathname.startsWith('/docs/legal/')
41+
}
42+
return true
43+
})
3044

3145
useEffect(() => {
3246
if (hoveredIndex !== null) {
@@ -52,14 +66,11 @@ export default function TopBarNaigation() {
5266
}
5367
}, [activeIndex])
5468

55-
// Determine which tab should be active based on the current path when component mounts
69+
// Determine which tab should be active based on the current path
5670
useEffect(() => {
57-
if (typeof window !== 'undefined') {
58-
const path = window.location.pathname;
59-
const activeTabIndex = tabs.findIndex(tab => path.startsWith(tab.href));
60-
setActiveIndex(activeTabIndex !== -1 ? activeTabIndex : 0);
61-
}
62-
}, []);
71+
const activeTabIndex = visibleTabs.findIndex(tab => pathname.startsWith(tab.href));
72+
setActiveIndex(activeTabIndex !== -1 ? activeTabIndex : 0);
73+
}, [pathname, visibleTabs]);
6374

6475
// Apply active style whenever activeIndex changes or after the component has mounted
6576
useEffect(() => {
@@ -95,7 +106,7 @@ export default function TopBarNaigation() {
95106

96107
{/* Tabs */}
97108
<div className="relative flex gap-3 items-center">
98-
{tabs.map((tab, index) => (
109+
{visibleTabs.map((tab, index) => (
99110
<Link key={index} href={tab.href}>
100111
<div
101112
ref={(el) => { tabRefs.current[index] = el; }}

content/legal/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"title": "Legal",
33
"pages": [
4+
"index",
45
"privacy",
56
"terms-of-service",
67
"aup",

lib/source.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import {blog, docs, scripts, selfHosting, changelog} from '@/.source';
1+
import {blog, docs, scripts, selfHosting, changelog, legalDocs} from '@/.source';
22
import {loader} from 'fumadocs-core/source';
33
import {createMDXSource} from "fumadocs-mdx";
44
import {iconMap, IconNameType} from "@/lib/iconMap";
@@ -45,3 +45,9 @@ export const changelogSource = loader({
4545
baseUrl: '/docs/changelog',
4646
source: changelog.toFumadocsSource(),
4747
})
48+
49+
50+
export const legalDocsSource = loader({
51+
baseUrl: '/docs/legal',
52+
source: legalDocs.toFumadocsSource(),
53+
})

scripts/validateLinks.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ async function checkLinks() {
2626
// Read changelog files
2727
const changelogFiles = await readFiles('content/changelog/**/*.{md,mdx}');
2828

29+
const legalDocsFiles = await readFiles('content/legal/**/*.{md,mdx}');
30+
2931
const scanned = await scanURLs({
3032
populate: {
3133
// Blog routes
@@ -51,6 +53,17 @@ async function checkLinks() {
5153
),
5254
};
5355
}),
56+
57+
'docs/legal/[[...slug]]': legalDocsFiles.map((file) => {
58+
const info = parseFilePath(path.relative('content/legal', file.path));
59+
60+
return {
61+
value: getSlugs(info),
62+
hashes: getTableOfContents(file.content).map((item) =>
63+
item.url.slice(1),
64+
),
65+
};
66+
}),
5467

5568
// Self-hosting docs routes
5669
'docs/self-hosting/[[...slug]]': selfHostingFiles.map((file) => {

source.config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export const changelog = defineDocs({
3030
dir: 'content/changelog',
3131
})
3232

33+
export const legalDocs = defineDocs({
34+
dir: 'content/legal',
35+
})
36+
3337
export default defineConfig({
3438
mdxOptions: {
3539
remarkPlugins: [remarkAdmonition],

0 commit comments

Comments
 (0)