-
Notifications
You must be signed in to change notification settings - Fork 426
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: add blog categories & improving design
- Loading branch information
Showing
22 changed files
with
629 additions
and
328 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
import { allPosts } from "contentlayer/generated"; | ||
import { notFound } from "next/navigation"; | ||
|
||
import { Mdx } from "@/components/content/mdx-components"; | ||
|
||
import "@/styles/mdx.css"; | ||
|
||
import { Metadata } from "next"; | ||
import Image from "next/image"; | ||
import Link from "next/link"; | ||
|
||
import Author from "@/components/content/author"; | ||
import MaxWidthWrapper from "@/components/shared/max-width-wrapper"; | ||
import { DashboardTableOfContents } from "@/components/shared/toc"; | ||
import { buttonVariants } from "@/components/ui/button"; | ||
import { BLOG_CATEGORIES } from "@/config/blog"; | ||
import { getTableOfContents } from "@/lib/toc"; | ||
import { cn, constructMetadata, formatDate } from "@/lib/utils"; | ||
|
||
export async function generateStaticParams() { | ||
return allPosts.map((post) => ({ | ||
slug: post.slugAsParams, | ||
})); | ||
} | ||
|
||
export async function generateMetadata({ | ||
params, | ||
}: { | ||
params: { slug: string }; | ||
}): Promise<Metadata | undefined> { | ||
const post = allPosts.find((post) => post.slugAsParams === params.slug); | ||
if (!post) { | ||
return; | ||
} | ||
|
||
const { title, description, image } = post; | ||
|
||
return constructMetadata({ | ||
title: `${title} – SaaS Starter`, | ||
description: description, | ||
image, | ||
}); | ||
} | ||
|
||
export default async function PostPage({ | ||
params, | ||
}: { | ||
params: { | ||
slug: string; | ||
}; | ||
}) { | ||
const post = allPosts.find((post) => post.slugAsParams === params.slug); | ||
|
||
if (!post) { | ||
notFound(); | ||
} | ||
|
||
const category = BLOG_CATEGORIES.find( | ||
(category) => category.slug === post.categories[0], | ||
)!; | ||
|
||
const relatedArticles = | ||
(post.related && | ||
post.related.map( | ||
(slug) => allPosts.find((post) => post.slugAsParams === slug)!, | ||
)) || | ||
[]; | ||
|
||
const toc = await getTableOfContents(post.body.raw); | ||
|
||
return ( | ||
<> | ||
<MaxWidthWrapper className="pt-6 md:pt-10"> | ||
<div className="flex flex-col space-y-4"> | ||
<div className="flex items-center space-x-4"> | ||
<Link | ||
href={`/blog/category/${category.slug}`} | ||
className={cn( | ||
buttonVariants({ | ||
variant: "outline", | ||
size: "sm", | ||
rounded: "lg", | ||
}), | ||
"h-8", | ||
)} | ||
> | ||
{category.title} | ||
</Link> | ||
<time | ||
dateTime={post.date} | ||
className="text-sm font-medium text-muted-foreground" | ||
> | ||
{formatDate(post.date)} | ||
</time> | ||
</div> | ||
<h1 className="font-heading text-3xl text-foreground sm:text-4xl"> | ||
{post.title} | ||
</h1> | ||
<p className="text-base text-muted-foreground md:text-lg"> | ||
{post.description} | ||
</p> | ||
<div className="flex flex-nowrap items-center space-x-5 pt-1 md:space-x-8"> | ||
{post.authors.map((author) => ( | ||
<Author username={author} key={post._id + author} /> | ||
))} | ||
</div> | ||
</div> | ||
</MaxWidthWrapper> | ||
|
||
<div className="relative"> | ||
<div className="absolute top-52 w-full border-t" /> | ||
|
||
<MaxWidthWrapper className="grid grid-cols-4 gap-10 pt-8 max-md:px-0"> | ||
<div className="relative col-span-4 mb-10 flex flex-col space-y-8 bg-background sm:border md:rounded-xl lg:col-span-3"> | ||
<Image | ||
className="aspect-[1200/630] border-b object-cover md:rounded-t-xl" | ||
src={post.image} | ||
width={1200} | ||
height={630} | ||
alt={post.title} | ||
priority | ||
/> | ||
<div className="px-[.8rem] pb-10 md:px-8"> | ||
<Mdx code={post.body.code} /> | ||
</div> | ||
</div> | ||
|
||
<div className="sticky top-20 col-span-1 mt-52 hidden flex-col divide-y divide-muted self-start pb-24 lg:flex"> | ||
<DashboardTableOfContents toc={toc} /> | ||
</div> | ||
</MaxWidthWrapper> | ||
</div> | ||
|
||
<MaxWidthWrapper> | ||
{relatedArticles.length > 0 && ( | ||
<div className="flex flex-col space-y-4 pb-16"> | ||
<p className="font-heading text-2xl text-foreground"> | ||
More Articles | ||
</p> | ||
|
||
<div className="grid grid-cols-1 gap-3 md:grid-cols-2 lg:gap-6"> | ||
{relatedArticles.map((post) => ( | ||
<Link | ||
key={post.slug} | ||
href={post.slug} | ||
className="flex flex-col space-y-2 rounded-xl border p-5 transition-colors duration-300 hover:bg-muted/80" | ||
> | ||
<h3 className="font-heading text-xl text-foreground"> | ||
{post.title} | ||
</h3> | ||
<p className="line-clamp-2 text-[15px] text-muted-foreground"> | ||
{post.description} | ||
</p> | ||
<p className="text-sm text-muted-foreground"> | ||
{formatDate(post.date)} | ||
</p> | ||
</Link> | ||
))} | ||
</div> | ||
</div> | ||
)} | ||
</MaxWidthWrapper> | ||
</> | ||
); | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
import { allPosts } from "contentlayer/generated"; | ||
import { Metadata } from "next"; | ||
import { notFound } from "next/navigation"; | ||
|
||
import { BlogCard } from "@/components/content/blog-card"; | ||
import { BLOG_CATEGORIES } from "@/config/blog"; | ||
import { constructMetadata } from "@/lib/utils"; | ||
|
||
export async function generateStaticParams() { | ||
return BLOG_CATEGORIES.map((category) => ({ | ||
slug: category.slug, | ||
})); | ||
} | ||
|
||
export async function generateMetadata({ | ||
params, | ||
}: { | ||
params: { slug: string }; | ||
}): Promise<Metadata | undefined> { | ||
const category = BLOG_CATEGORIES.find( | ||
(category) => category.slug === params.slug, | ||
); | ||
if (!category) { | ||
return; | ||
} | ||
|
||
const { title, description } = category; | ||
|
||
return constructMetadata({ | ||
title: `${title} Posts – Next SaaS Starter`, | ||
description, | ||
}); | ||
} | ||
|
||
export default async function BlogCategory({ | ||
params, | ||
}: { | ||
params: { | ||
slug: string; | ||
}; | ||
}) { | ||
const data = BLOG_CATEGORIES.find( | ||
(category) => category.slug === params.slug, | ||
); | ||
|
||
if (!data) { | ||
notFound(); | ||
} | ||
|
||
const articles = allPosts | ||
.filter((post) => post.categories.includes(data.slug)) | ||
.sort((a, b) => b.date.localeCompare(a.date)); | ||
|
||
return ( | ||
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> | ||
{articles.map((article, idx) => ( | ||
<BlogCard key={article._id} data={article} priority={idx <= 2} /> | ||
))} | ||
</div> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { BlogHeaderLayout } from "@/components/content/blog-header-layout"; | ||
import MaxWidthWrapper from "@/components/shared/max-width-wrapper"; | ||
|
||
export default function BlogLayout({ | ||
children, | ||
}: { | ||
children: React.ReactNode; | ||
}) { | ||
return ( | ||
<> | ||
<BlogHeaderLayout /> | ||
<MaxWidthWrapper className="pb-16">{children}</MaxWidthWrapper> | ||
</> | ||
); | ||
} |
Oops, something went wrong.