Files
mainasara.dev/src/app/stories/[slug]/page.tsx
neutrino2211 ac62ea0fe9 update
2025-12-17 11:03:29 +01:00

162 lines
5.2 KiB
TypeScript

import { notFound } from "next/navigation";
import Link from "next/link";
import { loadStories } from "@/utils/mdxLoader";
import { formatReadingTime } from "@/utils/readingTime";
interface StoryPageProps {
params: {
slug: string;
};
}
import fs from "fs";
import { Metadata } from "next";
export async function generateStaticParams() {
const storiesDirectory = process.cwd() + "/src/content/stories";
try {
const filenames = fs.readdirSync(storiesDirectory);
return filenames.map((filename: string) => ({
slug: filename.replace(/\.mdx$/, ""),
}));
} catch {
return [];
}
}
export async function generateMetadata({
params,
}: StoryPageProps): Promise<Metadata> {
const story = await getStoryBySlug(params.slug);
if (!story) {
return {
title: "Story Not Found",
};
}
return {
title: `${story.title} | Mainasara Tsowa`,
description: story.excerpt,
openGraph: {
title: `${story.title} | Mainasara Tsowa`,
description: story.excerpt,
url: `https://mainasara.dev/stories/${params.slug}`,
},
twitter: {
title: `${story.title} | Mainasara Tsowa`,
description: story.excerpt,
},
};
}
async function getStoryBySlug(slug: string) {
const stories = await loadStories();
return stories.find(
(story) => story.id === slug.replace(/[^a-zA-Z0-9]/g, "-"),
);
}
export default async function StoryPage({ params }: StoryPageProps) {
const story = await getStoryBySlug(params.slug);
if (!story) {
notFound();
}
return (
<div className="min-h-screen bg-[#FAF8F5] font-mono">
<div className="max-w-4xl mx-auto px-6 py-12">
<header className="text-center mb-16">
<div className="mb-8">
<div className="w-32 h-32 mx-auto bg-[#3D3D3D] rounded-full flex items-center justify-center border-[3px] border-[#F0E8D8]">
<div className="text-4xl font-bold text-[#F0E8D8]">📝</div>
</div>
</div>
<h1 className="text-4xl md:text-5xl font-bold text-[#3D3D3D] mb-4 uppercase tracking-wide">
Mini Stories
</h1>
<p className="text-lg text-[#5D5D5D] max-w-2xl mx-auto leading-relaxed font-semibold">
Capturing moments in brief narratives
</p>
</header>
<nav className="mb-16">
<div className="bg-[#FEFDFB] border-[3px] border-[#3D3D3D] rounded-md p-6 shadow-brutal">
<ul className="flex flex-wrap justify-center gap-4 md:gap-8">
<li>
<Link
href="/"
className="text-[#3D3D3D] font-bold hover:bg-[#CBD6C8] px-4 py-2 transition-colors border-2 border-[#3D3D3D] hover:border-[#5D5D5D] rounded-sm"
>
Home
</Link>
</li>
<li>
<Link
href="/digital-art"
className="text-[#3D3D3D] font-bold hover:bg-[#E0C9CC] px-4 py-2 transition-colors border-2 border-[#3D3D3D] hover:border-[#5D5D5D] rounded-sm"
>
Digital Art
</Link>
</li>
<li>
<Link
href="/stories"
className="border-[#3D3D3D] bg-[#F0E8D8] px-4 py-2 border-2 text-[#3D3D3D] rounded-sm"
>
All Stories
</Link>
</li>
<li>
<Link
href="/thoughts"
className="text-[#3D3D3D] font-bold hover:bg-[#B8C5CE] px-4 py-2 transition-colors border-2 border-[#3D3D3D] hover:border-[#5D5D5D] rounded-sm"
>
Thoughts
</Link>
</li>
</ul>
</div>
</nav>
<main>
<article className="bg-[#FEFDFB] border-[3px] border-[#3D3D3D] rounded-md p-8 shadow-brutal">
<div className="mb-8">
<h1 className="text-3xl md:text-4xl font-bold text-[#3D3D3D] uppercase mb-4">
{story.title}
</h1>
<div className="flex gap-4 text-sm mb-6">
<span className="bg-[#F0E8D8] text-[#3D3D3D] px-3 py-1 border-2 border-[#3D3D3D] font-bold rounded-sm">
{formatReadingTime(story.readTime)} read
</span>
<span className="bg-[#E8DCC8] text-[#3D3D3D] px-3 py-1 border-2 border-[#3D3D3D] font-bold rounded-sm">
{new Date(story.createdAt).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
</span>
</div>
</div>
<div className="prose prose-lg max-w-none">
<div className="text-[#5D5D5D] leading-relaxed whitespace-pre-line">
{story.content}
</div>
</div>
</article>
</main>
<footer className="mt-20 text-center">
<div className="bg-[#3D3D3D] text-white p-4 font-bold rounded-md">
<p className="text-sm uppercase">Built with neo-brutalist muted colors</p>
</div>
</footer>
</div>
</div>
);
}