├── .cursor └── rules │ └── main-context.mdc ├── .gitignore ├── .npmrc ├── .prettierrc ├── .vscode ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── apps └── web │ ├── .env.example │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── README.md │ ├── app │ ├── (blog) │ │ ├── blog │ │ │ ├── [slug] │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── ui │ │ │ │ └── Tags.tsx │ │ └── custom-renderer.tsx │ ├── (docs) │ │ ├── docs │ │ │ ├── ai-rules │ │ │ │ └── page.tsx │ │ │ ├── api │ │ │ │ └── [endpointId] │ │ │ │ │ ├── code-examples.tsx │ │ │ │ │ └── page.tsx │ │ │ ├── getting-started │ │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── nextjs │ │ │ │ └── page.tsx │ │ │ ├── page.tsx │ │ │ └── typescript │ │ │ │ └── page.tsx │ │ └── ui │ │ │ ├── object-renderer.tsx │ │ │ └── sidebar.tsx │ ├── (legal) │ │ ├── layout.tsx │ │ ├── privacy │ │ │ └── page.tsx │ │ └── terms │ │ │ └── page.tsx │ ├── api │ │ ├── [...route] │ │ │ └── route.ts │ │ └── public │ │ │ ├── [...route] │ │ │ ├── public-api.checks.ts │ │ │ ├── public-api.constants.ts │ │ │ ├── public-api.errors.ts │ │ │ ├── public-api.types.ts │ │ │ └── route.ts │ │ │ ├── og │ │ │ └── route.tsx │ │ │ └── public-posts.test.ts │ ├── auth │ │ └── confirm │ │ │ └── route.ts │ ├── contact │ │ └── page.tsx │ ├── forms │ │ ├── lib.tsx │ │ └── page.tsx │ ├── globals.css │ ├── layout.tsx │ ├── pub │ │ ├── [subdomain] │ │ │ ├── [slug] │ │ │ │ ├── loading.tsx │ │ │ │ └── page.tsx │ │ │ ├── icon.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── queries.ts │ │ └── themes │ │ │ ├── blog-home.tsx │ │ │ ├── default │ │ │ ├── blog-post-item.tsx │ │ │ ├── home.tsx │ │ │ ├── post-dark.tsx │ │ │ └── post.tsx │ │ │ ├── directory │ │ │ ├── home.tsx │ │ │ └── post.tsx │ │ │ ├── garden │ │ │ └── home.tsx │ │ │ ├── instrument │ │ │ ├── home.tsx │ │ │ └── styles.css │ │ │ └── newsroom │ │ │ └── home.tsx │ ├── supa.ts │ ├── types.ts │ ├── ui │ │ ├── SocialLinks.tsx │ │ ├── docs-page-layout.tsx │ │ ├── fade-in.tsx │ │ ├── table-of-contents.tsx │ │ └── zenblog-footer.tsx │ └── utils │ │ ├── api-client.ts │ │ ├── dates.ts │ │ ├── slugify.test.ts │ │ ├── slugify.ts │ │ └── wait.ts │ ├── assets │ ├── Inter-Bold.ttf │ ├── Inter-Regular.ttf │ └── InterVariable.ttf │ ├── components.json │ ├── constants │ └── themes.ts │ ├── env.d.ts │ ├── lib │ ├── axiom.ts │ ├── openai.ts │ └── use-url-anchor.ts │ ├── next.config.mjs │ ├── package.json │ ├── postcss.config.cjs │ ├── public │ ├── 18fcdaa0b91d4461ee42613bf6988ba0ee2ba8ccf810e838d204aeaedf0fb0f6.txt │ └── static │ │ ├── editor-screenshot.webp │ │ ├── favicon.ico │ │ ├── landing_screenshot.png │ │ ├── leaf.png │ │ ├── logo.svg │ │ ├── logotype.svg │ │ ├── og.jpg │ │ ├── screaming-cowboy-cat.png │ │ ├── tweets │ │ ├── jordienr.jpg │ │ ├── lawrencejessej.jpg │ │ ├── leximory.jpg │ │ ├── metasurfero.jpg │ │ ├── pqoqubbw.jpg │ │ ├── williamhzo.jpg │ │ └── zenbloghq.jpg │ │ ├── ui-1.png │ │ ├── ui-2.png │ │ ├── ui-3.png │ │ ├── zenblog-nextjs-template.webp │ │ ├── zenblog-ui.png │ │ └── zenblog-ui.webp │ ├── src │ ├── analytics │ │ └── index.ts │ ├── cms │ │ ├── ContentRenderer.tsx │ │ ├── code-block-sugar.tsx │ │ ├── index.ts │ │ └── sugar-plugin.ts │ ├── components │ │ ├── 3d │ │ │ └── leaves.tsx │ │ ├── Blogs │ │ │ └── BlogSelector.tsx │ │ ├── CodeBlock.tsx │ │ ├── Content │ │ │ ├── ContentEditor.tsx │ │ │ └── ContentRenderer.tsx │ │ ├── CopyButton.tsx │ │ ├── Debugger.tsx │ │ ├── Editor.tsx │ │ ├── Editor │ │ │ ├── Editor.constants.tsx │ │ │ ├── Editor.queries.ts │ │ │ ├── Editor.state.ts │ │ │ ├── EditorMenu.tsx │ │ │ ├── EditorSettings.tsx │ │ │ ├── TagManagement.tsx │ │ │ ├── ZendoEditor.tsx │ │ │ ├── editor-category-picker.tsx │ │ │ ├── editor-media-node.tsx │ │ │ ├── slash-commands │ │ │ │ └── slash-commands.tsx │ │ │ ├── trailing-node.ts │ │ │ └── upload-image.ts │ │ ├── EmojiPicker.tsx │ │ ├── Feedback.tsx │ │ ├── Feedback │ │ │ └── feedback-form.tsx │ │ ├── Footer.tsx │ │ ├── HiddenField.tsx │ │ ├── Homepage │ │ │ └── Cards │ │ │ │ ├── BaseCard.tsx │ │ │ │ ├── CodeExamples.tsx │ │ │ │ ├── OpenSource.tsx │ │ │ │ ├── ReactComponents.tsx │ │ │ │ └── TypeSafety.tsx │ │ ├── Images │ │ │ ├── ImagePicker.tsx │ │ │ ├── ImageUploader.tsx │ │ │ └── Images.queries.ts │ │ ├── Insights │ │ │ └── TextTypePicker.tsx │ │ ├── LoggedInUser.tsx │ │ ├── LoggedInUserChecks.tsx │ │ ├── MultiSelect.tsx │ │ ├── Notifications.tsx │ │ ├── Shortcut.tsx │ │ ├── Spinner.tsx │ │ ├── Tags │ │ │ ├── CreateTagDialog.tsx │ │ │ ├── CreateTagForm.tsx │ │ │ ├── TagPicker.tsx │ │ │ └── UpdateTagDialog.tsx │ │ ├── Tiptap.tsx │ │ ├── UserButton.tsx │ │ ├── ZendoLogo.tsx │ │ ├── code-block.tsx │ │ ├── confirm-dialog.tsx │ │ ├── copy-cell.tsx │ │ ├── dev │ │ │ └── zenblog-toolbar.tsx │ │ ├── integration-guide.tsx │ │ ├── is-dev-mode.tsx │ │ ├── magicui │ │ │ └── marquee.tsx │ │ ├── marketing │ │ │ └── Navigation.tsx │ │ ├── onboarding.tsx │ │ └── ui │ │ │ ├── accordion.tsx │ │ │ ├── button.tsx │ │ │ ├── calendar.tsx │ │ │ ├── carousel.tsx │ │ │ ├── checkbox.tsx │ │ │ ├── collapsible.tsx │ │ │ ├── command.tsx │ │ │ ├── dialog.tsx │ │ │ ├── drawer.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── editor-date-input.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── popover.tsx │ │ │ ├── resizable.tsx │ │ │ ├── select.tsx │ │ │ ├── sheet.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── switch.tsx │ │ │ ├── table.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── toggle-group.tsx │ │ │ ├── toggle.tsx │ │ │ └── tooltip.tsx │ ├── env.mjs │ ├── hooks │ │ ├── use-blog-id.ts │ │ ├── useKeyboard.ts │ │ └── useRouterTabs.ts │ ├── layouts │ │ └── AppLayout.tsx │ ├── lib │ │ ├── ai │ │ │ └── insights.ts │ │ ├── client │ │ │ └── use-local-storage.ts │ │ ├── config.ts │ │ ├── constants.ts │ │ ├── create-id.ts │ │ ├── models │ │ │ ├── blogs │ │ │ │ └── Blogs.ts │ │ │ └── posts │ │ │ │ └── Posts.ts │ │ ├── pricing.constants.ts │ │ ├── server │ │ │ ├── deprecated │ │ │ │ └── supabase.ts │ │ │ ├── stripe.constants.ts │ │ │ ├── stripe.ts │ │ │ └── supabase │ │ │ │ ├── admin.ts │ │ │ │ └── index.ts │ │ ├── supabase.ts │ │ ├── tremor │ │ │ └── utils.ts │ │ ├── utils.ts │ │ └── utils │ │ │ ├── auth.ts │ │ │ └── slugs.ts │ ├── middleware.ts │ ├── pages │ │ ├── _app.tsx │ │ ├── account │ │ │ └── index.tsx │ │ ├── api │ │ │ └── webhooks │ │ │ │ └── stripe.ts │ │ ├── blogs │ │ │ ├── [blogId] │ │ │ │ ├── authors.tsx │ │ │ │ ├── categories.tsx │ │ │ │ ├── create.tsx │ │ │ │ ├── customise.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── media.tsx │ │ │ │ ├── post │ │ │ │ │ └── [postSlug].tsx │ │ │ │ ├── posts.tsx │ │ │ │ ├── settings.tsx │ │ │ │ ├── tags.tsx │ │ │ │ └── usage │ │ │ │ │ └── index.tsx │ │ │ ├── _ │ │ │ │ └── [...rest].tsx │ │ │ ├── create.tsx │ │ │ └── index.tsx │ │ ├── index.tsx │ │ ├── insights │ │ │ └── index.tsx │ │ ├── pricing.tsx │ │ ├── reset-password-confirmation.tsx │ │ ├── reset-password.tsx │ │ ├── sign-in.tsx │ │ ├── sign-out.tsx │ │ ├── sign-up.tsx │ │ ├── test.tsx │ │ └── uploader.tsx │ ├── queries │ │ ├── authors.ts │ │ ├── blogs.ts │ │ ├── categories.ts │ │ ├── onboarding.ts │ │ ├── posts.ts │ │ ├── prices.ts │ │ ├── products.ts │ │ ├── subscription.ts │ │ └── tags.ts │ ├── scripts │ │ └── stripe-sync.ts │ ├── store │ │ └── app.ts │ ├── styles │ │ └── globals.css │ ├── trpc │ │ ├── client │ │ │ └── index.ts │ │ ├── server │ │ │ ├── context.ts │ │ │ ├── index.ts │ │ │ └── trpc.ts │ │ └── utils.ts │ ├── types │ │ └── supabase.ts │ └── utils │ │ ├── get-hosted-blog-url.ts │ │ └── supabase │ │ ├── browser.tsx │ │ └── middleware.ts │ ├── tailwind.config.ts │ ├── tinybird │ └── index.ts │ ├── tsconfig.json │ └── vercel.json ├── package-lock.json ├── package.json ├── packages ├── code-block-sugar-high │ ├── README.md │ ├── code-block-sugar.tsx │ ├── index.ts │ ├── package.json │ └── sugar-plugin.ts ├── hash │ ├── index.ts │ └── package.json ├── types │ ├── index.ts │ └── package.json └── zenblog │ ├── README.md │ ├── demo │ └── index.ts │ ├── dist │ ├── index.d.ts │ ├── index.d.ts.map │ ├── index.js │ ├── index.js.map │ ├── lib │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ ├── index.js │ │ └── index.js.map │ ├── types.d.ts │ ├── types.d.ts.map │ ├── types.js │ └── types.js.map │ ├── package.json │ ├── src │ ├── index.ts │ ├── lib │ │ └── index.ts │ └── types.ts │ ├── tests │ └── index.test.ts │ └── tsconfig.json ├── todo ├── cleanup.md ├── mvp.md ├── postmvp.md └── templates.md └── turbo.json /.cursor/rules/main-context.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 3 | globs: 4 | --- 5 | 6 | # Project context 7 | 8 | - You're building Zenblog, a headless blogging CMS. 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | .pnp 6 | .pnp.js 7 | 8 | # testing 9 | coverage 10 | 11 | # next.js 12 | .next/ 13 | out/ 14 | build 15 | 16 | # misc 17 | .DS_Store 18 | *.pem 19 | .backup 20 | 21 | # debug 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log* 25 | 26 | # local env files 27 | .env 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # turbo 34 | .turbo 35 | 36 | # vercel 37 | .vercel 38 | 39 | supabase/.temp -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | auto-install-peers = true 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "useTabs": false 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Next.js: debug server-side", 6 | "type": "node-terminal", 7 | "request": "launch", 8 | "command": "npm run dev" 9 | }, 10 | { 11 | "name": "Next.js: debug client-side", 12 | "type": "chrome", 13 | "request": "launch", 14 | "url": "http://localhost:3000" 15 | }, 16 | { 17 | "name": "Next.js: debug full stack", 18 | "type": "node-terminal", 19 | "request": "launch", 20 | "command": "npm run dev", 21 | "serverReadyAction": { 22 | "pattern": "- Local:.+(https?://.+)", 23 | "uriFormat": "%s", 24 | "action": "debugWithChrome" 25 | } 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.editor.customLabels.patterns": { 3 | "**/app/**/page.tsx": "${dirname}/page" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_SUPABASE_URL= 2 | NEXT_PUBLIC_SUPABASE_ANON_KEY= 3 | SUPABASE_SERVICE_ROLE= 4 | RESEND= 5 | STRIPE_PUBLISHABLE_KEY= 6 | STRIPE_SECRET_KEY= 7 | STRIPE_WEBHOOK_SECRET= -------------------------------------------------------------------------------- /apps/web/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | /** @type {import("eslint").Linter.Config} */ 4 | const config = { 5 | extends: [ "next/core-web-vitals" ], 6 | rules: { 7 | 8 | }, 9 | }; 10 | 11 | module.exports = config; 12 | -------------------------------------------------------------------------------- /apps/web/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | next-env.d.ts 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | .pnpm-debug.log* 28 | 29 | # local env files 30 | # do not commit any .env files to git, except for the .env.example file. https://create.t3.gg/en/usage/env-variables#using-environment-variables 31 | .env 32 | .env*.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | .turbo -------------------------------------------------------------------------------- /apps/web/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jordienr/zenblog/5f32f66370eff35bb59e24893fc5ef2e31ce6571/apps/web/README.md -------------------------------------------------------------------------------- /apps/web/app/(blog)/blog/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @next/next/no-img-element */ 2 | import { getBlogClient } from "@/cms"; 3 | import { CustomRenderer } from "app/(blog)/custom-renderer"; 4 | import { ArrowLeftIcon } from "lucide-react"; 5 | import Link from "next/link"; 6 | import React from "react"; 7 | import ReactSyntaxHighlighter from "react-syntax-highlighter"; 8 | 9 | export const dynamic = "force-dynamic"; 10 | type Params = { 11 | params: { 12 | slug: string; 13 | }; 14 | }; 15 | 16 | export async function generateMetadata({ params }: Params) { 17 | const blog = getBlogClient(); 18 | const { data: post } = await blog.posts.get({ slug: params.slug }); 19 | 20 | return { 21 | title: post.title, 22 | description: post.excerpt, 23 | }; 24 | } 25 | const Post = async ({ params: { slug } }: Params) => { 26 | const blog = getBlogClient(); 27 | const { data: post } = await blog.posts.get({ slug }); 28 | 29 | return ( 30 |
31 |
32 | 33 | 34 | All posts 35 | 36 |

37 | {new Date(post.published_at).toLocaleDateString("en-US", { 38 | year: "numeric", 39 | month: "long", 40 | day: "numeric", 41 | })} 42 |

43 |

44 | {post.title} 45 |

46 |
47 |
48 | {post.cover_image && ( 49 | {post.title} 57 | )} 58 |
59 |
60 |
61 |
62 |
63 | ); 64 | }; 65 | 66 | export default Post; 67 | -------------------------------------------------------------------------------- /apps/web/app/(blog)/blog/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from "@/components/Footer"; 2 | import Navigation from "@/components/marketing/Navigation"; 3 | import React, { PropsWithChildren } from "react"; 4 | 5 | type Props = {}; 6 | 7 | const layout = (props: PropsWithChildren) => { 8 | return ( 9 | <> 10 |
11 | 12 |
13 | {props.children} 14 |
15 |
16 |
17 |
18 | 19 | ); 20 | }; 21 | 22 | export default layout; 23 | -------------------------------------------------------------------------------- /apps/web/app/(blog)/blog/ui/Tags.tsx: -------------------------------------------------------------------------------- 1 | import { HashIcon } from "lucide-react"; 2 | import Link from "next/link"; 3 | 4 | export function Tags({ tags }: { tags: { name: string; slug: string }[] }) { 5 | return ( 6 |
7 | {tags.map((tag) => ( 8 | 13 | 14 | {tag.name} 15 | 16 | ))} 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /apps/web/app/(blog)/custom-renderer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React from "react"; 4 | import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; 5 | import { dracula } from "react-syntax-highlighter/dist/esm/styles/prism"; 6 | 7 | export function CustomRenderer({ html_content }: { html_content: string }) { 8 | const parser = new DOMParser(); 9 | const doc = parser.parseFromString(html_content, "text/html"); 10 | 11 | const renderElement = (element: Element, index?: number): React.ReactNode => { 12 | // Create a custom renderer for
 elements to highlight code
13 |     if (element.tagName.toLowerCase() === "pre") {
14 |       const code = element.querySelector("code");
15 |       const language = code?.className.replace("language-", "") || "tsx";
16 |       return (
17 |         
18 |           {code?.textContent || ""}
19 |         
20 |       );
21 |     }
22 | 
23 |     // For other elements, create React elements
24 |     const children = Array.from(element.childNodes).map((node, index) => {
25 |       if (node.nodeType === Node.ELEMENT_NODE) {
26 |         return renderElement(node as Element);
27 |       }
28 |       return node.textContent;
29 |     });
30 | 
31 |     return React.createElement(
32 |       element.tagName.toLowerCase(),
33 |       { key: index },
34 |       ...children
35 |     );
36 |   };
37 | 
38 |   return (
39 |     
40 | {Array.from(doc.body.children).map((element, index) => 41 | renderElement(element, index) 42 | )} 43 |
44 | ); 45 | } 46 | -------------------------------------------------------------------------------- /apps/web/app/(docs)/docs/ai-rules/page.tsx: -------------------------------------------------------------------------------- 1 | import { CodeBlock } from "@/components/CodeBlock"; 2 | import { endpoints } from "app/api/public/[...route]/public-api.constants"; 3 | import { DocsPageLayout } from "app/ui/docs-page-layout"; 4 | 5 | export default function AiRules() { 6 | return ( 7 | 12 |
13 | 14 | {`This is the documentation to integrate zenblog into your website. 15 | Zenblog is a headless CMS that allows you to create and manage your blog. It will host images and videos for you. 16 | You can install the zenblog package with npm i zenblog to use the typed http client for the API. Make sure the package version is 1.2.0 or higher. 17 | To integrate zenblog into your website, you just need to make an HTTP request to the API with the Blog ID which you can find in the Zenblog dashboard. The content is returned as an html string. You have to render it to the dom. You can parse it however you want and change the styles and dom elements for your website as you need. 18 | 19 | Here is the schema for the API and typescript/javascript client: 20 | 21 | ${JSON.stringify(endpoints, null, 2) 22 | .trim() 23 | .replace(/^```text\n/, "") 24 | .replace(/\n```$/, "") 25 | // replace escaped quotes with actual quotes 26 | .replace(/\\"/g, '"') 27 | .replace(/\\'/g, "'") 28 | .replace(/\\`/g, "`") 29 | .replace(/\\n/g, "\n") 30 | .replace(/\\t/g, "\t") 31 | .replace(/\\r/g, "\r") 32 | .replace(/\\f/g, "\f")} 33 | `} 34 | 35 |
36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /apps/web/app/(docs)/docs/api/[endpointId]/code-examples.tsx: -------------------------------------------------------------------------------- 1 | import { CodeBlock } from "@/components/CodeBlock"; 2 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 3 | import { EndpointCodeExamples } from "app/api/public/[...route]/public-api.types"; 4 | 5 | export function CodeExamples({ examples }: { examples: EndpointCodeExamples }) { 6 | const keys = Object.keys(examples) as (keyof EndpointCodeExamples)[]; 7 | 8 | return ( 9 |
10 | 11 | 12 | {keys.map((key) => ( 13 | 14 | {key} 15 | 16 | ))} 17 | 18 | {keys.map((key) => ( 19 | 20 |
21 | {examples[key].map((example) => ( 22 | 23 | {`// ${example.description} 24 | ${example.code.trim()} 25 | `} 26 | 27 | ))} 28 |
29 |
30 | ))} 31 |
32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /apps/web/app/(docs)/docs/page.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation"; 2 | 3 | export default function Docs() { 4 | redirect("/docs/getting-started"); 5 | } 6 | -------------------------------------------------------------------------------- /apps/web/app/(docs)/ui/object-renderer.tsx: -------------------------------------------------------------------------------- 1 | export function ObjectRenderer({ object }: { object: any }) { 2 | const keys = Object.keys(object); 3 | 4 | return ( 5 |
6 | {keys.map((key) => ( 7 |
8 |

{key}

9 |

{object[key]}

10 |
11 | ))} 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /apps/web/app/(docs)/ui/sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { cn } from "@/lib/utils"; 3 | import Link from "next/link"; 4 | import { usePathname } from "next/navigation"; 5 | 6 | export function SidebarTitle({ 7 | children, 8 | className, 9 | }: { 10 | children: React.ReactNode; 11 | className?: string; 12 | }) { 13 | return ( 14 |

20 | {children} 21 |

22 | ); 23 | } 24 | 25 | export function SidebarLink({ 26 | children, 27 | href, 28 | className, 29 | }: { 30 | children: React.ReactNode; 31 | href: string; 32 | className?: string; 33 | }) { 34 | const pathname = usePathname(); 35 | 36 | const isActive = pathname?.includes(href); 37 | return ( 38 | 46 |
{children}
47 | 48 | ); 49 | } 50 | -------------------------------------------------------------------------------- /apps/web/app/(legal)/layout.tsx: -------------------------------------------------------------------------------- 1 | import Footer from "@/components/Footer"; 2 | import Navigation from "@/components/marketing/Navigation"; 3 | 4 | export default function LegalLayout({ 5 | children, 6 | }: { 7 | children: React.ReactNode; 8 | }) { 9 | return ( 10 | <> 11 | 12 |
{children}
13 |