import { PortableText, type PortableTextComponents, type SanityDocument } from "next-sanity"; import { client } from "../../../sanity/client"; import Link from "next/link"; import { ArrowLeft } from 'lucide-react'; import styles from '../styles.module.css'; import { postBySlugQuery } from '../queries'; import ReactMarkdown from 'react-markdown'; import type { Components } from 'react-markdown'; import React from 'react'; const options = { next: { revalidate: 30 } }; // Define custom PortableText components to adjust heading levels const portableTextComponents: Partial = { block: { h1: ({value, children}) =>

{children}

, h2: ({value, children}) =>

{children}

, h3: ({value, children}) =>
{children}
, h4: ({value, children}) =>
{children}
, } }; interface SanityImage { asset: { _id: string; url: string; metadata: { dimensions?: { width: number; height: number; aspectRatio: number; }; }; }; hotspot?: { x: number; y: number }; crop?: { top: number; bottom: number; left: number; right: number }; } interface ImageWithDescription { image: SanityImage; description: string | null; } interface PostSection { _type: 'postSection'; _key: string; sectionTitle?: string | null; sectionImage?: ImageWithDescription; sectionBody: any[]; } interface MarkdownSection { _type: 'markdownSection'; _key: string; content: string; } type Section = PostSection | MarkdownSection; interface BlogPost extends SanityDocument { _id: string; _createdAt: string; title: string; slug: string; publishedAt: string; excerpt?: string; mainImage?: ImageWithDescription; sections?: Section[]; } // Function to render a regular post section function RenderPostSection({ section }: { section: PostSection }) { return (
{section.sectionTitle && (

{section.sectionTitle}

)} {section.sectionImage?.image?.asset?.url && (
{section.sectionImage.description {section.sectionImage.description && (
{section.sectionImage.description}
)}
)}
); } // Function to render a markdown section function RenderMarkdownSection({ section }: { section: MarkdownSection }) { if (!section.content) { console.warn('Markdown section has no content:', section._key); return null; } const markdownComponents: Components = { h1: ({node, ...props}) => (

), h2: ({node, ...props}) => (

), h3: ({node, ...props}) => (

), h4: ({node, ...props}) => (

), // Handle images outside of paragraphs p: ({node, children, ...props}) => { // Check if the only child is an img element const hasOnlyImage = React.Children.toArray(children).every( child => React.isValidElement(child) && child.type === 'img' ); // If it's just an image, don't wrap in

if (hasOnlyImage) { return <>{children}; } return

{children}

; }, // Handle images with custom figure wrapper img: ({node, src, alt}) => (
{alt {alt && (
{alt}
)}
), // Add blockquote styling blockquote: ({node, ...props}) => (
), // Add list styling ul: ({node, ...props}) => (
    ), ol: ({node, ...props}) => (
      ), }; return (
      {section.content}
      ); } export default async function PostPage({ params, }: { params: Promise<{ slug: string }>; }) { const post = await client.fetch(postBySlugQuery, await params, options); if (!post) { return (

      Post not found

      Return to blog
      ); } return (

      {post.title}

      {post.excerpt && (

      {post.excerpt}

      )} {post.mainImage?.image?.asset?.url && (
      {post.mainImage.description {post.mainImage.description && (
      {post.mainImage.description}
      )}
      )}
      {post.sections?.map((section) => { if (!section._type) { console.warn('Section missing _type:', section); return null; } return section._type === 'markdownSection' ? : ; })}
      ); }