From 009d3542dec277468762262f93780599274c85b5 Mon Sep 17 00:00:00 2001 From: Ali Farooq Date: Sat, 20 Apr 2024 14:22:51 +0500 Subject: [PATCH] feat(saas): fetch blogs from content/blog folder dynamicly --- .../src/app/(web)/blog/[...slug]/page.tsx | 18 +++ starterkits/saas/src/app/(web)/blog/page.tsx | 66 ++++------- .../saas/src/app/docs/[[...slug]]/page.tsx | 9 +- .../src/content/blog/create-saas-in-1-day.mdx | 103 ++++++++++++++++++ .../saas/src/content/blog/introduction.mdx | 54 +++++++++ starterkits/saas/src/lib/mdx.ts | 8 +- starterkits/saas/src/server/actions/blog.ts | 9 ++ starterkits/saas/src/server/actions/docs.ts | 9 ++ starterkits/saas/src/validations/mdx.ts | 20 +++- 9 files changed, 239 insertions(+), 57 deletions(-) create mode 100644 starterkits/saas/src/app/(web)/blog/[...slug]/page.tsx create mode 100644 starterkits/saas/src/content/blog/create-saas-in-1-day.mdx create mode 100644 starterkits/saas/src/content/blog/introduction.mdx create mode 100644 starterkits/saas/src/server/actions/blog.ts create mode 100644 starterkits/saas/src/server/actions/docs.ts diff --git a/starterkits/saas/src/app/(web)/blog/[...slug]/page.tsx b/starterkits/saas/src/app/(web)/blog/[...slug]/page.tsx new file mode 100644 index 0000000..07c70b5 --- /dev/null +++ b/starterkits/saas/src/app/(web)/blog/[...slug]/page.tsx @@ -0,0 +1,18 @@ +import { WebPageWrapper } from "@/app/(web)/_components/general-components"; + +type BlogSlugPageProps = { + params: { + slug: string[]; + }; +}; + +export default function BlogSlugPage({ params }: BlogSlugPageProps) { + return ( + +
+

Blog Slug Page

+ {JSON.stringify(params)} +
+
+ ); +} diff --git a/starterkits/saas/src/app/(web)/blog/page.tsx b/starterkits/saas/src/app/(web)/blog/page.tsx index b0189e6..fa774b4 100644 --- a/starterkits/saas/src/app/(web)/blog/page.tsx +++ b/starterkits/saas/src/app/(web)/blog/page.tsx @@ -3,43 +3,15 @@ import { WebPageWrapper, } from "@/app/(web)/_components/general-components"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { siteUrls } from "@/config/urls"; +import { getBlogs } from "@/server/actions/blog"; import { format } from "date-fns"; import Image from "next/image"; import Link from "next/link"; -export const blogs = [ - { - id: 1, - title: "Blog Post 1", - description: - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet.", - date: "2022-01-01", - tumbnail: "https://fakeimg.pl/700x400/d1d1d1/6b6b6b", - }, - { - id: 2, - title: "Blog Post 2", - description: "This is the second blog post", - date: "2022-01-02", - tumbnail: "https://fakeimg.pl/700x400/d1d1d1/6b6b6b", - }, - { - id: 1, - title: "Blog Post 1", - description: "This is the first blog post", - date: "2022-01-01", - tumbnail: "https://fakeimg.pl/700x400/d1d1d1/6b6b6b", - }, - { - id: 2, - title: "Blog Post 2", - description: "This is the second blog post", - date: "2022-01-02", - tumbnail: "https://fakeimg.pl/700x400/d1d1d1/6b6b6b", - }, -]; +export default async function BlogsPage() { + const blogs = await getBlogs(); -export default function BlogPage() { return ( @@ -55,32 +27,42 @@ export default function BlogPage() { - {blogs.map((blog) => ( - + {blogs?.map((blog) => ( +
{blog.title}

- {blog.title} + {blog.metaData.title}

-

{blog.description}

-
+

{blog.metaData.description}

+

Created at{" "} - {format(new Date(blog.date), "PPP")} + {format( + new Date(blog.metaData.publishedAt), + "PPP", + )}

Updated at{" "} - {format(new Date(blog.date), "PPP")} + {format( + new Date(blog.metaData.updatedAt), + "PPP", + )}

- 5 min read + {blog.metaData.readTime} read

diff --git a/starterkits/saas/src/app/docs/[[...slug]]/page.tsx b/starterkits/saas/src/app/docs/[[...slug]]/page.tsx index d0ea027..988e257 100644 --- a/starterkits/saas/src/app/docs/[[...slug]]/page.tsx +++ b/starterkits/saas/src/app/docs/[[...slug]]/page.tsx @@ -1,6 +1,6 @@ import { notFound, redirect } from "next/navigation"; import { Toc } from "@/components/toc"; -import { getMDXData } from "@/lib/mdx"; +import { getDocs } from "@/server/actions/docs"; type DocsSlugPageProps = { params: { @@ -8,11 +8,6 @@ type DocsSlugPageProps = { }; }; -async function getDocs() { - const dir = "src/content/docs"; - return await getMDXData(dir); -} - export async function generateStaticParams() { const docs = await getDocs(); @@ -38,7 +33,7 @@ export default async function DocsSlugPage({ params }: DocsSlugPageProps) { <>
-

+

{doc.metaData.title}

{doc.metaData.description && ( diff --git a/starterkits/saas/src/content/blog/create-saas-in-1-day.mdx b/starterkits/saas/src/content/blog/create-saas-in-1-day.mdx new file mode 100644 index 0000000..4ccacca --- /dev/null +++ b/starterkits/saas/src/content/blog/create-saas-in-1-day.mdx @@ -0,0 +1,103 @@ +--- +title: Create a SaaS in 1 day +slug: create-saas-in-1-day +publishedAt: 2022-01-01 +updatedAt: 2022-01-01 +readTime: 5 min +tags: ["saas", "introduction"] +description: This is the introduction +tumbnail: https://fakeimg.pl/700x400/d1d1d1/6b6b6b +--- + +# Create a SaaS in 1 day + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + +## Heading 2 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + + + + + +```tsx +import { useState } from "react"; + +function Counter() { + const [count, setCount] = useState(0); + + return ( +
+

You clicked {count} times

+ +
+ ); +} +``` + +
+ + + +```tsx +import { useState } from "react"; + +function Counter() { + const [count, setCount] = useState(0); + + return ( +
+

You clicked {count} times

+ +
+ ); +} + +export default Counter; +``` + +
+ +
+ + +### Heading 3 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + +#### Heading 4 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + +##### Heading 5 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + +###### Heading 6 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + + + + +This is the first step + + + + + +This is the second step + + + + \ No newline at end of file diff --git a/starterkits/saas/src/content/blog/introduction.mdx b/starterkits/saas/src/content/blog/introduction.mdx new file mode 100644 index 0000000..321cb54 --- /dev/null +++ b/starterkits/saas/src/content/blog/introduction.mdx @@ -0,0 +1,54 @@ +--- +title: Introduction +slug: introduction +publishedAt: 2022-01-01 +updatedAt: 2022-01-01 +readTime: 5 min +tags: ["introduction", "saas"] +description: This is the introduction +tumbnail: https://fakeimg.pl/700x400/d1d1d1/6b6b6b +--- + +# Introduction + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + +## Heading 2 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + +### Heading 3 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + +#### Heading 4 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + +##### Heading 5 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + +###### Heading 6 + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer nec odio. Praesent libero. Sed cursus ante dapibus diam. Sed nisi. Nulla quis sem at nibh elementum imperdiet. + + + + + +This is the first step + + + + + +This is the second step + + + + \ No newline at end of file diff --git a/starterkits/saas/src/lib/mdx.ts b/starterkits/saas/src/lib/mdx.ts index f3360dd..ce5fd53 100644 --- a/starterkits/saas/src/lib/mdx.ts +++ b/starterkits/saas/src/lib/mdx.ts @@ -1,13 +1,13 @@ import { compileMDX } from "next-mdx-remote/rsc"; import { readdir, readFile } from "fs/promises"; -import { mdxMetaSchema, type MDXMetaData } from "@/validations/mdx"; +import { docsMetaSchema } from "@/validations/mdx"; import path from "path"; import { getTableOfContents } from "@/lib/toc"; import { mdxComponents } from "@/components/mdx-components"; import { AutoIdsToHeading } from "@/lib/rehype-plugins"; import rehypePrism from "rehype-prism-plus"; -export async function getMDXData(dir: string) { +export async function getMDXData(dir: string) { const files = (await readdir(dir, "utf-8")).filter( (file) => path.extname(file) === ".mdx", ); @@ -17,7 +17,7 @@ export async function getMDXData(dir: string) { return await Promise.all( files.map(async (file) => { const fileData = await readFile(`${dir}/${file}`, "utf-8"); - const mdxData = await compileMDX({ + const mdxData = await compileMDX({ source: fileData, options: { parseFrontmatter: true, @@ -30,7 +30,7 @@ export async function getMDXData(dir: string) { components, }); - const validate = await mdxMetaSchema.safeParseAsync( + const validate = await docsMetaSchema.safeParseAsync( mdxData.frontmatter, ); diff --git a/starterkits/saas/src/server/actions/blog.ts b/starterkits/saas/src/server/actions/blog.ts new file mode 100644 index 0000000..20c0bd9 --- /dev/null +++ b/starterkits/saas/src/server/actions/blog.ts @@ -0,0 +1,9 @@ +import "server-only"; + +import { getMDXData } from "@/lib/mdx"; +import type { BlogMetaData } from "@/validations/mdx"; + +export async function getBlogs() { + const dir = "src/content/blog"; + return await getMDXData(dir); +} diff --git a/starterkits/saas/src/server/actions/docs.ts b/starterkits/saas/src/server/actions/docs.ts new file mode 100644 index 0000000..0d44191 --- /dev/null +++ b/starterkits/saas/src/server/actions/docs.ts @@ -0,0 +1,9 @@ +import "server-only"; + +import { getMDXData } from "@/lib/mdx"; +import type { DocsMetaData } from "@/validations/mdx"; + +export async function getDocs() { + const dir = "src/content/docs"; + return await getMDXData(dir); +} diff --git a/starterkits/saas/src/validations/mdx.ts b/starterkits/saas/src/validations/mdx.ts index ccfb1d1..64f90a8 100644 --- a/starterkits/saas/src/validations/mdx.ts +++ b/starterkits/saas/src/validations/mdx.ts @@ -1,14 +1,26 @@ import { z } from "zod"; -export const mdxMetaSchema = z.object({ +export const docsMetaSchema = z.object({ title: z.string(), slug: z.string(), - publishedAt: z.string().optional(), tags: z.array(z.string()).optional(), description: z.string().optional(), - image: z.string().optional(), + isDraft: z.boolean().optional(), +}); + +export type DocsMetaData = z.infer; + +export const blogMetaSchema = z.object({ + title: z.string(), + slug: z.string(), + publishedAt: z.string().datetime(), + updatedAt: z.string().datetime(), + readTime: z.string(), + tags: z.array(z.string()).optional(), + description: z.string(), + tumbnail: z.string().url(), featured: z.boolean().optional(), isDraft: z.boolean().optional(), }); -export type MDXMetaData = z.infer; +export type BlogMetaData = z.infer;