svrjs-nextjs-website/app/(root)/blog/[slug]/page.tsx

186 lines
4.5 KiB
TypeScript
Raw Normal View History

2024-08-08 11:21:56 +02:00
import { client, urlFor } from "@/lib/sanity";
2024-08-26 17:26:02 +02:00
import { PortableText, PortableTextComponents } from "@portabletext/react";
2024-08-08 11:21:56 +02:00
import Image from "next/image";
import Link from "next/link";
2024-08-26 18:20:10 +02:00
import { ArrowLeft } from "lucide-react";
2024-08-08 11:21:56 +02:00
import { Separator } from "@/components/ui/separator";
2024-08-20 16:28:54 +02:00
import { notFound } from "next/navigation";
2024-08-08 19:23:56 +02:00
import { Metadata } from "next";
2024-08-20 16:28:54 +02:00
import { format } from "date-fns";
2024-08-26 17:26:02 +02:00
import Prism from "prismjs";
2024-08-26 18:20:10 +02:00
import "prismjs/components/prism-javascript";
import "prismjs/components/prism-python";
import "prismjs/components/prism-php";
2024-08-26 17:26:02 +02:00
import CopyButton from "@/components/shared/copyButton";
2024-08-26 17:40:18 +02:00
import "./_styles/prism-twilight.css";
import "./_styles/prism.twilight.min.css";
2024-08-26 18:20:10 +02:00
import PrismLoader from "@/components/loader/prismLoader";
2024-08-08 11:21:56 +02:00
async function getData(slug: string) {
const query = `
*[_type == "blog" && slug.current == '${slug}'] {
"currentSlug": slug.current,
2024-08-20 16:28:54 +02:00
title,
content,
titleImage,
_createdAt
2024-08-08 11:21:56 +02:00
}[0]`;
const data = await client.fetch(query);
return data;
}
interface BlogSlugArticle {
currentSlug: string;
title: string;
content: any;
titleImage: string;
2024-08-20 16:28:54 +02:00
_createdAt: string;
2024-08-08 11:21:56 +02:00
}
2024-08-08 19:23:56 +02:00
export async function generateMetadata({
params,
}: {
params: { slug: string };
}): Promise<Metadata> {
const data = await getData(params.slug);
if (!data) {
return {
title: "Not Found",
description: "Blog post not found",
};
}
return {
2024-08-26 13:11:47 +02:00
title: `${data.title} - SVRJS`,
2024-08-08 19:23:56 +02:00
description: data.smallDescription,
openGraph: {
2024-08-26 13:11:47 +02:00
title: `${data.title} - SVRJS`,
2024-08-08 19:23:56 +02:00
description: data.smallDescription,
url: `https://svrjs.org/blog/${data.currentSlug}`,
type: "website",
images: [
{
url: urlFor(data.titleImage).url(),
width: 800,
height: 600,
2024-08-26 13:11:47 +02:00
alt: `${data.title} - SVRJS`,
2024-08-08 19:23:56 +02:00
},
],
},
twitter: {
card: "summary_large_image",
site: "@SVR_JS",
2024-08-26 13:11:47 +02:00
title: `${data.title} - SVRJS`,
2024-08-08 19:23:56 +02:00
description: data.smallDescription,
images: [urlFor(data.titleImage).url()],
creator: "@SVR_JS",
},
};
}
2024-08-26 17:26:02 +02:00
const customPortableTextComponents: PortableTextComponents = {
types: {
image: ({ value }) => {
return (
<div className="my-8">
<Image
src={urlFor(value).url()}
alt={value.alt || "Blog Image"}
width={1200}
height={800}
className="w-full h-auto rounded-lg"
/>
{value.caption && (
<p className="mt-2 text-center text-sm text-muted-foreground">
{value.caption}
</p>
)}
</div>
);
},
code: ({ value }) => {
2024-08-26 18:20:10 +02:00
const language = value.language || "javascript";
const grammar = Prism.languages[language];
if (!grammar) {
console.error(`No grammar found for language: "${language}"`);
return (
<pre className="p-4 rounded-md overflow-x-auto text-sm">
2024-08-26 18:20:10 +02:00
<code>{value.code}</code>
</pre>
);
}
2024-08-26 17:26:02 +02:00
return (
<div className="relative my-8">
<pre
className={`language-${language} p-4 rounded-md overflow-x-auto text-sm`}
2024-08-26 17:26:02 +02:00
>
2024-08-26 18:20:10 +02:00
<code className={`language-${language}`}>{value.code}</code>
2024-08-26 17:26:02 +02:00
</pre>
2024-08-26 18:20:10 +02:00
<PrismLoader />
2024-08-26 17:26:02 +02:00
<CopyButton code={value.code} />
</div>
);
},
},
};
2024-08-08 11:21:56 +02:00
export default async function BlogSlugArticle({
params,
}: {
params: { slug: string };
}) {
const data: BlogSlugArticle = await getData(params.slug);
2024-08-08 19:23:56 +02:00
if (!data) {
2024-08-20 16:28:54 +02:00
notFound();
2024-08-08 19:23:56 +02:00
}
2024-08-20 16:28:54 +02:00
const formattedDate = format(new Date(data._createdAt), "MMMM d, yyyy");
2024-08-08 11:21:56 +02:00
return (
2024-08-08 19:23:56 +02:00
<>
<section className="max-w-5xl container mx-auto py-8 md:py-28 flex flex-col items-center px-4">
<Link
href="/blog?page=1"
className="self-start mb-4 text-primary hover:text-green-300 transition-all flex items-center"
2024-08-08 19:23:56 +02:00
>
<ArrowLeft className="mr-2" />
Back to Blog
</Link>
2024-08-20 16:28:54 +02:00
<header className="text-start mb-8 w-full">
2024-08-08 19:23:56 +02:00
{data.titleImage && (
2024-08-20 16:28:54 +02:00
<div className="mb-2">
<h1 className="text-3xl md:text-5xl mb-8 py-4 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
2024-08-08 19:23:56 +02:00
{data.title}
</h1>
<Image
src={urlFor(data.titleImage).url()}
alt={data.title}
width={1200}
height={800}
priority
className="w-full h-auto object-cover rounded-md"
/>
<p className="mt-4 text-lg md:text-xl text-muted-foreground">
2024-08-20 16:28:54 +02:00
Uploaded at {formattedDate}
2024-08-26 18:20:10 +02:00
</p>
2024-08-08 19:23:56 +02:00
</div>
)}
</header>
<Separator className="mb-6" />
<article className="prose max-w-full md:prose-lg dark:prose-invert">
2024-08-26 17:26:02 +02:00
<PortableText
value={data.content}
components={customPortableTextComponents}
/>
2024-08-08 19:23:56 +02:00
</article>
</section>
</>
2024-08-08 11:21:56 +02:00
);
}