import { notFound } from "next/navigation"; import Image from "next/image"; import { getPostBySlug, getPosts } from "@/lib/query/post"; import type { Metadata } from "next"; import { format } from "date-fns"; import { ContentBlock, OutputData, ParagraphData, HeaderData, ListData, ImageData, QuoteData, CodeData, } from "@/types"; type Props = { params: { slug: string }; }; export async function generateStaticParams() { const posts = await getPosts(); if (!Array.isArray(posts)) { console.error("getPosts did not return an array:", posts); return []; } return posts.map((post) => ({ slug: post.slug, })); } function extractDescriptionFromBlocks( content: OutputData | null ): string | null { if ( !content || !content.blocks || !Array.isArray(content.blocks) || content.blocks.length === 0 ) { return null; } const firstParagraph = content.blocks.find( (block): block is ContentBlock & { data: ParagraphData } => block.type === "paragraph" ); if (firstParagraph?.data?.text) { const plainText = firstParagraph.data.text .replace(/<[^>]*>/g, "") .replace(/&[^;]+;/g, ""); return plainText.substring(0, 160) + (plainText.length > 160 ? "..." : ""); } return null; } export async function generateMetadata({ params }: Props): Promise { const { slug } = await params; const post = await getPostBySlug(slug, { fields: [ "slug", "status", "user_created", "date_created", "user_updated", "date_updated", "title", "content", "excerpt", "featured_image", ], }); if (!post) { return { title: "Post Not Found", }; } const imageUrl = post.featured_image; const descriptionFromContent = post.content && typeof post.content === "object" && Array.isArray(post.content.blocks) ? extractDescriptionFromBlocks(post.content as OutputData) : null; const description = post.excerpt || descriptionFromContent || "Read this OMS TechTalk article."; const publishedTime = post.date_created ? new Date(post.date_created).toISOString() : ""; return { title: `${post.title} | OMS TechTalk`, description: description, alternates: { canonical: `/tech-talk/${post.slug}`, }, openGraph: { title: post.title, description: description, url: `https://oms.africa/tech-talk/${post.slug}`, type: "article", publishedTime: publishedTime, ...(imageUrl && { images: [ { url: imageUrl, width: 1200, height: 630, alt: post.title, }, ], }), }, twitter: { card: "summary_large_image", title: post.title, description: description, ...(imageUrl && { images: [imageUrl] }), }, }; } const renderBlock = (block: ContentBlock) => { const renderListItems = (items: string[]) => { return items.map((item, index) => (
  • )); }; switch (block.type) { case "header": const headerBlock = block as ContentBlock & { data: HeaderData }; const level = headerBlock.data.level || 2; // Ensure HeaderTag has a type that JSX understands for intrinsic elements. const HeaderTag = `h${level}` as "h1" | "h2" | "h3" | "h4" | "h5" | "h6"; return ( ); case "paragraph": const paragraphBlock = block as ContentBlock & { data: ParagraphData }; return (

    ); case "list": const listBlock = block as ContentBlock & { data: ListData }; const ListTag = listBlock.data.style === "ordered" ? "ol" : "ul"; return ( {listBlock.data.items && Array.isArray(listBlock.data.items) && renderListItems(listBlock.data.items)} ); case "image": const imageBlock = block as ContentBlock & { data: ImageData }; const imageUrl = imageBlock.data.file?.url; if (!imageUrl) return null; return (

    {imageBlock.data.caption {imageBlock.data.caption && (

    {imageBlock.data.caption}

    )}
    ); case "quote": const quoteBlock = block as ContentBlock & { data: QuoteData }; return (

    {quoteBlock.data.caption && (

    - {quoteBlock.data.caption}
    )}
    ); case "code": const codeBlock = block as ContentBlock & { data: CodeData }; const codeContent = codeBlock.data.code || ""; return (
              {codeContent}
            
    ); default: console.warn(`Unknown block type: ${block.type}`); return null; } }; export default async function PostPage({ params }: Props) { const { slug } = await params; const post = await getPostBySlug(slug); if (!post) { notFound(); } const contentBlocks = post.content && typeof post.content === "object" && Array.isArray(post.content.blocks) ? (post.content.blocks as ContentBlock[]) : null; const coverImageUrl = post.featured_image + "?width=1200&height=600&quality=80&fit=cover"; return (
    {coverImageUrl && (
    {`Cover
    )}

    {post.title}

    {post.date_created && (

    Published on {format(new Date(post.date_created), "MMMM dd, yyyy")}

    )} {post.excerpt && (

    {post.excerpt}

    )}
    {contentBlocks && contentBlocks.length > 0 ? ( contentBlocks.map((block) => renderBlock(block)) ) : (

    Content is not available or in an unexpected format.

    )}
    ); }