Skip to content

Commit

Permalink
refactor: add blog categories & improving design
Browse files Browse the repository at this point in the history
  • Loading branch information
mickasmt committed Jun 19, 2024
1 parent 651f5c8 commit 3b711bd
Show file tree
Hide file tree
Showing 22 changed files with 629 additions and 328 deletions.
165 changes: 165 additions & 0 deletions app/(marketing)/(blog-post)/blog/[slug]/page.tsx
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>
</>
);
}
130 changes: 0 additions & 130 deletions app/(marketing)/blog/[slug]/page.tsx

This file was deleted.

61 changes: 61 additions & 0 deletions app/(marketing)/blog/category/[slug]/page.tsx
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>
);
}
15 changes: 15 additions & 0 deletions app/(marketing)/blog/layout.tsx
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>
</>
);
}
Loading

0 comments on commit 3b711bd

Please sign in to comment.