'use client' import dayjs from 'dayjs' import { MDXRemote } from 'next-mdx-remote' import dynamic from 'next/dynamic' import Image from 'next/image' import Link from 'next/link' import { useState, useMemo } from 'react' import { ChevronLeft } from 'lucide-react' import { Badge } from 'ui' import mdxComponents from 'lib/mdx/mdxComponents' import type { CMSAuthor, PostReturnType, ProcessedBlogData, Tag } from 'types/post' import type { MDXRemoteSerializeResult } from 'next-mdx-remote' import type { ComponentType } from 'react' const ShareArticleActions = dynamic(() => import('components/Blog/ShareArticleActions')) const CTABanner = dynamic(() => import('components/CTABanner')) const LW11Summary = dynamic(() => import('components/LaunchWeek/11/LW11Summary')) const LW12Summary = dynamic(() => import('components/LaunchWeek/12/LWSummary')) const LW13Summary = dynamic(() => import('components/LaunchWeek/13/Releases/LWSummary')) const LW14Summary = dynamic(() => import('components/LaunchWeek/14/Releases/LWSummary')) const LW15Summary = dynamic(() => import('components/LaunchWeek/15/LWSummary')) const BlogLinks = dynamic(() => import('components/LaunchWeek/7/BlogLinks')) const LWXSummary = dynamic(() => import('components/LaunchWeek/X/LWXSummary')) const DefaultLayout = dynamic(() => import('components/Layouts/Default')) const DraftModeBanner = dynamic(() => import('components/Blog/DraftModeBanner')) const ReactMarkdown = dynamic<{ children: string }>( () => import('react-markdown').then( (m) => m.default as unknown as ComponentType<{ children: string }> ), { ssr: false } ) const BlogPostRenderer = ({ blog, blogMetaData, isDraftMode, livePreviewData, prevPost, nextPost, authors, }: { blog: ProcessedBlogData blogMetaData: ProcessedBlogData isDraftMode: boolean livePreviewData?: ProcessedBlogData isLivePreviewLoading?: boolean prevPost?: PostReturnType | null nextPost?: PostReturnType | null authors: CMSAuthor[] }) => { const [previewData] = useState(blog) const shouldUseLivePreview = isDraftMode && blog.isCMS // For LivePreview, we'll use the raw content directly with ReactMarkdown // instead of trying to use MDXRemote which requires specific serialization const isLivePreview = isDraftMode && (livePreviewData !== undefined || previewData !== blog) // Extract raw content from data if available const livePreviewContent = useMemo(() => { // Priority 1: Use data from LivePreview hook (only in draft mode) if ( isDraftMode && shouldUseLivePreview && livePreviewData && typeof livePreviewData === 'object' ) { // If content is a string, use it directly if (typeof (livePreviewData as unknown as { content?: unknown }).content === 'string') { return (livePreviewData as unknown as { content?: string }).content as string } // If content is from source property if ( (livePreviewData as unknown as { source?: unknown }).source && typeof (livePreviewData as unknown as { source?: unknown }).source === 'string' ) { return (livePreviewData as unknown as { source?: string }).source as string } } // Priority 2: Use data from postMessage updates if (isDraftMode && previewData !== blog) { // If content is a string, use it directly if (typeof (previewData as unknown as { content?: unknown }).content === 'string') { return (previewData as unknown as { content?: string }).content as string } // If content is from source property if ( (previewData as unknown as { source?: unknown }).source && typeof (previewData as unknown as { source?: unknown }).source === 'string' ) { return (previewData as unknown as { source?: string }).source as string } } return blog.source || '' }, [isDraftMode, shouldUseLivePreview, livePreviewData, previewData, blog]) const isCMS = blogMetaData.isCMS const isLaunchWeek7 = blogMetaData.launchweek === '7' const isLaunchWeekX = blogMetaData.launchweek?.toString().toLocaleLowerCase() === 'x' const isGAWeek = blogMetaData.launchweek?.toString().toLocaleLowerCase() === '11' const isLaunchWeek12 = blogMetaData.launchweek?.toString().toLocaleLowerCase() === '12' const isLaunchWeek13 = blogMetaData.launchweek?.toString().toLocaleLowerCase() === '13' const isLaunchWeek14 = blogMetaData.launchweek?.toString().toLocaleLowerCase() === '14' const isLaunchWeek15 = blogMetaData.launchweek?.toString().toLocaleLowerCase() === '15' type NextCardProps = { post: { path: string; title: string; formattedDate: string } label: string className?: string } const NextCard = (props: NextCardProps) => { const { post, label, className } = props return (

{label}

{'title' in post && (

{(post as { title?: string }).title}

)} {'formattedDate' in post && (

{(post as { formattedDate?: string }).formattedDate}

)}
) } const toc = blogMetaData.toc && (

On this page

{blogMetaData.toc && ( {typeof blogMetaData.toc === 'string' ? (blogMetaData.toc as string) : (blogMetaData.toc as { content: string }).content} )}
) const imageUrl = isCMS ? blogMetaData.thumb ?? '' : blogMetaData.thumb ? `/images/blog/${blogMetaData.thumb}` : '' return ( <> {isDraftMode && }
{/* Back button */} Back
{/* Title and description */}
Blog

{blogMetaData.title}

{dayjs(blogMetaData.date).format('DD MMM YYYY')}

•

{(blogMetaData as any).readingTime}

{authors.length > 0 && (
{authors.map((author, i: number) => { // Handle both static and CMS author image formats const authorImageUrl = typeof author.author_image_url === 'string' ? author.author_image_url : (author.author_image_url as { url: string })?.url || '' return (
{authorImageUrl && (
{`${author.author}
)}
{author.author} {author.position}
) })}
)}
{/* Content */}
{blogMetaData.youtubeHero ? (