diff --git a/app/modules/loyalty/locales/de.json b/app/modules/loyalty/locales/de.json index 9b6c5f9e..4b270028 100644 --- a/app/modules/loyalty/locales/de.json +++ b/app/modules/loyalty/locales/de.json @@ -405,7 +405,10 @@ "logo_url_help": "Erforderlich für Google Wallet-Integration. Muss eine öffentlich zugängliche Bild-URL sein (PNG oder JPG).", "hero_image_url": "Hintergrundbild-URL", "terms_privacy": "AGB & Datenschutz", - "terms_conditions": "Allgemeine Geschäftsbedingungen", + "terms_cms_page": "CMS-Seiten-Slug", + "terms_cms_page_hint": "Geben Sie einen CMS-Seiten-Slug ein (z.B. agb) um die vollständigen AGB aus dem CMS-Modul anzuzeigen", + "terms_conditions": "Allgemeine Geschäftsbedingungen (Fallback)", + "terms_fallback_hint": "Wird verwendet wenn kein CMS-Slug gesetzt ist", "privacy_policy_url": "Datenschutzrichtlinien-URL", "program_status": "Programmstatus", "program_active": "Programm aktiv", diff --git a/app/modules/loyalty/locales/en.json b/app/modules/loyalty/locales/en.json index 949eecdd..bee41b67 100644 --- a/app/modules/loyalty/locales/en.json +++ b/app/modules/loyalty/locales/en.json @@ -405,7 +405,10 @@ "logo_url_help": "Required for Google Wallet integration. Must be a publicly accessible image URL (PNG or JPG).", "hero_image_url": "Hero Image URL", "terms_privacy": "Terms & Privacy", - "terms_conditions": "Terms & Conditions", + "terms_cms_page": "CMS Page Slug", + "terms_cms_page_hint": "Enter a CMS page slug (e.g. terms-and-conditions) to display full T&C from the CMS module", + "terms_conditions": "Terms & Conditions (fallback)", + "terms_fallback_hint": "Used when no CMS page slug is set", "privacy_policy_url": "Privacy Policy URL", "program_status": "Program Status", "program_active": "Program Active", diff --git a/app/modules/loyalty/locales/fr.json b/app/modules/loyalty/locales/fr.json index 27a1969e..c5f3a92c 100644 --- a/app/modules/loyalty/locales/fr.json +++ b/app/modules/loyalty/locales/fr.json @@ -405,7 +405,10 @@ "logo_url_help": "Requis pour l'intégration Google Wallet. Doit être une URL d'image publique (PNG ou JPG).", "hero_image_url": "URL de l'image principale", "terms_privacy": "Conditions & Confidentialité", - "terms_conditions": "Conditions Générales", + "terms_cms_page": "Slug de page CMS", + "terms_cms_page_hint": "Entrez un slug de page CMS (ex. conditions-generales) pour afficher les CGV complètes depuis le module CMS", + "terms_conditions": "Conditions Générales (secours)", + "terms_fallback_hint": "Utilisé quand aucun slug CMS n'est défini", "privacy_policy_url": "URL politique de confidentialité", "program_status": "Statut du programme", "program_active": "Programme actif", diff --git a/app/modules/loyalty/locales/lb.json b/app/modules/loyalty/locales/lb.json index 0c4e0ab0..3cd43b3d 100644 --- a/app/modules/loyalty/locales/lb.json +++ b/app/modules/loyalty/locales/lb.json @@ -405,7 +405,10 @@ "logo_url_help": "Erfuerderlech fir Google Wallet-Integratioun. Muss eng ëffentlech zougänglech Bild-URL sinn (PNG oder JPG).", "hero_image_url": "Hannergrondbild-URL", "terms_privacy": "AGB & Dateschutz", - "terms_conditions": "Allgemeng Geschäftsbedingungen", + "terms_cms_page": "CMS-Säiten-Slug", + "terms_cms_page_hint": "Gitt e CMS-Säiten-Slug an (z.B. agb) fir déi komplett AGB vum CMS-Modul unzeweisen", + "terms_conditions": "Allgemeng Geschäftsbedingungen (Fallback)", + "terms_fallback_hint": "Gëtt benotzt wann keen CMS-Slug gesat ass", "privacy_policy_url": "Dateschutzrichtlinn-URL", "program_status": "Programmstatus", "program_active": "Programm aktiv", diff --git a/app/modules/loyalty/migrations/versions/loyalty_006_add_terms_cms_page_slug.py b/app/modules/loyalty/migrations/versions/loyalty_006_add_terms_cms_page_slug.py new file mode 100644 index 00000000..3917d5d5 --- /dev/null +++ b/app/modules/loyalty/migrations/versions/loyalty_006_add_terms_cms_page_slug.py @@ -0,0 +1,35 @@ +"""loyalty 006 - add terms_cms_page_slug to loyalty_programs + +Allows linking a loyalty program's T&C to a CMS page instead of +using the simple terms_text field. When set, the enrollment page +resolves the slug to a full CMS page. The legacy terms_text is +kept as a fallback for existing programs. + +Revision ID: loyalty_006 +Revises: loyalty_005 +Create Date: 2026-04-11 +""" +import sqlalchemy as sa + +from alembic import op + +revision = "loyalty_006" +down_revision = "loyalty_005" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column( + "loyalty_programs", + sa.Column( + "terms_cms_page_slug", + sa.String(200), + nullable=True, + comment="CMS page slug for full T&C content (overrides terms_text when set)", + ), + ) + + +def downgrade() -> None: + op.drop_column("loyalty_programs", "terms_cms_page_slug") diff --git a/app/modules/loyalty/models/loyalty_program.py b/app/modules/loyalty/models/loyalty_program.py index 8108247b..b1b1ec2e 100644 --- a/app/modules/loyalty/models/loyalty_program.py +++ b/app/modules/loyalty/models/loyalty_program.py @@ -227,7 +227,12 @@ class LoyaltyProgram(Base, TimestampMixin, SoftDeleteMixin): terms_text = Column( Text, nullable=True, - comment="Loyalty program terms and conditions", + comment="Loyalty program terms and conditions (legacy — use terms_cms_page_slug when available)", + ) + terms_cms_page_slug = Column( + String(200), + nullable=True, + comment="CMS page slug for full T&C content (overrides terms_text when set)", ) privacy_url = Column( String(500), diff --git a/app/modules/loyalty/schemas/program.py b/app/modules/loyalty/schemas/program.py index 774bd665..b8bab56f 100644 --- a/app/modules/loyalty/schemas/program.py +++ b/app/modules/loyalty/schemas/program.py @@ -110,7 +110,8 @@ class ProgramCreate(BaseModel): hero_image_url: str | None = Field(None, max_length=500, description="Hero image URL") # Terms - terms_text: str | None = Field(None, description="Terms and conditions") + terms_text: str | None = Field(None, description="Terms and conditions (legacy)") + terms_cms_page_slug: str | None = Field(None, max_length=200, description="CMS page slug for T&C") privacy_url: str | None = Field(None, max_length=500, description="Privacy policy URL") @@ -155,6 +156,7 @@ class ProgramUpdate(BaseModel): # Terms terms_text: str | None = None + terms_cms_page_slug: str | None = Field(None, max_length=200) privacy_url: str | None = Field(None, max_length=500) # Wallet integration @@ -202,6 +204,7 @@ class ProgramResponse(BaseModel): # Terms terms_text: str | None = None + terms_cms_page_slug: str | None = None privacy_url: str | None = None # Wallet diff --git a/app/modules/loyalty/static/storefront/js/loyalty-enroll.js b/app/modules/loyalty/static/storefront/js/loyalty-enroll.js index 8ad2e834..c532cf5a 100644 --- a/app/modules/loyalty/static/storefront/js/loyalty-enroll.js +++ b/app/modules/loyalty/static/storefront/js/loyalty-enroll.js @@ -27,10 +27,26 @@ function customerLoyaltyEnroll() { enrolledCard: null, error: null, showTerms: false, + termsHtml: null, async init() { loyaltyEnrollLog.info('Customer loyalty enroll initializing...'); await this.loadProgram(); + // Load CMS T&C content if a page slug is configured + if (this.program?.terms_cms_page_slug) { + this.loadTermsFromCms(this.program.terms_cms_page_slug); + } + }, + + async loadTermsFromCms(slug) { + try { + const response = await apiClient.get(`/storefront/cms/pages/${slug}`); + if (response?.content_html) { + this.termsHtml = response.content_html; + } + } catch (e) { + loyaltyEnrollLog.warn('Could not load CMS T&C page:', e.message); + } }, async loadProgram() { diff --git a/app/modules/loyalty/templates/loyalty/shared/program-form.html b/app/modules/loyalty/templates/loyalty/shared/program-form.html index b8167cef..23a87c5a 100644 --- a/app/modules/loyalty/templates/loyalty/shared/program-form.html +++ b/app/modules/loyalty/templates/loyalty/shared/program-form.html @@ -247,9 +247,11 @@
- - + + +

{{ _('loyalty.shared.program_form.terms_cms_page_hint') }}

@@ -257,6 +259,12 @@ class="w-full px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300">
+
+ + +
diff --git a/app/modules/loyalty/templates/loyalty/storefront/enroll.html b/app/modules/loyalty/templates/loyalty/storefront/enroll.html index 7f4865ae..62f7fe12 100644 --- a/app/modules/loyalty/templates/loyalty/storefront/enroll.html +++ b/app/modules/loyalty/templates/loyalty/storefront/enroll.html @@ -96,10 +96,10 @@ style="color: var(--color-primary)"> {{ _('loyalty.enrollment.form.terms_agree') }} -