From 72c7b0454a4747ac59b8fa5224bb485d6cdaa5d8 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 30 Jul 2026 22:33:20 +0000 Subject: [PATCH] Add category toggle-button filters to the blog overview MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit URL-search-param-driven (?categories=A,B), same shareable/bookmarkable approach as the order-list filters — plain server-rendered toggle Links, no client component needed since /blog already fetches every published post in one request (limit 100, no pagination). Co-Authored-By: Claude Sonnet 5 --- app/blog/page.tsx | 64 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/app/blog/page.tsx b/app/blog/page.tsx index 402797c..c453f1a 100644 --- a/app/blog/page.tsx +++ b/app/blog/page.tsx @@ -20,8 +20,33 @@ export const metadata: Metadata = { }, }; -export default async function BlogOverviewPage() { - const posts = await getBlogPosts(100); +// Every distinct category name in DOM order — Payload's `categories` field +// stores related docs, no separate slug on this side, but since this page +// already fetches every published post (limit 100, no pagination), a +// plain client-agnostic array filter is enough; no separate Payload query +// per category needed. +function distinctCategories(posts: { categories: string[] }[]): string[] { + const seen = new Set(); + for (const post of posts) for (const c of post.categories) seen.add(c); + return Array.from(seen); +} + +function buildCategoryHref(active: string[], category: string): string { + const next = active.includes(category) ? active.filter((c) => c !== category) : [...active, category]; + return next.length > 0 ? `/blog?categories=${next.map(encodeURIComponent).join(",")}` : "/blog"; +} + +export default async function BlogOverviewPage({ + searchParams, +}: { + searchParams: Promise<{ categories?: string }>; +}) { + const allPosts = await getBlogPosts(100); + const { categories: categoriesParam } = await searchParams; + const activeCategories = categoriesParam ? categoriesParam.split(",").filter(Boolean) : []; + const allCategories = distinctCategories(allPosts); + const posts = + activeCategories.length === 0 ? allPosts : allPosts.filter((post) => post.categories.some((c) => activeCategories.includes(c))); const [featured, ...rest] = posts; return ( @@ -49,6 +74,41 @@ export default async function BlogOverviewPage() { + {allCategories.length > 1 && ( + + {allCategories.map((category) => { + const active = activeCategories.includes(category); + return ( + + {category} + + ); + })} + {activeCategories.length > 0 && ( + + Zurücksetzen + + )} + + )} + + {posts.length === 0 && ( +

+ Keine Beiträge in dieser Kategorie gefunden. +

+ )} + {featured && ( // -mt-8, not pt-10 — pulls the card up to slightly overlap the // hero section's bottom edge instead of sitting flush below it.