v2.1: Gutschein-/Rabatt-Engine + editierbare gebrandete 404

Feature 1 — Rabatt-Engine (store-sqlite.js):
- Tabellen discounts + discount_redemptions; orders um discount_code/discount_cents erweitert.
- Helper: getDiscountByCode, listDiscounts, create/update/deleteDiscount,
  validateDiscount (Zeitplan/Mindestwert/Limits/pro-Kunde), bestAutoDiscount, redeemDiscount.
- Seed: WILLKOMMEN10, NAEHEN5, GRATISVERSAND (geplant), AUTO15AB75 (auto).
- Checkout: /api/discount (serverseitige Subtotal-Berechnung) + /api/checkout re-validiert,
  wendet Rabatt/Gratisversand an, speichert + redeemt, auto-Discount-Fallback, Stripe-Coupon.
- Cart/Checkout-UI mit Code-Feld + Einlösen; Rabattzeile in Order-Detail + Erfolgsseite.
- Admin "Rabatte" (owner+redaktion) mit Status-Badges + Editor (Zufallscode, Typ-abh. Wertfeld).
- Popups: Typ discount zeigt Code + Kopieren-Button; Stile modal/slidein/bar (CSS ergaenzt).

Feature 2 — 404:
- src/pages/404.astro nutzt Base + BlockRenderer, laedt System-Seite slug 404.
- ensureSystemPages() legt 404 idempotent bei jedem Boot an (INSERT OR IGNORE, flache Bloecke).
- 404/system erscheint in Admin "Inhalte" und oeffnet im Block-Editor.

API/MCP: discounts in /api/admin/* (CRUD), Manifest + ai-admin.txt ergaenzt
(inkl. Hinweis: Block-Objekte sind flach); MCP list/upsert/delete_discount.
README + Versionen (2.1.0) aktualisiert.
This commit is contained in:
2026-06-17 15:12:07 +00:00
parent aec179db36
commit 430fa718fa
21 changed files with 572 additions and 52 deletions
+29
View File
@@ -187,6 +187,35 @@ p{margin:0 0 1rem}
.hdc-popup .nl-form{flex-direction:column}
.hdc-popup .nl-form input{border:1.5px solid var(--border-2);width:100%}
/* Rabatt-Code-Box in Popups */
.hdc-code{display:flex;gap:8px;align-items:stretch;margin:6px 0 16px}
.hdc-code code{flex:1;display:flex;align-items:center;justify-content:center;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:18px;font-weight:700;letter-spacing:.08em;padding:12px 14px;border:1.5px dashed var(--accent);border-radius:10px;color:var(--ink);background:color-mix(in srgb,var(--accent) 8%,white)}
.hdc-copy{border:none;background:var(--accent);color:#fff;font-weight:600;padding:0 16px;border-radius:10px;cursor:pointer;font-size:14px}
.hdc-copy:hover{background:var(--accent-dark)}
/* Slide-in (unten rechts) */
.hdc-popup-slidein{position:fixed;right:20px;bottom:20px;z-index:120;max-width:360px;width:calc(100% - 40px);opacity:0;transform:translateY(24px);pointer-events:none;transition:.32s}
.hdc-popup-slidein.show{opacity:1;transform:none;pointer-events:auto}
.hdc-popup-slidein .hdc-pp-inner{position:relative;background:var(--surface);border-radius:var(--radius);padding:24px 22px;box-shadow:var(--shadow-lg);text-align:left}
.hdc-popup-slidein h3{font-size:20px;margin-bottom:8px}
.hdc-popup-slidein p{color:var(--subtle);margin-bottom:16px;font-size:14px}
.hdc-popup-slidein .px,.hdc-popup-bar .px{position:absolute;top:10px;right:12px;background:none;border:none;font-size:22px;color:var(--faint);cursor:pointer;line-height:1}
.hdc-popup-slidein .nl-form{flex-direction:column}
.hdc-popup-slidein .nl-form input{border:1.5px solid var(--border-2);width:100%}
/* Bar (oben) */
.hdc-popup-bar{position:fixed;left:0;right:0;top:0;z-index:130;background:var(--accent);color:#fff;transform:translateY(-100%);transition:.32s;opacity:0}
.hdc-popup-bar.show{transform:none;opacity:1}
.hdc-popup-bar .hdc-pp-inner{position:relative;max-width:1100px;margin:0 auto;padding:12px 48px 12px 20px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;justify-content:center}
.hdc-popup-bar h3{font-size:16px;margin:0;color:#fff}
.hdc-popup-bar p{margin:0;font-size:14px;color:rgba(255,255,255,.92)}
.hdc-popup-bar .px{color:#fff;top:50%;transform:translateY(-50%)}
.hdc-popup-bar img{display:none}
.hdc-popup-bar .hdc-code{margin:0}
.hdc-popup-bar .hdc-code code{background:rgba(255,255,255,.16);border-color:rgba(255,255,255,.6);color:#fff;padding:6px 12px;font-size:15px}
.hdc-popup-bar .hdc-copy{background:#fff;color:var(--accent)}
.hdc-popup-bar .btn{padding:7px 16px}
@media(max-width:880px){
.pdp{grid-template-columns:1fr;gap:28px}
.cart-wrap{grid-template-columns:1fr}