Files
Wabenhain/src/components/home-sections-a.tsx
T
Nick Szaunig c7927f2883 feat: Wabenhain Honig-Shop Anwendung
Vollstaendige Next.js/Prisma-E-Commerce-Anwendung fuer eine Imkerei:
Shop, Warenkorb, Stripe-Checkout, Kundenkonto mit 2FA, Admin-Bereich
(Produkte, Ernten, Chargen, Bestellungen, Gutscheine, admin-editierbare
Website-Inhalte), i18n (DE/EN), Rate-Limiting/CSP/Audit-Log-Haertung,
PostgreSQL- und MySQL-Unterstuetzung ueber austauschbare Prisma-
Treiber-Adapter, Unit-/Komponenten-/E2E-Tests.
2026-08-17 14:30:28 +02:00

187 lines
6.8 KiB
TypeScript

import { Container, SectionHeading, LinkButton } from "@/components/ui";
import { ScrollReveal } from "@/components/scroll-reveal";
import { PlaceholderImage } from "@/components/placeholder-image";
import { ProductCard, TasteScale, AddToCartButton } from "@/components/product-card";
import { formatPrice, formatDate } from "@/lib/format";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
import { getContentValues, parseListSimple, parseListTitled } from "@/lib/content";
import type { ProductWithRelations } from "@/lib/data";
export async function CurrentHarvest({
products,
reviewSummaries,
}: {
products: ProductWithRelations[];
reviewSummaries?: Map<string, { average: number | null; count: number }>;
}) {
const t = getDictionary(await getLocale());
return (
<section className="py-24">
<Container>
<ScrollReveal>
<SectionHeading
eyebrow={t.home.currentHarvest.eyebrow}
title={t.home.currentHarvest.title}
subtitle={t.home.currentHarvest.subtitle}
/>
</ScrollReveal>
<div className="mt-14 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-x-8 gap-y-14">
{products.map((product) => (
<ProductCard
key={product.id}
product={product}
reviewSummary={reviewSummaries?.get(product.id)}
/>
))}
</div>
</Container>
</section>
);
}
export async function OriginStory() {
const locale = await getLocale();
const t = getDictionary(locale);
const content = await getContentValues(
["home.originStory.paragraph1", "home.originStory.paragraph2", "home.originStory.facts"],
locale
);
const facts = parseListSimple(content["home.originStory.facts"]);
return (
<section className="py-24 bg-cream/60">
<Container className="grid md:grid-cols-2 gap-12 items-center">
<ScrollReveal>
<PlaceholderImage
tone="forest"
className="aspect-[4/5] w-full"
label={t.home.originStory.eyebrow}
/>
</ScrollReveal>
<ScrollReveal delay={0.1}>
<SectionHeading
eyebrow={t.home.originStory.eyebrow}
title={t.home.originStory.title}
/>
<p className="mt-6 text-ink/75 leading-relaxed">{content["home.originStory.paragraph1"]}</p>
<p className="mt-4 text-ink/75 leading-relaxed">{content["home.originStory.paragraph2"]}</p>
<ul className="mt-6 space-y-1 text-sm text-ink/70">
{facts.map((fact) => (
<li key={fact}>{fact}</li>
))}
</ul>
<LinkButton href="/herkunft" variant="secondary" className="mt-8">
{t.home.originStory.cta}
</LinkButton>
</ScrollReveal>
</Container>
</section>
);
}
export async function Philosophy() {
const locale = await getLocale();
const t = getDictionary(locale);
const content = await getContentValues(["home.philosophy.items"], locale);
const items = parseListTitled(content["home.philosophy.items"]);
return (
<section className="py-24">
<Container>
<ScrollReveal>
<SectionHeading
eyebrow={t.home.philosophy.eyebrow}
title={t.home.philosophy.title}
align="center"
className="mx-auto"
/>
</ScrollReveal>
<div className="mt-16 grid sm:grid-cols-2 lg:grid-cols-4 gap-x-8 gap-y-14">
{items.map((item, i) => (
<ScrollReveal key={item.title} delay={i * 0.08}>
<div className={`border-t border-ink/15 pt-6 ${i % 2 === 1 ? "lg:mt-10" : ""}`}>
<span className="font-serif text-3xl text-honey-dark">
{String(i + 1).padStart(2, "0")}
</span>
<h3 className="mt-3 text-lg text-forest">{item.title}</h3>
<p className="mt-2 text-sm text-ink/65 leading-relaxed">
{item.text}
</p>
</div>
</ScrollReveal>
))}
</div>
</Container>
</section>
);
}
type HoneySpotlightProduct = ProductWithRelations & {
harvest: NonNullable<ProductWithRelations["harvest"]>;
batch: NonNullable<ProductWithRelations["batch"]>;
};
export async function ProductSpotlight({ product }: { product: HoneySpotlightProduct }) {
const t = getDictionary(await getLocale());
return (
<section className="py-24 bg-forest text-cream">
<Container className="grid md:grid-cols-2 gap-14 items-center">
<ScrollReveal>
<PlaceholderImage
tone="honey"
className="aspect-square w-full"
label={product.name}
/>
</ScrollReveal>
<ScrollReveal delay={0.1}>
<p className="tracking-wide-caps text-xs text-honey-light mb-3">
{product.harvest.type} {product.harvest.year}
</p>
<h2 className="text-3xl md:text-4xl">{product.name}</h2>
<p className="mt-4 text-cream/75 leading-relaxed">
{t.home.productSpotlight.description}
</p>
<div className="mt-8 space-y-3">
<TasteScale label={t.product.tasteMild} value={product.intensityMild} tone="inverted" />
<TasteScale label={t.product.tasteFloral} value={product.intensityFloral} tone="inverted" />
<TasteScale label={t.product.tasteIntense} value={product.intensityKraeftig} tone="inverted" />
</div>
<p className="mt-4 text-sm text-cream/60">
{t.home.productSpotlight.consistency}: {product.consistency}
</p>
<dl className="mt-8 grid grid-cols-2 gap-4 text-sm text-cream/70">
<div>
<dt className="text-cream/65">{t.home.productSpotlight.harvest}</dt>
<dd>{formatDate(product.harvest.harvestDate)}</dd>
</div>
<div>
<dt className="text-cream/65">{t.home.productSpotlight.batch}</dt>
<dd>{product.batch.code}</dd>
</div>
<div>
<dt className="text-cream/65">{t.home.productSpotlight.weight}</dt>
<dd>{product.weightGrams} g</dd>
</div>
<div>
<dt className="text-cream/65">{t.home.productSpotlight.price}</dt>
<dd>{formatPrice(product.priceCents, product.currency)}</dd>
</div>
</dl>
<div className="mt-10 flex flex-wrap gap-4">
<LinkButton
href={`/honig/${product.slug}`}
variant="secondary"
className="!border-cream !text-cream hover:!bg-cream hover:!text-forest"
>
{t.home.productSpotlight.cta}
</LinkButton>
<AddToCartButton product={product} />
</div>
</ScrollReveal>
</Container>
</section>
);
}