Fix 15 accessibility issues across loyalty templates: Modals (4 fixes): - storefront/dashboard.html: barcode modal — add role="dialog", aria-modal, aria-labelledby, @keydown.escape - storefront/enroll.html: terms modal — add role="dialog", aria-modal, aria-labelledby, aria-label on close button - store/enroll.html: success modal — add role="dialog", aria-modal, aria-labelledby, @keydown.escape - store/terminal.html: PIN entry — add aria-live="polite" on digit display with role="status" for screen reader announcements Icon-only buttons (10 fixes): - shared/pins-list.html: edit, delete, unlock — add aria-label - admin/programs.html: view, edit, delete, activate/deactivate — add aria-label (dynamic for toggle state) - store/terminal.html: clear customer, backspace — add aria-label All buttons also get explicit type="button" where missing. 342 tests pass. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
375 lines
24 KiB
HTML
375 lines
24 KiB
HTML
{# app/modules/loyalty/templates/loyalty/store/terminal.html #}
|
|
{% extends "store/base.html" %}
|
|
{% from 'shared/macros/headers.html' import page_header_flex %}
|
|
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
|
{% from 'shared/macros/modals.html' import modal_simple %}
|
|
{% from 'shared/macros/inputs.html' import search_autocomplete %}
|
|
|
|
{% block title %}{{ _('loyalty.store.terminal.title') }}{% endblock %}
|
|
|
|
{% block i18n_modules %}['loyalty']{% endblock %}
|
|
|
|
{% block alpine_data %}storeLoyaltyTerminal(){% endblock %}
|
|
|
|
{% block content %}
|
|
<!-- Page Header -->
|
|
{% call page_header_flex(title=_('loyalty.store.terminal.title'), subtitle=_('loyalty.store.terminal.subtitle')) %}
|
|
<div class="flex items-center gap-3">
|
|
<a href="/store/{{ store_code }}/loyalty/cards"
|
|
class="flex items-center 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">
|
|
<span x-html="$icon('users', 'w-4 h-4 mr-2')"></span>
|
|
{{ _('loyalty.store.terminal.members') }}
|
|
</a>
|
|
<a href="/store/{{ store_code }}/loyalty/analytics"
|
|
class="flex items-center 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">
|
|
<span x-html="$icon('chart-bar', 'w-4 h-4 mr-2')"></span>
|
|
{{ _('loyalty.store.terminal.analytics') }}
|
|
</a>
|
|
</div>
|
|
{% endcall %}
|
|
|
|
{{ loading_state(_('loyalty.store.terminal.loading')) }}
|
|
|
|
{{ error_state(_('loyalty.store.terminal.error_loading')) }}
|
|
|
|
<!-- No Program Setup Notice -->
|
|
<div x-show="!loading && !program" class="mb-6 px-4 py-5 bg-yellow-50 border border-yellow-200 rounded-lg dark:bg-yellow-900/20 dark:border-yellow-800">
|
|
<div class="flex items-start">
|
|
<span x-html="$icon('exclamation-triangle', 'w-6 h-6 text-yellow-500 mr-3 flex-shrink-0')"></span>
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-yellow-800 dark:text-yellow-200">{{ _('loyalty.common.program_not_setup') }}</h3>
|
|
<p class="mt-1 text-sm text-yellow-700 dark:text-yellow-300">{{ _('loyalty.common.program_not_setup_desc') }}</p>
|
|
{% if user.role == 'merchant_owner' %}
|
|
<a href="/store/{{ store_code }}/loyalty/program/edit"
|
|
class="mt-3 inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-yellow-600 rounded-lg hover:bg-yellow-700">
|
|
<span x-html="$icon('cog', 'w-4 h-4 mr-2')"></span>
|
|
{{ _('loyalty.common.setup_program') }}
|
|
</a>
|
|
{% else %}
|
|
<p class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">{{ _('loyalty.common.contact_admin_setup') }}</p>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Terminal -->
|
|
<div x-show="!loading && program">
|
|
<div class="grid gap-6 lg:grid-cols-2">
|
|
<!-- Left: Customer Lookup -->
|
|
<div class="bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
<span x-html="$icon('search', 'inline w-5 h-5 mr-2')"></span>
|
|
{{ _('loyalty.store.terminal.find_customer') }}
|
|
</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<!-- Search Input with Autocomplete -->
|
|
<div class="mb-4">
|
|
{{ search_autocomplete(
|
|
search_var='searchQuery',
|
|
results_var='searchResults',
|
|
show_dropdown_var='showSearchDropdown',
|
|
loading_var='searchingCustomers',
|
|
search_action='debouncedSearchCustomers()',
|
|
select_action='selectCustomer(item)',
|
|
display_field='customer_name',
|
|
secondary_field='customer_email',
|
|
placeholder=_('loyalty.store.terminal.search_placeholder'),
|
|
min_chars=2,
|
|
no_results_text=_('loyalty.store.terminal.customer_not_found'),
|
|
loading_text=_('loyalty.store.terminal.looking_up')
|
|
) }}
|
|
</div>
|
|
<button
|
|
@click="lookupCustomer()"
|
|
:disabled="!searchQuery || lookingUp"
|
|
class="w-full flex items-center justify-center px-4 py-3 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 focus:outline-none disabled:opacity-50"
|
|
>
|
|
<span x-show="lookingUp" x-html="$icon('spinner', 'w-5 h-5 mr-2 animate-spin')"></span>
|
|
<span x-text="lookingUp ? '{{ _('loyalty.store.terminal.looking_up')|replace("'", "\\'") }}' : '{{ _('loyalty.store.terminal.look_up_customer')|replace("'", "\\'") }}'"></span>
|
|
</button>
|
|
|
|
<!-- Divider -->
|
|
<div class="relative my-6">
|
|
<div class="absolute inset-0 flex items-center">
|
|
<div class="w-full border-t border-gray-200 dark:border-gray-700"></div>
|
|
</div>
|
|
<div class="relative flex justify-center text-sm">
|
|
<span class="px-3 bg-white text-gray-500 dark:bg-gray-800 dark:text-gray-400">{{ _('loyalty.common.or') }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Enroll New Customer -->
|
|
<a href="/store/{{ store_code }}/loyalty/enroll"
|
|
class="w-full flex items-center justify-center px-4 py-3 text-sm font-medium text-purple-600 bg-purple-50 border border-purple-200 rounded-lg hover:bg-purple-100 dark:bg-purple-900/20 dark:text-purple-400 dark:border-purple-800">
|
|
<span x-html="$icon('user-plus', 'w-5 h-5 mr-2')"></span>
|
|
{{ _('loyalty.store.terminal.enroll_new_customer') }}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right: Customer Card (shown when found) -->
|
|
<div x-show="selectedCard" class="bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
<span x-html="$icon('user', 'inline w-5 h-5 mr-2')"></span>
|
|
{{ _('loyalty.store.terminal.customer_found') }}
|
|
</h3>
|
|
</div>
|
|
<div class="p-4">
|
|
<!-- Customer Info -->
|
|
<div class="flex items-start mb-4">
|
|
<div class="flex-shrink-0 w-12 h-12 rounded-full flex items-center justify-center"
|
|
:style="'background-color: ' + (program?.card_color || '#4F46E5') + '20'">
|
|
<span class="text-lg font-semibold"
|
|
:style="'color: ' + (program?.card_color || '#4F46E5')"
|
|
x-text="selectedCard?.customer_name?.charAt(0).toUpperCase() || '?'"></span>
|
|
</div>
|
|
<div class="ml-4 flex-1">
|
|
<p class="font-semibold text-gray-700 dark:text-gray-200" x-text="selectedCard?.customer_name || 'Unknown'"></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>
|
|
</div>
|
|
<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>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Balance Area -->
|
|
<div class="mb-6 p-4 rounded-lg text-center"
|
|
:style="'background-color: ' + (program?.card_color || '#4F46E5') + '10'">
|
|
<!-- Points balance (for points and hybrid) -->
|
|
<template x-if="program?.is_points_enabled">
|
|
<div>
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.terminal.points_balance') }}</p>
|
|
<p class="text-3xl font-bold"
|
|
:style="'color: ' + (program?.card_color || '#4F46E5')"
|
|
x-text="formatNumber(selectedCard?.points_balance || 0)"></p>
|
|
</div>
|
|
</template>
|
|
<!-- Stamps progress (for stamps and hybrid) -->
|
|
<template x-if="program?.is_stamps_enabled">
|
|
<div :class="program?.is_points_enabled ? 'mt-3 pt-3 border-t border-gray-200 dark:border-gray-700' : ''">
|
|
<p class="text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.terminal.stamps') }}</p>
|
|
<p class="text-3xl font-bold"
|
|
:style="'color: ' + (program?.card_color || '#4F46E5')"
|
|
x-text="(selectedCard?.stamp_count || 0) + ' / ' + (program?.stamps_target || 10)"></p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1"
|
|
x-text="selectedCard?.stamps_until_reward > 0 ? $t('loyalty.store.terminal.more_for_reward', {count: selectedCard.stamps_until_reward}) : $t('loyalty.store.terminal.ready_to_redeem')"></p>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- Action Panels -->
|
|
<div class="grid gap-4 md:grid-cols-2">
|
|
|
|
<!-- Stamp Panels (for stamps and hybrid) -->
|
|
<template x-if="program?.is_stamps_enabled">
|
|
<div class="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-3">
|
|
<span x-html="$icon('plus-circle', 'inline w-4 h-4 mr-1 text-green-500')"></span>
|
|
{{ _('loyalty.store.terminal.add_stamp') }}
|
|
</h4>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
{{ _('loyalty.store.terminal.current') }}: <span class="font-semibold" x-text="(selectedCard?.stamp_count || 0) + '/' + (program?.stamps_target || 10)"></span>
|
|
</p>
|
|
<button @click="showPinModal('stamp')"
|
|
:disabled="!selectedCard?.can_stamp"
|
|
class="w-full px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-lg hover:bg-green-700 disabled:opacity-50">
|
|
{{ _('loyalty.store.terminal.add_stamp') }}
|
|
</button>
|
|
<template x-if="!selectedCard?.can_stamp && selectedCard?.cooldown_ends_at">
|
|
<p class="text-xs text-red-500 mt-2">{{ _('loyalty.store.terminal.cooldown_active') }}</p>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<template x-if="program?.is_stamps_enabled">
|
|
<div class="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-3">
|
|
<span x-html="$icon('gift', 'inline w-4 h-4 mr-1 text-orange-500')"></span>
|
|
{{ _('loyalty.store.terminal.redeem_stamps') }}
|
|
</h4>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3"
|
|
x-text="selectedCard?.can_redeem_stamps ? $t('loyalty.store.terminal.reward_label') + ': ' + (selectedCard?.stamp_reward_description || program?.stamps_reward_description || $t('loyalty.store.terminal.free_item')) : $t('loyalty.store.terminal.not_enough_stamps')"></p>
|
|
<button @click="showPinModal('redeemStamps')"
|
|
:disabled="!selectedCard?.can_redeem_stamps"
|
|
class="w-full px-4 py-2 text-sm font-medium text-white bg-orange-600 rounded-lg hover:bg-orange-700 disabled:opacity-50">
|
|
{{ _('loyalty.store.terminal.redeem_stamps') }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Point Panels (for points and hybrid) -->
|
|
<template x-if="program?.is_points_enabled">
|
|
<div class="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-3">
|
|
<span x-html="$icon('plus-circle', 'inline w-4 h-4 mr-1 text-green-500')"></span>
|
|
{{ _('loyalty.store.terminal.earn_points') }}
|
|
</h4>
|
|
<div class="mb-3">
|
|
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.store.terminal.purchase_amount') }}</label>
|
|
<div class="relative">
|
|
<span class="absolute inset-y-0 left-0 flex items-center pl-3 text-gray-500">EUR</span>
|
|
<input type="number" step="0.01" min="0" {# noqa: FE-008 #}
|
|
x-model.number="earnAmount"
|
|
class="w-full pl-12 pr-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-green-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300">
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
{{ _('loyalty.store.terminal.points_to_award') }}: <span class="font-semibold text-green-600" x-text="Math.floor((earnAmount || 0) * (program?.points_per_euro || 1))"></span>
|
|
</p>
|
|
<button @click="showPinModal('earn')"
|
|
:disabled="!earnAmount || earnAmount <= 0"
|
|
class="w-full px-4 py-2 text-sm font-medium text-white bg-green-600 rounded-lg hover:bg-green-700 disabled:opacity-50">
|
|
{{ _('loyalty.store.terminal.award_points') }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
|
|
<template x-if="program?.is_points_enabled">
|
|
<div class="p-4 border border-gray-200 dark:border-gray-700 rounded-lg">
|
|
<h4 class="font-medium text-gray-700 dark:text-gray-300 mb-3">
|
|
<span x-html="$icon('gift', 'inline w-4 h-4 mr-1 text-orange-500')"></span>
|
|
{{ _('loyalty.store.terminal.redeem_reward') }}
|
|
</h4>
|
|
<div class="mb-3">
|
|
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.store.terminal.select_reward') }}</label>
|
|
<select x-model="selectedReward"
|
|
class="w-full px-4 py-2 text-sm border border-gray-300 dark:border-gray-600 rounded-lg focus:border-orange-400 focus:outline-none dark:bg-gray-700 dark:text-gray-300">
|
|
<option value="">{{ _('loyalty.store.terminal.select_reward_placeholder') }}</option>
|
|
<template x-for="reward in availableRewards" :key="reward.id">
|
|
<option :value="reward.id" :disabled="(selectedCard?.points_balance || 0) < reward.points_required"
|
|
x-text="reward.name + ' (' + reward.points_required + ' pts)'"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
<template x-if="selectedReward">
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
|
{{ _('loyalty.store.terminal.points_after') }}: <span class="font-semibold text-orange-600" x-text="formatNumber((selectedCard?.points_balance || 0) - (getSelectedRewardPoints() || 0))"></span>
|
|
</p>
|
|
</template>
|
|
<button @click="showPinModal('redeem')"
|
|
:disabled="!selectedReward"
|
|
class="w-full px-4 py-2 text-sm font-medium text-white bg-orange-600 rounded-lg hover:bg-orange-700 disabled:opacity-50">
|
|
{{ _('loyalty.store.terminal.redeem_reward') }}
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty State (when no customer selected) -->
|
|
<div x-show="!selectedCard" class="bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<div class="p-8 text-center">
|
|
<span x-html="$icon('user-circle', 'w-16 h-16 mx-auto text-gray-300 dark:text-gray-600')"></span>
|
|
<p class="mt-4 text-gray-500 dark:text-gray-400">{{ _('loyalty.store.terminal.search_empty_state') }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Recent Transactions -->
|
|
<div class="mt-6 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
|
<div class="px-4 py-3 border-b border-gray-200 dark:border-gray-700">
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
|
|
<span x-html="$icon('clock', 'inline w-5 h-5 mr-2')"></span>
|
|
{{ _('loyalty.store.terminal.recent_transactions') }}
|
|
</h3>
|
|
</div>
|
|
<div class="overflow-x-auto">
|
|
<table class="w-full">
|
|
<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-700">
|
|
<th class="px-4 py-3">{{ _('loyalty.store.terminal.col_time') }}</th>
|
|
<th class="px-4 py-3">{{ _('loyalty.store.terminal.col_customer') }}</th>
|
|
<th class="px-4 py-3">{{ _('loyalty.store.terminal.col_type') }}</th>
|
|
<th class="px-4 py-3 text-right">{{ _('loyalty.store.terminal.col_points') }}</th>
|
|
<th class="px-4 py-3">{{ _('loyalty.store.terminal.col_notes') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
|
<template x-if="recentTransactions.length === 0">
|
|
<tr>
|
|
<td colspan="5" class="px-4 py-6 text-center text-gray-500 dark:text-gray-400">
|
|
{{ _('loyalty.store.terminal.no_recent_transactions') }}
|
|
</td>
|
|
</tr>
|
|
</template>
|
|
<template x-for="tx in recentTransactions" :key="tx.id">
|
|
<tr class="text-gray-700 dark:text-gray-400">
|
|
<td class="px-4 py-3 text-sm" x-text="formatTime(tx.transaction_at)"></td>
|
|
<td class="px-4 py-3 text-sm" x-text="tx.customer_name || 'Unknown'"></td>
|
|
<td class="px-4 py-3">
|
|
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
|
:class="getTransactionColor(tx)"
|
|
x-text="getTransactionLabel(tx)"></span>
|
|
</td>
|
|
<td class="px-4 py-3 text-sm text-right font-medium"
|
|
:class="tx.points_delta > 0 ? 'text-green-600' : 'text-orange-600'"
|
|
x-text="(tx.points_delta > 0 ? '+' : '') + formatNumber(tx.points_delta)"></td>
|
|
<td class="px-4 py-3 text-sm text-gray-500" x-text="tx.notes || '-'"></td>
|
|
</tr>
|
|
</template>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Staff PIN Modal -->
|
|
{% call modal_simple(id='pinModal', title=_('loyalty.store.terminal.enter_staff_pin'), show_var='showPinEntry') %}
|
|
<div class="p-6">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
|
{{ _('loyalty.store.terminal.pin_authorize_text') }}
|
|
</p>
|
|
<div class="flex justify-center mb-4" aria-live="polite" aria-atomic="true">
|
|
<div class="flex gap-2" role="status" :aria-label="pinDigits.length + ' of 4 digits entered'">
|
|
<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"
|
|
:class="pinDigits.length >= i ? 'border-purple-500 bg-purple-50 dark:bg-purple-900/20' : 'border-gray-300 dark:border-gray-600'"
|
|
x-text="pinDigits.length >= i ? '*' : ''"></div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-3 gap-2 max-w-xs mx-auto">
|
|
<template x-for="digit in [1, 2, 3, 4, 5, 6, 7, 8, 9]">
|
|
<button @click="addPinDigit(digit)"
|
|
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">
|
|
<span x-text="digit"></span>
|
|
</button>
|
|
</template>
|
|
<button @click="pinDigits = ''"
|
|
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">
|
|
{{ _('loyalty.store.terminal.clear') }}
|
|
</button>
|
|
<button @click="addPinDigit(0)"
|
|
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
|
|
</button>
|
|
<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">
|
|
<span x-html="$icon('backspace', 'w-6 h-6 mx-auto')"></span>
|
|
</button>
|
|
</div>
|
|
<div class="mt-4 flex justify-end gap-3">
|
|
<button @click="cancelPinEntry()"
|
|
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">
|
|
{{ _('loyalty.common.cancel') }}
|
|
</button>
|
|
<button @click="submitTransaction()"
|
|
:disabled="pinDigits.length !== 4 || processing"
|
|
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50">
|
|
<span x-show="processing" x-html="$icon('spinner', 'w-4 h-4 mr-2 inline animate-spin')"></span>
|
|
<span x-text="processing ? '{{ _('loyalty.store.terminal.processing')|replace("'", "\\'") }}' : '{{ _('loyalty.store.terminal.confirm')|replace("'", "\\'") }}'"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
{% endcall %}
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script defer src="{{ url_for('loyalty_static', path='store/js/loyalty-terminal.js') }}"></script>
|
|
{% endblock %}
|