From 4d525715e730110eb80bfaa118334f612eb64fae Mon Sep 17 00:00:00 2001 From: Mauricio Siu <47042324+Siumauricio@users.noreply.github.com> Date: Wed, 5 Nov 2025 01:12:31 -0600 Subject: [PATCH] feat: implement blog page structure with post listing, filtering, and individual post views; add components for code highlighting, table of contents, and zoomable images --- apps/website/app/[locale]/api/og/route.ts | 67 ------------------- .../blog/[slug]/components/CodeBlock.tsx | 19 +----- .../blog/[slug]/components/Headings.tsx | 21 +----- .../[slug]/components/TableOfContents.tsx | 48 ++++++------- .../blog/[slug]/components/ZoomableImage.tsx | 1 + .../blog/[slug]/components/shared.ts | 7 +- .../app/{[locale] => }/blog/[slug]/page.tsx | 39 ++--------- .../blog/components/BlogPostCard.tsx | 6 +- .../blog/components/SearchAndFilter.tsx | 0 apps/website/app/{[locale] => }/blog/page.tsx | 10 ++- .../{[locale] => }/blog/tag/[tag]/page.tsx | 20 +++--- apps/website/components/blog/BlogCard.tsx | 7 +- 12 files changed, 52 insertions(+), 193 deletions(-) delete mode 100644 apps/website/app/[locale]/api/og/route.ts rename apps/website/app/{[locale] => }/blog/[slug]/components/CodeBlock.tsx (80%) rename apps/website/app/{[locale] => }/blog/[slug]/components/Headings.tsx (87%) rename apps/website/app/{[locale] => }/blog/[slug]/components/TableOfContents.tsx (64%) rename apps/website/app/{[locale] => }/blog/[slug]/components/ZoomableImage.tsx (99%) rename apps/website/app/{[locale] => }/blog/[slug]/components/shared.ts (84%) rename apps/website/app/{[locale] => }/blog/[slug]/page.tsx (91%) rename apps/website/app/{[locale] => }/blog/components/BlogPostCard.tsx (96%) rename apps/website/app/{[locale] => }/blog/components/SearchAndFilter.tsx (100%) rename apps/website/app/{[locale] => }/blog/page.tsx (91%) rename apps/website/app/{[locale] => }/blog/tag/[tag]/page.tsx (88%) diff --git a/apps/website/app/[locale]/api/og/route.ts b/apps/website/app/[locale]/api/og/route.ts deleted file mode 100644 index 7a2dc79..0000000 --- a/apps/website/app/[locale]/api/og/route.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { getPost } from "@/lib/ghost"; -import { generateOGImage } from "@/lib/og-image"; -import type { NextRequest } from "next/server"; - -export async function GET( - request: NextRequest, - { params }: { params: { locale: string } }, -) { - try { - const { searchParams } = new URL(request.url); - const slug = searchParams.get("slug"); - - console.log( - "Generating OG image for slug:", - slug, - "locale:", - params.locale, - ); - - if (!slug) { - console.error("Missing slug parameter"); - return new Response("Missing slug parameter", { status: 400 }); - } - - const post = await getPost(slug); - - if (!post) { - console.error("Post not found for slug:", slug); - return new Response("Post not found", { status: 404 }); - } - - console.log("Found post:", post.title); - - const formattedDate = new Date(post.published_at).toLocaleDateString( - params.locale, - { - year: "numeric", - month: "long", - day: "numeric", - }, - ); - - const ogImage = await generateOGImage({ - title: post.title, - author: post.primary_author - ? { - name: post.primary_author.name, - image: post.primary_author.profile_image || undefined, - } - : undefined, - date: formattedDate, - readingTime: post.reading_time, - }); - - console.log("Successfully generated OG image"); - - return new Response(ogImage, { - headers: { - "Content-Type": "image/png", - "Cache-Control": "public, max-age=31536000, immutable", - }, - }); - } catch (error) { - console.error("Error generating OG image:", error); - return new Response(`Error generating image: ${error}`, { status: 500 }); - } -} diff --git a/apps/website/app/[locale]/blog/[slug]/components/CodeBlock.tsx b/apps/website/app/blog/[slug]/components/CodeBlock.tsx similarity index 80% rename from apps/website/app/[locale]/blog/[slug]/components/CodeBlock.tsx rename to apps/website/app/blog/[slug]/components/CodeBlock.tsx index 0cee0a2..da8be4e 100644 --- a/apps/website/app/[locale]/blog/[slug]/components/CodeBlock.tsx +++ b/apps/website/app/blog/[slug]/components/CodeBlock.tsx @@ -18,9 +18,7 @@ interface CodeBlockProps { async function formatCode(code: string, lang: string) { try { let parser: string; - let plugins = []; - - // Select parser and plugins based on language + let plugins = [] as any[]; switch (lang.toLowerCase()) { case "yaml": case "yml": @@ -35,12 +33,8 @@ async function formatCode(code: string, lang: string) { plugins = [babel, estree]; break; default: - // For unsupported languages, return the original code return code; } - - console.log(`Formatting ${lang} with parser:`, parser); - const formatted = await prettier.format(code, { parser, plugins, @@ -50,12 +44,10 @@ async function formatCode(code: string, lang: string) { useTabs: false, printWidth: 120, }); - - console.log("Formatted code:", formatted); return formatted; } catch (error) { console.error("Error formatting code:", error); - return code; // Return original code if there's an error + return code; } } @@ -66,22 +58,15 @@ export function CodeBlock({ code, lang, initial }: CodeBlockProps) { useLayoutEffect(() => { async function formatAndHighlight() { try { - console.log("Original code:", code); - console.log("Language:", lang); const formatted = await formatCode(code, lang); setFormattedCode(formatted); - - // Then highlight the formatted code const highlighted = await highlight(formatted, lang); setNodes(highlighted); } catch (error) { - console.error("Error in formatAndHighlight:", error); - // If formatting fails, try to highlight the original code const highlighted = await highlight(code, lang); setNodes(highlighted); } } - void formatAndHighlight(); }, [code, lang]); diff --git a/apps/website/app/[locale]/blog/[slug]/components/Headings.tsx b/apps/website/app/blog/[slug]/components/Headings.tsx similarity index 87% rename from apps/website/app/[locale]/blog/[slug]/components/Headings.tsx rename to apps/website/app/blog/[slug]/components/Headings.tsx index 540c247..cc6ac6d 100644 --- a/apps/website/app/[locale]/blog/[slug]/components/Headings.tsx +++ b/apps/website/app/blog/[slug]/components/Headings.tsx @@ -29,15 +29,10 @@ function LinkIcon() { export function H1({ children, ...props }: HeadingProps) { const router = useRouter(); - const id = slugify(children?.toString() || "", { - lower: true, - strict: true, - }); - + const id = slugify(children?.toString() || "", { lower: true, strict: true }); const handleClick = () => { router.push(`#${id}`); }; - return (
Table of Contents
No headings found
diff --git a/apps/website/app/[locale]/blog/[slug]/components/ZoomableImage.tsx b/apps/website/app/blog/[slug]/components/ZoomableImage.tsx similarity index 99% rename from apps/website/app/[locale]/blog/[slug]/components/ZoomableImage.tsx rename to apps/website/app/blog/[slug]/components/ZoomableImage.tsx index 702c342..7b97960 100644 --- a/apps/website/app/[locale]/blog/[slug]/components/ZoomableImage.tsx +++ b/apps/website/app/blog/[slug]/components/ZoomableImage.tsx @@ -3,6 +3,7 @@ import { cn } from "@/lib/utils"; import { PhotoProvider, PhotoView } from "react-photo-view"; import "react-photo-view/dist/react-photo-view.css"; + interface ZoomableImageProps { src: string; alt: string; diff --git a/apps/website/app/[locale]/blog/[slug]/components/shared.ts b/apps/website/app/blog/[slug]/components/shared.ts similarity index 84% rename from apps/website/app/[locale]/blog/[slug]/components/shared.ts rename to apps/website/app/blog/[slug]/components/shared.ts index 377f2ca..4e6096b 100644 --- a/apps/website/app/[locale]/blog/[slug]/components/shared.ts +++ b/apps/website/app/blog/[slug]/components/shared.ts @@ -10,10 +10,5 @@ export async function highlight(code: string, lang: BundledLanguage) { lang, theme: "houston", }); - - return toJsxRuntime(out, { - Fragment, - jsx, - jsxs, - }) as JSX.Element; + return toJsxRuntime(out, { Fragment, jsx, jsxs }) as JSX.Element; } diff --git a/apps/website/app/[locale]/blog/[slug]/page.tsx b/apps/website/app/blog/[slug]/page.tsx similarity index 91% rename from apps/website/app/[locale]/blog/[slug]/page.tsx rename to apps/website/app/blog/[slug]/page.tsx index 95a95db..1907a88 100644 --- a/apps/website/app/[locale]/blog/[slug]/page.tsx +++ b/apps/website/app/blog/[slug]/page.tsx @@ -1,6 +1,5 @@ import { getPost, getPosts } from "@/lib/ghost"; import type { Metadata, ResolvingMetadata } from "next"; -import { getTranslations } from "next-intl/server"; import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; @@ -18,15 +17,17 @@ import { CodeBlock } from "./components/CodeBlock"; import { H1, H2, H3 } from "./components/Headings"; import { TableOfContents } from "./components/TableOfContents"; import { ZoomableImage } from "./components/ZoomableImage"; +import { useTranslations } from "@/lib/intl"; + type Props = { - params: { locale: string; slug: string }; + params: { slug: string }; }; export async function generateMetadata( { params }: Props, parent: ResolvingMetadata, ): Promise