import type { Metadata } from "next"; import Link from "next/link"; import Image from "next/image"; import { Reveal, RevealGroup, RevealItem } from "../components/Reveal"; import { Newsletter } from "../components/Newsletter"; import { Footer } from "../components/Footer"; import { getBlogPosts, getBlogFilterEnabled } from "../lib/payload"; import { formatDate } from "../lib/format"; export const metadata: Metadata = { title: "Blog", description: "Gedanken, Methoden und Impulse für einen leichteren und klareren Alltag.", alternates: { canonical: "/blog" }, openGraph: { title: "Blog | einfach produktiv.", description: "Gedanken, Methoden und Impulse für einen leichteren und klareren Alltag.", url: "/blog", type: "website", images: ["/blog-featured.jpg"], }, }; // 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, blogFilterEnabled] = await Promise.all([getBlogPosts(100), getBlogFilterEnabled()]); const { categories: categoriesParam } = await searchParams; const activeCategories = blogFilterEnabled && categoriesParam ? categoriesParam.split(",").filter(Boolean) : []; const allCategories = blogFilterEnabled ? distinctCategories(allPosts) : []; const posts = activeCategories.length === 0 ? allPosts : allPosts.filter((post) => post.categories.some((c) => activeCategories.includes(c))); const [featured, ...rest] = posts; return ( <>
{/* Header — copy left, photo bleeds to the viewport edge on the right with a left-edge fade into bg-base, matching Figma's hero-fade-left/hero-fade-bottom overlays (node 4577:330). */}

Blog

Gedanken, Methoden und Impulse für einen leichteren und klareren Alltag.

{/* Moved into the hero's own text column (was a separate full- width bar below the entire hero, including the photo) — on mobile especially (text stacks above the photo here), that pushed the filters below a full extra screen's worth of hero image before they were even visible. Living right under the description keeps them in view immediately, on every width, no scrolling past the photo needed. */} {allCategories.length > 1 && (
{allCategories.map((category) => { const active = activeCategories.includes(category); return ( {category} ); })}
)}
{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. // relative z-10 keeps it stacked above the hero's own photo.
{featured.thumbnail && ( )}
{featured.categories.join(", ")} {featured.readTime} Min {formatDate(featured.publishedAt)}

{featured.title}

{featured.excerpt}

Zum Beitrag
)} {rest.length > 0 && ( {rest.map((post) => (
{post.thumbnail && ( )}
{post.categories.join(", ")} {post.readTime} Min {formatDate(post.publishedAt)}

{post.title}

{post.excerpt}

Zum Beitrag
))}
)}