Some checks failed
Build & Deploy / 🔍 Prepare (push) Successful in 10s
Build & Deploy / 🧪 QA (push) Failing after 2m24s
Build & Deploy / 🏗️ Build (push) Failing after 3m40s
Build & Deploy / 🚀 Deploy (push) Has been skipped
Build & Deploy / 🩺 Health Check (push) Has been skipped
Build & Deploy / 🔔 Notify (push) Successful in 3s
137 lines
4.0 KiB
TypeScript
137 lines
4.0 KiB
TypeScript
import * as React from "react";
|
|
import type { Metadata } from "next";
|
|
import { notFound, redirect } from "next/navigation";
|
|
import { getPayloadHMR } from "@payloadcms/next/utilities";
|
|
import configPromise from "@payload-config";
|
|
import { getAllPosts } from "@/src/lib/posts";
|
|
import { BlogPostHeader } from "@/src/components/blog/BlogPostHeader";
|
|
import { Section } from "@/src/components/Section";
|
|
import { Reveal } from "@/src/components/Reveal";
|
|
import { BlogPostClient } from "@/src/components/BlogPostClient";
|
|
import { TextSelectionShare } from "@/src/components/TextSelectionShare";
|
|
import { BlogPostStickyBar } from "@/src/components/blog/BlogPostStickyBar";
|
|
import { MDXContent } from "@/src/components/MDXContent";
|
|
import { PayloadRichText } from "@/src/components/PayloadRichText";
|
|
import { TableOfContents } from "@/src/components/TableOfContents";
|
|
|
|
export async function generateStaticParams() {
|
|
const allPosts = await getAllPosts();
|
|
return allPosts.map((post) => ({
|
|
slug: post.slug,
|
|
}));
|
|
}
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>;
|
|
}): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const allPosts = await getAllPosts();
|
|
const post = allPosts.find((p) => p.slug === slug);
|
|
|
|
if (!post) return {};
|
|
|
|
return {
|
|
title: post.title,
|
|
description: post.description,
|
|
openGraph: {
|
|
title: post.title,
|
|
description: post.description,
|
|
type: "article",
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: post.title,
|
|
description: post.description,
|
|
},
|
|
};
|
|
}
|
|
|
|
export default async function BlogPostPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ slug: string }>;
|
|
}) {
|
|
const { slug } = await params;
|
|
const allPosts = await getAllPosts();
|
|
const post = allPosts.find((p) => p.slug === slug);
|
|
|
|
if (!post) {
|
|
const payload = await getPayloadHMR({ config: configPromise });
|
|
const redirectDoc = await payload.find({
|
|
collection: "redirects",
|
|
where: {
|
|
from: { equals: slug },
|
|
},
|
|
});
|
|
|
|
if (redirectDoc.docs.length > 0) {
|
|
redirect(`/blog/${redirectDoc.docs[0].to}`);
|
|
}
|
|
|
|
notFound();
|
|
}
|
|
|
|
const formattedDate = new Date(post.date).toLocaleDateString("de-DE", {
|
|
month: "long",
|
|
day: "numeric",
|
|
year: "numeric",
|
|
});
|
|
|
|
const wordCount = post.description.split(/\s+/).length + 300;
|
|
const readingTime = Math.max(1, Math.ceil(wordCount / 200));
|
|
|
|
return (
|
|
<div className="flex flex-col gap-8 md:gap-12 py-8 md:py-24 overflow-hidden">
|
|
<BlogPostClient readingTime={readingTime} title={post.title} />
|
|
|
|
<BlogPostHeader
|
|
title={post.title}
|
|
description={post.description}
|
|
date={formattedDate}
|
|
readingTime={readingTime}
|
|
slug={slug}
|
|
thumbnail={post.thumbnail}
|
|
/>
|
|
|
|
<main id="post-content">
|
|
<BlogPostStickyBar
|
|
title={post.title}
|
|
url={`https://mintel.me/blog/${slug}`}
|
|
/>
|
|
|
|
<Section containerVariant="wide" className="pt-0 md:pt-0">
|
|
<div className="max-w-4xl mx-auto px-5 md:px-0">
|
|
<Reveal delay={0.4} width="100%">
|
|
{post.tags && post.tags.length > 0 && (
|
|
<div className="flex flex-wrap gap-2 mb-10 md:mb-12">
|
|
{post.tags?.map((tag) => (
|
|
<span
|
|
key={tag}
|
|
className="px-2.5 py-1 bg-slate-50 border border-slate-100 rounded text-[9px] md:text-[10px] font-mono text-slate-500 uppercase tracking-widest"
|
|
>
|
|
#{tag}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
|
|
<div className="article-content max-w-none">
|
|
<TableOfContents />
|
|
{post.lexicalContent ? (
|
|
<PayloadRichText data={post.lexicalContent} />
|
|
) : (
|
|
<MDXContent code={post.body.code} />
|
|
)}
|
|
</div>
|
|
</Reveal>
|
|
</div>
|
|
</Section>
|
|
</main>
|
|
|
|
<TextSelectionShare />
|
|
</div>
|
|
);
|
|
}
|