feat: Geschenk- und Honigwissen-Hinweise auf der Produktseite, Umlaut-Suche fixen

- Produktdetailseite: dezenter Hinweis direkt bei der Kaufentscheidung, dass
  beim Checkout eine persoenliche Geschenknachricht hinterlegt werden kann
  (Feature existierte bereits, war aber nur im Checkout-Formular selbst
  sichtbar — zu spaet, um Mengen-/Produktwahl noch zu beeinflussen)
- Produktdetailseite (Honig): kontextuelle Links zu passenden
  Honigwissen-Artikeln (Kristallisation immer, Fruehtracht/Sommertracht
  wenn aus Harvest.type erkennbar) direkt bei den Produktdetails statt
  einer isolierten, von der Kaufseite aus unverlinkten Wissenssektion
- Suche (Shop-Uebersicht + Live-Suchvorschlaege) gleicht jetzt deutsche
  Umlaute/scharfes S gegen ihre ASCII-Transliteration ab (z.B. "Bluete"
  findet "Bluete"-Produkte, "kraeftig" findet "kräftig") — vorher lieferte
  eine Suche in der jeweils anderen Schreibweise keine Treffer, obwohl der
  Katalog durchgaengig echte Umlaute verwendet; Logik in neuer, reiner
  lib/search-match.ts mit eigenen Tests ausgelagert
- Bewusst NICHT angefasst: Newsletter-Rabattanreiz (fehlt, aber Prozentsatz/
  Mechanik ist eine Owner-Entscheidung) und Trust-Badges am Kaufabschluss
  (bereits vorhanden auf Produktseite/Warenkorb/Checkout)

Verifikation: pnpm exec tsc --noEmit clean, pnpm test 151/151 (15 Dateien,
+9 neue Tests fuer search-match), pnpm build erfolgreich.
This commit is contained in:
Nick Szaunig committed 2026-09-06 03:07:24 +02:00
1 parent de0f6ee249
commit a579ebabeb
7 files changed
+114 -6

No files matched your search

+27 -1
View File
@@ -22,6 +22,7 @@ import { resolveShippingCostCents } from "@/lib/shipping-calc";
import { submitReviewAction, toggleWishlistAction, subscribeStockNotificationAction } from "../actions";
import { getLocale } from "@/i18n/locale";
import { getDictionary } from "@/i18n/get-dictionary";
import type { WissenSlug } from "@/i18n/dictionary-type";
// Produktdaten (Preis, Bestand, Charge) aendern sich laufend — Rendering pro
// Request statt statischem Build entkoppelt zudem den Docker-Build-Schritt
@@ -53,6 +54,16 @@ export default async function ProductDetailPage(
if (!product) notFound();
const isHoney = product.category === "HONIG" && product.harvest && product.batch;
// Verlinkt die Ernteart auf den passenden Honigwissen-Artikel, sofern der
// Ernte-Typ (freier Admin-Text, siehe Harvest.type) eindeutig als Frueh-
// oder Sommertracht erkennbar ist — sonst einfach kein zusaetzlicher Link,
// statt eine falsche Zuordnung zu raten.
const harvestTypeLower = product.harvest?.type.toLowerCase() ?? "";
const harvestWissenSlug: WissenSlug | null = harvestTypeLower.includes("früh")
? "was-bedeutet-fruehtracht"
: harvestTypeLower.includes("sommer")
? "was-bedeutet-sommertracht"
: null;
const stock = product.inventory?.quantity ?? 0;
const inStock = stock > 0;
const lowStock = inStock && stock <= 5;
@@ -183,6 +194,7 @@ export default async function ProductDetailPage(
baseShippingCents === 0 ? t.cartPage.free : formatPrice(baseShippingCents, "EUR", locale)
)}
</Link>
<p className="mt-2 text-xs text-ink/65">{t.productPage.giftHint}</p>
{isHoney ? (
<div className="mt-8 space-y-3">
@@ -286,10 +298,24 @@ export default async function ProductDetailPage(
)}
</div>
{isHoney ? (
<div className="mt-6 flex flex-wrap items-baseline gap-x-3 gap-y-1 text-sm">
<span className="text-ink/65">{t.productPage.learnMoreLabel}</span>
<Link href="/wissen/warum-kristallisiert-honig" className="text-forest underline">
{t.wissenArticles["warum-kristallisiert-honig"].title}
</Link>
{harvestWissenSlug ? (
<Link href={`/wissen/${harvestWissenSlug}`} className="text-forest underline">
{t.wissenArticles[harvestWissenSlug].title}
</Link>
) : null}
</div>
) : null}
{isHoney && product.batch ? (
<Link
href={`/charge/${product.batch.code}`}
className="mt-6 inline-block text-sm text-forest underline"
className="mt-3 inline-block text-sm text-forest underline"
>
{t.productPage.viewBatchOrigin}
</Link>