Complete implementation of loyalty module Phase 2 features: Database & Models: - Add company_id to LoyaltyProgram for chain-wide loyalty - Add company_id to LoyaltyCard for multi-location support - Add CompanyLoyaltySettings model for admin-controlled settings - Add points expiration, welcome bonus, and minimum redemption fields - Add POINTS_EXPIRED, WELCOME_BONUS transaction types Services: - Update program_service for company-based queries - Update card_service with enrollment and welcome bonus - Update points_service with void_points for returns - Update stamp_service for company context - Update pin_service for company-wide operations API Endpoints: - Admin: Program listing with stats, company detail views - Vendor: Terminal operations, card management, settings - Storefront: Customer card/transactions, self-enrollment UI Templates: - Admin: Programs dashboard, company detail, settings - Vendor: Terminal, cards list, card detail, settings, stats, enrollment - Storefront: Dashboard, history, enrollment, success pages Background Tasks: - Point expiration task (daily, based on inactivity) - Wallet sync task (hourly) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
147 lines
8.0 KiB
HTML
147 lines
8.0 KiB
HTML
{# app/modules/loyalty/templates/loyalty/vendor/enroll.html #}
|
|
{% extends "vendor/base.html" %}
|
|
{% from 'shared/macros/headers.html' import detail_page_header %}
|
|
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
|
|
|
{% block title %}Enroll Customer{% endblock %}
|
|
|
|
{% block alpine_data %}vendorLoyaltyEnroll(){% endblock %}
|
|
|
|
{% block content %}
|
|
{% call detail_page_header("'Enroll New Customer'", '/vendor/' + vendor_code + '/loyalty/terminal') %}
|
|
Add a new member to your loyalty program
|
|
{% endcall %}
|
|
|
|
{{ loading_state('Loading...') }}
|
|
{{ error_state('Error loading enrollment form') }}
|
|
|
|
<div x-show="!loading" class="max-w-2xl">
|
|
<form @submit.prevent="enrollCustomer">
|
|
<!-- Customer Information -->
|
|
<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('user', 'inline w-5 h-5 mr-2')"></span>
|
|
Customer Information
|
|
</h3>
|
|
|
|
<div class="space-y-4">
|
|
<div class="grid gap-4 md:grid-cols-2">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
First Name <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="text" x-model="form.first_name" 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">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Last Name</label>
|
|
<input type="text" x-model="form.last_name"
|
|
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>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
|
Email <span class="text-red-500">*</span>
|
|
</label>
|
|
<input type="email" x-model="form.email" 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">
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Phone</label>
|
|
<input type="tel" x-model="form.phone"
|
|
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">Birthday</label>
|
|
<input type="date" x-model="form.birthday"
|
|
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">For birthday rewards (optional)</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Marketing Consent -->
|
|
<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('bell', 'inline w-5 h-5 mr-2')"></span>
|
|
Communication Preferences
|
|
</h3>
|
|
|
|
<div class="space-y-3">
|
|
<label class="flex items-center">
|
|
<input type="checkbox" x-model="form.marketing_email"
|
|
class="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500">
|
|
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">Send promotional emails</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="checkbox" x-model="form.marketing_sms"
|
|
class="w-4 h-4 text-purple-600 border-gray-300 rounded focus:ring-purple-500">
|
|
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">Send promotional SMS</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Welcome Bonus Info -->
|
|
<div x-show="program?.welcome_bonus_points > 0" class="px-4 py-4 mb-6 bg-green-50 border border-green-200 rounded-lg dark:bg-green-900/20 dark:border-green-800">
|
|
<div class="flex items-center">
|
|
<span x-html="$icon('gift', 'w-5 h-5 text-green-500 mr-3')"></span>
|
|
<div>
|
|
<p class="text-sm font-medium text-green-800 dark:text-green-200">Welcome Bonus</p>
|
|
<p class="text-sm text-green-700 dark:text-green-300">
|
|
Customer will receive <span class="font-bold" x-text="program?.welcome_bonus_points"></span> bonus points!
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="flex items-center gap-4">
|
|
<a href="/vendor/{{ vendor_code }}/loyalty/terminal"
|
|
class="px-6 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">
|
|
Cancel
|
|
</a>
|
|
<button type="submit" :disabled="enrolling || !form.first_name || !form.email"
|
|
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="enrolling" x-html="$icon('spinner', 'w-4 h-4 mr-2 animate-spin')"></span>
|
|
<span x-text="enrolling ? 'Enrolling...' : 'Enroll Customer'"></span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Success Modal -->
|
|
<div x-show="enrolledCard" class="fixed inset-0 z-50 flex items-center justify-center bg-black/50">
|
|
<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="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>
|
|
</div>
|
|
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200 mb-2">Customer Enrolled!</h3>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">
|
|
Card Number: <span class="font-mono font-semibold" x-text="enrolledCard?.card_number"></span>
|
|
</p>
|
|
<p class="text-sm text-gray-600 dark:text-gray-300 mb-6">
|
|
Starting Balance: <span class="font-bold text-purple-600" x-text="enrolledCard?.points_balance"></span> points
|
|
</p>
|
|
<div class="flex gap-3 justify-center">
|
|
<a href="/vendor/{{ vendor_code }}/loyalty/terminal"
|
|
class="px-4 py-2 text-sm font-medium text-gray-700 bg-gray-100 rounded-lg hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300">
|
|
Back to Terminal
|
|
</a>
|
|
<button @click="enrolledCard = null; resetForm()"
|
|
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
|
Enroll Another
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script src="{{ url_for('loyalty_static', path='vendor/js/loyalty-enroll.js') }}"></script>
|
|
{% endblock %}
|