diff --git a/public/admin/config.yml b/public/admin/config.yml index 14e386a..bee4106 100644 --- a/public/admin/config.yml +++ b/public/admin/config.yml @@ -284,6 +284,36 @@ collections: - { 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 } + - 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: "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: bankverbindung label: "🏦 Bankverbindung" icon: account_balance diff --git a/src/content/partnercodes.json b/src/content/partnercodes.json new file mode 100644 index 0000000..dec17ab --- /dev/null +++ b/src/content/partnercodes.json @@ -0,0 +1,3 @@ +{ + "codes": [] +} diff --git a/src/data/partnercodes.ts b/src/data/partnercodes.ts new file mode 100644 index 0000000..ffbde48 --- /dev/null +++ b/src/data/partnercodes.ts @@ -0,0 +1,56 @@ +// Partner-/Influencer-Rabattcodes — eigene Codes, die VanVan z.B. mit Streamer:innen oder +// Content-Creator:innen aushandelt: Kund:innen bekommen damit Rabatt beim Einkauf, während VanVan +// im Adminbereich für jeden Code hinterlegt, zu welcher Partnerin/welchem Partner er gehört (für +// die eigene Übersicht, z.B. welche vereinbarte Provision dahintersteckt). +// +// Fachlich fast identisch zu einem normalen Gutschein (Rabatt auf den Warenwert), aber bewusst als +// eigene, separate Liste geführt — allgemeine Rabattaktionen (Gutscheine) und individuell mit +// einzelnen Partner:innen ausgehandelte Codes sollen im Admin nicht durcheinandergeraten. +// +// WICHTIG (Grenze von Phase 1 — reine statische Seite ohne eigenes Backend): Diese Codes können +// einen Rabatt gewähren, aber es gibt (noch) keine automatische Zähl-/Provisions-Auswertung, WER +// wie oft welchen Code eingelöst hat — das bräuchte einen echten Server (Phase 2, siehe +// Tech-Stack-Entscheidung im Projekt-Vault). Bis dahin: das `notiz`-Feld im Admin dient VanVan als +// Gedächtnisstütze für die vereinbarten Konditionen. + +// @ts-ignore -- JSON-Import, zur Build-Zeit von Astro/Vite aufgelöst +import partnercodesRaw from "../content/partnercodes.json"; + +export interface Partnercode { + code: string; + /** Name der Partnerin/des Partners (Streamer:in, Creator:in, ...) — nur für VanVans eigene + * Übersicht im Admin, wird Kund:innen nirgends angezeigt. */ + partner: string; + prozent?: number; + betrag?: number; + bis?: string; // ISO-Datum, optional + /** Auf false setzen, um einen Code zu pausieren, ohne ihn (und die Partner-Historie) zu löschen. */ + aktiv?: boolean; +} + +const partnercodes = (partnercodesRaw as { codes: Partnercode[] }).codes ?? []; + +function istGueltig(bis?: string): boolean { + if (!bis) return true; + return new Date() <= new Date(`${bis}T23:59:59`); +} + +/** Sucht einen Partnercode anhand des eingegebenen Codes (Groß-/Kleinschreibung egal). Pausierte + * (aktiv: false) oder abgelaufene Codes gelten als nicht gefunden. */ +export function findePartnercode(code: string): Partnercode | undefined { + const normalisiert = code.trim().toUpperCase(); + return partnercodes.find( + (c) => c.code.toUpperCase() === normalisiert && c.aktiv !== false && istGueltig(c.bis) + ); +} + +/** Berechnet den Rabatt-Betrag in Euro für einen gegebenen Warenwert. */ +export function partnercodeRabatt(code: Partnercode, warenwert: number): number { + if (typeof code.prozent === "number" && code.prozent > 0) { + return Math.min(warenwert, warenwert * (code.prozent / 100)); + } + if (typeof code.betrag === "number" && code.betrag > 0) { + return Math.min(warenwert, code.betrag); + } + return 0; +} diff --git a/src/i18n/ui.ts b/src/i18n/ui.ts index 1b809aa..4e80f5d 100644 --- a/src/i18n/ui.ts +++ b/src/i18n/ui.ts @@ -155,6 +155,9 @@ export const ui = { couponLabel: "🎟️ Gutscheincode", couponPlaceholder: "Code eingeben", couponApply: "Einlösen", couponRemove: "Entfernen", buyNow: "Jetzt kaufen", keptInCart: "Bleibt im Warenkorb, wird noch nicht gekauft", couponInvalid: "Code ungültig oder abgelaufen.", couponApplied: (code: string) => `Gutschein „${code}" eingelöst`, + partnerDiscount: "🤝 Partner-Rabatt", + partnerCodeLabel: "🤝 Partner-Rabattcode", partnerCodePlaceholder: "Code eingeben", partnerCodeApply: "Einlösen", partnerCodeRemove: "Entfernen", + partnerCodeInvalid: "Code ungültig oder abgelaufen.", partnerCodeApplied: (code: string) => `Partner-Code „${code}" eingelöst`, }, checkout: { eyebrow: "Checkout", title: "Bestellung abschließen", @@ -338,6 +341,9 @@ export const ui = { couponLabel: "🎟️ Coupon code", couponPlaceholder: "Enter code", couponApply: "Apply", couponRemove: "Remove", buyNow: "Buy now", keptInCart: "Stays in cart, not purchased yet", couponInvalid: "Code invalid or expired.", couponApplied: (code: string) => `Coupon "${code}" applied`, + partnerDiscount: "🤝 Partner discount", + partnerCodeLabel: "🤝 Partner discount code", partnerCodePlaceholder: "Enter code", partnerCodeApply: "Apply", partnerCodeRemove: "Remove", + partnerCodeInvalid: "Code invalid or expired.", partnerCodeApplied: (code: string) => `Partner code "${code}" applied`, }, checkout: { eyebrow: "Checkout", title: "Complete your order", @@ -521,6 +527,9 @@ export const ui = { couponLabel: "🎟️ Gutschy-Code", couponPlaceholder: "Code igäh", couponApply: "Ilöse", couponRemove: "Ewägmache", buyNow: "Jetzt chaufe", keptInCart: "Blibt im Warenchorb, wird no nid chauft", couponInvalid: "Code ungültig oder abgloffe.", couponApplied: (code: string) => `Gutschy „${code}" iglöst`, + partnerDiscount: "🤝 Partner-Rabatt", + partnerCodeLabel: "🤝 Partner-Rabattcode", partnerCodePlaceholder: "Code igäh", partnerCodeApply: "Ilöse", partnerCodeRemove: "Ewägmache", + partnerCodeInvalid: "Code ungültig oder abgloffe.", partnerCodeApplied: (code: string) => `Partner-Code „${code}" iglöst`, }, checkout: { eyebrow: "Checkout", title: "Bschtellig abschliesse", @@ -704,6 +713,9 @@ export const ui = { couponLabel: "🎟️ Code promo", couponPlaceholder: "Entrer le code", couponApply: "Valider", couponRemove: "Retirer", buyNow: "Acheter maintenant", keptInCart: "Reste dans le panier, pas encore acheté", couponInvalid: "Code invalide ou expiré.", couponApplied: (code: string) => `Code promo « ${code} » appliqué`, + partnerDiscount: "🤝 Remise partenaire", + partnerCodeLabel: "🤝 Code partenaire", partnerCodePlaceholder: "Entrer le code", partnerCodeApply: "Valider", partnerCodeRemove: "Retirer", + partnerCodeInvalid: "Code invalide ou expiré.", partnerCodeApplied: (code: string) => `Code partenaire « ${code} » appliqué`, }, checkout: { eyebrow: "Paiement", title: "Finaliser la commande", diff --git a/src/pages/ch/checkout/index.astro b/src/pages/ch/checkout/index.astro index 84cbd48..5aec746 100644 --- a/src/pages/ch/checkout/index.astro +++ b/src/pages/ch/checkout/index.astro @@ -134,6 +134,15 @@ const t = useTranslations(lang);


+
+ +
+ + +
+

+
+

🚚 {t.checkout.shippingHintTitle}

{t.checkout.shippingHint}

{t.checkout.shippingLink} @@ -142,19 +151,19 @@ const t = useTranslations(lang); -