Compare commits
2 Commits
fde58bea06
...
4c1608f78a
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c1608f78a | |||
| 24219e4d9a |
@@ -405,7 +405,10 @@
|
|||||||
"logo_url_help": "Erforderlich für Google Wallet-Integration. Muss eine öffentlich zugängliche Bild-URL sein (PNG oder JPG).",
|
"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",
|
"hero_image_url": "Hintergrundbild-URL",
|
||||||
"terms_privacy": "AGB & Datenschutz",
|
"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",
|
"privacy_policy_url": "Datenschutzrichtlinien-URL",
|
||||||
"program_status": "Programmstatus",
|
"program_status": "Programmstatus",
|
||||||
"program_active": "Programm aktiv",
|
"program_active": "Programm aktiv",
|
||||||
|
|||||||
@@ -405,7 +405,10 @@
|
|||||||
"logo_url_help": "Required for Google Wallet integration. Must be a publicly accessible image URL (PNG or JPG).",
|
"logo_url_help": "Required for Google Wallet integration. Must be a publicly accessible image URL (PNG or JPG).",
|
||||||
"hero_image_url": "Hero Image URL",
|
"hero_image_url": "Hero Image URL",
|
||||||
"terms_privacy": "Terms & Privacy",
|
"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",
|
"privacy_policy_url": "Privacy Policy URL",
|
||||||
"program_status": "Program Status",
|
"program_status": "Program Status",
|
||||||
"program_active": "Program Active",
|
"program_active": "Program Active",
|
||||||
|
|||||||
@@ -405,7 +405,10 @@
|
|||||||
"logo_url_help": "Requis pour l'intégration Google Wallet. Doit être une URL d'image publique (PNG ou JPG).",
|
"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",
|
"hero_image_url": "URL de l'image principale",
|
||||||
"terms_privacy": "Conditions & Confidentialité",
|
"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é",
|
"privacy_policy_url": "URL politique de confidentialité",
|
||||||
"program_status": "Statut du programme",
|
"program_status": "Statut du programme",
|
||||||
"program_active": "Programme actif",
|
"program_active": "Programme actif",
|
||||||
|
|||||||
@@ -405,7 +405,10 @@
|
|||||||
"logo_url_help": "Erfuerderlech fir Google Wallet-Integratioun. Muss eng ëffentlech zougänglech Bild-URL sinn (PNG oder JPG).",
|
"logo_url_help": "Erfuerderlech fir Google Wallet-Integratioun. Muss eng ëffentlech zougänglech Bild-URL sinn (PNG oder JPG).",
|
||||||
"hero_image_url": "Hannergrondbild-URL",
|
"hero_image_url": "Hannergrondbild-URL",
|
||||||
"terms_privacy": "AGB & Dateschutz",
|
"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",
|
"privacy_policy_url": "Dateschutzrichtlinn-URL",
|
||||||
"program_status": "Programmstatus",
|
"program_status": "Programmstatus",
|
||||||
"program_active": "Programm aktiv",
|
"program_active": "Programm aktiv",
|
||||||
|
|||||||
@@ -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")
|
||||||
@@ -227,7 +227,12 @@ class LoyaltyProgram(Base, TimestampMixin, SoftDeleteMixin):
|
|||||||
terms_text = Column(
|
terms_text = Column(
|
||||||
Text,
|
Text,
|
||||||
nullable=True,
|
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(
|
privacy_url = Column(
|
||||||
String(500),
|
String(500),
|
||||||
|
|||||||
@@ -110,7 +110,8 @@ class ProgramCreate(BaseModel):
|
|||||||
hero_image_url: str | None = Field(None, max_length=500, description="Hero image URL")
|
hero_image_url: str | None = Field(None, max_length=500, description="Hero image URL")
|
||||||
|
|
||||||
# Terms
|
# 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")
|
privacy_url: str | None = Field(None, max_length=500, description="Privacy policy URL")
|
||||||
|
|
||||||
|
|
||||||
@@ -155,6 +156,7 @@ class ProgramUpdate(BaseModel):
|
|||||||
|
|
||||||
# Terms
|
# Terms
|
||||||
terms_text: str | None = None
|
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)
|
privacy_url: str | None = Field(None, max_length=500)
|
||||||
|
|
||||||
# Wallet integration
|
# Wallet integration
|
||||||
@@ -202,6 +204,7 @@ class ProgramResponse(BaseModel):
|
|||||||
|
|
||||||
# Terms
|
# Terms
|
||||||
terms_text: str | None = None
|
terms_text: str | None = None
|
||||||
|
terms_cms_page_slug: str | None = None
|
||||||
privacy_url: str | None = None
|
privacy_url: str | None = None
|
||||||
|
|
||||||
# Wallet
|
# Wallet
|
||||||
|
|||||||
@@ -27,10 +27,26 @@ function customerLoyaltyEnroll() {
|
|||||||
enrolledCard: null,
|
enrolledCard: null,
|
||||||
error: null,
|
error: null,
|
||||||
showTerms: false,
|
showTerms: false,
|
||||||
|
termsHtml: null,
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
loyaltyEnrollLog.info('Customer loyalty enroll initializing...');
|
loyaltyEnrollLog.info('Customer loyalty enroll initializing...');
|
||||||
await this.loadProgram();
|
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() {
|
async loadProgram() {
|
||||||
|
|||||||
@@ -215,7 +215,7 @@
|
|||||||
<a
|
<a
|
||||||
:href="'/admin/loyalty/merchants/' + program.merchant_id"
|
:href="'/admin/loyalty/merchants/' + program.merchant_id"
|
||||||
class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
class="flex items-center justify-center p-2 text-blue-600 rounded-lg hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
||||||
title="{{ _('loyalty.common.view') }}"
|
aria-label="{{ _('loyalty.common.view') }}"
|
||||||
>
|
>
|
||||||
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
||||||
</a>
|
</a>
|
||||||
@@ -224,26 +224,26 @@
|
|||||||
<a
|
<a
|
||||||
:href="'/admin/loyalty/merchants/' + program.merchant_id + '/program'"
|
:href="'/admin/loyalty/merchants/' + program.merchant_id + '/program'"
|
||||||
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
class="flex items-center justify-center p-2 text-purple-600 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
||||||
title="{{ _('loyalty.common.edit') }}"
|
aria-label="{{ _('loyalty.common.edit') }}"
|
||||||
>
|
>
|
||||||
<span x-html="$icon('edit', 'w-5 h-5')"></span>
|
<span x-html="$icon('edit', 'w-5 h-5')"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<!-- Delete Button -->
|
<!-- Delete Button -->
|
||||||
<button
|
<button type="button"
|
||||||
@click="confirmDeleteProgram(program)"
|
@click="confirmDeleteProgram(program)"
|
||||||
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
class="flex items-center justify-center p-2 text-red-600 rounded-lg hover:bg-red-50 dark:text-red-400 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
||||||
title="{{ _('loyalty.common.delete') }}"
|
aria-label="{{ _('loyalty.common.delete') }}"
|
||||||
>
|
>
|
||||||
<span x-html="$icon('delete', 'w-5 h-5')"></span>
|
<span x-html="$icon('delete', 'w-5 h-5')"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Activate/Deactivate Toggle -->
|
<!-- Activate/Deactivate Toggle -->
|
||||||
<button
|
<button type="button"
|
||||||
@click="toggleProgramActive(program)"
|
@click="toggleProgramActive(program)"
|
||||||
class="flex items-center justify-center p-2 rounded-lg focus:outline-none transition-colors"
|
class="flex items-center justify-center p-2 rounded-lg focus:outline-none transition-colors"
|
||||||
:class="program.is_active ? 'text-orange-600 hover:bg-orange-50 dark:text-orange-400 dark:hover:bg-gray-700' : 'text-green-600 hover:bg-green-50 dark:text-green-400 dark:hover:bg-gray-700'"
|
:class="program.is_active ? 'text-orange-600 hover:bg-orange-50 dark:text-orange-400 dark:hover:bg-gray-700' : 'text-green-600 hover:bg-green-50 dark:text-green-400 dark:hover:bg-gray-700'"
|
||||||
:title="program.is_active ? 'Deactivate program' : 'Activate program'"
|
:aria-label="program.is_active ? '{{ _('loyalty.common.deactivate') }}' : '{{ _('loyalty.common.activate') }}'"
|
||||||
>
|
>
|
||||||
<span x-html="$icon(program.is_active ? 'pause' : 'play', 'w-5 h-5')"></span>
|
<span x-html="$icon(program.is_active ? 'pause' : 'play', 'w-5 h-5')"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -103,17 +103,19 @@
|
|||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
{% if show_crud %}
|
{% if show_crud %}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button @click="openEditModal(pin)"
|
<button @click="openEditModal(pin)" type="button"
|
||||||
|
aria-label="{{ _('loyalty.common.edit') }}"
|
||||||
class="text-purple-600 hover:text-purple-700 dark:text-purple-400 text-sm">
|
class="text-purple-600 hover:text-purple-700 dark:text-purple-400 text-sm">
|
||||||
<span x-html="$icon('pencil', 'w-4 h-4')"></span>
|
<span x-html="$icon('pencil', 'w-4 h-4')"></span>
|
||||||
</button>
|
</button>
|
||||||
<button @click="openDeleteModal(pin)"
|
<button @click="openDeleteModal(pin)" type="button"
|
||||||
|
aria-label="{{ _('loyalty.common.delete') }}"
|
||||||
class="text-red-600 hover:text-red-700 dark:text-red-400 text-sm">
|
class="text-red-600 hover:text-red-700 dark:text-red-400 text-sm">
|
||||||
<span x-html="$icon('trash', 'w-4 h-4')"></span>
|
<span x-html="$icon('trash', 'w-4 h-4')"></span>
|
||||||
</button>
|
</button>
|
||||||
<button x-show="pin.is_locked" @click="unlockPin(pin)"
|
<button x-show="pin.is_locked" @click="unlockPin(pin)" type="button"
|
||||||
class="text-orange-600 hover:text-orange-700 dark:text-orange-400 text-sm"
|
aria-label="{{ _('loyalty.shared.pins.unlock') }}"
|
||||||
:title="$t('loyalty.shared.pins.unlock')">
|
class="text-orange-600 hover:text-orange-700 dark:text-orange-400 text-sm">
|
||||||
<span x-html="$icon('lock-open', 'w-4 h-4')"></span>
|
<span x-html="$icon('lock-open', 'w-4 h-4')"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -247,9 +247,11 @@
|
|||||||
</h3>
|
</h3>
|
||||||
<div class="grid gap-6 md:grid-cols-2">
|
<div class="grid gap-6 md:grid-cols-2">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.terms_conditions') }}</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.terms_cms_page') }}</label>
|
||||||
<textarea x-model="settings.terms_text" rows="3"
|
<input type="text" x-model="settings.terms_cms_page_slug" maxlength="200"
|
||||||
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"></textarea>
|
placeholder="e.g. terms-and-conditions"
|
||||||
|
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">
|
||||||
|
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ _('loyalty.shared.program_form.terms_cms_page_hint') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.privacy_policy_url') }}</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.privacy_policy_url') }}</label>
|
||||||
@@ -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">
|
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">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.terms_conditions') }}</label>
|
||||||
|
<textarea x-model="settings.terms_text" rows="3"
|
||||||
|
placeholder="{{ _('loyalty.shared.program_form.terms_fallback_hint') }}"
|
||||||
|
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"></textarea>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Program Status -->
|
<!-- Program Status -->
|
||||||
|
|||||||
@@ -114,13 +114,16 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<!-- Success Modal --> {# noqa: FE-004 #}
|
<!-- Success Modal --> {# noqa: FE-004 #}
|
||||||
<div x-show="enrolledCard" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
<div x-show="enrolledCard"
|
||||||
|
role="dialog" aria-modal="true" aria-labelledby="enroll-success-title"
|
||||||
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50"
|
||||||
|
@keydown.escape.window="enrolledCard = null">
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full mx-4 p-6">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-md w-full mx-4 p-6">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center">
|
<div class="w-16 h-16 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center">
|
||||||
<span x-html="$icon('check', 'w-8 h-8 text-green-500')"></span>
|
<span x-html="$icon('check', 'w-8 h-8 text-green-500')"></span>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">{{ _('loyalty.store.enroll.customer_enrolled') }}</h3>
|
<h3 id="enroll-success-title" class="text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">{{ _('loyalty.store.enroll.customer_enrolled') }}</h3>
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
||||||
{{ _('loyalty.store.enroll.card_number_label') }}: <span class="font-mono font-semibold" x-text="enrolledCard?.card_number"></span>
|
{{ _('loyalty.store.enroll.card_number_label') }}: <span class="font-mono font-semibold" x-text="enrolledCard?.card_number"></span>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
<p class="text-sm text-gray-500 dark:text-gray-400" x-text="selectedCard?.customer_email"></p>
|
<p class="text-sm text-gray-500 dark:text-gray-400" x-text="selectedCard?.customer_email"></p>
|
||||||
<p class="text-xs text-gray-400 dark:text-gray-500" x-text="$t('loyalty.store.terminal.card_label') + ': ' + selectedCard?.card_number"></p>
|
<p class="text-xs text-gray-400 dark:text-gray-500" x-text="$t('loyalty.store.terminal.card_label') + ': ' + selectedCard?.card_number"></p>
|
||||||
</div>
|
</div>
|
||||||
<button @click="clearCustomer()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
<button @click="clearCustomer()" type="button" aria-label="{{ _('loyalty.common.close') }}" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||||
<span x-html="$icon('x', 'w-5 h-5')"></span>
|
<span x-html="$icon('x', 'w-5 h-5')"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -324,8 +324,8 @@
|
|||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||||
{{ _('loyalty.store.terminal.pin_authorize_text') }}
|
{{ _('loyalty.store.terminal.pin_authorize_text') }}
|
||||||
</p>
|
</p>
|
||||||
<div class="flex justify-center mb-4">
|
<div class="flex justify-center mb-4" aria-live="polite" aria-atomic="true">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2" role="status" :aria-label="pinDigits.length + ' of 4 digits entered'">
|
||||||
<template x-for="i in 4">
|
<template x-for="i in 4">
|
||||||
<div class="w-12 h-12 border-2 rounded-lg flex items-center justify-center text-2xl font-bold"
|
<div class="w-12 h-12 border-2 rounded-lg flex items-center justify-center text-2xl font-bold"
|
||||||
:class="pinDigits.length >= i ? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600'"
|
:class="pinDigits.length >= i ? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600'"
|
||||||
@@ -348,7 +348,7 @@
|
|||||||
class="h-14 text-xl font-semibold rounded-lg bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600">
|
class="h-14 text-xl font-semibold rounded-lg bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600">
|
||||||
0
|
0
|
||||||
</button>
|
</button>
|
||||||
<button @click="removePinDigit()"
|
<button @click="removePinDigit()" type="button" aria-label="{{ _('loyalty.common.back') }}"
|
||||||
class="h-14 text-sm font-medium rounded-lg bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600">
|
class="h-14 text-sm font-medium rounded-lg bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600">
|
||||||
<span x-html="$icon('backspace', 'w-6 h-6 mx-auto')"></span>
|
<span x-html="$icon('backspace', 'w-6 h-6 mx-auto')"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -186,10 +186,12 @@
|
|||||||
|
|
||||||
<!-- Barcode Modal -->
|
<!-- Barcode Modal -->
|
||||||
<div x-show="showBarcode" x-cloak
|
<div x-show="showBarcode" x-cloak
|
||||||
|
role="dialog" aria-modal="true" aria-labelledby="barcode-modal-title"
|
||||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||||
@click.self="showBarcode = false">
|
@click.self="showBarcode = false"
|
||||||
|
@keydown.escape.window="showBarcode = false">
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-xl max-w-sm w-full p-6 text-center">
|
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-xl max-w-sm w-full p-6 text-center">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ _('loyalty.storefront.dashboard.your_loyalty_card') }}</h3>
|
<h3 id="barcode-modal-title" class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ _('loyalty.storefront.dashboard.your_loyalty_card') }}</h3>
|
||||||
|
|
||||||
<!-- Barcode Placeholder -->
|
<!-- Barcode Placeholder -->
|
||||||
<div class="bg-white p-4 rounded-lg mb-4">
|
<div class="bg-white p-4 rounded-lg mb-4">
|
||||||
|
|||||||
@@ -96,10 +96,10 @@
|
|||||||
style="color: var(--color-primary)">
|
style="color: var(--color-primary)">
|
||||||
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">
|
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">
|
||||||
{{ _('loyalty.enrollment.form.terms_agree') }}
|
{{ _('loyalty.enrollment.form.terms_agree') }}
|
||||||
<template x-if="program?.terms_text">
|
<template x-if="program?.terms_text || program?.terms_cms_page_slug">
|
||||||
<a href="#" @click.prevent="showTerms = true" class="underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.form.terms') }}</a>
|
<a href="#" @click.prevent="showTerms = true" class="underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.form.terms') }}</a>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="!program?.terms_text">
|
<template x-if="!program?.terms_text && !program?.terms_cms_page_slug">
|
||||||
<span class="underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.form.terms') }}</span>
|
<span class="underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.form.terms') }}</span>
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
@@ -137,22 +137,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# TODO: Rework T&C strategy - current approach (small text field on program model) won't scale
|
|
||||||
for full legal T&C. Options: (1) leverage the CMS module to host T&C pages, or
|
|
||||||
(2) create a dedicated T&C page within the loyalty module. Decision pending. #}
|
|
||||||
<!-- Terms & Conditions Modal -->
|
<!-- Terms & Conditions Modal -->
|
||||||
<div x-show="showTerms" x-cloak
|
<div x-show="showTerms" x-cloak
|
||||||
|
role="dialog" aria-modal="true" aria-labelledby="terms-modal-title"
|
||||||
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50"
|
class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50"
|
||||||
@click.self="showTerms = false"
|
@click.self="showTerms = false"
|
||||||
@keydown.escape.window="showTerms = false">
|
@keydown.escape.window="showTerms = false">
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-lg w-full max-h-[80vh] flex flex-col">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-lg w-full max-h-[80vh] flex flex-col">
|
||||||
<div class="flex items-center justify-between p-4 border-b dark:border-gray-700">
|
<div class="flex items-center justify-between p-4 border-b dark:border-gray-700">
|
||||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ _('loyalty.enrollment.form.terms') }}</h3>
|
<h3 id="terms-modal-title" class="text-lg font-semibold text-gray-900 dark:text-white">{{ _('loyalty.enrollment.form.terms') }}</h3>
|
||||||
<button @click="showTerms = false" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
<button @click="showTerms = false" type="button" aria-label="{{ _('loyalty.enrollment.close') }}" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||||
<span x-html="$icon('x-mark', 'w-5 h-5')"></span>
|
<span x-html="$icon('x-mark', 'w-5 h-5')"></span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-4 overflow-y-auto text-sm text-gray-700 dark:text-gray-300 whitespace-pre-line" x-text="program?.terms_text"></div>
|
{# CMS page content takes priority over legacy terms_text #}
|
||||||
|
<div x-show="termsHtml" class="p-4 overflow-y-auto text-sm text-gray-700 dark:text-gray-300 prose dark:prose-invert max-w-none" x-html="termsHtml"></div>
|
||||||
|
<div x-show="!termsHtml && program?.terms_text" class="p-4 overflow-y-auto text-sm text-gray-700 dark:text-gray-300 whitespace-pre-line" x-text="program?.terms_text"></div>
|
||||||
<template x-if="program?.privacy_url">
|
<template x-if="program?.privacy_url">
|
||||||
<div class="px-4 pb-2">
|
<div class="px-4 pb-2">
|
||||||
<a :href="program.privacy_url" target="_blank" class="text-sm underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.privacy_policy') }}</a>
|
<a :href="program.privacy_url" target="_blank" class="text-sm underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.privacy_policy') }}</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user