1) Gutscheincodes können jetzt im Adminbereich auf "nur einmal pro Person einlösbar" gestellt werden. Wird serverseitig durchgesetzt (neue Spalte orders.gutschein_code + Prüfung in functions/api/orders.js gegen die echte Bestelldatenbank, case-insensitiv nach E-Mail-Adresse) — kann also NICHT durch Löschen von localStorage/einen anderen Browser umgangen werden, anders als die bisherige rein clientseitige Rabattlogik. Stornierte Bestellungen zählen nicht als "schon benutzt". Ausführlich getestet: echter Browser-Checkout mit Einmal-Code funktioniert beim ersten Versuch, wird beim zweiten Versuch (gleiche E-Mail) korrekt mit klarer Fehlermeldung abgelehnt, mit anderer E-Mail weiterhin nutzbar. 2) Bestell-E-Mail-Benachrichtigung an [email protected]: recherchiert und vorbereitet (existierte vorher nirgends im Code). Läuft über Resend (kostenlos bis 3.000 E-Mails/Monat), im Hintergrund per context.waitUntil() nach jeder neuen Bestellung — verzögert nie die Antwort an die Kundschaft und kann eine Bestellung niemals zum Scheitern bringen, selbst wenn der Versand fehlschlägt (mit ungültigem Testschlüssel verifiziert: Bestellung bleibt trotzdem erfolgreich gespeichert). Wird automatisch aktiv, sobald ein RESEND_API_KEY als Secret hinterlegt wird. Co-Authored-By: Claude Sonnet 5 <[email protected]>
553 lines
29 KiB
YAML
553 lines
29 KiB
YAML
backend:
|
||
name: github
|
||
repo: vans-diy-bastelbedarf/website
|
||
branch: main
|
||
base_url: https://vans-diy-bastelbedarf-cms-auth.dogfather1608.workers.dev
|
||
|
||
logo:
|
||
src: /logo.png
|
||
|
||
app_title: "Van's Shop – Adminbereich"
|
||
|
||
# Bilder, die VanVan hier hochlädt, landen automatisch hier im Projekt und sind danach
|
||
# unter /img/products/<dateiname> auf der Website erreichbar.
|
||
media_folder: "public/img/products"
|
||
public_folder: "/img/products"
|
||
|
||
collections:
|
||
- name: products
|
||
label: Produkte
|
||
label_singular: Produkt
|
||
icon: shopping_bag
|
||
folder: src/content/products
|
||
create: true
|
||
delete: true
|
||
slug: "{{fields.slug}}"
|
||
identifier_field: slug
|
||
extension: json
|
||
format: json
|
||
summary: "{{name.de}} — {{preis}} €"
|
||
fields:
|
||
- label: "📷 Fotos"
|
||
name: bilder
|
||
widget: list
|
||
required: false
|
||
hint: "Hier Fotos hochladen oder per Drag & Drop reinziehen. Das erste Foto ist das Hauptbild, das überall zuerst angezeigt wird."
|
||
field: { label: Foto, name: bild, widget: image }
|
||
- label: Name
|
||
name: name
|
||
widget: object
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: string }
|
||
- { label: Englisch, name: en, widget: string }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
||
- { label: Französisch, name: fr, widget: string }
|
||
- label: Beschreibung
|
||
name: beschreibung
|
||
widget: object
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: text }
|
||
- { label: Englisch, name: en, widget: text }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: text }
|
||
- { label: Französisch, name: fr, widget: text }
|
||
- label: Kategorie
|
||
name: kategorie
|
||
widget: relation
|
||
collection: categories
|
||
search_fields: ["name.de"]
|
||
value_field: "slug"
|
||
display_fields: ["name.de"]
|
||
- label: Auszeichnungen
|
||
name: badges
|
||
widget: select
|
||
multiple: true
|
||
required: false
|
||
hint: "Erscheinen als kleine Sticker auf dem Produktbild"
|
||
options:
|
||
- { label: "✨ Neu", value: neu }
|
||
- { label: "🔥 Bestseller", value: bestseller }
|
||
- { label: "🏷️ Im Sale", value: sale }
|
||
- { label: "🧶 Handgemacht", value: handgemacht }
|
||
- label: "Unterkategorie (optional)"
|
||
name: unterkategorie
|
||
widget: string
|
||
required: false
|
||
hint: "Interner Code der Unterkategorie eintragen, z.B. hase (siehe Bereich Kategorien)"
|
||
- label: "Charakter innerhalb der Unterkategorie (optional)"
|
||
name: charakter
|
||
widget: string
|
||
required: false
|
||
hint: "Nur nötig, wenn die Unterkategorie mehrere Charaktere hat (z.B. 'hase' hat HasiDog, HasiGott, Mama Hase) — dann hier den Charakter-Code eintragen, z.B. hasidog (siehe Bereich Kategorien)."
|
||
- label: "Preis (€)"
|
||
name: preis
|
||
widget: number
|
||
value_type: float
|
||
step: 0.01
|
||
hint: "Der ganz normale Verkaufspreis, z.B. 19.99"
|
||
- label: "Reduzierter Preis? (nur ausfüllen, wenn im Sale)"
|
||
name: preisAlt
|
||
widget: number
|
||
value_type: float
|
||
step: 0.01
|
||
required: false
|
||
hint: "Hier den ALTEN, höheren Preis eintragen — er wird dann durchgestrichen neben dem neuen Preis angezeigt. Leer lassen, wenn kein Sale."
|
||
- label: "🏷️ Rabatte (optional — alles leer lassen, wenn kein automatischer Rabatt gelten soll)"
|
||
name: rabatt
|
||
widget: object
|
||
required: false
|
||
hint: "Zusätzlich zum 'Reduzierten Preis' oben kannst du hier einen automatischen Rabatt einrichten, der von selbst wieder abläuft, sowie einen Mengenrabatt (z.B. ab 3 Stück günstiger). Du musst nichts selbst ausrechnen — sobald du oben rechts auf 'Speichern' klickst, berechnet die Website den neuen Preis automatisch und zeigt ihn den Kund:innen sofort an."
|
||
fields:
|
||
- { label: "Rabatt in Prozent (z.B. 20 für 20%)", name: prozent, widget: number, value_type: int, required: false, hint: "Nur ausfüllen ODER den Betrag unten — nicht beides." }
|
||
- { label: "ODER: Rabatt als fester Betrag (€)", name: betrag, widget: number, value_type: float, step: 0.01, required: false }
|
||
- { label: "Rabatt gültig bis (leer = läuft nicht automatisch ab)", name: bis, widget: datetime, format: "YYYY-MM-DD", date_format: "YYYY-MM-DD", time_format: false, required: false, hint: "Nach diesem Datum verschwindet der Rabatt automatisch — ohne dass du etwas tun musst." }
|
||
- label: "Mengenrabatt-Staffel (z.B. ab 3 Stück 10% Rabatt)"
|
||
name: mengenrabatt
|
||
widget: list
|
||
required: false
|
||
summary: "ab {{fields.abMenge}} Stück → -{{fields.prozent}}%"
|
||
hint: "Gilt automatisch im Warenkorb, wenn jemand mehrere Stück dieses Produkts kauft."
|
||
fields:
|
||
- { label: "Ab wie vielen Stück", name: abMenge, widget: number, value_type: int }
|
||
- { label: "Rabatt in Prozent", name: prozent, widget: number, value_type: int }
|
||
- label: "🚚 Eigene Versandkosten je Land (nur für Einzelkauf dieses Produkts)"
|
||
name: versand
|
||
widget: object
|
||
hint: "Gilt NUR, wenn jemand genau 1 Stück von genau diesem Produkt kauft. Bei mehreren Produkten/Stück im Warenkorb gilt automatisch die allgemeine Versandkosten-Staffel (siehe Bereich '🚚 Versandkosten-Staffel' weiter unten in der Liste links). Ein Land leer lassen = für dieses Land gilt die Staffel auch beim Einzelkauf."
|
||
fields:
|
||
- { label: "Deutschland (€)", name: de, widget: number, value_type: float, step: 0.01, required: false }
|
||
- { label: "Österreich (€)", name: at, widget: number, value_type: float, step: 0.01, required: false }
|
||
- { label: "Schweiz (€)", name: ch, widget: number, value_type: float, step: 0.01, required: false }
|
||
- { label: "Luxemburg (€)", name: lu, widget: number, value_type: float, step: 0.01, required: false }
|
||
- label: "Auf Lager"
|
||
name: bestand
|
||
widget: number
|
||
value_type: int
|
||
hint: "Wie viele Stück hast du vorrätig? 0 = ausverkauft (Artikel bleibt trotzdem sichtbar)."
|
||
- { label: Artikelnummer, name: artikelnummer, widget: string, required: false }
|
||
- { label: Material, name: material, widget: string, required: false }
|
||
- { label: "Größe / Maße", name: groesse, widget: string, required: false }
|
||
- label: Farben
|
||
name: farben
|
||
widget: list
|
||
required: false
|
||
hint: "Eine Farbe pro Zeile, z.B. Lavendel"
|
||
- { label: Lieferumfang, name: lieferumfang, widget: string, required: false }
|
||
- { label: Pflegehinweise, name: pflegehinweise, widget: string, required: false }
|
||
- { label: Lieferzeit, name: lieferzeit, widget: string, required: false }
|
||
# Kein "Bewertungen"-Feld mehr hier: Kund:innen schreiben ihre Rezensionen jetzt direkt auf
|
||
# der Produktseite selbst (Herzen + Text), die Durchschnittsbewertung wird automatisch
|
||
# berechnet und überall auf der Seite live angezeigt (Produktkarten, Filter, Startseite).
|
||
# VanVan muss dafür nichts mehr im Adminbereich eintragen. Das Datenfeld "bewertungen" bleibt
|
||
# im Hintergrund als technische Grundlage bestehen (src/data/products.ts), ist hier aber
|
||
# bewusst nicht mehr als Formularfeld sichtbar.
|
||
- label: "Interner Code (URL) — nach Veröffentlichung nicht mehr ändern!"
|
||
name: slug
|
||
widget: string
|
||
hint: "Nur Kleinbuchstaben, Zahlen und Bindestriche, z.B. wolke-plushie-lavendel"
|
||
|
||
- name: categories
|
||
label: Kategorien
|
||
label_singular: Kategorie
|
||
icon: category
|
||
folder: src/content/categories
|
||
create: true
|
||
delete: false
|
||
slug: "{{fields.slug}}"
|
||
identifier_field: slug
|
||
extension: json
|
||
format: json
|
||
summary: "{{name.de}}"
|
||
fields:
|
||
- label: "Icon (Emoji)"
|
||
name: icon
|
||
widget: string
|
||
hint: "z.B. 🧸"
|
||
- label: Name
|
||
name: name
|
||
widget: object
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: string }
|
||
- { label: Englisch, name: en, widget: string }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
||
- { label: Französisch, name: fr, widget: string }
|
||
- label: Beschreibung
|
||
name: description
|
||
widget: object
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: text }
|
||
- { label: Englisch, name: en, widget: text }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: text }
|
||
- { label: Französisch, name: fr, widget: text }
|
||
- label: Unterkategorien
|
||
name: subcategories
|
||
widget: list
|
||
required: false
|
||
summary: "{{fields.name.de}}"
|
||
fields:
|
||
- { label: "Interner Code (URL)", name: slug, widget: string, hint: "z.B. hase — steht für die ganze Tier-Serie, nicht nur einen einzelnen Charakter." }
|
||
- label: Name
|
||
name: name
|
||
widget: object
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: string }
|
||
- { label: Englisch, name: en, widget: string }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
||
- { label: Französisch, name: fr, widget: string }
|
||
- label: "Name im großen Banner oben (optional)"
|
||
name: bannerName
|
||
widget: object
|
||
required: false
|
||
hint: "Zeigt diesen Namen groß im Banner ganz oben auf der Seite, statt des Serien-Namens (z.B. bei 'Hase' steht dort 'HasiDog', der bekannteste Charakter). Leer lassen = der normale Serien-Name oben wird angezeigt. Betrifft NUR die große Überschrift im Bild oben, nirgends sonst."
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: string }
|
||
- { label: Englisch, name: en, widget: string }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
||
- { label: Französisch, name: fr, widget: string }
|
||
- label: "Charaktere dieser Serie (z.B. mehrere Hasen-Varianten)"
|
||
name: characters
|
||
widget: list
|
||
required: false
|
||
summary: "{{fields.name.de}}"
|
||
hint: "Jeder Charakter bekommt auf der Unterkategorie-Seite seine eigene Box — die Foto-Seite wechselt automatisch ab (1. links, 2. rechts, 3. wieder links, usw.). Trage beim passenden Produkt weiter oben unter 'Charakter innerhalb der Unterkategorie' genau denselben Code ein, damit Foto und Text zusammenfinden."
|
||
fields:
|
||
- { label: "Interner Code", name: slug, widget: string, hint: "z.B. hasidog — muss beim Produkt unter 'Charakter' genau so eingetragen werden." }
|
||
- label: Name
|
||
name: name
|
||
widget: object
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: string }
|
||
- { label: Englisch, name: en, widget: string }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
||
- { label: Französisch, name: fr, widget: string }
|
||
- label: "Persönliche Vorstellung (1. Zeile 'Hallo, ich bin ...', 2. Zeile Persönlichkeit)"
|
||
name: vorstellung
|
||
widget: object
|
||
required: false
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: text }
|
||
- { label: Englisch, name: en, widget: text }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: text }
|
||
- { label: Französisch, name: fr, widget: text }
|
||
- { label: "Link zu einer externen 'Mehr über mich'-Seite (optional)", name: mehrLink, widget: string, required: false }
|
||
- label: "Reihenfolge (0 = zuerst)"
|
||
name: order
|
||
widget: number
|
||
value_type: int
|
||
- label: "Interner Code (URL) — nach Veröffentlichung nicht mehr ändern!"
|
||
name: slug
|
||
widget: string
|
||
|
||
- name: versandkosten
|
||
label: "🚚 Versandkosten-Staffel"
|
||
icon: local_shipping
|
||
files:
|
||
- name: staffel
|
||
label: "Versandkosten-Staffel nach Warenwert"
|
||
file: src/content/versandstaffel.json
|
||
description: >
|
||
Gilt automatisch im Warenkorb/an der Kasse, sobald jemand mehrere Produkte oder
|
||
mehrere Stück kauft (bei genau 1 Stück von 1 Produkt gilt stattdessen der eigene
|
||
Versandpreis am Produkt, falls dort einer eingetragen ist). Ab der unten je Land
|
||
eingestellten Freigrenze ist der Versand immer kostenlos, unabhängig von dieser Tabelle.
|
||
fields:
|
||
- label: "Versandkostenfreigrenze pro Land (€) — ab diesem Warenwert ist der Versand immer kostenlos"
|
||
name: freigrenze
|
||
widget: object
|
||
fields:
|
||
- { label: Deutschland, name: de, widget: number, value_type: float, step: 0.01 }
|
||
- { label: Österreich, name: at, widget: number, value_type: float, step: 0.01 }
|
||
- { label: Schweiz, name: ch, widget: number, value_type: float, step: 0.01 }
|
||
- { label: Luxemburg, name: lu, widget: number, value_type: float, step: 0.01 }
|
||
- label: Deutschland
|
||
name: de
|
||
widget: list
|
||
summary: "ab {{fields.abWarenwert}} € → {{fields.kosten}} €"
|
||
fields:
|
||
- { label: "Ab Warenwert (€)", name: abWarenwert, widget: number, value_type: float, step: 0.01 }
|
||
- { label: "Versandkosten (€)", name: kosten, widget: number, value_type: float, step: 0.01 }
|
||
- label: Österreich
|
||
name: at
|
||
widget: list
|
||
summary: "ab {{fields.abWarenwert}} € → {{fields.kosten}} €"
|
||
fields:
|
||
- { label: "Ab Warenwert (€)", name: abWarenwert, widget: number, value_type: float, step: 0.01 }
|
||
- { label: "Versandkosten (€)", name: kosten, widget: number, value_type: float, step: 0.01 }
|
||
- label: Schweiz
|
||
name: ch
|
||
widget: list
|
||
summary: "ab {{fields.abWarenwert}} € → {{fields.kosten}} €"
|
||
fields:
|
||
- { label: "Ab Warenwert (€)", name: abWarenwert, widget: number, value_type: float, step: 0.01 }
|
||
- { label: "Versandkosten (€)", name: kosten, widget: number, value_type: float, step: 0.01 }
|
||
- label: Luxemburg
|
||
name: lu
|
||
widget: list
|
||
summary: "ab {{fields.abWarenwert}} € → {{fields.kosten}} €"
|
||
fields:
|
||
- { label: "Ab Warenwert (€)", name: abWarenwert, widget: number, value_type: float, step: 0.01 }
|
||
- { label: "Versandkosten (€)", name: kosten, widget: number, value_type: float, step: 0.01 }
|
||
|
||
- name: gutscheine
|
||
label: "🎟️ Gutscheincodes"
|
||
icon: local_offer
|
||
files:
|
||
- name: codes
|
||
label: "Gutscheincodes"
|
||
file: src/content/gutscheine.json
|
||
description: >
|
||
Codes, die Kund:innen im Warenkorb/an der Kasse eingeben können, um Rabatt auf den
|
||
gesamten Warenwert zu bekommen (z.B. für Aktionen, Newsletter, Stammkund:innen).
|
||
fields:
|
||
- label: Gutscheincodes
|
||
name: codes
|
||
widget: list
|
||
summary: "{{fields.code}}"
|
||
fields:
|
||
- { label: "Code (z.B. WILLKOMMEN10)", name: code, widget: string, hint: "Groß-/Kleinschreibung ist egal — wird automatisch angepasst." }
|
||
- { label: "Rabatt in Prozent (z.B. 10 für 10%)", name: prozent, widget: number, value_type: int, required: false }
|
||
- { label: "ODER: Rabatt als fester Betrag (€)", name: betrag, widget: number, value_type: float, step: 0.01, required: false }
|
||
- { label: "Gültig bis (leer = unbegrenzt)", name: bis, widget: datetime, format: "YYYY-MM-DD", date_format: "YYYY-MM-DD", time_format: false, required: false }
|
||
- label: "Nur einmal pro Person einlösbar?"
|
||
name: einmaligProPerson
|
||
widget: boolean
|
||
default: false
|
||
hint: "Ja = derselbe Code kann nicht zweimal mit derselben E-Mail-Adresse bestellt werden (wird beim Bestellabschluss automatisch geprüft). Nein (Standard) = beliebig oft nutzbar, wie bisher."
|
||
|
||
- name: partnercodes
|
||
label: "🤝 Partner-Rabattcodes"
|
||
icon: handshake
|
||
files:
|
||
- name: codes
|
||
label: "Partner-Rabattcodes"
|
||
file: src/content/partnercodes.json
|
||
description: >
|
||
Eigene Rabattcodes für Kooperationen mit Streamer:innen, Content-Creator:innen o.ä.:
|
||
du handelst mit der Person einen Rabatt aus, den ihre Follower:innen mit diesem Code
|
||
beim Einkauf bekommen. Fachlich wie ein Gutscheincode, aber bewusst getrennt von den
|
||
allgemeinen Gutscheinen gelistet, damit du hier den Überblick behältst, welcher Code zu
|
||
welcher Partnerin/welchem Partner gehört. WICHTIG: Diese Seite kann noch nicht
|
||
automatisch mitzählen, wie oft ein Code eingelöst wurde (das bräuchte eine eigene
|
||
Datenbank, kommt evtl. in einer späteren Ausbaustufe) — nutze das Notizfeld unten, um
|
||
dir die vereinbarte Provision/Konditionen selbst zu merken.
|
||
fields:
|
||
- label: Partner-Rabattcodes
|
||
name: codes
|
||
widget: list
|
||
summary: "{{fields.code}} — {{fields.partner}}"
|
||
fields:
|
||
- { label: "Name der Partnerin/des Partners (z.B. Streamer-Name)", name: partner, widget: string, hint: "Nur für deine eigene Übersicht — wird Kund:innen nirgends angezeigt." }
|
||
- { label: "Code (z.B. MAXPLAYS15)", name: code, widget: string, hint: "Groß-/Kleinschreibung ist egal — wird automatisch angepasst." }
|
||
- { label: "Rabatt in Prozent (z.B. 15 für 15%)", name: prozent, widget: number, value_type: int, required: false }
|
||
- { label: "ODER: Rabatt als fester Betrag (€)", name: betrag, widget: number, value_type: float, step: 0.01, required: false }
|
||
- { label: "Gültig bis (leer = unbegrenzt)", name: bis, widget: datetime, format: "YYYY-MM-DD", date_format: "YYYY-MM-DD", time_format: false, required: false }
|
||
- { label: "Aktiv", name: aktiv, widget: boolean, default: true, hint: "Ausschalten, um den Code vorübergehend zu sperren, ohne ihn (und die Historie) zu löschen." }
|
||
- { label: "Gilt auch für Artikel, die schon im Sale sind?", name: giltAufSale, widget: boolean, default: true, hint: "Anlassen (Ja) = der Rabatt gilt auf ALLES im Warenkorb, auch auf schon reduzierte Artikel. Ausschalten (Nein) = der Rabatt gilt NUR auf Artikel zum normalen Preis — schon reduzierte/Sale-Artikel bekommen dann keinen zusätzlichen Partner-Rabatt obendrauf." }
|
||
- { label: "Notiz zu den Konditionen (z.B. vereinbarte Provision)", name: notiz, widget: text, required: false, hint: "Rein intern für dich — wirkt sich nicht auf die Website aus." }
|
||
|
||
- name: abo-einstellungen
|
||
label: "🎁 Abo-System"
|
||
icon: card_membership
|
||
files:
|
||
- name: einstellungen
|
||
label: "Abo-Stufen, Rabatte & Regeln"
|
||
file: src/content/abo-einstellungen.json
|
||
description: >
|
||
Preise, Rabatthöhen und Verhaltensregeln für das monatliche Abo-System (siehe
|
||
scripts/account.ts + data/abo.ts). Änderungen gelten ab dem nächsten Deploy für alle
|
||
Kund:innen. Die eigentlichen Kundendaten (wer welches Abo hat, Zahlungsstand,
|
||
freigeschaltete Prämien) liegen NICHT hier, sondern pro Kundenkonto (Phase 1: im
|
||
Browser der jeweiligen Person, Phase 2: in einer echten Datenbank) — hier werden nur
|
||
die grundsätzlichen Konditionen festgelegt, zu denen JEDES Abo läuft.
|
||
fields:
|
||
- label: "Kleines Abo"
|
||
name: klein
|
||
widget: object
|
||
fields: &abo-stufe-fields
|
||
- { label: "Preis pro Monat (€)", name: preis, widget: number, value_type: float, step: 0.01 }
|
||
- { label: "Rabatt auf jede Bestellung (%)", name: rabattProzent, widget: number, value_type: int, min: 0, max: 100 }
|
||
- { label: "Aktuell neu buchbar?", name: aktiv, widget: boolean, default: true, hint: "Aus: Diese Stufe erscheint im Konto ausgegraut und kann nicht neu abgeschlossen werden. Bestehende Kund:innen dieser Stufe behalten trotzdem alle Vorteile." }
|
||
- { label: "Kleiner Teddy: alle wie viele Monate?", name: kleinIntervallMonate, widget: number, value_type: int, min: 0, required: false, hint: "0 oder leer lassen = diese Stufe vergibt gar keinen kleinen Teddy." }
|
||
- { label: "Großer Teddy: alle wie viele Monate?", name: grossIntervallMonate, widget: number, value_type: int, min: 0, required: false, hint: "0 oder leer lassen = diese Stufe vergibt gar keinen großen Teddy." }
|
||
- label: "Name der Stufe"
|
||
name: name
|
||
widget: object
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: string }
|
||
- { label: Englisch, name: en, widget: string }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: string }
|
||
- { label: Französisch, name: fr, widget: string }
|
||
- label: "Vorteilstext (erscheint auf der Plan-Karte im Kundenkonto)"
|
||
name: vorteil
|
||
widget: object
|
||
fields:
|
||
- { label: Deutsch, name: de, widget: text }
|
||
- { label: Englisch, name: en, widget: text }
|
||
- { label: "Schweizerdeutsch", name: ch, widget: text }
|
||
- { label: Französisch, name: fr, widget: text }
|
||
- label: "Mittleres Abo"
|
||
name: mittel
|
||
widget: object
|
||
fields: *abo-stufe-fields
|
||
- label: "Großes Abo"
|
||
name: gross
|
||
widget: object
|
||
fields: *abo-stufe-fields
|
||
- label: "Welches Produkt ist der 'kleine Teddy'?"
|
||
name: kleinTeddySlug
|
||
widget: relation
|
||
collection: products
|
||
search_fields: ["name.de"]
|
||
value_field: "slug"
|
||
display_fields: ["name.de"]
|
||
hint: "Dieses Produkt wird kostenlos in den Warenkorb gelegt, sobald jemand eine 'kleiner Teddy'-Prämie einlöst."
|
||
- label: "Welches Produkt ist der 'große Teddy'?"
|
||
name: grossTeddySlug
|
||
widget: relation
|
||
collection: products
|
||
search_fields: ["name.de"]
|
||
value_field: "slug"
|
||
display_fields: ["name.de"]
|
||
hint: "Dieses Produkt wird kostenlos in den Warenkorb gelegt, sobald jemand eine 'großer Teddy'-Prämie einlöst."
|
||
- label: "Abo-Rabatt mit anderen Rabatten kombinierbar (Gutschein/Partnercode/Treuebonus)?"
|
||
name: kombinierbarMitAnderenRabatten
|
||
widget: boolean
|
||
default: false
|
||
hint: "Aus: Bei aktivem Abo gilt NUR der Abo-Rabatt, andere Rabatte werden für diese Bestellung ignoriert. An: alle Rabatte werden zusammengezählt."
|
||
- label: "Versand bei Teddy-Prämie ebenfalls kostenlos?"
|
||
name: versandKostenlosBeiPraemie
|
||
widget: boolean
|
||
default: false
|
||
hint: "Aus: Für den kostenlosen Teddy gelten die normalen Versandregeln des Shops."
|
||
- label: "Bei Wiederanmeldung nach vollständigem Auslaufen: bisherige Laufzeit fortsetzen?"
|
||
name: fortsetzungBeiWiederanmeldung
|
||
widget: boolean
|
||
default: false
|
||
hint: "Aus: Der Monatszähler für die nächste Prämie beginnt bei einer Wiederanmeldung wieder bei 0. An: er läuft dort weiter, wo er beim Auslaufen stand. Bereits eingelöste/freigeschaltete Teddys gehen NIE verloren, unabhängig von dieser Einstellung."
|
||
- label: "Monate aus verschiedenen Abo-Stufen beim Wechsel zusammenzählen?"
|
||
name: monateUeberStufenZusammenzaehlen
|
||
widget: boolean
|
||
default: false
|
||
hint: "Aus (empfohlen): Beim Wechsel in eine andere Stufe beginnt der Monatszähler für die nächste Prämie bei 0. An: die bisher gesammelten Monate werden in die neue Stufe übernommen."
|
||
|
||
- name: bankverbindung
|
||
label: "🏦 Bankverbindung"
|
||
icon: account_balance
|
||
files:
|
||
- name: konto
|
||
label: "Bankverbindung für Vorkasse"
|
||
file: src/content/bankverbindung.json
|
||
description: >
|
||
Wird Kund:innen direkt im Checkout angezeigt, sobald sie "Banküberweisung (Vorkasse)"
|
||
als Zahlungsart wählen — sie überweisen dann selbst auf dieses Konto. Bitte einmal mit
|
||
deinen echten Kontodaten ausfüllen, sonst bekommen Kund:innen nur einen Platzhalter
|
||
angezeigt.
|
||
fields:
|
||
- { label: Kontoinhaber:in, name: kontoinhaber, widget: string, required: false }
|
||
- { label: IBAN, name: iban, widget: string, required: false, hint: "z.B. DE12 3456 7890 1234 5678 90" }
|
||
- { label: BIC, name: bic, widget: string, required: false }
|
||
- { label: Bank (Name), name: bank, widget: string, required: false }
|
||
|
||
- name: rechtstexte
|
||
label: "⚖️ Rechtstexte & FAQ"
|
||
icon: gavel
|
||
files:
|
||
- name: impressum
|
||
label: "Impressum"
|
||
file: src/content/impressum.json
|
||
description: >
|
||
Erscheint auf /impressum/. Jeder Abschnitt hat eine Überschrift + einen Text — Absätze
|
||
durch eine Leerzeile trennen, einzelne Zeilenumbrüche (z.B. für Adresszeilen) reichen
|
||
mit Enter. Links (z.B. https://...) werden automatisch klickbar, du musst kein HTML
|
||
schreiben. Abschnitte können frei ergänzt, umbenannt oder gelöscht werden.
|
||
fields:
|
||
- label: "USt-IdNr. (optional)"
|
||
name: ustId
|
||
widget: string
|
||
required: false
|
||
hint: "Nur ausfüllen, falls vorhanden — als Kleinunternehmer nach §19 UStG oft nicht nötig. Erscheint als eigener Absatz unter den Abschnitten."
|
||
- label: Abschnitte
|
||
name: sections
|
||
widget: list
|
||
summary: "{{fields.heading}}"
|
||
fields:
|
||
- { label: Überschrift, name: heading, widget: string }
|
||
- { label: Text, name: body, widget: text }
|
||
- name: agb
|
||
label: "Allgemeine Geschäftsbedingungen (AGB)"
|
||
file: src/content/agb.json
|
||
description: >
|
||
Erscheint auf /agb/. Gleiches Prinzip wie beim Impressum — Abschnitte mit Überschrift +
|
||
Text, frei ergänzbar/änderbar.
|
||
fields:
|
||
- label: Abschnitte
|
||
name: sections
|
||
widget: list
|
||
summary: "{{fields.heading}}"
|
||
fields:
|
||
- { label: Überschrift, name: heading, widget: string }
|
||
- { label: Text, name: body, widget: text }
|
||
- name: datenschutz
|
||
label: "Datenschutzerklärung"
|
||
file: src/content/datenschutz.json
|
||
description: >
|
||
Erscheint auf /datenschutz/. Gleiches Prinzip — Abschnitte mit Überschrift + Text.
|
||
fields:
|
||
- label: Abschnitte
|
||
name: sections
|
||
widget: list
|
||
summary: "{{fields.heading}}"
|
||
fields:
|
||
- { label: Überschrift, name: heading, widget: string }
|
||
- { label: Text, name: body, widget: text }
|
||
- name: widerruf
|
||
label: "Widerrufsbelehrung"
|
||
file: src/content/widerruf.json
|
||
description: >
|
||
Erscheint auf /widerruf/. Gleiches Prinzip — Abschnitte mit Überschrift + Text. Der
|
||
Link zum Muster-Widerrufsformular am Ende der Seite ist fest eingebaut und muss hier
|
||
nicht gepflegt werden.
|
||
fields:
|
||
- label: Abschnitte
|
||
name: sections
|
||
widget: list
|
||
summary: "{{fields.heading}}"
|
||
fields:
|
||
- { label: Überschrift, name: heading, widget: string }
|
||
- { label: Text, name: body, widget: text }
|
||
- name: faq
|
||
label: "FAQ (Häufig gestellte Fragen)"
|
||
file: src/content/faq.json
|
||
description: >
|
||
Erscheint auf /faq/ — in allen 4 Sprachen separat pflegbar. Achtung: die 4 Sprachen
|
||
sind unabhängig voneinander, eine neue Frage muss in jeder Sprache einzeln ergänzt
|
||
werden, damit sie überall erscheint.
|
||
fields:
|
||
- label: Deutsch
|
||
name: de
|
||
widget: list
|
||
summary: "{{fields.q}}"
|
||
fields:
|
||
- { label: Frage, name: q, widget: string }
|
||
- { label: Antwort, name: a, widget: text }
|
||
- label: Englisch
|
||
name: en
|
||
widget: list
|
||
summary: "{{fields.q}}"
|
||
fields:
|
||
- { label: Frage, name: q, widget: string }
|
||
- { label: Antwort, name: a, widget: text }
|
||
- label: "Schweizerdeutsch"
|
||
name: ch
|
||
widget: list
|
||
summary: "{{fields.q}}"
|
||
fields:
|
||
- { label: Frage, name: q, widget: string }
|
||
- { label: Antwort, name: a, widget: text }
|
||
- label: Französisch
|
||
name: fr
|
||
widget: list
|
||
summary: "{{fields.q}}"
|
||
fields:
|
||
- { label: Frage, name: q, widget: string }
|
||
- { label: Antwort, name: a, widget: text }
|