Rabatt-System, größerer Mengen-Stepper, Zahlungsart zuerst, echte HasiDog-Inhalte

- Neues Rabatt-System: Prozent-/Betrag-Rabatt mit optionalem Ablaufdatum,
  Mengenrabatt-Staffel pro Produkt, sowie site-weite Gutscheincodes.
  Wird live im Warenkorb/Checkout berechnet (src/data/rabatt.ts,
  src/data/gutscheine.ts, cart.ts) und ist im Adminbereich direkt unter dem
  Preis bearbeitbar (config.yml: "Rabatte" + neue Collection
  "Gutscheincodes"). Effektiver Preis + Rabatt-Badge auf Produktkarten und
  Produktseite sichtbar.
- Mengen-Stepper auf der Produktseite vergrößert (bereits vorbereitet,
  jetzt mit Build/Test verifiziert).
- Zahlungsart im Checkout auf Platz 1, mit markenfarbigen, fertig
  gestalteten Buttons (PayPal/Klarna/Banküberweisung).
- Unterkategorie-Seiten zeigen jetzt VanVans eigene Produktbeschreibung
  und das echte Fotos des Produkts statt Platzhaltertext, mit kleinem
  Link zur HasiDog-Universum-Seite statt großem Button.

Verifiziert per Puppeteer-Funktionstest gegen den echten Production-Build
(Rabatt-Berechnung, Mengenrabatt, Gutschein-Einlösung, Checkout-Summary,
keine Konsolenfehler).
This commit is contained in:
qcigano
2026-08-02 18:59:11 +02:00
parent 0f93ab73c3
commit 6f850181f1
25 changed files with 1098 additions and 194 deletions
@@ -1,6 +1,8 @@
---
import Layout from "../../../layouts/Layout.astro";
import ProductCard from "../../../components/ProductCard.astro";
import { categories, getCategory, getSubcategory } from "../../../data/categories";
import { productsByUnterkategorie } from "../../../data/products";
import type { Locale } from "../../../i18n/config";
import { useTranslations } from "../../../i18n/ui";
@@ -15,6 +17,13 @@ const t = useTranslations(lang);
const { kategorie, unterkategorie } = Astro.params;
const category = getCategory(kategorie!)!;
const sub = getSubcategory(kategorie!, unterkategorie!)!;
// Sobald VanVan im Adminbereich ein echtes Produkt dieser Unterkategorie zuordnet, wird es
// hier automatisch featured angezeigt (echtes Foto + ihr echter Text) statt des generischen
// Platzhaltertexts.
const echtProdukte = productsByUnterkategorie(category.slug, sub.slug);
const featured = echtProdukte[0];
const weitere = echtProdukte.slice(1);
---
<Layout title={`${sub.name[lang]} ${category.name[lang]}`} description={`${sub.name[lang]} bei Van's DIY & Bastelbedarf.`} lang={lang} path={`/shop/${category.slug}/${sub.slug}/`}>
<div class="container">
@@ -29,11 +38,23 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
</section>
</div>
<section class="section-tight">
<div class="container">
{sub.vorstellung && (
<div class="character-intro">
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
{featured ? (
<section class="section-tight">
<div class="container grid grid-2 intro">
<div class="portrait-frame portrait">
{featured.bilder && featured.bilder.length > 0 ? (
<img class="product-photo" src={featured.bilder[0]} alt={featured.name[lang]} loading="lazy" />
) : (
<div class="img-placeholder" role="img" aria-label={featured.name[lang]}></div>
)}
</div>
<div>
{sub.vorstellung && <span class="eyebrow">{sub.vorstellung[lang].split("\n")[0]}</span>}
<h2>{featured.name[lang]}</h2>
<p class="lead">{featured.beschreibung[lang]}</p>
<div class="btn-row">
<a class="btn btn-primary" href={`/produkt/${featured.slug}/`}>{t.common.discoverNow} →</a>
</div>
{sub.mehrLink && (
<p class="character-more">
Du willst mehr über {sub.name[lang]} erfahren, dann klicke hier:{" "}
@@ -41,8 +62,37 @@ const sub = getSubcategory(kategorie!, unterkategorie!)!;
</p>
)}
</div>
)}
<p class="small">{t.shop.subcategoryEmpty}</p>
</div>
</section>
) : (
<section class="section-tight">
<div class="container">
{sub.vorstellung && (
<div class="character-intro">
{sub.vorstellung[lang].split("\n").map((line) => <p>{line}</p>)}
{sub.mehrLink && (
<p class="character-more">
Du willst mehr über {sub.name[lang]} erfahren, dann klicke hier:{" "}
<a class="character-more-link" href={sub.mehrLink} target="_blank" rel="noopener">hier →</a>
</p>
)}
</div>
)}
<p class="small">{t.shop.subcategoryEmpty}</p>
</div>
</section>
)}
{weitere.length > 0 && (
<section class="section-tight">
<div class="container">
<div class="grid grid-3">{weitere.map((p) => <ProductCard product={p} lang={lang} />)}</div>
</div>
</section>
)}
<section class="section-tight">
<div class="container">
<div class="btn-row">
<a class="btn btn-outline" href={`/shop/${category.slug}/`}>← {category.name[lang]}</a>
<a class="btn btn-outline" href="/shop/">{t.common.toShop}</a>