feat(loyalty): add full i18n support for all loyalty module pages

Replace hardcoded English strings across all 22 templates, 10 JS files,
and 4 locale files (en/fr/de/lb) with ~300 translation keys per language.
Uses server-side _() for Jinja2 templates and I18n.t() for JS toast
messages and dynamic Alpine.js expressions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 19:53:17 +01:00
parent 826ef2ddd2
commit 694a1cd1a5
37 changed files with 2916 additions and 563 deletions

View File

@@ -15,11 +15,9 @@
<span x-html="$icon('gift', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Total Programs</p>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.total_programs') }}</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.total_programs)">0</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
<span x-text="stats.active_programs"></span> active
</p>
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="$t('loyalty.shared.analytics.x_active', {count: stats.active_programs})"></p>
</div>
</div>
{% else %}
@@ -29,11 +27,9 @@
<span x-html="$icon('users', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Total Members</p>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.total_members') }}</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.total_cards)">0</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
<span x-text="formatNumber(stats.active_cards)"></span> active
</p>
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="$t('loyalty.shared.analytics.x_active', {count: formatNumber(stats.active_cards)})"></p>
</div>
</div>
{% endif %}
@@ -45,7 +41,7 @@
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
{% if show_programs_card %}Total Members{% else %}Active Members{% endif %}
{% if show_programs_card %}{{ _('loyalty.shared.analytics.total_members') }}{% else %}{{ _('loyalty.shared.analytics.active_members') }}{% endif %}
</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200"
x-text="formatNumber({% if show_programs_card %}stats.total_cards{% else %}stats.active_cards{% endif %})">0</p>
@@ -58,7 +54,7 @@
<span x-html="$icon('trending-up', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Points Issued (30d)</p>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.points_issued_30d') }}</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.points_issued_30d)">0</p>
</div>
</div>
@@ -69,7 +65,7 @@
<span x-html="$icon('chart-bar', 'w-5 h-5')"></span>
</div>
<div>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Transactions (30d)</p>
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.transactions_30d') }}</p>
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.transactions_30d)">0</p>
</div>
</div>
@@ -81,13 +77,13 @@
<div class="px-4 py-5 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('currency-dollar', 'inline w-5 h-5 mr-2')"></span>
Points Overview
{{ _('loyalty.shared.analytics.points_overview') }}
</h3>
<div class="space-y-4">
<!-- Progress bar: Issued vs Redeemed (30d) -->
<div>
<div class="flex items-center justify-between mb-1">
<span class="text-sm text-gray-600 dark:text-gray-400">Points Issued vs Redeemed (30d)</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.points_issued_vs_redeemed') }}</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-4 dark:bg-gray-700">
<div class="h-4 rounded-full flex">
@@ -96,16 +92,16 @@
</div>
</div>
<div class="flex justify-between mt-2 text-xs text-gray-500 dark:text-gray-400">
<span><span class="inline-block w-3 h-3 bg-green-500 rounded-full mr-1"></span>Issued: <span x-text="formatNumber(stats.points_issued_30d)"></span></span>
<span><span class="inline-block w-3 h-3 bg-orange-500 rounded-full mr-1"></span>Redeemed: <span x-text="formatNumber(stats.points_redeemed_30d)"></span></span>
<span><span class="inline-block w-3 h-3 bg-green-500 rounded-full mr-1"></span>{{ _('loyalty.shared.analytics.issued') }} <span x-text="formatNumber(stats.points_issued_30d)"></span></span>
<span><span class="inline-block w-3 h-3 bg-orange-500 rounded-full mr-1"></span>{{ _('loyalty.shared.analytics.redeemed') }} <span x-text="formatNumber(stats.points_redeemed_30d)"></span></span>
</div>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Redemption Rate</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.redemption_rate') }}</span>
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="redemptionRate + '%'">0%</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Outstanding Balance</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.outstanding_balance') }}</span>
<span class="font-semibold text-purple-600 dark:text-purple-400" x-text="formatNumber(stats.total_points_balance || 0)">0</span>
</div>
</div>
@@ -115,25 +111,25 @@
<div class="px-4 py-5 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('users', 'inline w-5 h-5 mr-2')"></span>
Member Activity
{{ _('loyalty.shared.analytics.member_activity') }}
</h3>
<div class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Active Members (30d)</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.active_members_30d') }}</span>
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.active_cards)">0</span>
</div>
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">New This Month</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.new_this_month') }}</span>
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.new_this_month || 0)">0</span>
</div>
{% if show_merchants_metric %}
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Merchants with Programs</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.merchants_with_programs') }}</span>
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.merchants_with_programs || 0)">0</span>
</div>
{% else %}
<div class="flex items-center justify-between">
<span class="text-sm text-gray-600 dark:text-gray-400">Avg Points Per Member</span>
<span class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.shared.analytics.avg_points_per_member') }}</span>
<span class="font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.avg_points_per_member || 0)">0</span>
</div>
{% endif %}
@@ -144,24 +140,24 @@
<!-- All-Time Statistics -->
<div class="mb-8 bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">All-Time Statistics</h3>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">{{ _('loyalty.shared.analytics.all_time_statistics') }}</h3>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Total Points Issued</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.shared.analytics.total_points_issued') }}</p>
<p class="text-xl font-bold text-gray-900 dark:text-white" x-text="formatNumber(stats.total_points_issued || 0)">0</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Total Points Redeemed</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.shared.analytics.total_points_redeemed') }}</p>
<p class="text-xl font-bold text-gray-900 dark:text-white" x-text="formatNumber(stats.total_points_redeemed || 0)">0</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Points Redeemed (30d)</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.shared.analytics.points_redeemed_30d') }}</p>
<p class="text-xl font-bold text-gray-900 dark:text-white" x-text="formatNumber(stats.points_redeemed_30d || 0)">0</p>
</div>
<div>
<p class="text-sm text-gray-500 dark:text-gray-400">Outstanding Liability</p>
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.shared.analytics.outstanding_liability') }}</p>
<p class="text-xl font-bold text-gray-900 dark:text-white"
x-text="'&euro;' + ((stats.estimated_liability_cents || 0) / 100).toFixed(2)">0</p>
</div>
@@ -173,17 +169,17 @@
{% if show_locations %}
<div x-show="locations && locations.length > 0" class="mb-8 bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">Location Breakdown</h3>
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">{{ _('loyalty.shared.analytics.location_breakdown') }}</h3>
</div>
<div class="overflow-x-auto">
<table class="w-full whitespace-nowrap">
<thead>
<tr class="text-xs font-semibold tracking-wide text-left text-gray-500 uppercase border-b dark:border-gray-700 bg-gray-50 dark:text-gray-400 dark:bg-gray-800">
<th class="px-4 py-3">Store</th>
<th class="px-4 py-3">Enrolled</th>
<th class="px-4 py-3">Points Earned</th>
<th class="px-4 py-3">Points Redeemed</th>
<th class="px-4 py-3">Transactions (30d)</th>
<th class="px-4 py-3">{{ _('loyalty.shared.analytics.store') }}</th>
<th class="px-4 py-3">{{ _('loyalty.shared.analytics.enrolled') }}</th>
<th class="px-4 py-3">{{ _('loyalty.shared.analytics.points_earned') }}</th>
<th class="px-4 py-3">{{ _('loyalty.shared.analytics.points_redeemed') }}</th>
<th class="px-4 py-3">{{ _('loyalty.shared.analytics.transactions_30d') }}</th>
</tr>
</thead>
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">

View File

@@ -23,31 +23,31 @@
<div x-show="isNewProgram" class="px-4 py-5 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('squares-2x2', 'inline w-5 h-5 mr-2')"></span>
Program Type
{{ _('loyalty.shared.program_form.program_type') }}
</h3>
<div class="grid gap-4 md:grid-cols-3">
<label class="flex items-start p-4 border rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
:class="settings.loyalty_type === 'points' ? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-200 dark:border-gray-600'">
<input type="radio" name="loyalty_type" value="points" x-model="settings.loyalty_type" class="mt-1 text-purple-600 form-radio">
<div class="ml-3">
<p class="font-medium text-gray-700 dark:text-gray-300">Points</p>
<p class="text-sm text-gray-500">Earn points per EUR spent</p>
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.loyalty.program.type.points') }}</p>
<p class="text-sm text-gray-500">{{ _('loyalty.shared.program_form.points_type_desc') }}</p>
</div>
</label>
<label class="flex items-start p-4 border rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
:class="settings.loyalty_type === 'stamps' ? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-200 dark:border-gray-600'">
<input type="radio" name="loyalty_type" value="stamps" x-model="settings.loyalty_type" class="mt-1 text-purple-600 form-radio">
<div class="ml-3">
<p class="font-medium text-gray-700 dark:text-gray-300">Stamps</p>
<p class="text-sm text-gray-500">Collect N stamps, get reward</p>
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.loyalty.program.type.stamps') }}</p>
<p class="text-sm text-gray-500">{{ _('loyalty.shared.program_form.stamps_type_desc') }}</p>
</div>
</label>
<label class="flex items-start p-4 border rounded-lg cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-700"
:class="settings.loyalty_type === 'hybrid' ? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-200 dark:border-gray-600'">
<input type="radio" name="loyalty_type" value="hybrid" x-model="settings.loyalty_type" class="mt-1 text-purple-600 form-radio">
<div class="ml-3">
<p class="font-medium text-gray-700 dark:text-gray-300">Hybrid</p>
<p class="text-sm text-gray-500">Both stamps and points</p>
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.loyalty.program.type.hybrid') }}</p>
<p class="text-sm text-gray-500">{{ _('loyalty.shared.program_form.hybrid_type_desc') }}</p>
</div>
</label>
</div>
@@ -57,22 +57,22 @@
<div x-show="settings.loyalty_type === 'stamps' || settings.loyalty_type === 'hybrid'" class="px-4 py-5 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('star', 'inline w-5 h-5 mr-2')"></span>
Stamps Configuration
{{ _('loyalty.shared.program_form.stamps_configuration') }}
</h3>
<div class="grid gap-6 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Stamps Target</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.stamps_target') }}</label>
<input type="number" min="2" max="50" x-model.number="settings.stamps_target"
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">Number of stamps needed for reward</p>
<p class="mt-1 text-xs text-gray-500">{{ _('loyalty.shared.program_form.stamps_target_help') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Reward Description</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.reward_description') }}</label>
<input type="text" x-model="settings.stamps_reward_description" placeholder="e.g., Free coffee" maxlength="255"
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>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Reward Value (cents)</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.reward_value_cents') }}</label>
<input type="number" min="0" x-model.number="settings.stamps_reward_value_cents"
placeholder="e.g., 500 for 5 EUR"
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">
@@ -84,38 +84,38 @@
<div x-show="settings.loyalty_type === 'points' || settings.loyalty_type === 'hybrid'" class="px-4 py-5 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('currency-dollar', 'inline w-5 h-5 mr-2')"></span>
Points Configuration
{{ _('loyalty.shared.program_form.points_configuration') }}
</h3>
<div class="grid gap-6 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Points per EUR spent</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.points_per_eur') }}</label>
<input type="number" min="1" max="100" x-model.number="settings.points_per_euro"
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">1 EUR = <span x-text="settings.points_per_euro || 1"></span> point(s)</p>
<p class="mt-1 text-xs text-gray-500" x-text="$t('loyalty.shared.program_form.eur_equals_points', {points: settings.points_per_euro || 1})"></p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Welcome Bonus Points</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.welcome_bonus_points') }}</label>
<input type="number" min="0" x-model.number="settings.welcome_bonus_points"
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">Bonus points awarded on enrollment</p>
<p class="mt-1 text-xs text-gray-500">{{ _('loyalty.shared.program_form.welcome_bonus_help') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Minimum Redemption Points</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.minimum_redemption_points') }}</label>
<input type="number" min="1" x-model.number="settings.minimum_redemption_points"
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>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Minimum Purchase (cents)</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.minimum_purchase_cents') }}</label>
<input type="number" min="0" x-model.number="settings.minimum_purchase_cents"
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">Minimum purchase amount to earn points (0 = no minimum)</p>
<p class="mt-1 text-xs text-gray-500">{{ _('loyalty.shared.program_form.minimum_purchase_help') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Points Expiration (days)</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.points_expiration_days') }}</label>
<input type="number" min="0" x-model.number="settings.points_expiration_days"
placeholder="0 = never expire"
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">Days of inactivity before points expire (0 = never)</p>
<p class="mt-1 text-xs text-gray-500">{{ _('loyalty.shared.program_form.points_expiration_help') }}</p>
</div>
</div>
</div>
@@ -125,33 +125,33 @@
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('gift', 'inline w-5 h-5 mr-2')"></span>
Redemption Rewards
{{ _('loyalty.shared.program_form.redemption_rewards') }}
</h3>
<button type="button" @click="addReward()"
class="flex items-center px-3 py-1 text-sm text-purple-600 hover:text-purple-700">
<span x-html="$icon('plus', 'w-4 h-4 mr-1')"></span>
Add Reward
{{ _('loyalty.shared.program_form.add_reward') }}
</button>
</div>
<div class="space-y-4">
<template x-if="settings.points_rewards.length === 0">
<p class="text-gray-500 dark:text-gray-400 text-sm">No rewards configured. Add a reward to allow customers to redeem points.</p>
<p class="text-gray-500 dark:text-gray-400 text-sm">{{ _('loyalty.shared.program_form.no_rewards_configured') }}</p>
</template>
<template x-for="(reward, index) in settings.points_rewards" :key="index">
<div class="flex items-start gap-4 p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
<div class="flex-1 grid gap-4 md:grid-cols-3">
<div>
<label class="block text-xs text-gray-500 mb-1">Reward Name</label>
<label class="block text-xs text-gray-500 mb-1">{{ _('loyalty.shared.program_form.reward_name') }}</label>
<input type="text" x-model="reward.name" placeholder="e.g., EUR5 off"
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-gray-300">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">Points Required</label>
<label class="block text-xs text-gray-500 mb-1">{{ _('loyalty.shared.program_form.points_required') }}</label>
<input type="number" min="1" x-model.number="reward.points_required"
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-gray-300">
</div>
<div>
<label class="block text-xs text-gray-500 mb-1">Description</label>
<label class="block text-xs text-gray-500 mb-1">{{ _('loyalty.shared.program_form.description') }}</label>
<input type="text" x-model="reward.description" placeholder="Optional description"
class="w-full px-3 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg dark:bg-gray-700 dark:text-gray-300">
</div>
@@ -169,26 +169,26 @@
<div class="px-4 py-5 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('shield-check', 'inline w-5 h-5 mr-2')"></span>
Anti-Fraud Settings
{{ _('loyalty.shared.program_form.anti_fraud_settings') }}
</h3>
<div class="grid gap-6 md:grid-cols-3">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Cooldown (minutes)</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.cooldown_minutes') }}</label>
<input type="number" min="0" max="1440" x-model.number="settings.cooldown_minutes"
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">Time between stamps from the same card</p>
<p class="mt-1 text-xs text-gray-500">{{ _('loyalty.shared.program_form.cooldown_help') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Max Daily Stamps</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.max_daily_stamps') }}</label>
<input type="number" min="1" max="50" x-model.number="settings.max_daily_stamps"
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">Maximum stamps per card per day</p>
<p class="mt-1 text-xs text-gray-500">{{ _('loyalty.shared.program_form.max_daily_stamps_help') }}</p>
</div>
<div class="flex items-end">
<label class="flex items-center gap-2 cursor-pointer">
<input type="checkbox" x-model="settings.require_staff_pin"
class="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500">
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Require Staff PIN</span>
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.shared.program_form.require_staff_pin') }}</span>
</label>
</div>
</div>
@@ -198,16 +198,16 @@
<div class="px-4 py-5 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('paint-brush', 'inline w-5 h-5 mr-2')"></span>
Branding
{{ _('loyalty.shared.program_form.branding') }}
</h3>
<div class="grid gap-6 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Card Name</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.card_name') }}</label>
<input type="text" x-model="settings.card_name" placeholder="e.g., VIP Rewards" maxlength="100"
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>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Primary Color</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.primary_color') }}</label>
<div class="flex items-center gap-3">
<input type="color" x-model="settings.card_color"
class="w-12 h-10 rounded cursor-pointer">
@@ -216,7 +216,7 @@
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Secondary Color</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.secondary_color') }}</label>
<div class="flex items-center gap-3">
<input type="color" x-model="settings.card_secondary_color"
class="w-12 h-10 rounded cursor-pointer">
@@ -226,13 +226,13 @@
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Logo URL <span class="text-red-500">*</span></label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.logo_url') }} <span class="text-red-500">*</span></label>
<input type="url" x-model="settings.logo_url" maxlength="500" placeholder="https://..." required
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">Required for Google Wallet integration. Must be a publicly accessible image URL (PNG or JPG).</p>
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ _('loyalty.shared.program_form.logo_url_help') }}</p>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Hero Image URL</label>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.shared.program_form.hero_image_url') }}</label>
<input type="url" x-model="settings.hero_image_url" maxlength="500" placeholder="https://..."
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>
@@ -243,16 +243,16 @@
<div class="px-4 py-5 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('document-text', 'inline w-5 h-5 mr-2')"></span>
Terms & Privacy
{{ _('loyalty.shared.program_form.terms_privacy') }}
</h3>
<div class="grid gap-6 md:grid-cols-2">
<div>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Terms & Conditions</label>
<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"
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>
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">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>
<input type="url" x-model="settings.privacy_url" maxlength="500" placeholder="https://..."
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>
@@ -264,12 +264,12 @@
<div class="px-4 py-5 mb-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('power', 'inline w-5 h-5 mr-2')"></span>
Program Status
{{ _('loyalty.shared.program_form.program_status') }}
</h3>
<label class="flex items-center justify-between p-4 border border-gray-200 dark:border-gray-600 rounded-lg">
<div>
<p class="font-medium text-gray-700 dark:text-gray-300">Program Active</p>
<p class="text-sm text-gray-500">When disabled, customers cannot earn or redeem</p>
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.shared.program_form.program_active') }}</p>
<p class="text-sm text-gray-500">{{ _('loyalty.shared.program_form.program_active_help') }}</p>
</div>
<div class="relative">
<input type="checkbox" x-model="settings.is_active" class="sr-only peer">
@@ -292,7 +292,7 @@
<button type="button" @click="confirmDelete()"
class="flex items-center px-4 py-2 text-sm font-medium text-red-600 border border-red-300 rounded-lg hover:bg-red-50 dark:text-red-400 dark:border-red-700 dark:hover:bg-red-900/20">
<span x-html="$icon('trash', 'w-4 h-4 mr-2')"></span>
Delete Program
{{ _('loyalty.shared.program_form.delete_program') }}
</button>
</template>
{% endif %}
@@ -300,12 +300,12 @@
<div class="flex items-center gap-3">
<a href="{{ cancel_url }}"
class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-700">
Cancel
{{ _('loyalty.common.cancel') }}
</a>
<button type="submit" :disabled="saving"
class="flex items-center px-6 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50">
<span x-show="saving" x-html="$icon('spinner', 'w-4 h-4 mr-2 animate-spin')"></span>
<span x-text="saving ? 'Saving...' : (isNewProgram ? 'Create Program' : 'Save Changes')"></span>
<span x-text="saving ? $t('loyalty.common.saving') : (isNewProgram ? $t('loyalty.shared.program_form.create_program') : $t('loyalty.shared.program_form.save_changes'))"></span>
</button>
</div>
</div>

View File

@@ -16,7 +16,7 @@
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
<span x-html="$icon('cog', 'inline w-5 h-5 mr-2')"></span>
Program Configuration
{{ _('loyalty.shared.program_view.program_configuration') }}
</h3>
<div class="flex items-center gap-2">
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium capitalize"
@@ -28,13 +28,13 @@
x-text="program?.loyalty_type || 'unknown'"></span>
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold leading-tight rounded-full"
:class="program?.is_active ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-gray-700 bg-gray-100 dark:text-gray-100 dark:bg-gray-700'">
<span x-text="program?.is_active ? 'Active' : 'Inactive'"></span>
<span x-text="program?.is_active ? $t('loyalty.common.active') : $t('loyalty.common.inactive')"></span>
</span>
{% if show_edit_button is not defined or show_edit_button %}
<a href="{{ edit_url }}"
class="flex items-center px-3 py-1.5 text-sm font-medium text-purple-600 border border-purple-300 rounded-lg hover:bg-purple-50 dark:text-purple-400 dark:border-purple-700 dark:hover:bg-purple-900/20">
<span x-html="$icon('pencil', 'w-4 h-4 mr-1')"></span>
Edit
{{ _('loyalty.common.edit') }}
</a>
{% endif %}
</div>
@@ -43,11 +43,11 @@
<!-- Program Info -->
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3 mb-6">
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Program Name</p>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">{{ _('loyalty.shared.program_view.program_name') }}</p>
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="program?.display_name || '-'">-</p>
</div>
<div>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Card Name</p>
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">{{ _('loyalty.shared.program_view.card_name') }}</p>
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="program?.card_name || '-'">-</p>
</div>
</div>
@@ -57,19 +57,19 @@
<div class="mb-6">
<h4 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3 flex items-center">
<span x-html="$icon('star', 'inline w-4 h-4 mr-1')"></span>
Stamps Configuration
{{ _('loyalty.shared.program_view.stamps_configuration') }}
</h4>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Stamps Target</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.stamps_target') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="program?.stamps_target || '-'">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Reward Description</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.reward_description') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="program?.stamps_reward_description || '-'">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Reward Value</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.reward_value') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300"
x-text="program?.stamps_reward_value_cents ? '€' + (program.stamps_reward_value_cents / 100).toFixed(2) : '-'">-</p>
</div>
@@ -82,32 +82,32 @@
<div class="mb-6">
<h4 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3 flex items-center">
<span x-html="$icon('currency-dollar', 'inline w-4 h-4 mr-1')"></span>
Points Configuration
{{ _('loyalty.shared.program_view.points_configuration') }}
</h4>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Points per EUR</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.points_per_eur') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="program?.points_per_euro || 1">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Welcome Bonus</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.welcome_bonus') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300"
x-text="program?.welcome_bonus_points ? program.welcome_bonus_points + ' points' : 'None'">-</p>
x-text="program?.welcome_bonus_points ? $t('loyalty.shared.program_view.x_points', {count: program.welcome_bonus_points}) : $t('loyalty.common.none')">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Minimum Redemption</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.minimum_redemption') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300"
x-text="program?.minimum_redemption_points ? program.minimum_redemption_points + ' points' : 'None'">-</p>
x-text="program?.minimum_redemption_points ? $t('loyalty.shared.program_view.x_points', {count: program.minimum_redemption_points}) : $t('loyalty.common.none')">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Minimum Purchase</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.minimum_purchase') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300"
x-text="program?.minimum_purchase_cents ? '€' + (program.minimum_purchase_cents / 100).toFixed(2) : 'None'">-</p>
x-text="program?.minimum_purchase_cents ? '€' + (program.minimum_purchase_cents / 100).toFixed(2) : $t('loyalty.common.none')">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Points Expiration</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.points_expiration') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300"
x-text="program?.points_expiration_days ? program.points_expiration_days + ' days of inactivity' : 'Never'">-</p>
x-text="program?.points_expiration_days ? $t('loyalty.shared.program_view.x_days_inactivity', {days: program.points_expiration_days}) : $t('loyalty.common.never')">-</p>
</div>
</div>
</div>
@@ -118,15 +118,15 @@
<div class="mb-6">
<h4 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3 flex items-center">
<span x-html="$icon('gift', 'inline w-4 h-4 mr-1')"></span>
Redemption Rewards
{{ _('loyalty.shared.program_view.redemption_rewards') }}
</h4>
<div class="overflow-hidden border border-gray-200 dark:border-gray-700 rounded-lg">
<table class="w-full text-sm">
<thead class="bg-gray-50 dark:bg-gray-700">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-gray-300 uppercase">Reward</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-gray-300 uppercase">Points Required</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-gray-300 uppercase">Description</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-gray-300 uppercase">{{ _('loyalty.shared.program_view.reward') }}</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-gray-300 uppercase">{{ _('loyalty.shared.program_view.points_required') }}</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-gray-300 uppercase">{{ _('loyalty.shared.program_view.description') }}</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
@@ -147,23 +147,23 @@
<div class="mb-6">
<h4 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3 flex items-center">
<span x-html="$icon('shield-check', 'inline w-4 h-4 mr-1')"></span>
Anti-Fraud
{{ _('loyalty.shared.program_view.anti_fraud') }}
</h4>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Cooldown</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.cooldown') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300"
x-text="program?.cooldown_minutes ? program.cooldown_minutes + ' minutes' : 'None'">-</p>
x-text="program?.cooldown_minutes ? $t('loyalty.shared.program_view.x_minutes', {count: program.cooldown_minutes}) : $t('loyalty.common.none')">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Max Daily Stamps</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.max_daily_stamps') }}</p>
<p class="text-sm font-medium text-gray-700 dark:text-gray-300" x-text="program?.max_daily_stamps || '-'">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Staff PIN Required</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.staff_pin_required') }}</p>
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold leading-tight rounded-full"
:class="program?.require_staff_pin ? 'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100' : 'text-gray-700 bg-gray-100 dark:bg-gray-700 dark:text-gray-100'">
<span x-text="program?.require_staff_pin ? 'Yes' : 'No'"></span>
<span x-text="program?.require_staff_pin ? $t('loyalty.common.yes') : $t('loyalty.common.no')"></span>
</span>
</div>
</div>
@@ -173,11 +173,11 @@
<div class="mb-6">
<h4 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3 flex items-center">
<span x-html="$icon('paint-brush', 'inline w-4 h-4 mr-1')"></span>
Branding
{{ _('loyalty.shared.program_view.branding') }}
</h4>
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Primary Color</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.primary_color') }}</p>
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded border border-gray-300 dark:border-gray-600"
:style="'background-color: ' + (program?.card_color || '#6B21A8')"></div>
@@ -185,7 +185,7 @@
</div>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Secondary Color</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.secondary_color') }}</p>
<div class="flex items-center gap-2">
<div class="w-6 h-6 rounded border border-gray-300 dark:border-gray-600"
:style="'background-color: ' + (program?.card_secondary_color || '#FFFFFF')"></div>
@@ -193,11 +193,11 @@
</div>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Logo URL</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.logo_url') }}</p>
<p class="text-sm text-gray-700 dark:text-gray-300 truncate" x-text="program?.logo_url || '-'">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Hero Image URL</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.hero_image_url') }}</p>
<p class="text-sm text-gray-700 dark:text-gray-300 truncate" x-text="program?.hero_image_url || '-'">-</p>
</div>
</div>
@@ -207,15 +207,15 @@
<div>
<h4 class="text-sm font-semibold text-gray-600 dark:text-gray-400 uppercase mb-3 flex items-center">
<span x-html="$icon('document-text', 'inline w-4 h-4 mr-1')"></span>
Terms & Privacy
{{ _('loyalty.shared.program_view.terms_privacy') }}
</h4>
<div class="grid gap-6 md:grid-cols-2">
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Terms & Conditions</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.terms_conditions') }}</p>
<p class="text-sm text-gray-700 dark:text-gray-300 whitespace-pre-line" x-text="program?.terms_text || '-'">-</p>
</div>
<div>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">Privacy Policy URL</p>
<p class="text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.shared.program_view.privacy_policy_url') }}</p>
<template x-if="program?.privacy_url">
<a :href="program.privacy_url" target="_blank" class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400" x-text="program.privacy_url"></a>
</template>