Versandfreigrenze pro Land, USt-ID, Rechtstexte+FAQ jetzt admin-editierbar

1) Versandkostenfreigrenze ist jetzt länderabhängig statt einer einzigen Zahl für alle:
   Deutschland 75 €, Österreich/Luxemburg 85 €, Schweiz 100 € (im Adminbereich unter
   "Versandkosten-Staffel" änderbar). Betrifft Warenkorb (live je nach gewähltem Land),
   Checkout-Berechnung, sowie alle Text-Hinweise (Topbar, FAQ, AGB) in allen 4 Sprachen.

2) Neues Feld "USt-IdNr." im Impressum (optional, admin-editierbar) — erscheint nur, wenn
   ausgefüllt.

3) Impressum, AGB, Datenschutzerklärung, Widerrufsbelehrung und FAQ sind jetzt vollständig
   im Adminbereich editierbar (neue Collection "⚖️ Rechtstexte & FAQ") statt fest im Code zu
   stehen. Jede Rechtsseite besteht aus frei bearbeitbaren Abschnitten (Überschrift + Text) —
   VanVan kann selbst Abschnitte ergänzen, ändern oder löschen. Freitext wird beim Anzeigen
   sicher escaped (kein kaputtes HTML durch Tippfehler möglich) und nackte Links automatisch
   klickbar gemacht. FAQ bleibt dabei weiterhin in allen 4 Sprachen separat pflegbar.

Ausführlich getestet: Freigrenze pro Land im echten Warenkorb verifiziert (Puppeteer,
84€ Warenwert -> DE kostenlos, AT/LU "noch 1€", CH "noch 16€"), Rechtstexte/FAQ/USt-ID
gegen den fertigen Build geprüft, EU-Streitschlichtung-Link-Bug (Satzzeichen wurde
mitverlinkt) gefunden und behoben.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-03 22:04:01 +02:00
co-authored by Claude Sonnet 5
parent 9a2c75ebe6
commit 0c1c8f075f
23 changed files with 495 additions and 310 deletions
+3 -1
View File
@@ -2,9 +2,11 @@
import Layout from "../../../layouts/Layout.astro";
import type { Locale } from "../../../i18n/config";
import { useTranslations } from "../../../i18n/ui";
import { faqEintraege } from "../../../data/faq";
const lang: Locale = "en";
const t = useTranslations(lang);
const items = faqEintraege(lang);
---
<Layout title="FAQ" description="Frequently asked questions about Van's DIY & Bastelbedarf." lang={lang} path="/faq/">
<section class="section-tight">
@@ -15,7 +17,7 @@ const t = useTranslations(lang);
</section>
<section class="section-tight">
<div class="container faq-list">
{t.faq.items.map((f) => (
{items.map((f) => (
<details class="card faq-item">
<summary>{f.q}</summary>
<p class="small">{f.a}</p>
+5 -4
View File
@@ -78,19 +78,19 @@ const t = useTranslations(lang);
</section>
</Layout>
<script define:vars={{ freeShipFrom: 75, removeLabel: t.cart.remove, perItemLabel: t.cart.perItem, remainingTemplate: t.cart.remaining("__V__"), freeShippingText: t.cart.freeShipping, freeLabel: t.checkout.free, cartLang: lang, mengenrabattTemplate: t.cart.mengenrabatt("__P__"), couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, buyNowLabel: t.cart.buyNow, keptInCartLabel: t.cart.keptInCart, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove, loyaltySelectLabel: t.cart.loyaltySelectLabel, loyaltySelectedLabel: t.cart.loyaltySelectedLabel, gratisLabel: t.cart.gratisPraemie }}>
<script define:vars={{ removeLabel: t.cart.remove, perItemLabel: t.cart.perItem, remainingTemplate: t.cart.remaining("__V__"), freeShippingText: t.cart.freeShipping, freeLabel: t.checkout.free, cartLang: lang, mengenrabattTemplate: t.cart.mengenrabatt("__P__"), couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove, buyNowLabel: t.cart.buyNow, keptInCartLabel: t.cart.keptInCart, partnerCodeInvalid: t.cart.partnerCodeInvalid, partnerCodeAppliedTemplate: t.cart.partnerCodeApplied("__C__"), partnerCodeRemoveLabel: t.cart.partnerCodeRemove, loyaltySelectLabel: t.cart.loyaltySelectLabel, loyaltySelectedLabel: t.cart.loyaltySelectedLabel, gratisLabel: t.cart.gratisPraemie }}>
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltySelectLabel, loyaltySelectedLabel, gratisLabel };
window.__cartVars = { removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltySelectLabel, loyaltySelectedLabel, gratisLabel };
</script>
<script type="module">
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon, appliedPartnerCode, partnerCodeDiscount, setPartnerCode, clearPartnerCode, totalDiscount, treuebonusDiscount, aboDiscount, istAusgewaehlt, setAusgewaehlt, zeilenpreisFuer } from "../../../scripts/cart";
import { formatPrice } from "../../../i18n/format";
import { getProduct, products } from "../../../data/products";
import { berechneVersandkosten } from "../../../data/versand";
import { berechneVersandkosten, freieVersandGrenze } from "../../../data/versand";
import { mengenrabattProzent } from "../../../data/rabatt";
import { getAccount, treueFortschritt, setTreuebonusArtikel } from "../../../scripts/account";
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltySelectLabel, loyaltySelectedLabel, gratisLabel } = window.__cartVars;
const { removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel, buyNowLabel, keptInCartLabel, partnerCodeInvalid, partnerCodeAppliedTemplate, partnerCodeRemoveLabel, loyaltySelectLabel, loyaltySelectedLabel, gratisLabel } = window.__cartVars;
const landSelect = document.getElementById("cart-land");
function render() {
@@ -177,6 +177,7 @@ import { getAccount, treueFortschritt, setTreuebonusArtikel } from "../../../scr
).join("");
const subtotal = cartTotal();
const freeShipFrom = freieVersandGrenze(landSelect.value);
const remaining = Math.max(0, freeShipFrom - subtotal);
const shipping = berechneVersandkosten(landSelect.value, ausgewaehlteZeilen.map(({ item: i }) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
const coupon = appliedCoupon();