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
+9 -65
View File
@@ -1,5 +1,6 @@
---
import Layout from "../../layouts/Layout.astro";
import { agb, rechtstextAlsHtml } from "../../data/legal";
---
<Layout title="Allgemeine Geschäftsbedingungen (AGB)" path="/agb/" legalOnly={true}>
<section class="section-tight">
@@ -7,73 +8,16 @@ import Layout from "../../layouts/Layout.astro";
<h1>Allgemeine Geschäftsbedingungen</h1>
<div class="legal-note">
⚠️ <strong>Muster-Text.</strong> Vorbereiteter Platzhalter, ersetzt keine rechtliche
Prüfung durch eine fachkundige Stelle vor Veröffentlichung.
Prüfung durch eine fachkundige Stelle vor Veröffentlichung — im Adminbereich unter
„⚖️ Rechtstexte & FAQ" bearbeitbar.
</div>
<h2>§ 1 Geltungsbereich</h2>
<p>
Diese Allgemeinen Geschäftsbedingungen gelten für alle Bestellungen, die Verbraucher über
den Onlineshop von Van's DIY & Bastelbedarf (Einzelunternehmen, [Anschrift]) aufgeben.
</p>
<h2>§ 2 Vertragspartner, Vertragsschluss</h2>
<p>
Der Kaufvertrag kommt zustande mit Van's DIY & Bastelbedarf. Die Darstellung der Produkte
im Shop stellt kein rechtlich bindendes Angebot dar, sondern eine unverbindliche
Aufforderung, ein Angebot abzugeben. Mit Anklicken des Buttons „Jetzt zahlungspflichtig
bestellen" gibst du ein verbindliches Angebot zum Kauf ab. Wir bestätigen den Eingang
deiner Bestellung per E-Mail; diese Bestätigung stellt noch keine Vertragsannahme dar. Der
Vertrag kommt erst mit unserer gesonderten Auftragsbestätigung oder Versand der Ware
zustande.
</p>
<h2>§ 3 Preise und Versandkosten</h2>
<p>
Alle angegebenen Preise verstehen sich als Endpreise. Gemäß §19 UStG wird als
Kleinunternehmer keine Umsatzsteuer berechnet und ausgewiesen. Es kommen ggf.
Versandkosten hinzu, die im Bestellprozess vor Abschluss der Bestellung ausgewiesen
werden. Ab einem Warenwert von 75 € liefern wir versandkostenfrei.
</p>
<h2>§ 4 Zahlung</h2>
<p>Die Zahlung erfolgt wahlweise per PayPal, Klarna, Kreditkarte (Visa & Mastercard) oder Banküberweisung (Vorkasse).</p>
<h2>§ 5 Lieferung</h2>
<p>
Die Lieferung erfolgt ausschließlich mit DHL innerhalb Deutschlands, Österreichs, der
Schweiz und Luxemburgs. Die Lieferzeit beträgt in der Regel 27 Werktage nach
Zahlungseingang.
</p>
<h2>§ 6 Eigentumsvorbehalt</h2>
<p>Bis zur vollständigen Bezahlung bleibt die gelieferte Ware unser Eigentum.</p>
<h2>§ 7 Widerrufsrecht</h2>
<p>
Es gilt das gesetzliche Widerrufsrecht für Verbraucher gemäß unserer gesonderten
Widerrufsbelehrung. Bei Waren, die nach Kundenspezifikation angefertigt oder eindeutig auf
die persönlichen Bedürfnisse zugeschnitten sind, kann das Widerrufsrecht gemäß § 312g Abs.
2 Nr. 1 BGB ausgeschlossen sein.
</p>
<h2>§ 8 Gewährleistung</h2>
<p>Es gilt das gesetzliche Mängelhaftungsrecht.</p>
<h2>§ 9 Streitbeilegung</h2>
<p>
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
<a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener">https://ec.europa.eu/consumers/odr/</a>.
Wir sind nicht verpflichtet und nicht bereit, an einem Streitbeilegungsverfahren vor einer
Verbraucherschlichtungsstelle teilzunehmen.
</p>
<h2>§ 10 Schlussbestimmungen</h2>
<p>
Es gilt das Recht der Bundesrepublik Deutschland unter Ausschluss des UN-Kaufrechts. Hast
du als Verbraucher:in deinen gewöhnlichen Aufenthalt in einem anderen EU-Mitgliedstaat,
bleiben die zwingenden Verbraucherschutzvorschriften dieses Staates von dieser
Rechtswahl unberührt.
</p>
{agb.sections.map((s) => (
<>
<h2>{s.heading}</h2>
<Fragment set:html={rechtstextAlsHtml(s.body)} />
</>
))}
</div>
</section>
</Layout>
+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 = "ch";
const t = useTranslations(lang);
const items = faqEintraege(lang);
---
<Layout title="FAQ" description="Häufigi Frage zu 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();
+9 -79
View File
@@ -1,5 +1,6 @@
---
import Layout from "../../layouts/Layout.astro";
import { datenschutz, rechtstextAlsHtml } from "../../data/legal";
---
<Layout title="Datenschutzerklärung" path="/datenschutz/" legalOnly={true}>
<section class="section-tight">
@@ -8,87 +9,16 @@ import Layout from "../../layouts/Layout.astro";
<div class="legal-note">
⚠️ <strong>Muster-Text.</strong> Vorbereiteter Platzhalter, ersetzt keine rechtliche
Prüfung. Vor Veröffentlichung an die tatsächlich eingesetzten Dienste (z. B. konkreter
Hosting-Anbieter, Zahlungsdienstleister, Versandtool) anpassen und rechtlich prüfen lassen.
Hosting-Anbieter, Zahlungsdienstleister, Versandtool) anpassen und rechtlich prüfen
lassen — im Adminbereich unter „⚖️ Rechtstexte & FAQ" bearbeitbar.
</div>
<h2>1. Verantwortlicher</h2>
<p>[Vorname Nachname], Van's DIY & Bastelbedarf, [Anschrift], [E-Mail-Adresse]</p>
<h2>2. Allgemeines zur Datenverarbeitung</h2>
<p>
Wir verarbeiten personenbezogene Daten unserer Nutzer grundsätzlich nur, soweit dies zur
Bereitstellung einer funktionsfähigen Website sowie unserer Inhalte und Leistungen
erforderlich ist, auf Grundlage der Art. 6 Abs. 1 lit. a, b und f DSGVO.
</p>
<h2>3. Hosting</h2>
<p>
Diese Website wird bei einem externen Dienstleister gehostet (Hoster). Die personenbezogenen
Daten, die auf dieser Website erfasst werden, werden auf den Servern des Hosters
gespeichert. [Konkreten Hosting-Anbieter, z. B. Cloudflare Pages, hier ergänzen inkl.
Standort der Server und ggf. Auftragsverarbeitungsvertrag.]
</p>
<h2>4. Bestellabwicklung</h2>
<p>
Zur Abwicklung deiner Bestellung erheben und verarbeiten wir personenbezogene Daten, wenn
dies zur Erfüllung des Vertrags erforderlich ist (Art. 6 Abs. 1 lit. b DSGVO). Dazu gehören
Name, Anschrift, E-Mail-Adresse und Zahlungsdaten. Die Daten werden ausschließlich zur
Vertragsabwicklung genutzt.
</p>
<h2>5. Zahlungsdienstleister</h2>
<p>
Je nach gewählter Zahlungsart geben wir Daten an die eingesetzten Zahlungsdienstleister
weiter (PayPal, Klarna, Kreditkartenanbieter). Es gelten deren jeweils eigene
Datenschutzhinweise. Bei Banküberweisung werden Daten an unser kontoführendes
Kreditinstitut übermittelt.
</p>
<h2>6. Kontaktformular</h2>
<p>
Wenn du uns über das Kontaktformular oder per E-Mail Anfragen zukommen lässt, werden deine
Angaben aus dem Anfrageformular inklusive der von dir dort angegebenen Kontaktdaten zwecks
Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Die
Verarbeitung dieser Daten erfolgt auf Grundlage deiner Einwilligung (Art. 6 Abs. 1 lit. a
DSGVO) bzw., sofern die Anfrage der Anbahnung eines Vertrags dient, auf Grundlage des
Art. 6 Abs. 1 lit. b DSGVO. Eine erteilte Einwilligung kannst du jederzeit mit Wirkung für
die Zukunft widerrufen, etwa per E-Mail an die oben genannte Adresse.
</p>
<h2>7. Cookies</h2>
<p>
Diese Website verwendet, soweit technisch erforderlich, Cookies zur Bereitstellung des
Warenkorbs. Weitere, nicht zwingend erforderliche Cookies (z. B. Statistik/Marketing)
werden nur nach vorheriger Einwilligung gesetzt. [Cookie-Banner/Consent-Tool ergänzen,
sobald solche Cookies tatsächlich eingesetzt werden.]
</p>
<h2>8. Deine Rechte</h2>
<p>
Du hast jederzeit das Recht auf Auskunft (Art. 15 DSGVO), Berichtigung (Art. 16 DSGVO),
Löschung (Art. 17 DSGVO), Einschränkung der Verarbeitung (Art. 18 DSGVO),
Datenübertragbarkeit (Art. 20 DSGVO) und Widerspruch gegen die Verarbeitung deiner
personenbezogenen Daten (Art. 21 DSGVO). Soweit die Verarbeitung auf deiner Einwilligung
beruht, kannst du diese jederzeit mit Wirkung für die Zukunft widerrufen (Art. 7 Abs. 3
DSGVO), ohne dass die Rechtmäßigkeit der bis zum Widerruf erfolgten Verarbeitung berührt
wird. Außerdem hast du das Recht, dich bei einer Datenschutz-Aufsichtsbehörde zu
beschweren — insbesondere in dem Mitgliedstaat deines gewöhnlichen Aufenthaltsorts,
deines Arbeitsplatzes oder des Orts des mutmaßlichen Verstoßes.
</p>
<h2>9. Automatisierte Entscheidungsfindung</h2>
<p>
Wir setzen keine automatisierte Entscheidungsfindung einschließlich Profiling im Sinne
von Art. 22 DSGVO ein.
</p>
<h2>10. Speicherdauer</h2>
<p>
Wir speichern personenbezogene Daten nur so lange, wie es für den jeweiligen Zweck
erforderlich ist oder gesetzliche Aufbewahrungsfristen (insbesondere handels- und
steuerrechtlich) dies vorschreiben.
</p>
{datenschutz.sections.map((s) => (
<>
<h2>{s.heading}</h2>
<Fragment set:html={rechtstextAlsHtml(s.body)} />
</>
))}
</div>
</section>
</Layout>
+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();
+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 = "de";
const t = useTranslations(lang);
const items = faqEintraege(lang);
---
<Layout title="FAQ" description="Häufig gestellte Fragen zu 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>
+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 = "fr";
const t = useTranslations(lang);
const items = faqEintraege(lang);
---
<Layout title="FAQ" description="Questions fréquentes sur 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();
+15 -34
View File
@@ -1,5 +1,6 @@
---
import Layout from "../../layouts/Layout.astro";
import { impressum, rechtstextAlsHtml } from "../../data/legal";
---
<Layout title="Impressum" path="/impressum/" legalOnly={true}>
<section class="section-tight">
@@ -9,43 +10,23 @@ import Layout from "../../layouts/Layout.astro";
⚠️ <strong>Muster-Text.</strong> Dies ist ein vorbereiteter Platzhalter gemäß Pflichtenheft
und ersetzt keine rechtliche Prüfung. Vor Veröffentlichung bitte von einer fachkundigen
Stelle (z. B. Anwalt/Anwältin oder IHK-Impressumsgenerator) prüfen und mit den echten
Angaben von VanVan vervollständigen.
Angaben von VanVan vervollständigen — im Adminbereich unter „⚖️ Rechtstexte & FAQ"
bearbeitbar.
</div>
<h2>Angaben gemäß § 5 DDG</h2>
<p>
[Vorname Nachname] („VanVan")<br />
Van's DIY & Bastelbedarf<br />
Einzelunternehmen<br />
[Straße und Hausnummer]<br />
[PLZ Ort]<br />
Deutschland
</p>
{impressum.sections.map((s) => (
<>
<h2>{s.heading}</h2>
<Fragment set:html={rechtstextAlsHtml(s.body)} />
</>
))}
<h2>Kontakt</h2>
<p>
Telefon: [Telefonnummer]<br />
E-Mail: [E-Mail-Adresse]
</p>
<h2>Umsatzsteuer</h2>
<p>Gemäß §19 UStG wird als Kleinunternehmer keine Umsatzsteuer berechnet und ausgewiesen.</p>
<h2>Verantwortlich für den Inhalt nach § 18 Abs. 2 MStV</h2>
<p>[Vorname Nachname], Anschrift wie oben.</p>
<h2>EU-Streitschlichtung</h2>
<p>
Die Europäische Kommission stellt eine Plattform zur Online-Streitbeilegung (OS) bereit:
<a href="https://ec.europa.eu/consumers/odr/" target="_blank" rel="noopener">https://ec.europa.eu/consumers/odr/</a>.
Unsere E-Mail-Adresse finden Sie oben im Impressum.
</p>
<h2>Verbraucherstreitbeilegung/Universalschlichtungsstelle</h2>
<p>
Wir sind nicht bereit oder verpflichtet, an Streitbeilegungsverfahren vor einer
Verbraucherschlichtungsstelle teilzunehmen. [Ggf. anpassen, falls sich das ändert.]
</p>
{impressum.ustId && impressum.ustId.trim() !== "" && (
<>
<h2>Umsatzsteuer-Identifikationsnummer</h2>
<p>{impressum.ustId}</p>
</>
)}
</div>
</section>
</Layout>
+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() {
@@ -179,6 +179,7 @@ import { getAccount, treueFortschritt, setTreuebonusArtikel } from "../../script
).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();
+13 -54
View File
@@ -1,5 +1,6 @@
---
import Layout from "../../layouts/Layout.astro";
import { widerruf, rechtstextAlsHtml } from "../../data/legal";
---
<Layout title="Widerrufsbelehrung" path="/widerruf/" legalOnly={true}>
<section class="section-tight">
@@ -7,64 +8,22 @@ import Layout from "../../layouts/Layout.astro";
<h1>Widerrufsbelehrung</h1>
<div class="legal-note">
⚠️ <strong>Muster-Text.</strong> Vorbereiteter Platzhalter, ersetzt keine rechtliche
Prüfung durch eine fachkundige Stelle vor Veröffentlichung.
Prüfung durch eine fachkundige Stelle vor Veröffentlichung — im Adminbereich unter
„⚖️ Rechtstexte & FAQ" bearbeitbar.
</div>
<h2>Widerrufsrecht</h2>
<p>
Du hast das Recht, binnen vierzehn Tagen ohne Angabe von Gründen diesen Vertrag zu
widerrufen. Die Widerrufsfrist beträgt vierzehn Tage ab dem Tag, an dem du oder ein von dir
benannter Dritter, der nicht der Beförderer ist, die Waren in Besitz genommen hast bzw. hat.
</p>
<p>
Um dein Widerrufsrecht auszuüben, musst du uns ([Vorname Nachname], Van's DIY &
Bastelbedarf, [Anschrift], [E-Mail-Adresse]) mittels einer eindeutigen Erklärung (z. B. ein
mit der Post versandter Brief oder E-Mail) über deinen Entschluss, diesen Vertrag zu
widerrufen, informieren. Du kannst dafür das beigefügte
<a href="/muster-widerrufsformular/">Muster-Widerrufsformular</a> verwenden, das jedoch
nicht vorgeschrieben ist.
</p>
<p>
Zur Wahrung der Widerrufsfrist reicht es aus, dass du die Mitteilung über die Ausübung des
Widerrufsrechts vor Ablauf der Widerrufsfrist absendest.
</p>
{widerruf.sections.map((s) => (
<>
<h2>{s.heading}</h2>
<Fragment set:html={rechtstextAlsHtml(s.body)} />
</>
))}
<h2>Folgen des Widerrufs</h2>
{/* Fester, nicht editierbarer Link auf das separate Formular-Seiten-Template — bewusst
nicht Teil des frei bearbeitbaren Textes oben, damit dieser wichtige Link nie aus
Versehen beim Bearbeiten verloren gehen kann. */}
<p>
Wenn du diesen Vertrag widerrufst, haben wir dir alle Zahlungen, die wir von dir erhalten
haben, einschließlich der Lieferkosten (mit Ausnahme der zusätzlichen Kosten, die sich
daraus ergeben, dass du eine andere Art der Lieferung als die von uns angebotene, günstigste
Standardlieferung gewählt hast), unverzüglich und spätestens binnen vierzehn Tagen ab dem
Tag zurückzuzahlen, an dem die Mitteilung über deinen Widerruf dieses Vertrags bei uns
eingegangen ist. Für diese Rückzahlung verwenden wir dasselbe Zahlungsmittel, das du bei der
ursprünglichen Transaktion eingesetzt hast, es sei denn, mit dir wurde ausdrücklich etwas
anderes vereinbart; in keinem Fall werden dir wegen dieser Rückzahlung Entgelte berechnet.
</p>
<p>
Wir können die Rückzahlung verweigern, bis wir die Waren wieder zurückerhalten haben oder
bis du den Nachweis erbracht hast, dass du die Waren zurückgesandt hast, je nachdem, welches
der frühere Zeitpunkt ist.
</p>
<p>
Du hast die Waren unverzüglich und in jedem Fall spätestens binnen vierzehn Tagen ab dem
Tag, an dem du uns über den Widerruf dieses Vertrags unterrichtest, an uns zurückzusenden
oder zu übergeben. Die Frist ist gewahrt, wenn du die Waren vor Ablauf der Frist von
vierzehn Tagen absendest. Du trägst die unmittelbaren Kosten der Rücksendung der Waren.
</p>
<p>
Du musst für einen etwaigen Wertverlust der Waren nur aufkommen, wenn dieser Wertverlust auf
einen zur Prüfung der Beschaffenheit, Eigenschaften und Funktionsweise der Waren nicht
notwendigen Umgang mit ihnen zurückzuführen ist.
</p>
<h2>Ausschluss/vorzeitiges Erlöschen des Widerrufsrechts</h2>
<p>
Das Widerrufsrecht besteht nicht bzw. erlischt vorzeitig unter anderem bei Verträgen zur
Lieferung von Waren, die nicht vorgefertigt sind und für deren Herstellung eine individuelle
Auswahl oder Bestimmung durch dich maßgeblich ist oder die eindeutig auf deine persönlichen
Bedürfnisse zugeschnitten sind (§ 312g Abs. 2 Nr. 1 BGB) — z. B. bei individuellen
Sonderanfertigungen auf Kundenwunsch. Bei Standardartikeln aus unserem Sortiment gilt das
Widerrufsrecht regulär.
📄 <a href="/muster-widerrufsformular/">Muster-Widerrufsformular</a> (nicht verpflichtend, kann für den Widerruf genutzt werden).
</p>
</div>
</section>