Skip to content

Commit

Permalink
feat(saas): fetch blogs from content/blog folder dynamicly
Browse files Browse the repository at this point in the history
  • Loading branch information
alifarooq9 committed Apr 20, 2024
1 parent 2c435fa commit 009d354
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 57 deletions.
18 changes: 18 additions & 0 deletions starterkits/saas/src/app/(web)/blog/[...slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { WebPageWrapper } from "@/app/(web)/_components/general-components";

type BlogSlugPageProps = {
params: {
slug: string[];
};
};

export default function BlogSlugPage({ params }: BlogSlugPageProps) {
return (
<WebPageWrapper>
<div className="w-full">
<h1>Blog Slug Page</h1>
{JSON.stringify(params)}
</div>
</WebPageWrapper>
);
}
66 changes: 24 additions & 42 deletions starterkits/saas/src/app/(web)/blog/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<WebPageWrapper>
<WebPageHeading title="Blog">
Expand All @@ -55,32 +27,42 @@ export default function BlogPage() {
</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-2 gap-8">
{blogs.map((blog) => (
<Link href="/" key={blog.id} className="space-y-4">
{blogs?.map((blog) => (
<Link
href={`${siteUrls.blog}/${blog.metaData.slug}`}
key={blog.metaData.slug}
className="space-y-4"
>
<div className="relative h-screen max-h-[350px] w-full overflow-hidden rounded-md bg-muted/60">
<Image
src={blog.tumbnail}
alt={blog.title}
src={blog.metaData.tumbnail}
alt={blog.metaData.title}
fill
className="object-cover"
/>
</div>
<h2 className="font-heading text-2xl font-semibold">
{blog.title}
{blog.metaData.title}
</h2>
<p>{blog.description}</p>
<div className="grid gap-0.5">
<p>{blog.metaData.description}</p>
<div className="grid gap-0.5 font-light">
<p className="text-sm text-muted-foreground">
Created at{" "}
{format(new Date(blog.date), "PPP")}
{format(
new Date(blog.metaData.publishedAt),
"PPP",
)}
</p>

<p className="text-sm text-muted-foreground">
Updated at{" "}
{format(new Date(blog.date), "PPP")}
{format(
new Date(blog.metaData.updatedAt),
"PPP",
)}
</p>
<p className="text-sm text-muted-foreground">
5 min read
{blog.metaData.readTime} read
</p>
</div>
</Link>
Expand Down
9 changes: 2 additions & 7 deletions starterkits/saas/src/app/docs/[[...slug]]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
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: {
slug: string[];
};
};

async function getDocs() {
const dir = "src/content/docs";
return await getMDXData(dir);
}

export async function generateStaticParams() {
const docs = await getDocs();

Expand All @@ -38,7 +33,7 @@ export default async function DocsSlugPage({ params }: DocsSlugPageProps) {
<>
<article className="flex-1 py-10">
<div className="space-y-2">
<h1 className="font-heading scroll-m-20 text-4xl font-bold">
<h1 className="scroll-m-20 font-heading text-4xl font-bold">
{doc.metaData.title}
</h1>
{doc.metaData.description && (
Expand Down
103 changes: 103 additions & 0 deletions starterkits/saas/src/content/blog/create-saas-in-1-day.mdx
Original file line number Diff line number Diff line change
@@ -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.


<Tabs>

<Tab>

```tsx
import { useState } from "react";

function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
```

</Tab>

<Tab>

```tsx
import { useState } from "react";

function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}

export default Counter;
```

</Tab>

</Tabs>


### 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.

<Steps>

<Step>

This is the first step

</Step>

<Step>

This is the second step

</Step>

</Steps>
54 changes: 54 additions & 0 deletions starterkits/saas/src/content/blog/introduction.mdx
Original file line number Diff line number Diff line change
@@ -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.

<Steps>

<Step>

This is the first step

</Step>

<Step>

This is the second step

</Step>

</Steps>
8 changes: 4 additions & 4 deletions starterkits/saas/src/lib/mdx.ts
Original file line number Diff line number Diff line change
@@ -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<MetaData>(dir: string) {
const files = (await readdir(dir, "utf-8")).filter(
(file) => path.extname(file) === ".mdx",
);
Expand All @@ -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<MDXMetaData>({
const mdxData = await compileMDX<MetaData>({
source: fileData,
options: {
parseFrontmatter: true,
Expand All @@ -30,7 +30,7 @@ export async function getMDXData(dir: string) {
components,
});

const validate = await mdxMetaSchema.safeParseAsync(
const validate = await docsMetaSchema.safeParseAsync(
mdxData.frontmatter,
);

Expand Down
9 changes: 9 additions & 0 deletions starterkits/saas/src/server/actions/blog.ts
Original file line number Diff line number Diff line change
@@ -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<BlogMetaData>(dir);
}
9 changes: 9 additions & 0 deletions starterkits/saas/src/server/actions/docs.ts
Original file line number Diff line number Diff line change
@@ -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<DocsMetaData>(dir);
}
20 changes: 16 additions & 4 deletions starterkits/saas/src/validations/mdx.ts
Original file line number Diff line number Diff line change
@@ -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<typeof docsMetaSchema>;

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<typeof mdxMetaSchema>;
export type BlogMetaData = z.infer<typeof blogMetaSchema>;

0 comments on commit 009d354

Please sign in to comment.