e3352d7e32
- Posts.categories is now hasMany — blog list/detail/live-preview render a comma-joined list instead of a single category. - Checkout's "Abweichende Lieferadresse" gains optional Firma + Kontakt- E-Mail/Telefon fields (handed to the shipping carrier, not used for customer communication). - Customer profile can now store its own shipping address (mirroring Customers.ts's new "Lieferadresse" tab), prefilling the checkout override instead of always starting blank. - Product-level optional SKU (previously only on variants) snapshots onto each order item and shows up on invoices (visual + EN16931 XML), the confirmation email, and the account order detail page.
149 lines
7.2 KiB
TypeScript
149 lines
7.2 KiB
TypeScript
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 } 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"],
|
|
},
|
|
};
|
|
|
|
export default async function BlogOverviewPage() {
|
|
const posts = await getBlogPosts(100);
|
|
const [featured, ...rest] = posts;
|
|
|
|
return (
|
|
<>
|
|
<main className="flex flex-col flex-1 bg-bg-base">
|
|
{/* 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). */}
|
|
<Reveal className="relative flex flex-col sm:flex-row items-center w-full min-h-[20rem] sm:min-h-[27.5rem] border-b border-border overflow-hidden">
|
|
<div className="relative z-10 flex flex-col gap-4 items-start px-[var(--layout-padding-x)] py-10 sm:py-0 w-full sm:w-auto sm:max-w-[26rem]">
|
|
<p
|
|
className="font-semibold text-display text-text-primary"
|
|
style={{ fontFamily: "var(--font-lora)" }}
|
|
>
|
|
Blog
|
|
</p>
|
|
<p className="text-body text-text-muted">
|
|
Gedanken, Methoden und Impulse für einen leichteren und klareren Alltag.
|
|
</p>
|
|
</div>
|
|
<div className="relative w-full sm:absolute sm:inset-y-0 sm:right-0 sm:w-[68%] h-56 sm:h-full">
|
|
<Image alt="" src="/hero.png" fill sizes="(min-width: 640px) 68vw, 100vw" className="object-cover object-top" />
|
|
<div className="hidden sm:block absolute inset-y-0 left-0 w-72 bg-gradient-to-r from-bg-base to-transparent" />
|
|
<div className="absolute inset-x-0 bottom-0 h-16 bg-gradient-to-t from-bg-base to-transparent" />
|
|
</div>
|
|
</Reveal>
|
|
|
|
{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.
|
|
<Reveal delay={0.1} className="relative z-10 w-full px-[var(--layout-padding-x)] -mt-8 pb-4">
|
|
<Link
|
|
href={`/blog/${featured.slug}`}
|
|
className="group grid grid-cols-1 sm:grid-cols-2 w-full max-w-[80rem] mx-auto rounded-md overflow-hidden bg-bg-muted transition-transform duration-300 hover:-translate-y-1"
|
|
>
|
|
<div className="relative w-full aspect-video sm:aspect-auto sm:h-full bg-bg-muted">
|
|
{featured.thumbnail && (
|
|
<Image alt="" src={featured.thumbnail} fill sizes="(min-width: 640px) 40rem, 100vw" className="object-cover" />
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col justify-center gap-3 p-8 sm:p-12 min-w-0">
|
|
<div className="flex items-center gap-2 font-semibold text-text-muted text-body-sm uppercase tracking-wide">
|
|
<span>{featured.categories.join(", ")}</span>
|
|
<span>•</span>
|
|
<span>{featured.readTime} Min</span>
|
|
<span>•</span>
|
|
<span className="uppercase">{formatDate(featured.publishedAt)}</span>
|
|
</div>
|
|
<p
|
|
className="font-semibold text-h-section text-text-primary"
|
|
style={{ fontFamily: "var(--font-lora)" }}
|
|
>
|
|
{featured.title}
|
|
</p>
|
|
<p className="text-body text-text-muted line-clamp-3">{featured.excerpt}</p>
|
|
<span className="flex items-center gap-1.5 font-bold text-body text-text-primary mt-1">
|
|
Zum Beitrag
|
|
<svg
|
|
viewBox="0 0 20 20"
|
|
className="size-4 transition-transform duration-200 group-hover:translate-x-1"
|
|
fill="none"
|
|
aria-hidden="true"
|
|
>
|
|
<path d="M4 10h12m0 0-5-5m5 5-5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
</Reveal>
|
|
)}
|
|
|
|
{rest.length > 0 && (
|
|
<RevealGroup className="flex flex-col w-full max-w-[80rem] mx-auto px-[var(--layout-padding-x)] py-6 divide-y divide-border">
|
|
{rest.map((post) => (
|
|
<RevealItem key={post.id} className="py-8 first:pt-0 last:pb-0">
|
|
<Link href={`/blog/${post.slug}`} className="group flex flex-col sm:flex-row gap-6 items-start">
|
|
<div className="relative w-full sm:w-[18.75rem] aspect-video sm:aspect-[300/192] shrink-0 rounded-md overflow-hidden bg-bg-muted">
|
|
{post.thumbnail && (
|
|
<Image alt="" src={post.thumbnail} fill sizes="(min-width: 640px) 18.75rem, 100vw" className="object-cover" />
|
|
)}
|
|
</div>
|
|
<div className="flex flex-col gap-2 min-w-0 flex-1">
|
|
<div className="flex items-center gap-2 font-semibold text-text-muted text-body-sm uppercase tracking-wide">
|
|
<span>{post.categories.join(", ")}</span>
|
|
<span>•</span>
|
|
<span>{post.readTime} Min</span>
|
|
<span>•</span>
|
|
<span className="uppercase">{formatDate(post.publishedAt)}</span>
|
|
</div>
|
|
<p
|
|
className="font-semibold text-h-small text-text-primary"
|
|
style={{ fontFamily: "var(--font-lora)" }}
|
|
>
|
|
{post.title}
|
|
</p>
|
|
<p className="text-body text-text-muted">{post.excerpt}</p>
|
|
<span className="flex items-center gap-1.5 font-bold text-body-sm text-text-primary mt-1">
|
|
Zum Beitrag
|
|
<svg
|
|
viewBox="0 0 20 20"
|
|
className="size-3.5 transition-transform duration-200 group-hover:translate-x-1"
|
|
fill="none"
|
|
aria-hidden="true"
|
|
>
|
|
<path d="M4 10h12m0 0-5-5m5 5-5 5" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
</svg>
|
|
</span>
|
|
</div>
|
|
</Link>
|
|
</RevealItem>
|
|
))}
|
|
</RevealGroup>
|
|
)}
|
|
|
|
<Newsletter
|
|
title="Neue Impulse im richtigen Moment."
|
|
description="Melde dich zum Newsletter an und erhalte regelmäßig Gedanken, Methoden und praktische Tipps."
|
|
/>
|
|
</main>
|
|
<Footer />
|
|
</>
|
|
);
|
|
}
|