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:
@@ -96,5 +96,589 @@
|
||||
"title": "Treueprogramm erstellen",
|
||||
"description": "Erstellen Sie Ihr erstes Stempel- oder Punkteprogramm"
|
||||
}
|
||||
},
|
||||
"enrollment": {
|
||||
"title": "Treten Sie unserem Prämienprogramm bei!",
|
||||
"subtitle": "Verdienen Sie {points} Punkt pro ausgegebenem EUR",
|
||||
"not_available_title": "Programm nicht verfügbar",
|
||||
"not_available_message": "Dieser Shop hat noch kein Treueprogramm eingerichtet.",
|
||||
"welcome_bonus": "Erhalten Sie {points} Bonuspunkte bei der Anmeldung!",
|
||||
"already_member": "Bereits Mitglied? Ihre Punkte sind mit Ihrer E-Mail verknüpft.",
|
||||
"form": {
|
||||
"email": "E-Mail",
|
||||
"first_name": "Vorname",
|
||||
"last_name": "Nachname",
|
||||
"phone": "Telefon (optional)",
|
||||
"birthday": "Geburtstag (optional)",
|
||||
"birthday_hint": "Für besondere Geburtstagsbelohnungen",
|
||||
"terms_agree": "Ich stimme den",
|
||||
"terms": "Allgemeinen Geschäftsbedingungen",
|
||||
"marketing_consent": "Neuigkeiten und Sonderangebote senden",
|
||||
"joining": "Anmeldung läuft...",
|
||||
"join_button": "Beitreten & {points} Punkte erhalten"
|
||||
},
|
||||
"privacy_policy": "Datenschutzrichtlinie",
|
||||
"close": "Schließen",
|
||||
"success": {
|
||||
"title": "Willkommen!",
|
||||
"message": "Sie sind jetzt Mitglied unseres Prämienprogramms.",
|
||||
"card_number": "Ihre Kartennummer",
|
||||
"wallet_prompt": "Speichern Sie Ihre Karte auf Ihrem Handy für einfachen Zugriff:",
|
||||
"next_steps_title": "Wie geht es weiter?",
|
||||
"step_earn": "Zeigen Sie Ihre Kartennummer beim Einkauf, um Punkte zu sammeln",
|
||||
"step_balance": "Prüfen Sie Ihren Kontostand online oder in der App",
|
||||
"step_redeem": "Lösen Sie Punkte gegen Prämien an allen unseren Standorten ein",
|
||||
"view_dashboard": "Mein Treue-Dashboard anzeigen",
|
||||
"continue_shopping": "Weiter einkaufen"
|
||||
},
|
||||
"errors": {
|
||||
"load_failed": "Programminformationen konnten nicht geladen werden",
|
||||
"email_exists": "Diese E-Mail ist bereits in unserem Treueprogramm registriert.",
|
||||
"failed": "Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"cancel": "Abbrechen",
|
||||
"save": "Speichern",
|
||||
"delete": "Löschen",
|
||||
"confirm": "Bestätigen",
|
||||
"refresh": "Aktualisieren",
|
||||
"loading": "Laden...",
|
||||
"saving": "Speichern...",
|
||||
"view": "Anzeigen",
|
||||
"edit": "Bearbeiten",
|
||||
"yes": "Ja",
|
||||
"no": "Nein",
|
||||
"none": "Keine",
|
||||
"never": "Nie",
|
||||
"total": "GESAMT",
|
||||
"continue": "Weiter",
|
||||
"back": "Zurück",
|
||||
"points": "Punkte",
|
||||
"minutes": "Minuten",
|
||||
"or": "oder",
|
||||
"at": "bei"
|
||||
},
|
||||
"transactions": {
|
||||
"card_created": "Angemeldet",
|
||||
"welcome_bonus": "Willkommensbonus",
|
||||
"stamp_earned": "Stempel erhalten",
|
||||
"stamp_redeemed": "Stempel eingelöst",
|
||||
"stamp_voided": "Stempel storniert",
|
||||
"stamp_adjustment": "Stempel angepasst",
|
||||
"points_earned": "Punkte verdient",
|
||||
"points_redeemed": "Punkte eingelöst",
|
||||
"points_voided": "Punkte storniert",
|
||||
"points_adjustment": "Punkte angepasst",
|
||||
"points_expired": "Punkte verfallen",
|
||||
"card_deactivated": "Deaktiviert",
|
||||
"reward_redeemed": "Prämie eingelöst"
|
||||
},
|
||||
"shared": {
|
||||
"analytics": {
|
||||
"total_programs": "Programme insgesamt",
|
||||
"total_members": "Mitglieder insgesamt",
|
||||
"active_members": "Aktive Mitglieder",
|
||||
"points_issued_30d": "Punkte vergeben (30T)",
|
||||
"transactions_30d": "Transaktionen (30T)",
|
||||
"x_active": "{count} aktiv",
|
||||
"points_overview": "Punkteübersicht",
|
||||
"points_issued_vs_redeemed": "Punkte vergeben vs eingelöst (30T)",
|
||||
"issued": "Vergeben:",
|
||||
"redeemed": "Eingelöst:",
|
||||
"redemption_rate": "Einlösungsrate",
|
||||
"outstanding_balance": "Ausstehender Saldo",
|
||||
"member_activity": "Mitgliederaktivität",
|
||||
"active_members_30d": "Aktive Mitglieder (30T)",
|
||||
"new_this_month": "Neu diesen Monat",
|
||||
"merchants_with_programs": "Händler mit Programmen",
|
||||
"avg_points_per_member": "Durchschn. Punkte pro Mitglied",
|
||||
"all_time_statistics": "Gesamtstatistiken",
|
||||
"total_points_issued": "Punkte insgesamt vergeben",
|
||||
"total_points_redeemed": "Punkte insgesamt eingelöst",
|
||||
"points_redeemed_30d": "Punkte eingelöst (30T)",
|
||||
"outstanding_liability": "Ausstehende Verbindlichkeit",
|
||||
"location_breakdown": "Aufschlüsselung nach Standort",
|
||||
"store": "Geschäft",
|
||||
"enrolled": "Angemeldet",
|
||||
"points_earned": "Punkte verdient",
|
||||
"points_redeemed": "Punkte eingelöst"
|
||||
},
|
||||
"program_view": {
|
||||
"program_configuration": "Programmkonfiguration",
|
||||
"program_name": "Programmname",
|
||||
"card_name": "Kartenname",
|
||||
"stamps_configuration": "Stempelkonfiguration",
|
||||
"stamps_target": "Stempelziel",
|
||||
"reward_description": "Prämienbeschreibung",
|
||||
"reward_value": "Prämienwert",
|
||||
"points_configuration": "Punktekonfiguration",
|
||||
"points_per_eur": "Punkte pro EUR",
|
||||
"welcome_bonus": "Willkommensbonus",
|
||||
"x_points": "{count} Punkte",
|
||||
"minimum_redemption": "Mindesteinlösung",
|
||||
"minimum_purchase": "Mindesteinkauf",
|
||||
"points_expiration": "Punkteverfall",
|
||||
"x_days_inactivity": "{days} Tage Inaktivität",
|
||||
"redemption_rewards": "Einlösungsprämien",
|
||||
"reward": "Prämie",
|
||||
"points_required": "Benötigte Punkte",
|
||||
"description": "Beschreibung",
|
||||
"anti_fraud": "Betrugsschutz",
|
||||
"cooldown": "Wartezeit",
|
||||
"x_minutes": "{count} Minuten",
|
||||
"max_daily_stamps": "Max. Stempel pro Tag",
|
||||
"staff_pin_required": "Mitarbeiter-PIN erforderlich",
|
||||
"branding": "Branding",
|
||||
"primary_color": "Primärfarbe",
|
||||
"secondary_color": "Sekundärfarbe",
|
||||
"logo_url": "Logo-URL",
|
||||
"hero_image_url": "Hintergrundbild-URL",
|
||||
"terms_privacy": "AGB & Datenschutz",
|
||||
"terms_conditions": "Allgemeine Geschäftsbedingungen",
|
||||
"privacy_policy_url": "Datenschutzrichtlinien-URL"
|
||||
},
|
||||
"program_form": {
|
||||
"program_type": "Programmtyp",
|
||||
"points_type_desc": "Punkte pro ausgegebenem EUR verdienen",
|
||||
"stamps_type_desc": "N Stempel sammeln, Prämie erhalten",
|
||||
"hybrid_type_desc": "Stempel und Punkte kombiniert",
|
||||
"stamps_configuration": "Stempelkonfiguration",
|
||||
"stamps_target": "Stempelziel",
|
||||
"stamps_target_help": "Anzahl der Stempel für die Prämie",
|
||||
"reward_description": "Prämienbeschreibung",
|
||||
"reward_value_cents": "Prämienwert (Cent)",
|
||||
"points_configuration": "Punktekonfiguration",
|
||||
"points_per_eur": "Punkte pro ausgegebenem EUR",
|
||||
"eur_equals_points": "1 EUR = {points} Punkt(e)",
|
||||
"welcome_bonus_points": "Willkommensbonuspunkte",
|
||||
"welcome_bonus_help": "Bonuspunkte bei der Anmeldung",
|
||||
"minimum_redemption_points": "Mindesteinlösungspunkte",
|
||||
"minimum_purchase_cents": "Mindesteinkauf (Cent)",
|
||||
"minimum_purchase_help": "Mindesteinkaufsbetrag zum Punktesammeln (0 = kein Minimum)",
|
||||
"points_expiration_days": "Punkteverfall (Tage)",
|
||||
"points_expiration_help": "Tage der Inaktivität bis zum Punkteverfall (0 = nie)",
|
||||
"redemption_rewards": "Einlösungsprämien",
|
||||
"add_reward": "Prämie hinzufügen",
|
||||
"no_rewards_configured": "Keine Prämien konfiguriert. Fügen Sie eine Prämie hinzu, damit Kunden Punkte einlösen können.",
|
||||
"reward_name": "Prämienname",
|
||||
"points_required": "Benötigte Punkte",
|
||||
"description": "Beschreibung",
|
||||
"anti_fraud_settings": "Betrugsschutz-Einstellungen",
|
||||
"cooldown_minutes": "Wartezeit (Minuten)",
|
||||
"cooldown_help": "Zeit zwischen Stempeln derselben Karte",
|
||||
"max_daily_stamps": "Max. Stempel pro Tag",
|
||||
"max_daily_stamps_help": "Maximale Stempel pro Karte pro Tag",
|
||||
"require_staff_pin": "Mitarbeiter-PIN verlangen",
|
||||
"branding": "Branding",
|
||||
"card_name": "Kartenname",
|
||||
"primary_color": "Primärfarbe",
|
||||
"secondary_color": "Sekundärfarbe",
|
||||
"logo_url": "Logo-URL",
|
||||
"logo_url_help": "Erforderlich für Google Wallet-Integration. Muss eine öffentlich zugängliche Bild-URL sein (PNG oder JPG).",
|
||||
"hero_image_url": "Hintergrundbild-URL",
|
||||
"terms_privacy": "AGB & Datenschutz",
|
||||
"terms_conditions": "Allgemeine Geschäftsbedingungen",
|
||||
"privacy_policy_url": "Datenschutzrichtlinien-URL",
|
||||
"program_status": "Programmstatus",
|
||||
"program_active": "Programm aktiv",
|
||||
"program_active_help": "Deaktiviert können Kunden weder sammeln noch einlösen",
|
||||
"delete_program": "Programm löschen",
|
||||
"create_program": "Programm erstellen",
|
||||
"save_changes": "Änderungen speichern"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"programs": {
|
||||
"title": "Treueprogramme",
|
||||
"create_program": "Programm erstellen",
|
||||
"loading": "Treueprogramme werden geladen...",
|
||||
"error_loading": "Fehler beim Laden der Treueprogramme",
|
||||
"total_programs": "Programme insgesamt",
|
||||
"active": "Aktiv",
|
||||
"total_members": "Mitglieder insgesamt",
|
||||
"transactions_30d": "Transaktionen (30T)",
|
||||
"search_placeholder": "Nach Händlername suchen...",
|
||||
"all_status": "Alle Status",
|
||||
"table_merchant": "Händler",
|
||||
"table_program_type": "Programmtyp",
|
||||
"table_members": "Mitglieder",
|
||||
"table_points_issued": "Punkte vergeben",
|
||||
"table_status": "Status",
|
||||
"table_created": "Erstellt",
|
||||
"table_actions": "Aktionen",
|
||||
"no_programs": "Keine Treueprogramme gefunden",
|
||||
"adjust_filters": "Versuchen Sie, Ihre Suche oder Filter anzupassen",
|
||||
"no_merchants_yet": "Noch keine Händler haben Treueprogramme erstellt",
|
||||
"x_active": "({count} aktiv)",
|
||||
"x_redeemed": "{count} eingelöst",
|
||||
"pt_per_eur": "Pkt/EUR",
|
||||
"delete_title": "Treueprogramm löschen",
|
||||
"delete_message": "Treueprogramm für \"{name}\" löschen? Alle zugehörigen Daten (Karten, Transaktionen, Prämien) werden dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden.",
|
||||
"delete_confirm": "Programm löschen",
|
||||
"create_title": "Treueprogramm erstellen",
|
||||
"create_description": "Wählen Sie einen Händler, um ein Treueprogramm zu erstellen.",
|
||||
"search_merchant": "Händler suchen",
|
||||
"type_merchant_name": "Händlername eingeben...",
|
||||
"no_merchants_found": "Keine Händler gefunden",
|
||||
"existing_program_warning": "Dieser Händler hat bereits ein Treueprogramm.",
|
||||
"view_edit_existing": "Bestehendes Programm anzeigen / bearbeiten"
|
||||
},
|
||||
"merchant_detail": {
|
||||
"title": "Händler-Treuedetails",
|
||||
"loading": "Treuedetails werden geladen...",
|
||||
"error_loading": "Fehler beim Laden der Händlertreue",
|
||||
"program_active": "Treueprogramm aktiv",
|
||||
"no_program_subtitle": "Kein Treueprogramm",
|
||||
"quick_actions": "Schnellaktionen",
|
||||
"edit_program": "Programm bearbeiten",
|
||||
"admin_policy": "Admin-Richtlinie",
|
||||
"view_merchant": "Händler anzeigen",
|
||||
"total_members": "Mitglieder insgesamt",
|
||||
"active_30d": "Aktiv (30T)",
|
||||
"points_issued_30d": "Punkte vergeben (30T)",
|
||||
"points_redeemed_30d": "Punkte eingelöst (30T)",
|
||||
"no_program": "Kein Treueprogramm",
|
||||
"no_program_desc": "Dieser Händler hat noch kein Treueprogramm eingerichtet.",
|
||||
"create_program": "Programm erstellen",
|
||||
"delete_title": "Treueprogramm löschen",
|
||||
"delete_message": "Das Treueprogramm und alle zugehörigen Daten werden dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden.",
|
||||
"delete_confirm": "Programm löschen",
|
||||
"location_breakdown": "Aufschlüsselung nach Standort",
|
||||
"table_location": "Standort",
|
||||
"table_enrolled": "Angemeldet",
|
||||
"table_points_earned": "Punkte verdient",
|
||||
"table_points_redeemed": "Punkte eingelöst",
|
||||
"table_transactions_30d": "Transaktionen (30T)",
|
||||
"admin_policy_settings": "Admin-Richtlinieneinstellungen",
|
||||
"staff_pin_policy": "Mitarbeiter-PIN-Richtlinie",
|
||||
"self_enrollment": "Selbstanmeldung",
|
||||
"cross_location_redemption": "Standortübergreifende Einlösung",
|
||||
"allowed": "Erlaubt",
|
||||
"disabled": "Deaktiviert",
|
||||
"modify_policy": "Admin-Richtlinie ändern"
|
||||
},
|
||||
"merchant_settings": {
|
||||
"title": "Händler-Treueeinstellungen",
|
||||
"loading": "Einstellungen werden geladen...",
|
||||
"error_loading": "Fehler beim Laden der Einstellungen",
|
||||
"admin_controlled": "Admin-kontrollierte Einstellungen für das Treueprogramm dieses Händlers",
|
||||
"staff_pin_policy": "Mitarbeiter-PIN-Richtlinie",
|
||||
"staff_pin_description": "Legen Sie fest, ob Mitarbeiter einen PIN eingeben müssen, um Treuetransaktionen zu verarbeiten.",
|
||||
"required": "Erforderlich",
|
||||
"required_desc": "Mitarbeiter müssen ihren PIN bei jeder Transaktion eingeben. Empfohlen für die Sicherheit.",
|
||||
"optional": "Optional",
|
||||
"optional_desc": "Geschäfte können wählen, ob PINs erforderlich sind.",
|
||||
"pin_disabled": "Deaktiviert",
|
||||
"pin_disabled_desc": "Mitarbeiter-PINs werden nicht verwendet. Jeder Mitarbeiter kann Transaktionen verarbeiten.",
|
||||
"pin_lockout_settings": "PIN-Sperreinstellungen",
|
||||
"max_failed_attempts": "Max. Fehlversuche",
|
||||
"max_failed_attempts_help": "Anzahl falscher Versuche vor Sperrung (3-10)",
|
||||
"lockout_duration": "Sperrdauer (Minuten)",
|
||||
"lockout_duration_help": "Sperrdauer nach Fehlversuchen (5-120 Minuten)",
|
||||
"enrollment_settings": "Anmeldeeinstellungen",
|
||||
"allow_self_enrollment": "Selbstanmeldung erlauben",
|
||||
"self_enrollment_desc": "Kunden können sich per QR-Code ohne Personal anmelden",
|
||||
"transaction_settings": "Transaktionseinstellungen",
|
||||
"allow_cross_location": "Standortübergreifende Einlösung erlauben",
|
||||
"cross_location_desc": "Kunden können Punkte an allen Standorten des Händlers einlösen",
|
||||
"allow_void": "Stornierungen erlauben",
|
||||
"void_desc": "Mitarbeiter können Punkte/Stempel bei Rückgaben stornieren",
|
||||
"save_settings": "Einstellungen speichern"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Treue-Analytik",
|
||||
"subtitle": "Plattformweite Treueprogramm-Statistiken",
|
||||
"loading": "Analytik wird geladen...",
|
||||
"error_loading": "Fehler beim Laden der Analytik",
|
||||
"filter_by_merchant": "Nach Händler filtern",
|
||||
"search_merchants_placeholder": "Händler nach Name suchen...",
|
||||
"showing_stats_for": "Statistiken für:",
|
||||
"wallet_status": "Wallet-Integrationsstatus",
|
||||
"google_wallet": "Google Wallet",
|
||||
"apple_wallet": "Apple Wallet",
|
||||
"connected": "Verbunden",
|
||||
"error": "Fehler",
|
||||
"not_configured": "Nicht konfiguriert",
|
||||
"issuer_id": "Aussteller-ID",
|
||||
"project": "Projekt",
|
||||
"wallet_objects": "Wallet-Objekte",
|
||||
"loyalty_classes": "Treueklassen",
|
||||
"pass_type_id": "Pass-Typ-ID",
|
||||
"team_id": "Team-ID",
|
||||
"active_passes": "Aktive Pässe",
|
||||
"quick_actions": "Schnellaktionen",
|
||||
"view_all_programs": "Alle Programme anzeigen",
|
||||
"manage_merchants": "Händler verwalten"
|
||||
},
|
||||
"program_edit": {
|
||||
"title": "Programmkonfiguration",
|
||||
"loading": "Konfiguration wird geladen...",
|
||||
"error_loading": "Fehler beim Laden der Programmkonfiguration",
|
||||
"create_subtitle": "Treueprogramm für diesen Händler erstellen",
|
||||
"edit_subtitle": "Programmkonfiguration bearbeiten",
|
||||
"delete_title": "Treueprogramm löschen",
|
||||
"delete_message": "Das Treueprogramm und alle zugehörigen Daten (Karten, Transaktionen, Prämien) werden dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden.",
|
||||
"delete_confirm": "Programm löschen"
|
||||
}
|
||||
},
|
||||
"merchant": {
|
||||
"program": {
|
||||
"title": "Treueprogramm",
|
||||
"subtitle": "Ihre Treueprogramm-Konfiguration.",
|
||||
"edit_program": "Programm bearbeiten",
|
||||
"no_program": "Kein Treueprogramm",
|
||||
"no_program_desc": "Ihr Treueprogramm wurde noch nicht eingerichtet. Erstellen Sie eines, um Ihre Kunden zu belohnen.",
|
||||
"create_program": "Programm erstellen"
|
||||
},
|
||||
"program_edit": {
|
||||
"title": "Treue-Einstellungen",
|
||||
"page_title": "Treueprogramm-Einstellungen",
|
||||
"subtitle": "Konfigurieren Sie Ihr Treueprogramm",
|
||||
"loading": "Einstellungen werden geladen...",
|
||||
"error_loading": "Fehler beim Laden der Einstellungen",
|
||||
"delete_title": "Treueprogramm löschen",
|
||||
"delete_message": "Ihr Treueprogramm und alle zugehörigen Daten (Karten, Transaktionen, Prämien) werden dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden.",
|
||||
"delete_confirm": "Programm löschen"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Treue-Analytik",
|
||||
"subtitle": "Treueprogramm-Statistiken für alle Ihre Geschäfte",
|
||||
"loading": "Analytik wird geladen...",
|
||||
"error_loading": "Fehler beim Laden der Analytik",
|
||||
"no_program": "Kein Treueprogramm",
|
||||
"no_program_desc": "Richten Sie ein Treueprogramm ein, um hier Analytik zu sehen.",
|
||||
"create_program": "Programm erstellen",
|
||||
"quick_actions": "Schnellaktionen",
|
||||
"view_program": "Programm anzeigen",
|
||||
"edit_program": "Programm bearbeiten"
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"terminal": {
|
||||
"title": "Treue-Terminal",
|
||||
"subtitle": "Treuetransaktionen verarbeiten",
|
||||
"members": "Mitglieder",
|
||||
"analytics": "Analytik",
|
||||
"loading": "Treue-Terminal wird geladen...",
|
||||
"error_loading": "Fehler beim Laden des Terminals",
|
||||
"not_setup": "Treueprogramm nicht eingerichtet",
|
||||
"not_setup_desc": "Ihr Händler hat noch kein Treueprogramm konfiguriert.",
|
||||
"setup_program": "Treueprogramm einrichten",
|
||||
"contact_admin": "Kontaktieren Sie Ihren Administrator, um die Einrichtung abzuschließen.",
|
||||
"find_customer": "Kunde finden",
|
||||
"search_placeholder": "E-Mail, Telefon oder Kartennummer...",
|
||||
"looking_up": "Suche läuft...",
|
||||
"look_up_customer": "Kunde suchen",
|
||||
"enroll_new_customer": "Neuen Kunden anmelden",
|
||||
"customer_found": "Kunde gefunden",
|
||||
"points_balance": "Punktestand",
|
||||
"stamps": "Stempel",
|
||||
"x_more_for_reward": "Noch {count} für Prämie",
|
||||
"ready_to_redeem": "Bereit zum Einlösen!",
|
||||
"add_stamp": "Stempel hinzufügen",
|
||||
"current": "Aktuell:",
|
||||
"cooldown_active": "Wartezeit aktiv",
|
||||
"redeem_stamps": "Stempel einlösen",
|
||||
"not_enough_stamps": "Noch nicht genug Stempel",
|
||||
"earn_points": "Punkte sammeln",
|
||||
"purchase_amount": "Einkaufsbetrag",
|
||||
"points_to_award": "Zu vergebende Punkte:",
|
||||
"award_points": "Punkte vergeben",
|
||||
"redeem_reward": "Prämie einlösen",
|
||||
"select_reward": "Prämie auswählen",
|
||||
"select_reward_placeholder": "Prämie auswählen...",
|
||||
"points_after": "Punkte danach:",
|
||||
"search_to_process": "Suchen Sie einen Kunden, um eine Transaktion zu verarbeiten",
|
||||
"recent_transactions": "Letzte Transaktionen an diesem Standort",
|
||||
"table_time": "Zeit",
|
||||
"table_customer": "Kunde",
|
||||
"table_type": "Typ",
|
||||
"table_points": "Punkte",
|
||||
"table_notes": "Notizen",
|
||||
"no_recent_transactions": "Keine aktuellen Transaktionen",
|
||||
"enter_staff_pin": "Mitarbeiter-PIN eingeben",
|
||||
"pin_authorize": "Geben Sie Ihren Mitarbeiter-PIN ein, um diese Transaktion zu autorisieren.",
|
||||
"clear": "Löschen",
|
||||
"processing": "Verarbeitung...",
|
||||
"customer_not_found": "Kunde nicht gefunden. Sie können ihn als neues Mitglied anmelden.",
|
||||
"error_lookup": "Fehler bei der Kundensuche: {message}",
|
||||
"transaction_failed": "Transaktion fehlgeschlagen: {message}",
|
||||
"stamp_added": "Stempel hinzugefügt!",
|
||||
"stamps_redeemed": "Stempel eingelöst! Prämie erhalten.",
|
||||
"x_points_awarded": "{points} Punkte vergeben!",
|
||||
"reward_redeemed": "Prämie eingelöst: {name}"
|
||||
},
|
||||
"cards": {
|
||||
"title": "Treue-Mitglieder",
|
||||
"subtitle": "Mitglieder Ihres Treueprogramms anzeigen und verwalten",
|
||||
"enroll_new": "Anmelden",
|
||||
"loading": "Mitglieder werden geladen...",
|
||||
"error_loading": "Fehler beim Laden der Mitglieder",
|
||||
"total_members": "Mitglieder insgesamt",
|
||||
"active_30d": "Aktiv (30T)",
|
||||
"new_this_month": "Neu diesen Monat",
|
||||
"total_points_balance": "Gesamtpunktestand",
|
||||
"search_placeholder": "Nach Name, E-Mail, Telefon oder Karte suchen...",
|
||||
"all_status": "Alle Status",
|
||||
"table_member": "Mitglied",
|
||||
"table_card_number": "Kartennummer",
|
||||
"table_points_balance": "Punktestand",
|
||||
"table_last_activity": "Letzte Aktivität",
|
||||
"table_status": "Status",
|
||||
"table_actions": "Aktionen",
|
||||
"no_members": "Keine Mitglieder gefunden",
|
||||
"adjust_search": "Versuchen Sie, Ihre Suche anzupassen",
|
||||
"enroll_first": "Melden Sie Ihren ersten Kunden an"
|
||||
},
|
||||
"card_detail": {
|
||||
"title": "Mitgliederdetails",
|
||||
"loading": "Mitgliederdetails werden geladen...",
|
||||
"error_loading": "Fehler beim Laden des Mitglieds",
|
||||
"points_balance": "Punktestand",
|
||||
"total_earned": "Insgesamt verdient",
|
||||
"total_redeemed": "Insgesamt eingelöst",
|
||||
"member_since": "Mitglied seit",
|
||||
"customer_information": "Kundeninformation",
|
||||
"name": "Name",
|
||||
"email": "E-Mail",
|
||||
"phone": "Telefon",
|
||||
"birthday": "Geburtstag",
|
||||
"card_details": "Kartendetails",
|
||||
"card_number": "Kartennummer",
|
||||
"status": "Status",
|
||||
"last_activity": "Letzte Aktivität",
|
||||
"enrolled_at": "Angemeldet am",
|
||||
"transaction_history": "Transaktionsverlauf",
|
||||
"table_date": "Datum",
|
||||
"table_type": "Typ",
|
||||
"table_points": "Punkte",
|
||||
"table_location": "Standort",
|
||||
"table_notes": "Notizen",
|
||||
"no_transactions": "Noch keine Transaktionen"
|
||||
},
|
||||
"enroll": {
|
||||
"title": "Kunde anmelden",
|
||||
"page_title": "Neuen Kunden anmelden",
|
||||
"subtitle": "Neues Mitglied zu Ihrem Treueprogramm hinzufügen",
|
||||
"loading": "Laden...",
|
||||
"error_loading": "Fehler beim Laden des Anmeldeformulars",
|
||||
"customer_information": "Kundeninformation",
|
||||
"first_name": "Vorname",
|
||||
"last_name": "Nachname",
|
||||
"email": "E-Mail",
|
||||
"phone": "Telefon",
|
||||
"birthday": "Geburtstag",
|
||||
"birthday_help": "Für Geburtstagsbelohnungen (optional)",
|
||||
"communication_preferences": "Kommunikationseinstellungen",
|
||||
"send_emails": "Werbe-E-Mails senden",
|
||||
"send_sms": "Werbe-SMS senden",
|
||||
"welcome_bonus": "Willkommensbonus",
|
||||
"welcome_bonus_desc": "Kunde erhält {points} Bonuspunkte!",
|
||||
"enroll_customer": "Kunde anmelden",
|
||||
"enrolling": "Anmeldung...",
|
||||
"customer_enrolled": "Kunde angemeldet!",
|
||||
"starting_balance": "Anfangssaldo:",
|
||||
"x_points": "{count} Punkte",
|
||||
"back_to_terminal": "Zurück zum Terminal",
|
||||
"enroll_another": "Weiteren anmelden",
|
||||
"enrollment_failed": "Anmeldung fehlgeschlagen: {message}"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Treue-Analytik",
|
||||
"subtitle": "Verfolgen Sie die Leistung Ihres Treueprogramms",
|
||||
"loading": "Analytik wird geladen...",
|
||||
"error_loading": "Fehler beim Laden der Analytik",
|
||||
"quick_actions": "Schnellaktionen",
|
||||
"open_terminal": "Terminal öffnen",
|
||||
"view_members": "Mitglieder anzeigen",
|
||||
"view_program": "Programm anzeigen"
|
||||
},
|
||||
"program": {
|
||||
"title": "Treueprogramm",
|
||||
"subtitle": "Ihre Treueprogramm-Konfiguration",
|
||||
"edit_program": "Programm bearbeiten",
|
||||
"loading": "Programm wird geladen...",
|
||||
"error_loading": "Fehler beim Laden des Programms",
|
||||
"no_program": "Kein Treueprogramm",
|
||||
"no_program_desc": "Ihr Händler hat noch kein Treueprogramm konfiguriert.",
|
||||
"create_program": "Programm erstellen",
|
||||
"contact_admin": "Kontaktieren Sie Ihren Administrator, um ein Treueprogramm einzurichten."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Treue-Einstellungen",
|
||||
"page_title": "Treueprogramm-Einstellungen",
|
||||
"subtitle": "Konfigurieren Sie Ihr Treueprogramm",
|
||||
"back_to_program": "Zurück zum Programm",
|
||||
"loading": "Einstellungen werden geladen...",
|
||||
"error_loading": "Fehler beim Laden der Einstellungen",
|
||||
"access_restricted": "Zugriff eingeschränkt",
|
||||
"owner_only": "Nur der Geschäftsinhaber kann die Treueprogramm-Einstellungen verwalten.",
|
||||
"delete_title": "Treueprogramm löschen",
|
||||
"delete_message": "Das Treueprogramm und alle zugehörigen Daten (Karten, Transaktionen, Prämien) werden dauerhaft gelöscht. Dies kann nicht rückgängig gemacht werden.",
|
||||
"delete_confirm": "Programm löschen",
|
||||
"program_created": "Programm erfolgreich erstellt",
|
||||
"program_updated": "Programm erfolgreich aktualisiert",
|
||||
"program_deleted": "Treueprogramm gelöscht",
|
||||
"save_failed": "Speichern fehlgeschlagen: {message}",
|
||||
"delete_failed": "Löschen fehlgeschlagen: {message}"
|
||||
}
|
||||
},
|
||||
"storefront": {
|
||||
"dashboard": {
|
||||
"back_to_account": "Zurück zum Konto",
|
||||
"my_loyalty": "Meine Treue",
|
||||
"join_title": "Treten Sie unserem Prämienprogramm bei!",
|
||||
"join_subtitle": "Sammeln Sie Punkte bei jedem Einkauf und lösen Sie sie gegen Prämien ein.",
|
||||
"join_now": "Jetzt beitreten",
|
||||
"points_balance": "Punktestand",
|
||||
"card_number": "Kartennummer",
|
||||
"show_card": "Karte anzeigen",
|
||||
"total_earned": "Insgesamt verdient",
|
||||
"total_redeemed": "Insgesamt eingelöst",
|
||||
"available_rewards": "Verfügbare Prämien",
|
||||
"no_rewards_yet": "Noch keine Prämien verfügbar",
|
||||
"ready_to_redeem": "Bereit zum Einlösen",
|
||||
"x_more_to_go": "Noch {count}",
|
||||
"redeem_hint": "Zeigen Sie Ihre Karte dem Personal, um Prämien im Geschäft einzulösen.",
|
||||
"recent_activity": "Letzte Aktivität",
|
||||
"view_all": "Alle anzeigen",
|
||||
"no_transactions": "Noch keine Transaktionen. Machen Sie einen Einkauf, um Punkte zu sammeln!",
|
||||
"earn_redeem_locations": "Sammel- & Einlöse-Standorte",
|
||||
"your_loyalty_card": "Ihre Treuekarte",
|
||||
"show_to_staff": "Zeigen Sie dies dem Personal beim Einkauf oder Einlösen von Prämien."
|
||||
},
|
||||
"history": {
|
||||
"back_to_loyalty": "Zurück zur Treue",
|
||||
"title": "Transaktionsverlauf",
|
||||
"subtitle": "Alle Ihre Treuepunkttransaktionen anzeigen",
|
||||
"current_balance": "Aktueller Saldo",
|
||||
"total_earned": "Insgesamt verdient",
|
||||
"total_redeemed": "Insgesamt eingelöst",
|
||||
"no_transactions": "Noch keine Transaktionen",
|
||||
"balance": "Saldo:",
|
||||
"previous": "Zurück",
|
||||
"next": "Weiter",
|
||||
"page_x_of_y": "Seite {page} von {pages}"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"program_activated": "Programm erfolgreich aktiviert",
|
||||
"program_deactivated": "Programm erfolgreich deaktiviert",
|
||||
"activate_failed": "Programm konnte nicht aktiviert werden: {message}",
|
||||
"deactivate_failed": "Programm konnte nicht deaktiviert werden: {message}",
|
||||
"program_deleted": "Programm erfolgreich gelöscht",
|
||||
"delete_failed": "Programm konnte nicht gelöscht werden: {message}",
|
||||
"program_created": "Programm erfolgreich erstellt",
|
||||
"program_updated": "Programm erfolgreich aktualisiert",
|
||||
"loyalty_program_created": "Treueprogramm erstellt",
|
||||
"loyalty_program_deleted": "Treueprogramm gelöscht",
|
||||
"settings_saved": "Einstellungen erfolgreich gespeichert",
|
||||
"save_failed": "Speichern fehlgeschlagen: {message}",
|
||||
"settings_save_failed": "Einstellungen konnten nicht gespeichert werden: {message}",
|
||||
"create_failed": "Programm konnte nicht erstellt werden: {message}",
|
||||
"logo_required": "Logo-URL ist für die Wallet-Integration erforderlich."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,5 +97,589 @@
|
||||
"title": "Create a loyalty program",
|
||||
"description": "Set up your first stamp or points program"
|
||||
}
|
||||
},
|
||||
"enrollment": {
|
||||
"title": "Join Our Rewards Program!",
|
||||
"subtitle": "Earn {points} point for every EUR you spend",
|
||||
"not_available_title": "Program Not Available",
|
||||
"not_available_message": "This store doesn't have a loyalty program set up yet.",
|
||||
"welcome_bonus": "Get {points} bonus points when you join!",
|
||||
"already_member": "Already a member? Your points are linked to your email.",
|
||||
"form": {
|
||||
"email": "Email",
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"phone": "Phone (optional)",
|
||||
"birthday": "Birthday (optional)",
|
||||
"birthday_hint": "For special birthday rewards",
|
||||
"terms_agree": "I agree to the",
|
||||
"terms": "Terms & Conditions",
|
||||
"marketing_consent": "Send me news and special offers",
|
||||
"joining": "Joining...",
|
||||
"join_button": "Join & Get {points} Points"
|
||||
},
|
||||
"privacy_policy": "Privacy Policy",
|
||||
"close": "Close",
|
||||
"success": {
|
||||
"title": "Welcome!",
|
||||
"message": "You're now a member of our rewards program.",
|
||||
"card_number": "Your Card Number",
|
||||
"wallet_prompt": "Save your card to your phone for easy access:",
|
||||
"next_steps_title": "What's Next?",
|
||||
"step_earn": "Show your card number when making purchases to earn points",
|
||||
"step_balance": "Check your balance online or in the app",
|
||||
"step_redeem": "Redeem points for rewards at any of our locations",
|
||||
"view_dashboard": "View My Loyalty Dashboard",
|
||||
"continue_shopping": "Continue Shopping"
|
||||
},
|
||||
"errors": {
|
||||
"load_failed": "Failed to load program information",
|
||||
"email_exists": "This email is already registered in our loyalty program.",
|
||||
"failed": "Enrollment failed. Please try again."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"active": "Active",
|
||||
"inactive": "Inactive",
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"delete": "Delete",
|
||||
"confirm": "Confirm",
|
||||
"refresh": "Refresh",
|
||||
"loading": "Loading...",
|
||||
"saving": "Saving...",
|
||||
"view": "View",
|
||||
"edit": "Edit",
|
||||
"yes": "Yes",
|
||||
"no": "No",
|
||||
"none": "None",
|
||||
"never": "Never",
|
||||
"total": "TOTAL",
|
||||
"continue": "Continue",
|
||||
"back": "Back",
|
||||
"points": "points",
|
||||
"minutes": "minutes",
|
||||
"or": "or",
|
||||
"at": "at"
|
||||
},
|
||||
"transactions": {
|
||||
"card_created": "Enrolled",
|
||||
"welcome_bonus": "Welcome Bonus",
|
||||
"stamp_earned": "Stamp Earned",
|
||||
"stamp_redeemed": "Stamp Redeemed",
|
||||
"stamp_voided": "Stamp Voided",
|
||||
"stamp_adjustment": "Stamp Adjusted",
|
||||
"points_earned": "Points Earned",
|
||||
"points_redeemed": "Points Redeemed",
|
||||
"points_voided": "Points Voided",
|
||||
"points_adjustment": "Points Adjusted",
|
||||
"points_expired": "Points Expired",
|
||||
"card_deactivated": "Deactivated",
|
||||
"reward_redeemed": "Reward Redeemed"
|
||||
},
|
||||
"shared": {
|
||||
"analytics": {
|
||||
"total_programs": "Total Programs",
|
||||
"total_members": "Total Members",
|
||||
"active_members": "Active Members",
|
||||
"points_issued_30d": "Points Issued (30d)",
|
||||
"transactions_30d": "Transactions (30d)",
|
||||
"x_active": "{count} active",
|
||||
"points_overview": "Points Overview",
|
||||
"points_issued_vs_redeemed": "Points Issued vs Redeemed (30d)",
|
||||
"issued": "Issued:",
|
||||
"redeemed": "Redeemed:",
|
||||
"redemption_rate": "Redemption Rate",
|
||||
"outstanding_balance": "Outstanding Balance",
|
||||
"member_activity": "Member Activity",
|
||||
"active_members_30d": "Active Members (30d)",
|
||||
"new_this_month": "New This Month",
|
||||
"merchants_with_programs": "Merchants with Programs",
|
||||
"avg_points_per_member": "Avg Points Per Member",
|
||||
"all_time_statistics": "All-Time Statistics",
|
||||
"total_points_issued": "Total Points Issued",
|
||||
"total_points_redeemed": "Total Points Redeemed",
|
||||
"points_redeemed_30d": "Points Redeemed (30d)",
|
||||
"outstanding_liability": "Outstanding Liability",
|
||||
"location_breakdown": "Location Breakdown",
|
||||
"store": "Store",
|
||||
"enrolled": "Enrolled",
|
||||
"points_earned": "Points Earned",
|
||||
"points_redeemed": "Points Redeemed"
|
||||
},
|
||||
"program_view": {
|
||||
"program_configuration": "Program Configuration",
|
||||
"program_name": "Program Name",
|
||||
"card_name": "Card Name",
|
||||
"stamps_configuration": "Stamps Configuration",
|
||||
"stamps_target": "Stamps Target",
|
||||
"reward_description": "Reward Description",
|
||||
"reward_value": "Reward Value",
|
||||
"points_configuration": "Points Configuration",
|
||||
"points_per_eur": "Points per EUR",
|
||||
"welcome_bonus": "Welcome Bonus",
|
||||
"x_points": "{count} points",
|
||||
"minimum_redemption": "Minimum Redemption",
|
||||
"minimum_purchase": "Minimum Purchase",
|
||||
"points_expiration": "Points Expiration",
|
||||
"x_days_inactivity": "{days} days of inactivity",
|
||||
"redemption_rewards": "Redemption Rewards",
|
||||
"reward": "Reward",
|
||||
"points_required": "Points Required",
|
||||
"description": "Description",
|
||||
"anti_fraud": "Anti-Fraud",
|
||||
"cooldown": "Cooldown",
|
||||
"x_minutes": "{count} minutes",
|
||||
"max_daily_stamps": "Max Daily Stamps",
|
||||
"staff_pin_required": "Staff PIN Required",
|
||||
"branding": "Branding",
|
||||
"primary_color": "Primary Color",
|
||||
"secondary_color": "Secondary Color",
|
||||
"logo_url": "Logo URL",
|
||||
"hero_image_url": "Hero Image URL",
|
||||
"terms_privacy": "Terms & Privacy",
|
||||
"terms_conditions": "Terms & Conditions",
|
||||
"privacy_policy_url": "Privacy Policy URL"
|
||||
},
|
||||
"program_form": {
|
||||
"program_type": "Program Type",
|
||||
"points_type_desc": "Earn points per EUR spent",
|
||||
"stamps_type_desc": "Collect N stamps, get reward",
|
||||
"hybrid_type_desc": "Both stamps and points",
|
||||
"stamps_configuration": "Stamps Configuration",
|
||||
"stamps_target": "Stamps Target",
|
||||
"stamps_target_help": "Number of stamps needed for reward",
|
||||
"reward_description": "Reward Description",
|
||||
"reward_value_cents": "Reward Value (cents)",
|
||||
"points_configuration": "Points Configuration",
|
||||
"points_per_eur": "Points per EUR spent",
|
||||
"eur_equals_points": "1 EUR = {points} point(s)",
|
||||
"welcome_bonus_points": "Welcome Bonus Points",
|
||||
"welcome_bonus_help": "Bonus points awarded on enrollment",
|
||||
"minimum_redemption_points": "Minimum Redemption Points",
|
||||
"minimum_purchase_cents": "Minimum Purchase (cents)",
|
||||
"minimum_purchase_help": "Minimum purchase amount to earn points (0 = no minimum)",
|
||||
"points_expiration_days": "Points Expiration (days)",
|
||||
"points_expiration_help": "Days of inactivity before points expire (0 = never)",
|
||||
"redemption_rewards": "Redemption Rewards",
|
||||
"add_reward": "Add Reward",
|
||||
"no_rewards_configured": "No rewards configured. Add a reward to allow customers to redeem points.",
|
||||
"reward_name": "Reward Name",
|
||||
"points_required": "Points Required",
|
||||
"description": "Description",
|
||||
"anti_fraud_settings": "Anti-Fraud Settings",
|
||||
"cooldown_minutes": "Cooldown (minutes)",
|
||||
"cooldown_help": "Time between stamps from the same card",
|
||||
"max_daily_stamps": "Max Daily Stamps",
|
||||
"max_daily_stamps_help": "Maximum stamps per card per day",
|
||||
"require_staff_pin": "Require Staff PIN",
|
||||
"branding": "Branding",
|
||||
"card_name": "Card Name",
|
||||
"primary_color": "Primary Color",
|
||||
"secondary_color": "Secondary Color",
|
||||
"logo_url": "Logo URL",
|
||||
"logo_url_help": "Required for Google Wallet integration. Must be a publicly accessible image URL (PNG or JPG).",
|
||||
"hero_image_url": "Hero Image URL",
|
||||
"terms_privacy": "Terms & Privacy",
|
||||
"terms_conditions": "Terms & Conditions",
|
||||
"privacy_policy_url": "Privacy Policy URL",
|
||||
"program_status": "Program Status",
|
||||
"program_active": "Program Active",
|
||||
"program_active_help": "When disabled, customers cannot earn or redeem",
|
||||
"delete_program": "Delete Program",
|
||||
"create_program": "Create Program",
|
||||
"save_changes": "Save Changes"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"programs": {
|
||||
"title": "Loyalty Programs",
|
||||
"create_program": "Create Program",
|
||||
"loading": "Loading loyalty programs...",
|
||||
"error_loading": "Error loading loyalty programs",
|
||||
"total_programs": "Total Programs",
|
||||
"active": "Active",
|
||||
"total_members": "Total Members",
|
||||
"transactions_30d": "Transactions (30d)",
|
||||
"search_placeholder": "Search by merchant name...",
|
||||
"all_status": "All Status",
|
||||
"table_merchant": "Merchant",
|
||||
"table_program_type": "Program Type",
|
||||
"table_members": "Members",
|
||||
"table_points_issued": "Points Issued",
|
||||
"table_status": "Status",
|
||||
"table_created": "Created",
|
||||
"table_actions": "Actions",
|
||||
"no_programs": "No loyalty programs found",
|
||||
"adjust_filters": "Try adjusting your search or filters",
|
||||
"no_merchants_yet": "No merchants have set up loyalty programs yet",
|
||||
"x_active": "({count} active)",
|
||||
"x_redeemed": "{count} redeemed",
|
||||
"pt_per_eur": "pt/EUR",
|
||||
"delete_title": "Delete Loyalty Program",
|
||||
"delete_message": "Delete the loyalty program for \"{name}\"? This will permanently remove all associated data (cards, transactions, rewards). This cannot be undone.",
|
||||
"delete_confirm": "Delete Program",
|
||||
"create_title": "Create Loyalty Program",
|
||||
"create_description": "Select a merchant to create a loyalty program for.",
|
||||
"search_merchant": "Search Merchant",
|
||||
"type_merchant_name": "Type merchant name...",
|
||||
"no_merchants_found": "No merchants found",
|
||||
"existing_program_warning": "This merchant already has a loyalty program.",
|
||||
"view_edit_existing": "View / Edit existing program"
|
||||
},
|
||||
"merchant_detail": {
|
||||
"title": "Merchant Loyalty Details",
|
||||
"loading": "Loading merchant loyalty details...",
|
||||
"error_loading": "Error loading merchant loyalty",
|
||||
"program_active": "Loyalty Program Active",
|
||||
"no_program_subtitle": "No Loyalty Program",
|
||||
"quick_actions": "Quick Actions",
|
||||
"edit_program": "Edit Program",
|
||||
"admin_policy": "Admin Policy",
|
||||
"view_merchant": "View Merchant",
|
||||
"total_members": "Total Members",
|
||||
"active_30d": "Active (30d)",
|
||||
"points_issued_30d": "Points Issued (30d)",
|
||||
"points_redeemed_30d": "Points Redeemed (30d)",
|
||||
"no_program": "No Loyalty Program",
|
||||
"no_program_desc": "This merchant has not set up a loyalty program yet.",
|
||||
"create_program": "Create Program",
|
||||
"delete_title": "Delete Loyalty Program",
|
||||
"delete_message": "This will permanently delete the loyalty program and all associated data. This action cannot be undone.",
|
||||
"delete_confirm": "Delete Program",
|
||||
"location_breakdown": "Location Breakdown",
|
||||
"table_location": "Location",
|
||||
"table_enrolled": "Enrolled",
|
||||
"table_points_earned": "Points Earned",
|
||||
"table_points_redeemed": "Points Redeemed",
|
||||
"table_transactions_30d": "Transactions (30d)",
|
||||
"admin_policy_settings": "Admin Policy Settings",
|
||||
"staff_pin_policy": "Staff PIN Policy",
|
||||
"self_enrollment": "Self Enrollment",
|
||||
"cross_location_redemption": "Cross-Location Redemption",
|
||||
"allowed": "Allowed",
|
||||
"disabled": "Disabled",
|
||||
"modify_policy": "Modify admin policy"
|
||||
},
|
||||
"merchant_settings": {
|
||||
"title": "Merchant Loyalty Settings",
|
||||
"loading": "Loading settings...",
|
||||
"error_loading": "Error loading settings",
|
||||
"admin_controlled": "Admin-controlled settings for this merchant's loyalty program",
|
||||
"staff_pin_policy": "Staff PIN Policy",
|
||||
"staff_pin_description": "Control whether staff members at this merchant's locations must enter a PIN to process loyalty transactions.",
|
||||
"required": "Required",
|
||||
"required_desc": "Staff must enter their PIN for every transaction. Recommended for security.",
|
||||
"optional": "Optional",
|
||||
"optional_desc": "Stores can choose whether to require PINs at their locations.",
|
||||
"pin_disabled": "Disabled",
|
||||
"pin_disabled_desc": "Staff PINs are not used. Any staff member can process transactions.",
|
||||
"pin_lockout_settings": "PIN Lockout Settings",
|
||||
"max_failed_attempts": "Max Failed Attempts",
|
||||
"max_failed_attempts_help": "Number of wrong attempts before lockout (3-10)",
|
||||
"lockout_duration": "Lockout Duration (minutes)",
|
||||
"lockout_duration_help": "How long to lock out after failed attempts (5-120 minutes)",
|
||||
"enrollment_settings": "Enrollment Settings",
|
||||
"allow_self_enrollment": "Allow Self-Service Enrollment",
|
||||
"self_enrollment_desc": "Customers can sign up via QR code without staff assistance",
|
||||
"transaction_settings": "Transaction Settings",
|
||||
"allow_cross_location": "Allow Cross-Location Redemption",
|
||||
"cross_location_desc": "Customers can redeem points at any merchant location",
|
||||
"allow_void": "Allow Void Transactions",
|
||||
"void_desc": "Staff can void points/stamps for returns",
|
||||
"save_settings": "Save Settings"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Loyalty Analytics",
|
||||
"subtitle": "Platform-wide loyalty program statistics",
|
||||
"loading": "Loading analytics...",
|
||||
"error_loading": "Error loading analytics",
|
||||
"filter_by_merchant": "Filter by Merchant",
|
||||
"search_merchants_placeholder": "Search merchants by name...",
|
||||
"showing_stats_for": "Showing stats for:",
|
||||
"wallet_status": "Wallet Integration Status",
|
||||
"google_wallet": "Google Wallet",
|
||||
"apple_wallet": "Apple Wallet",
|
||||
"connected": "Connected",
|
||||
"error": "Error",
|
||||
"not_configured": "Not Configured",
|
||||
"issuer_id": "Issuer ID",
|
||||
"project": "Project",
|
||||
"wallet_objects": "Wallet Objects",
|
||||
"loyalty_classes": "Loyalty Classes",
|
||||
"pass_type_id": "Pass Type ID",
|
||||
"team_id": "Team ID",
|
||||
"active_passes": "Active Passes",
|
||||
"quick_actions": "Quick Actions",
|
||||
"view_all_programs": "View All Programs",
|
||||
"manage_merchants": "Manage Merchants"
|
||||
},
|
||||
"program_edit": {
|
||||
"title": "Program Configuration",
|
||||
"loading": "Loading program configuration...",
|
||||
"error_loading": "Error loading program configuration",
|
||||
"create_subtitle": "Create a loyalty program for this merchant",
|
||||
"edit_subtitle": "Edit program configuration",
|
||||
"delete_title": "Delete Loyalty Program",
|
||||
"delete_message": "This will permanently delete the loyalty program and all associated data (cards, transactions, rewards). This action cannot be undone.",
|
||||
"delete_confirm": "Delete Program"
|
||||
}
|
||||
},
|
||||
"merchant": {
|
||||
"program": {
|
||||
"title": "Loyalty Program",
|
||||
"subtitle": "Your loyalty program configuration.",
|
||||
"edit_program": "Edit Program",
|
||||
"no_program": "No Loyalty Program",
|
||||
"no_program_desc": "Your loyalty program hasn't been set up yet. Create one to start rewarding your customers.",
|
||||
"create_program": "Create Program"
|
||||
},
|
||||
"program_edit": {
|
||||
"title": "Loyalty Settings",
|
||||
"page_title": "Loyalty Program Settings",
|
||||
"subtitle": "Configure your loyalty program",
|
||||
"loading": "Loading settings...",
|
||||
"error_loading": "Error loading settings",
|
||||
"delete_title": "Delete Loyalty Program",
|
||||
"delete_message": "This will permanently delete your loyalty program and all associated data (cards, transactions, rewards). This action cannot be undone.",
|
||||
"delete_confirm": "Delete Program"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Loyalty Analytics",
|
||||
"subtitle": "Loyalty program statistics across all your stores",
|
||||
"loading": "Loading analytics...",
|
||||
"error_loading": "Error loading analytics",
|
||||
"no_program": "No Loyalty Program",
|
||||
"no_program_desc": "Set up a loyalty program to see analytics here.",
|
||||
"create_program": "Create Program",
|
||||
"quick_actions": "Quick Actions",
|
||||
"view_program": "View Program",
|
||||
"edit_program": "Edit Program"
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"terminal": {
|
||||
"title": "Loyalty Terminal",
|
||||
"subtitle": "Process loyalty transactions",
|
||||
"members": "Members",
|
||||
"analytics": "Analytics",
|
||||
"loading": "Loading loyalty terminal...",
|
||||
"error_loading": "Error loading terminal",
|
||||
"not_setup": "Loyalty Program Not Set Up",
|
||||
"not_setup_desc": "Your merchant doesn't have a loyalty program configured yet.",
|
||||
"setup_program": "Set Up Loyalty Program",
|
||||
"contact_admin": "Contact your administrator to complete the setup.",
|
||||
"find_customer": "Find Customer",
|
||||
"search_placeholder": "Email, phone, or card number...",
|
||||
"looking_up": "Looking up...",
|
||||
"look_up_customer": "Look Up Customer",
|
||||
"enroll_new_customer": "Enroll New Customer",
|
||||
"customer_found": "Customer Found",
|
||||
"points_balance": "Points Balance",
|
||||
"stamps": "Stamps",
|
||||
"x_more_for_reward": "{count} more for reward",
|
||||
"ready_to_redeem": "Ready to redeem!",
|
||||
"add_stamp": "Add Stamp",
|
||||
"current": "Current:",
|
||||
"cooldown_active": "Cooldown active",
|
||||
"redeem_stamps": "Redeem Stamps",
|
||||
"not_enough_stamps": "Not enough stamps yet",
|
||||
"earn_points": "Earn Points",
|
||||
"purchase_amount": "Purchase Amount",
|
||||
"points_to_award": "Points to award:",
|
||||
"award_points": "Award Points",
|
||||
"redeem_reward": "Redeem Reward",
|
||||
"select_reward": "Select Reward",
|
||||
"select_reward_placeholder": "Select reward...",
|
||||
"points_after": "Points after:",
|
||||
"search_to_process": "Search for a customer to process a transaction",
|
||||
"recent_transactions": "Recent Transactions at This Location",
|
||||
"table_time": "Time",
|
||||
"table_customer": "Customer",
|
||||
"table_type": "Type",
|
||||
"table_points": "Points",
|
||||
"table_notes": "Notes",
|
||||
"no_recent_transactions": "No recent transactions",
|
||||
"enter_staff_pin": "Enter Staff PIN",
|
||||
"pin_authorize": "Enter your staff PIN to authorize this transaction.",
|
||||
"clear": "Clear",
|
||||
"processing": "Processing...",
|
||||
"customer_not_found": "Customer not found. You can enroll them as a new member.",
|
||||
"error_lookup": "Error looking up customer: {message}",
|
||||
"transaction_failed": "Transaction failed: {message}",
|
||||
"stamp_added": "Stamp added!",
|
||||
"stamps_redeemed": "Stamps redeemed! Reward earned.",
|
||||
"x_points_awarded": "{points} points awarded!",
|
||||
"reward_redeemed": "Reward redeemed: {name}"
|
||||
},
|
||||
"cards": {
|
||||
"title": "Loyalty Members",
|
||||
"subtitle": "View and manage your loyalty program members",
|
||||
"enroll_new": "Enroll New",
|
||||
"loading": "Loading members...",
|
||||
"error_loading": "Error loading members",
|
||||
"total_members": "Total Members",
|
||||
"active_30d": "Active (30d)",
|
||||
"new_this_month": "New This Month",
|
||||
"total_points_balance": "Total Points Balance",
|
||||
"search_placeholder": "Search by name, email, phone, or card...",
|
||||
"all_status": "All Status",
|
||||
"table_member": "Member",
|
||||
"table_card_number": "Card Number",
|
||||
"table_points_balance": "Points Balance",
|
||||
"table_last_activity": "Last Activity",
|
||||
"table_status": "Status",
|
||||
"table_actions": "Actions",
|
||||
"no_members": "No members found",
|
||||
"adjust_search": "Try adjusting your search",
|
||||
"enroll_first": "Enroll your first customer to get started"
|
||||
},
|
||||
"card_detail": {
|
||||
"title": "Member Details",
|
||||
"loading": "Loading member details...",
|
||||
"error_loading": "Error loading member",
|
||||
"points_balance": "Points Balance",
|
||||
"total_earned": "Total Earned",
|
||||
"total_redeemed": "Total Redeemed",
|
||||
"member_since": "Member Since",
|
||||
"customer_information": "Customer Information",
|
||||
"name": "Name",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"birthday": "Birthday",
|
||||
"card_details": "Card Details",
|
||||
"card_number": "Card Number",
|
||||
"status": "Status",
|
||||
"last_activity": "Last Activity",
|
||||
"enrolled_at": "Enrolled At",
|
||||
"transaction_history": "Transaction History",
|
||||
"table_date": "Date",
|
||||
"table_type": "Type",
|
||||
"table_points": "Points",
|
||||
"table_location": "Location",
|
||||
"table_notes": "Notes",
|
||||
"no_transactions": "No transactions yet"
|
||||
},
|
||||
"enroll": {
|
||||
"title": "Enroll Customer",
|
||||
"page_title": "Enroll New Customer",
|
||||
"subtitle": "Add a new member to your loyalty program",
|
||||
"loading": "Loading...",
|
||||
"error_loading": "Error loading enrollment form",
|
||||
"customer_information": "Customer Information",
|
||||
"first_name": "First Name",
|
||||
"last_name": "Last Name",
|
||||
"email": "Email",
|
||||
"phone": "Phone",
|
||||
"birthday": "Birthday",
|
||||
"birthday_help": "For birthday rewards (optional)",
|
||||
"communication_preferences": "Communication Preferences",
|
||||
"send_emails": "Send promotional emails",
|
||||
"send_sms": "Send promotional SMS",
|
||||
"welcome_bonus": "Welcome Bonus",
|
||||
"welcome_bonus_desc": "Customer will receive {points} bonus points!",
|
||||
"enroll_customer": "Enroll Customer",
|
||||
"enrolling": "Enrolling...",
|
||||
"customer_enrolled": "Customer Enrolled!",
|
||||
"starting_balance": "Starting Balance:",
|
||||
"x_points": "{count} points",
|
||||
"back_to_terminal": "Back to Terminal",
|
||||
"enroll_another": "Enroll Another",
|
||||
"enrollment_failed": "Enrollment failed: {message}"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Loyalty Analytics",
|
||||
"subtitle": "Track your loyalty program performance",
|
||||
"loading": "Loading analytics...",
|
||||
"error_loading": "Error loading analytics",
|
||||
"quick_actions": "Quick Actions",
|
||||
"open_terminal": "Open Terminal",
|
||||
"view_members": "View Members",
|
||||
"view_program": "View Program"
|
||||
},
|
||||
"program": {
|
||||
"title": "Loyalty Program",
|
||||
"subtitle": "Your loyalty program configuration",
|
||||
"edit_program": "Edit Program",
|
||||
"loading": "Loading program...",
|
||||
"error_loading": "Error loading program",
|
||||
"no_program": "No Loyalty Program",
|
||||
"no_program_desc": "Your merchant doesn't have a loyalty program configured yet.",
|
||||
"create_program": "Create Program",
|
||||
"contact_admin": "Contact your administrator to set up a loyalty program."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Loyalty Settings",
|
||||
"page_title": "Loyalty Program Settings",
|
||||
"subtitle": "Configure your loyalty program",
|
||||
"back_to_program": "Back to Program",
|
||||
"loading": "Loading settings...",
|
||||
"error_loading": "Error loading settings",
|
||||
"access_restricted": "Access Restricted",
|
||||
"owner_only": "Only the merchant owner can manage loyalty program settings.",
|
||||
"delete_title": "Delete Loyalty Program",
|
||||
"delete_message": "This will permanently delete the loyalty program and all associated data (cards, transactions, rewards). This action cannot be undone.",
|
||||
"delete_confirm": "Delete Program",
|
||||
"program_created": "Program created successfully",
|
||||
"program_updated": "Program updated successfully",
|
||||
"program_deleted": "Loyalty program deleted",
|
||||
"save_failed": "Failed to save: {message}",
|
||||
"delete_failed": "Failed to delete: {message}"
|
||||
}
|
||||
},
|
||||
"storefront": {
|
||||
"dashboard": {
|
||||
"back_to_account": "Back to Account",
|
||||
"my_loyalty": "My Loyalty",
|
||||
"join_title": "Join Our Rewards Program!",
|
||||
"join_subtitle": "Earn points on every purchase and redeem for rewards.",
|
||||
"join_now": "Join Now",
|
||||
"points_balance": "Points Balance",
|
||||
"card_number": "Card Number",
|
||||
"show_card": "Show Card",
|
||||
"total_earned": "Total Earned",
|
||||
"total_redeemed": "Total Redeemed",
|
||||
"available_rewards": "Available Rewards",
|
||||
"no_rewards_yet": "No rewards available yet",
|
||||
"ready_to_redeem": "Ready to redeem",
|
||||
"x_more_to_go": "{count} more to go",
|
||||
"redeem_hint": "Show your card to staff to redeem rewards in-store.",
|
||||
"recent_activity": "Recent Activity",
|
||||
"view_all": "View All",
|
||||
"no_transactions": "No transactions yet. Make a purchase to start earning points!",
|
||||
"earn_redeem_locations": "Earn & Redeem Locations",
|
||||
"your_loyalty_card": "Your Loyalty Card",
|
||||
"show_to_staff": "Show this to staff when making a purchase or redeeming rewards."
|
||||
},
|
||||
"history": {
|
||||
"back_to_loyalty": "Back to Loyalty",
|
||||
"title": "Transaction History",
|
||||
"subtitle": "View all your loyalty point transactions",
|
||||
"current_balance": "Current Balance",
|
||||
"total_earned": "Total Earned",
|
||||
"total_redeemed": "Total Redeemed",
|
||||
"no_transactions": "No transactions yet",
|
||||
"balance": "Balance:",
|
||||
"previous": "Previous",
|
||||
"next": "Next",
|
||||
"page_x_of_y": "Page {page} of {pages}"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"program_activated": "Program activated successfully",
|
||||
"program_deactivated": "Program deactivated successfully",
|
||||
"activate_failed": "Failed to activate program: {message}",
|
||||
"deactivate_failed": "Failed to deactivate program: {message}",
|
||||
"program_deleted": "Program deleted successfully",
|
||||
"delete_failed": "Failed to delete program: {message}",
|
||||
"program_created": "Program created successfully",
|
||||
"program_updated": "Program updated successfully",
|
||||
"loyalty_program_created": "Loyalty program created",
|
||||
"loyalty_program_deleted": "Loyalty program deleted",
|
||||
"settings_saved": "Settings saved successfully",
|
||||
"save_failed": "Failed to save: {message}",
|
||||
"settings_save_failed": "Failed to save settings: {message}",
|
||||
"create_failed": "Failed to create program: {message}",
|
||||
"logo_required": "Logo URL is required for wallet integration."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,5 +97,589 @@
|
||||
"title": "Créer un programme de fidélité",
|
||||
"description": "Créez votre premier programme de tampons ou de points"
|
||||
}
|
||||
},
|
||||
"enrollment": {
|
||||
"title": "Rejoignez notre programme de récompenses !",
|
||||
"subtitle": "Gagnez {points} point pour chaque EUR dépensé",
|
||||
"not_available_title": "Programme non disponible",
|
||||
"not_available_message": "Ce magasin n'a pas encore de programme de fidélité.",
|
||||
"welcome_bonus": "Obtenez {points} points bonus en vous inscrivant !",
|
||||
"already_member": "Déjà membre ? Vos points sont liés à votre e-mail.",
|
||||
"form": {
|
||||
"email": "E-mail",
|
||||
"first_name": "Prénom",
|
||||
"last_name": "Nom",
|
||||
"phone": "Téléphone (facultatif)",
|
||||
"birthday": "Date de naissance (facultatif)",
|
||||
"birthday_hint": "Pour des récompenses d'anniversaire spéciales",
|
||||
"terms_agree": "J'accepte les",
|
||||
"terms": "Conditions Générales",
|
||||
"marketing_consent": "M'envoyer des nouvelles et offres spéciales",
|
||||
"joining": "Inscription en cours...",
|
||||
"join_button": "S'inscrire et obtenir {points} points"
|
||||
},
|
||||
"privacy_policy": "Politique de confidentialité",
|
||||
"close": "Fermer",
|
||||
"success": {
|
||||
"title": "Bienvenue !",
|
||||
"message": "Vous êtes maintenant membre de notre programme de récompenses.",
|
||||
"card_number": "Votre numéro de carte",
|
||||
"wallet_prompt": "Enregistrez votre carte sur votre téléphone pour un accès facile :",
|
||||
"next_steps_title": "Et maintenant ?",
|
||||
"step_earn": "Présentez votre numéro de carte lors de vos achats pour gagner des points",
|
||||
"step_balance": "Consultez votre solde en ligne ou dans l'application",
|
||||
"step_redeem": "Échangez vos points contre des récompenses dans tous nos points de vente",
|
||||
"view_dashboard": "Voir mon tableau de bord fidélité",
|
||||
"continue_shopping": "Continuer mes achats"
|
||||
},
|
||||
"errors": {
|
||||
"load_failed": "Impossible de charger les informations du programme",
|
||||
"email_exists": "Cet e-mail est déjà inscrit dans notre programme de fidélité.",
|
||||
"failed": "L'inscription a échoué. Veuillez réessayer."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"active": "Actif",
|
||||
"inactive": "Inactif",
|
||||
"cancel": "Annuler",
|
||||
"save": "Enregistrer",
|
||||
"delete": "Supprimer",
|
||||
"confirm": "Confirmer",
|
||||
"refresh": "Actualiser",
|
||||
"loading": "Chargement...",
|
||||
"saving": "Enregistrement...",
|
||||
"view": "Voir",
|
||||
"edit": "Modifier",
|
||||
"yes": "Oui",
|
||||
"no": "Non",
|
||||
"none": "Aucun",
|
||||
"never": "Jamais",
|
||||
"total": "TOTAL",
|
||||
"continue": "Continuer",
|
||||
"back": "Retour",
|
||||
"points": "points",
|
||||
"minutes": "minutes",
|
||||
"or": "ou",
|
||||
"at": "à"
|
||||
},
|
||||
"transactions": {
|
||||
"card_created": "Inscrit",
|
||||
"welcome_bonus": "Bonus de bienvenue",
|
||||
"stamp_earned": "Tampon obtenu",
|
||||
"stamp_redeemed": "Tampon échangé",
|
||||
"stamp_voided": "Tampon annulé",
|
||||
"stamp_adjustment": "Tampon ajusté",
|
||||
"points_earned": "Points gagnés",
|
||||
"points_redeemed": "Points échangés",
|
||||
"points_voided": "Points annulés",
|
||||
"points_adjustment": "Points ajustés",
|
||||
"points_expired": "Points expirés",
|
||||
"card_deactivated": "Désactivé",
|
||||
"reward_redeemed": "Récompense échangée"
|
||||
},
|
||||
"shared": {
|
||||
"analytics": {
|
||||
"total_programs": "Programmes au total",
|
||||
"total_members": "Membres au total",
|
||||
"active_members": "Membres actifs",
|
||||
"points_issued_30d": "Points émis (30j)",
|
||||
"transactions_30d": "Transactions (30j)",
|
||||
"x_active": "{count} actifs",
|
||||
"points_overview": "Aperçu des points",
|
||||
"points_issued_vs_redeemed": "Points émis vs échangés (30j)",
|
||||
"issued": "Émis :",
|
||||
"redeemed": "Échangés :",
|
||||
"redemption_rate": "Taux d'échange",
|
||||
"outstanding_balance": "Solde restant",
|
||||
"member_activity": "Activité des membres",
|
||||
"active_members_30d": "Membres actifs (30j)",
|
||||
"new_this_month": "Nouveaux ce mois",
|
||||
"merchants_with_programs": "Commerçants avec programmes",
|
||||
"avg_points_per_member": "Moy. points par membre",
|
||||
"all_time_statistics": "Statistiques globales",
|
||||
"total_points_issued": "Total points émis",
|
||||
"total_points_redeemed": "Total points échangés",
|
||||
"points_redeemed_30d": "Points échangés (30j)",
|
||||
"outstanding_liability": "Passif en cours",
|
||||
"location_breakdown": "Répartition par point de vente",
|
||||
"store": "Magasin",
|
||||
"enrolled": "Inscrits",
|
||||
"points_earned": "Points gagnés",
|
||||
"points_redeemed": "Points échangés"
|
||||
},
|
||||
"program_view": {
|
||||
"program_configuration": "Configuration du programme",
|
||||
"program_name": "Nom du programme",
|
||||
"card_name": "Nom de la carte",
|
||||
"stamps_configuration": "Configuration des tampons",
|
||||
"stamps_target": "Objectif de tampons",
|
||||
"reward_description": "Description de la récompense",
|
||||
"reward_value": "Valeur de la récompense",
|
||||
"points_configuration": "Configuration des points",
|
||||
"points_per_eur": "Points par EUR",
|
||||
"welcome_bonus": "Bonus de bienvenue",
|
||||
"x_points": "{count} points",
|
||||
"minimum_redemption": "Échange minimum",
|
||||
"minimum_purchase": "Achat minimum",
|
||||
"points_expiration": "Expiration des points",
|
||||
"x_days_inactivity": "{days} jours d'inactivité",
|
||||
"redemption_rewards": "Récompenses d'échange",
|
||||
"reward": "Récompense",
|
||||
"points_required": "Points requis",
|
||||
"description": "Description",
|
||||
"anti_fraud": "Anti-fraude",
|
||||
"cooldown": "Temps d'attente",
|
||||
"x_minutes": "{count} minutes",
|
||||
"max_daily_stamps": "Tampons max par jour",
|
||||
"staff_pin_required": "PIN personnel requis",
|
||||
"branding": "Image de marque",
|
||||
"primary_color": "Couleur principale",
|
||||
"secondary_color": "Couleur secondaire",
|
||||
"logo_url": "URL du logo",
|
||||
"hero_image_url": "URL de l'image principale",
|
||||
"terms_privacy": "Conditions & Confidentialité",
|
||||
"terms_conditions": "Conditions Générales",
|
||||
"privacy_policy_url": "URL politique de confidentialité"
|
||||
},
|
||||
"program_form": {
|
||||
"program_type": "Type de programme",
|
||||
"points_type_desc": "Gagner des points par EUR dépensé",
|
||||
"stamps_type_desc": "Collecter N tampons, obtenir une récompense",
|
||||
"hybrid_type_desc": "Tampons et points combinés",
|
||||
"stamps_configuration": "Configuration des tampons",
|
||||
"stamps_target": "Objectif de tampons",
|
||||
"stamps_target_help": "Nombre de tampons nécessaires pour la récompense",
|
||||
"reward_description": "Description de la récompense",
|
||||
"reward_value_cents": "Valeur de la récompense (centimes)",
|
||||
"points_configuration": "Configuration des points",
|
||||
"points_per_eur": "Points par EUR dépensé",
|
||||
"eur_equals_points": "1 EUR = {points} point(s)",
|
||||
"welcome_bonus_points": "Points bonus de bienvenue",
|
||||
"welcome_bonus_help": "Points bonus attribués à l'inscription",
|
||||
"minimum_redemption_points": "Points minimum d'échange",
|
||||
"minimum_purchase_cents": "Achat minimum (centimes)",
|
||||
"minimum_purchase_help": "Montant minimum d'achat pour gagner des points (0 = pas de minimum)",
|
||||
"points_expiration_days": "Expiration des points (jours)",
|
||||
"points_expiration_help": "Jours d'inactivité avant expiration des points (0 = jamais)",
|
||||
"redemption_rewards": "Récompenses d'échange",
|
||||
"add_reward": "Ajouter une récompense",
|
||||
"no_rewards_configured": "Aucune récompense configurée. Ajoutez une récompense pour permettre aux clients d'échanger des points.",
|
||||
"reward_name": "Nom de la récompense",
|
||||
"points_required": "Points requis",
|
||||
"description": "Description",
|
||||
"anti_fraud_settings": "Paramètres anti-fraude",
|
||||
"cooldown_minutes": "Temps d'attente (minutes)",
|
||||
"cooldown_help": "Temps entre les tampons d'une même carte",
|
||||
"max_daily_stamps": "Tampons max par jour",
|
||||
"max_daily_stamps_help": "Maximum de tampons par carte par jour",
|
||||
"require_staff_pin": "Exiger le PIN du personnel",
|
||||
"branding": "Image de marque",
|
||||
"card_name": "Nom de la carte",
|
||||
"primary_color": "Couleur principale",
|
||||
"secondary_color": "Couleur secondaire",
|
||||
"logo_url": "URL du logo",
|
||||
"logo_url_help": "Requis pour l'intégration Google Wallet. Doit être une URL d'image publique (PNG ou JPG).",
|
||||
"hero_image_url": "URL de l'image principale",
|
||||
"terms_privacy": "Conditions & Confidentialité",
|
||||
"terms_conditions": "Conditions Générales",
|
||||
"privacy_policy_url": "URL politique de confidentialité",
|
||||
"program_status": "Statut du programme",
|
||||
"program_active": "Programme actif",
|
||||
"program_active_help": "Désactivé, les clients ne peuvent ni gagner ni échanger",
|
||||
"delete_program": "Supprimer le programme",
|
||||
"create_program": "Créer le programme",
|
||||
"save_changes": "Enregistrer les modifications"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"programs": {
|
||||
"title": "Programmes de fidélité",
|
||||
"create_program": "Créer un programme",
|
||||
"loading": "Chargement des programmes de fidélité...",
|
||||
"error_loading": "Erreur de chargement des programmes de fidélité",
|
||||
"total_programs": "Programmes au total",
|
||||
"active": "Actifs",
|
||||
"total_members": "Membres au total",
|
||||
"transactions_30d": "Transactions (30j)",
|
||||
"search_placeholder": "Rechercher par nom de commerçant...",
|
||||
"all_status": "Tous les statuts",
|
||||
"table_merchant": "Commerçant",
|
||||
"table_program_type": "Type de programme",
|
||||
"table_members": "Membres",
|
||||
"table_points_issued": "Points émis",
|
||||
"table_status": "Statut",
|
||||
"table_created": "Créé",
|
||||
"table_actions": "Actions",
|
||||
"no_programs": "Aucun programme de fidélité trouvé",
|
||||
"adjust_filters": "Essayez d'ajuster votre recherche ou vos filtres",
|
||||
"no_merchants_yet": "Aucun commerçant n'a encore créé de programme de fidélité",
|
||||
"x_active": "({count} actifs)",
|
||||
"x_redeemed": "{count} échangés",
|
||||
"pt_per_eur": "pt/EUR",
|
||||
"delete_title": "Supprimer le programme de fidélité",
|
||||
"delete_message": "Supprimer le programme de fidélité de \"{name}\" ? Cela supprimera définitivement toutes les données associées (cartes, transactions, récompenses). Cette action est irréversible.",
|
||||
"delete_confirm": "Supprimer le programme",
|
||||
"create_title": "Créer un programme de fidélité",
|
||||
"create_description": "Sélectionnez un commerçant pour créer un programme de fidélité.",
|
||||
"search_merchant": "Rechercher un commerçant",
|
||||
"type_merchant_name": "Tapez le nom du commerçant...",
|
||||
"no_merchants_found": "Aucun commerçant trouvé",
|
||||
"existing_program_warning": "Ce commerçant a déjà un programme de fidélité.",
|
||||
"view_edit_existing": "Voir / Modifier le programme existant"
|
||||
},
|
||||
"merchant_detail": {
|
||||
"title": "Détails fidélité du commerçant",
|
||||
"loading": "Chargement des détails de fidélité...",
|
||||
"error_loading": "Erreur de chargement de la fidélité du commerçant",
|
||||
"program_active": "Programme de fidélité actif",
|
||||
"no_program_subtitle": "Aucun programme de fidélité",
|
||||
"quick_actions": "Actions rapides",
|
||||
"edit_program": "Modifier le programme",
|
||||
"admin_policy": "Politique admin",
|
||||
"view_merchant": "Voir le commerçant",
|
||||
"total_members": "Membres au total",
|
||||
"active_30d": "Actifs (30j)",
|
||||
"points_issued_30d": "Points émis (30j)",
|
||||
"points_redeemed_30d": "Points échangés (30j)",
|
||||
"no_program": "Aucun programme de fidélité",
|
||||
"no_program_desc": "Ce commerçant n'a pas encore configuré de programme de fidélité.",
|
||||
"create_program": "Créer un programme",
|
||||
"delete_title": "Supprimer le programme de fidélité",
|
||||
"delete_message": "Cela supprimera définitivement le programme de fidélité et toutes les données associées. Cette action est irréversible.",
|
||||
"delete_confirm": "Supprimer le programme",
|
||||
"location_breakdown": "Répartition par point de vente",
|
||||
"table_location": "Point de vente",
|
||||
"table_enrolled": "Inscrits",
|
||||
"table_points_earned": "Points gagnés",
|
||||
"table_points_redeemed": "Points échangés",
|
||||
"table_transactions_30d": "Transactions (30j)",
|
||||
"admin_policy_settings": "Paramètres de politique admin",
|
||||
"staff_pin_policy": "Politique PIN du personnel",
|
||||
"self_enrollment": "Auto-inscription",
|
||||
"cross_location_redemption": "Échange inter-points de vente",
|
||||
"allowed": "Autorisé",
|
||||
"disabled": "Désactivé",
|
||||
"modify_policy": "Modifier la politique admin"
|
||||
},
|
||||
"merchant_settings": {
|
||||
"title": "Paramètres de fidélité du commerçant",
|
||||
"loading": "Chargement des paramètres...",
|
||||
"error_loading": "Erreur de chargement des paramètres",
|
||||
"admin_controlled": "Paramètres contrôlés par l'admin pour le programme de fidélité de ce commerçant",
|
||||
"staff_pin_policy": "Politique PIN du personnel",
|
||||
"staff_pin_description": "Contrôlez si les membres du personnel doivent saisir un PIN pour traiter les transactions de fidélité.",
|
||||
"required": "Obligatoire",
|
||||
"required_desc": "Le personnel doit saisir son PIN pour chaque transaction. Recommandé pour la sécurité.",
|
||||
"optional": "Facultatif",
|
||||
"optional_desc": "Les magasins peuvent choisir d'exiger ou non les PINs.",
|
||||
"pin_disabled": "Désactivé",
|
||||
"pin_disabled_desc": "Les PINs du personnel ne sont pas utilisés. Tout membre du personnel peut traiter les transactions.",
|
||||
"pin_lockout_settings": "Paramètres de verrouillage PIN",
|
||||
"max_failed_attempts": "Tentatives échouées max",
|
||||
"max_failed_attempts_help": "Nombre de tentatives erronées avant verrouillage (3-10)",
|
||||
"lockout_duration": "Durée de verrouillage (minutes)",
|
||||
"lockout_duration_help": "Durée du verrouillage après échecs (5-120 minutes)",
|
||||
"enrollment_settings": "Paramètres d'inscription",
|
||||
"allow_self_enrollment": "Autoriser l'auto-inscription",
|
||||
"self_enrollment_desc": "Les clients peuvent s'inscrire via QR code sans aide du personnel",
|
||||
"transaction_settings": "Paramètres de transaction",
|
||||
"allow_cross_location": "Autoriser l'échange inter-points de vente",
|
||||
"cross_location_desc": "Les clients peuvent échanger des points dans tous les points de vente du commerçant",
|
||||
"allow_void": "Autoriser les annulations",
|
||||
"void_desc": "Le personnel peut annuler des points/tampons pour les retours",
|
||||
"save_settings": "Enregistrer les paramètres"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Analytique fidélité",
|
||||
"subtitle": "Statistiques de fidélité à l'échelle de la plateforme",
|
||||
"loading": "Chargement des analytiques...",
|
||||
"error_loading": "Erreur de chargement des analytiques",
|
||||
"filter_by_merchant": "Filtrer par commerçant",
|
||||
"search_merchants_placeholder": "Rechercher des commerçants par nom...",
|
||||
"showing_stats_for": "Statistiques pour :",
|
||||
"wallet_status": "Statut de l'intégration Wallet",
|
||||
"google_wallet": "Google Wallet",
|
||||
"apple_wallet": "Apple Wallet",
|
||||
"connected": "Connecté",
|
||||
"error": "Erreur",
|
||||
"not_configured": "Non configuré",
|
||||
"issuer_id": "ID émetteur",
|
||||
"project": "Projet",
|
||||
"wallet_objects": "Objets Wallet",
|
||||
"loyalty_classes": "Classes de fidélité",
|
||||
"pass_type_id": "ID type de pass",
|
||||
"team_id": "ID équipe",
|
||||
"active_passes": "Pass actifs",
|
||||
"quick_actions": "Actions rapides",
|
||||
"view_all_programs": "Voir tous les programmes",
|
||||
"manage_merchants": "Gérer les commerçants"
|
||||
},
|
||||
"program_edit": {
|
||||
"title": "Configuration du programme",
|
||||
"loading": "Chargement de la configuration...",
|
||||
"error_loading": "Erreur de chargement de la configuration du programme",
|
||||
"create_subtitle": "Créer un programme de fidélité pour ce commerçant",
|
||||
"edit_subtitle": "Modifier la configuration du programme",
|
||||
"delete_title": "Supprimer le programme de fidélité",
|
||||
"delete_message": "Cela supprimera définitivement le programme de fidélité et toutes les données associées (cartes, transactions, récompenses). Cette action est irréversible.",
|
||||
"delete_confirm": "Supprimer le programme"
|
||||
}
|
||||
},
|
||||
"merchant": {
|
||||
"program": {
|
||||
"title": "Programme de fidélité",
|
||||
"subtitle": "Configuration de votre programme de fidélité.",
|
||||
"edit_program": "Modifier le programme",
|
||||
"no_program": "Aucun programme de fidélité",
|
||||
"no_program_desc": "Votre programme de fidélité n'a pas encore été configuré. Créez-en un pour commencer à récompenser vos clients.",
|
||||
"create_program": "Créer un programme"
|
||||
},
|
||||
"program_edit": {
|
||||
"title": "Paramètres de fidélité",
|
||||
"page_title": "Paramètres du programme de fidélité",
|
||||
"subtitle": "Configurez votre programme de fidélité",
|
||||
"loading": "Chargement des paramètres...",
|
||||
"error_loading": "Erreur de chargement des paramètres",
|
||||
"delete_title": "Supprimer le programme de fidélité",
|
||||
"delete_message": "Cela supprimera définitivement votre programme de fidélité et toutes les données associées (cartes, transactions, récompenses). Cette action est irréversible.",
|
||||
"delete_confirm": "Supprimer le programme"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Analytique fidélité",
|
||||
"subtitle": "Statistiques de fidélité pour tous vos magasins",
|
||||
"loading": "Chargement des analytiques...",
|
||||
"error_loading": "Erreur de chargement des analytiques",
|
||||
"no_program": "Aucun programme de fidélité",
|
||||
"no_program_desc": "Configurez un programme de fidélité pour voir les analytiques ici.",
|
||||
"create_program": "Créer un programme",
|
||||
"quick_actions": "Actions rapides",
|
||||
"view_program": "Voir le programme",
|
||||
"edit_program": "Modifier le programme"
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"terminal": {
|
||||
"title": "Terminal de fidélité",
|
||||
"subtitle": "Traiter les transactions de fidélité",
|
||||
"members": "Membres",
|
||||
"analytics": "Analytique",
|
||||
"loading": "Chargement du terminal de fidélité...",
|
||||
"error_loading": "Erreur de chargement du terminal",
|
||||
"not_setup": "Programme de fidélité non configuré",
|
||||
"not_setup_desc": "Votre commerçant n'a pas encore configuré de programme de fidélité.",
|
||||
"setup_program": "Configurer le programme de fidélité",
|
||||
"contact_admin": "Contactez votre administrateur pour terminer la configuration.",
|
||||
"find_customer": "Trouver un client",
|
||||
"search_placeholder": "E-mail, téléphone ou numéro de carte...",
|
||||
"looking_up": "Recherche en cours...",
|
||||
"look_up_customer": "Rechercher un client",
|
||||
"enroll_new_customer": "Inscrire un nouveau client",
|
||||
"customer_found": "Client trouvé",
|
||||
"points_balance": "Solde de points",
|
||||
"stamps": "Tampons",
|
||||
"x_more_for_reward": "Encore {count} pour la récompense",
|
||||
"ready_to_redeem": "Prêt à échanger !",
|
||||
"add_stamp": "Ajouter un tampon",
|
||||
"current": "Actuel :",
|
||||
"cooldown_active": "Temps d'attente actif",
|
||||
"redeem_stamps": "Échanger les tampons",
|
||||
"not_enough_stamps": "Pas assez de tampons encore",
|
||||
"earn_points": "Gagner des points",
|
||||
"purchase_amount": "Montant de l'achat",
|
||||
"points_to_award": "Points à attribuer :",
|
||||
"award_points": "Attribuer des points",
|
||||
"redeem_reward": "Échanger une récompense",
|
||||
"select_reward": "Sélectionner une récompense",
|
||||
"select_reward_placeholder": "Sélectionner une récompense...",
|
||||
"points_after": "Points après :",
|
||||
"search_to_process": "Recherchez un client pour traiter une transaction",
|
||||
"recent_transactions": "Transactions récentes dans ce point de vente",
|
||||
"table_time": "Heure",
|
||||
"table_customer": "Client",
|
||||
"table_type": "Type",
|
||||
"table_points": "Points",
|
||||
"table_notes": "Notes",
|
||||
"no_recent_transactions": "Aucune transaction récente",
|
||||
"enter_staff_pin": "Saisir le PIN du personnel",
|
||||
"pin_authorize": "Saisissez votre PIN personnel pour autoriser cette transaction.",
|
||||
"clear": "Effacer",
|
||||
"processing": "Traitement...",
|
||||
"customer_not_found": "Client introuvable. Vous pouvez l'inscrire comme nouveau membre.",
|
||||
"error_lookup": "Erreur de recherche du client : {message}",
|
||||
"transaction_failed": "Transaction échouée : {message}",
|
||||
"stamp_added": "Tampon ajouté !",
|
||||
"stamps_redeemed": "Tampons échangés ! Récompense obtenue.",
|
||||
"x_points_awarded": "{points} points attribués !",
|
||||
"reward_redeemed": "Récompense échangée : {name}"
|
||||
},
|
||||
"cards": {
|
||||
"title": "Membres fidélité",
|
||||
"subtitle": "Voir et gérer les membres de votre programme de fidélité",
|
||||
"enroll_new": "Inscrire",
|
||||
"loading": "Chargement des membres...",
|
||||
"error_loading": "Erreur de chargement des membres",
|
||||
"total_members": "Membres au total",
|
||||
"active_30d": "Actifs (30j)",
|
||||
"new_this_month": "Nouveaux ce mois",
|
||||
"total_points_balance": "Solde total de points",
|
||||
"search_placeholder": "Rechercher par nom, e-mail, téléphone ou carte...",
|
||||
"all_status": "Tous les statuts",
|
||||
"table_member": "Membre",
|
||||
"table_card_number": "Numéro de carte",
|
||||
"table_points_balance": "Solde de points",
|
||||
"table_last_activity": "Dernière activité",
|
||||
"table_status": "Statut",
|
||||
"table_actions": "Actions",
|
||||
"no_members": "Aucun membre trouvé",
|
||||
"adjust_search": "Essayez d'ajuster votre recherche",
|
||||
"enroll_first": "Inscrivez votre premier client pour commencer"
|
||||
},
|
||||
"card_detail": {
|
||||
"title": "Détails du membre",
|
||||
"loading": "Chargement des détails du membre...",
|
||||
"error_loading": "Erreur de chargement du membre",
|
||||
"points_balance": "Solde de points",
|
||||
"total_earned": "Total gagné",
|
||||
"total_redeemed": "Total échangé",
|
||||
"member_since": "Membre depuis",
|
||||
"customer_information": "Informations client",
|
||||
"name": "Nom",
|
||||
"email": "E-mail",
|
||||
"phone": "Téléphone",
|
||||
"birthday": "Date de naissance",
|
||||
"card_details": "Détails de la carte",
|
||||
"card_number": "Numéro de carte",
|
||||
"status": "Statut",
|
||||
"last_activity": "Dernière activité",
|
||||
"enrolled_at": "Inscrit le",
|
||||
"transaction_history": "Historique des transactions",
|
||||
"table_date": "Date",
|
||||
"table_type": "Type",
|
||||
"table_points": "Points",
|
||||
"table_location": "Point de vente",
|
||||
"table_notes": "Notes",
|
||||
"no_transactions": "Aucune transaction"
|
||||
},
|
||||
"enroll": {
|
||||
"title": "Inscrire un client",
|
||||
"page_title": "Inscrire un nouveau client",
|
||||
"subtitle": "Ajouter un nouveau membre à votre programme de fidélité",
|
||||
"loading": "Chargement...",
|
||||
"error_loading": "Erreur de chargement du formulaire d'inscription",
|
||||
"customer_information": "Informations client",
|
||||
"first_name": "Prénom",
|
||||
"last_name": "Nom",
|
||||
"email": "E-mail",
|
||||
"phone": "Téléphone",
|
||||
"birthday": "Date de naissance",
|
||||
"birthday_help": "Pour les récompenses d'anniversaire (facultatif)",
|
||||
"communication_preferences": "Préférences de communication",
|
||||
"send_emails": "Envoyer des e-mails promotionnels",
|
||||
"send_sms": "Envoyer des SMS promotionnels",
|
||||
"welcome_bonus": "Bonus de bienvenue",
|
||||
"welcome_bonus_desc": "Le client recevra {points} points bonus !",
|
||||
"enroll_customer": "Inscrire le client",
|
||||
"enrolling": "Inscription...",
|
||||
"customer_enrolled": "Client inscrit !",
|
||||
"starting_balance": "Solde initial :",
|
||||
"x_points": "{count} points",
|
||||
"back_to_terminal": "Retour au terminal",
|
||||
"enroll_another": "Inscrire un autre",
|
||||
"enrollment_failed": "Inscription échouée : {message}"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Analytique fidélité",
|
||||
"subtitle": "Suivez les performances de votre programme de fidélité",
|
||||
"loading": "Chargement des analytiques...",
|
||||
"error_loading": "Erreur de chargement des analytiques",
|
||||
"quick_actions": "Actions rapides",
|
||||
"open_terminal": "Ouvrir le terminal",
|
||||
"view_members": "Voir les membres",
|
||||
"view_program": "Voir le programme"
|
||||
},
|
||||
"program": {
|
||||
"title": "Programme de fidélité",
|
||||
"subtitle": "Configuration de votre programme de fidélité",
|
||||
"edit_program": "Modifier le programme",
|
||||
"loading": "Chargement du programme...",
|
||||
"error_loading": "Erreur de chargement du programme",
|
||||
"no_program": "Aucun programme de fidélité",
|
||||
"no_program_desc": "Votre commerçant n'a pas encore configuré de programme de fidélité.",
|
||||
"create_program": "Créer un programme",
|
||||
"contact_admin": "Contactez votre administrateur pour configurer un programme de fidélité."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Paramètres de fidélité",
|
||||
"page_title": "Paramètres du programme de fidélité",
|
||||
"subtitle": "Configurez votre programme de fidélité",
|
||||
"back_to_program": "Retour au programme",
|
||||
"loading": "Chargement des paramètres...",
|
||||
"error_loading": "Erreur de chargement des paramètres",
|
||||
"access_restricted": "Accès restreint",
|
||||
"owner_only": "Seul le propriétaire du commerce peut gérer les paramètres du programme de fidélité.",
|
||||
"delete_title": "Supprimer le programme de fidélité",
|
||||
"delete_message": "Cela supprimera définitivement le programme de fidélité et toutes les données associées (cartes, transactions, récompenses). Cette action est irréversible.",
|
||||
"delete_confirm": "Supprimer le programme",
|
||||
"program_created": "Programme créé avec succès",
|
||||
"program_updated": "Programme mis à jour avec succès",
|
||||
"program_deleted": "Programme de fidélité supprimé",
|
||||
"save_failed": "Échec de l'enregistrement : {message}",
|
||||
"delete_failed": "Échec de la suppression : {message}"
|
||||
}
|
||||
},
|
||||
"storefront": {
|
||||
"dashboard": {
|
||||
"back_to_account": "Retour au compte",
|
||||
"my_loyalty": "Ma fidélité",
|
||||
"join_title": "Rejoignez notre programme de récompenses !",
|
||||
"join_subtitle": "Gagnez des points à chaque achat et échangez-les contre des récompenses.",
|
||||
"join_now": "Rejoindre maintenant",
|
||||
"points_balance": "Solde de points",
|
||||
"card_number": "Numéro de carte",
|
||||
"show_card": "Afficher la carte",
|
||||
"total_earned": "Total gagné",
|
||||
"total_redeemed": "Total échangé",
|
||||
"available_rewards": "Récompenses disponibles",
|
||||
"no_rewards_yet": "Aucune récompense disponible pour le moment",
|
||||
"ready_to_redeem": "Prêt à échanger",
|
||||
"x_more_to_go": "Encore {count}",
|
||||
"redeem_hint": "Montrez votre carte au personnel pour échanger des récompenses en magasin.",
|
||||
"recent_activity": "Activité récente",
|
||||
"view_all": "Tout voir",
|
||||
"no_transactions": "Aucune transaction. Faites un achat pour commencer à gagner des points !",
|
||||
"earn_redeem_locations": "Points de vente partenaires",
|
||||
"your_loyalty_card": "Votre carte de fidélité",
|
||||
"show_to_staff": "Montrez ceci au personnel lors d'un achat ou d'un échange de récompense."
|
||||
},
|
||||
"history": {
|
||||
"back_to_loyalty": "Retour à la fidélité",
|
||||
"title": "Historique des transactions",
|
||||
"subtitle": "Voir toutes vos transactions de points de fidélité",
|
||||
"current_balance": "Solde actuel",
|
||||
"total_earned": "Total gagné",
|
||||
"total_redeemed": "Total échangé",
|
||||
"no_transactions": "Aucune transaction",
|
||||
"balance": "Solde :",
|
||||
"previous": "Précédent",
|
||||
"next": "Suivant",
|
||||
"page_x_of_y": "Page {page} sur {pages}"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"program_activated": "Programme activé avec succès",
|
||||
"program_deactivated": "Programme désactivé avec succès",
|
||||
"activate_failed": "Échec de l'activation du programme : {message}",
|
||||
"deactivate_failed": "Échec de la désactivation du programme : {message}",
|
||||
"program_deleted": "Programme supprimé avec succès",
|
||||
"delete_failed": "Échec de la suppression du programme : {message}",
|
||||
"program_created": "Programme créé avec succès",
|
||||
"program_updated": "Programme mis à jour avec succès",
|
||||
"loyalty_program_created": "Programme de fidélité créé",
|
||||
"loyalty_program_deleted": "Programme de fidélité supprimé",
|
||||
"settings_saved": "Paramètres enregistrés avec succès",
|
||||
"save_failed": "Échec de l'enregistrement : {message}",
|
||||
"settings_save_failed": "Échec de l'enregistrement des paramètres : {message}",
|
||||
"create_failed": "Échec de la création du programme : {message}",
|
||||
"logo_required": "L'URL du logo est requise pour l'intégration wallet."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,5 +96,589 @@
|
||||
"title": "Treieprogramm erstellen",
|
||||
"description": "Erstellt Äert éischt Stempel- oder Punkteprogramm"
|
||||
}
|
||||
},
|
||||
"enrollment": {
|
||||
"title": "Gitt Member vun eisem Belounungsprogramm!",
|
||||
"subtitle": "Verdéngt {points} Punkt pro ausgegoenen EUR",
|
||||
"not_available_title": "Programm net verfügbar",
|
||||
"not_available_message": "Dëse Buttek huet nach kee Treieprogramm ageriicht.",
|
||||
"welcome_bonus": "Kritt {points} Bonuspunkten wann Dir Iech umellt!",
|
||||
"already_member": "Schonn Member? Är Punkten sinn mat Ärer E-Mail verlinkt.",
|
||||
"form": {
|
||||
"email": "E-Mail",
|
||||
"first_name": "Virnumm",
|
||||
"last_name": "Nonumm",
|
||||
"phone": "Telefon (fakultativ)",
|
||||
"birthday": "Gebuertsdag (fakultativ)",
|
||||
"birthday_hint": "Fir speziell Gebuertsdagsbelounungen",
|
||||
"terms_agree": "Ech akzeptéieren d'",
|
||||
"terms": "Allgemeng Geschäftsbedingungen",
|
||||
"marketing_consent": "Mir Neiegkeeten an Sonderoffere schécken",
|
||||
"joining": "Umeldung leeft...",
|
||||
"join_button": "Bäitrieden & {points} Punkten kréien"
|
||||
},
|
||||
"privacy_policy": "Dateschutzrichtlinn",
|
||||
"close": "Zoumaachen",
|
||||
"success": {
|
||||
"title": "Wëllkomm!",
|
||||
"message": "Dir sidd elo Member vun eisem Belounungsprogramm.",
|
||||
"card_number": "Är Kaartnummer",
|
||||
"wallet_prompt": "Späichert Är Kaart op Ärem Handy fir einfachen Zougang:",
|
||||
"next_steps_title": "Wéi geet et weider?",
|
||||
"step_earn": "Weist Är Kaartnummer beim Akaf fir Punkten ze sammelen",
|
||||
"step_balance": "Préift Äre Kontostand online oder an der App",
|
||||
"step_redeem": "Léist Punkten géint Belounungen an all eise Standuerter an",
|
||||
"view_dashboard": "Mäin Treie-Dashboard kucken",
|
||||
"continue_shopping": "Weider akafen"
|
||||
},
|
||||
"errors": {
|
||||
"load_failed": "Programminformatiounen konnten net gelueden ginn",
|
||||
"email_exists": "Dës E-Mail ass schonn an eisem Treieprogramm registréiert.",
|
||||
"failed": "Umeldung feelgeschloen. Probéiert w.e.g. nach eng Kéier."
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"active": "Aktiv",
|
||||
"inactive": "Inaktiv",
|
||||
"cancel": "Ofbriechen",
|
||||
"save": "Späicheren",
|
||||
"delete": "Läschen",
|
||||
"confirm": "Bestätegen",
|
||||
"refresh": "Aktualiséieren",
|
||||
"loading": "Lueden...",
|
||||
"saving": "Späicheren...",
|
||||
"view": "Kucken",
|
||||
"edit": "Beaarbechten",
|
||||
"yes": "Jo",
|
||||
"no": "Neen",
|
||||
"none": "Keen",
|
||||
"never": "Ni",
|
||||
"total": "TOTAL",
|
||||
"continue": "Weider",
|
||||
"back": "Zréck",
|
||||
"points": "Punkten",
|
||||
"minutes": "Minutten",
|
||||
"or": "oder",
|
||||
"at": "bei"
|
||||
},
|
||||
"transactions": {
|
||||
"card_created": "Ageschriwwen",
|
||||
"welcome_bonus": "Wëllkommensbonus",
|
||||
"stamp_earned": "Stempel kritt",
|
||||
"stamp_redeemed": "Stempel agelées",
|
||||
"stamp_voided": "Stempel stornéiert",
|
||||
"stamp_adjustment": "Stempel ugepasst",
|
||||
"points_earned": "Punkten verdéngt",
|
||||
"points_redeemed": "Punkten agelées",
|
||||
"points_voided": "Punkten stornéiert",
|
||||
"points_adjustment": "Punkten ugepasst",
|
||||
"points_expired": "Punkten ofgelaf",
|
||||
"card_deactivated": "Deaktivéiert",
|
||||
"reward_redeemed": "Belounung agelées"
|
||||
},
|
||||
"shared": {
|
||||
"analytics": {
|
||||
"total_programs": "Programmer insgesamt",
|
||||
"total_members": "Memberen insgesamt",
|
||||
"active_members": "Aktiv Memberen",
|
||||
"points_issued_30d": "Punkten vergi (30D)",
|
||||
"transactions_30d": "Transaktiounen (30D)",
|
||||
"x_active": "{count} aktiv",
|
||||
"points_overview": "Punkteniwwersiicht",
|
||||
"points_issued_vs_redeemed": "Punkten vergi vs agelées (30D)",
|
||||
"issued": "Vergi:",
|
||||
"redeemed": "Agelées:",
|
||||
"redemption_rate": "Aléisungsquot",
|
||||
"outstanding_balance": "Ausstehende Solde",
|
||||
"member_activity": "Memberaktivitéit",
|
||||
"active_members_30d": "Aktiv Memberen (30D)",
|
||||
"new_this_month": "Nei dëse Mount",
|
||||
"merchants_with_programs": "Händler mat Programmer",
|
||||
"avg_points_per_member": "Durchschn. Punkten pro Member",
|
||||
"all_time_statistics": "Gesamtstatistiken",
|
||||
"total_points_issued": "Total Punkten vergi",
|
||||
"total_points_redeemed": "Total Punkten agelées",
|
||||
"points_redeemed_30d": "Punkten agelées (30D)",
|
||||
"outstanding_liability": "Ausstehend Verbindlechkeet",
|
||||
"location_breakdown": "Opschlësselung no Standuert",
|
||||
"store": "Geschäft",
|
||||
"enrolled": "Ageschriwwen",
|
||||
"points_earned": "Punkten verdéngt",
|
||||
"points_redeemed": "Punkten agelées"
|
||||
},
|
||||
"program_view": {
|
||||
"program_configuration": "Programmkonfiguratioun",
|
||||
"program_name": "Programmnumm",
|
||||
"card_name": "Kaartnumm",
|
||||
"stamps_configuration": "Stempelkonfiguratioun",
|
||||
"stamps_target": "Stempelzil",
|
||||
"reward_description": "Belounungsbeschreiwung",
|
||||
"reward_value": "Belounungswäert",
|
||||
"points_configuration": "Punktekonfiguratioun",
|
||||
"points_per_eur": "Punkten pro EUR",
|
||||
"welcome_bonus": "Wëllkommensbonus",
|
||||
"x_points": "{count} Punkten",
|
||||
"minimum_redemption": "Mindest-Aléisung",
|
||||
"minimum_purchase": "Mindest-Akaf",
|
||||
"points_expiration": "Punktenoflaaf",
|
||||
"x_days_inactivity": "{days} Deeg Inaktivitéit",
|
||||
"redemption_rewards": "Aléisungsbelounungen",
|
||||
"reward": "Belounung",
|
||||
"points_required": "Néideg Punkten",
|
||||
"description": "Beschreiwung",
|
||||
"anti_fraud": "Betrugsschutz",
|
||||
"cooldown": "Waardezäit",
|
||||
"x_minutes": "{count} Minutten",
|
||||
"max_daily_stamps": "Max. Stempelen pro Dag",
|
||||
"staff_pin_required": "Personal-PIN erfuerderlech",
|
||||
"branding": "Branding",
|
||||
"primary_color": "Haaptfaarf",
|
||||
"secondary_color": "Secondärfaarf",
|
||||
"logo_url": "Logo-URL",
|
||||
"hero_image_url": "Hannergrondbild-URL",
|
||||
"terms_privacy": "AGB & Dateschutz",
|
||||
"terms_conditions": "Allgemeng Geschäftsbedingungen",
|
||||
"privacy_policy_url": "Dateschutzrichtlinn-URL"
|
||||
},
|
||||
"program_form": {
|
||||
"program_type": "Programmtyp",
|
||||
"points_type_desc": "Punkten pro ausgegoenen EUR verdéngen",
|
||||
"stamps_type_desc": "N Stempelen sammelen, Belounung kréien",
|
||||
"hybrid_type_desc": "Stempelen a Punkten kombinéiert",
|
||||
"stamps_configuration": "Stempelkonfiguratioun",
|
||||
"stamps_target": "Stempelzil",
|
||||
"stamps_target_help": "Unzuel vun de Stempelen fir d'Belounung",
|
||||
"reward_description": "Belounungsbeschreiwung",
|
||||
"reward_value_cents": "Belounungswäert (Cent)",
|
||||
"points_configuration": "Punktekonfiguratioun",
|
||||
"points_per_eur": "Punkten pro ausgegoenen EUR",
|
||||
"eur_equals_points": "1 EUR = {points} Punkt(en)",
|
||||
"welcome_bonus_points": "Wëllkommensbonuspunkten",
|
||||
"welcome_bonus_help": "Bonuspunkten bei der Umeldung",
|
||||
"minimum_redemption_points": "Mindest-Aléisungspunkten",
|
||||
"minimum_purchase_cents": "Mindest-Akaf (Cent)",
|
||||
"minimum_purchase_help": "Mindestakafsbetrag fir Punkten ze sammelen (0 = kee Minimum)",
|
||||
"points_expiration_days": "Punktenoflaaf (Deeg)",
|
||||
"points_expiration_help": "Deeg vun Inaktivitéit bis zum Punktenoflaaf (0 = ni)",
|
||||
"redemption_rewards": "Aléisungsbelounungen",
|
||||
"add_reward": "Belounung derbäisetzen",
|
||||
"no_rewards_configured": "Keng Belounungen konfiguréiert. Setzt eng Belounung derbäi fir datt Clienten Punkten aléise kënnen.",
|
||||
"reward_name": "Belounungsnumm",
|
||||
"points_required": "Néideg Punkten",
|
||||
"description": "Beschreiwung",
|
||||
"anti_fraud_settings": "Betrugsschutz-Astellungen",
|
||||
"cooldown_minutes": "Waardezäit (Minutten)",
|
||||
"cooldown_help": "Zäit tëschent Stempelen vun der selwechter Kaart",
|
||||
"max_daily_stamps": "Max. Stempelen pro Dag",
|
||||
"max_daily_stamps_help": "Maximal Stempelen pro Kaart pro Dag",
|
||||
"require_staff_pin": "Personal-PIN verlaangen",
|
||||
"branding": "Branding",
|
||||
"card_name": "Kaartnumm",
|
||||
"primary_color": "Haaptfaarf",
|
||||
"secondary_color": "Secondärfaarf",
|
||||
"logo_url": "Logo-URL",
|
||||
"logo_url_help": "Erfuerderlech fir Google Wallet-Integratioun. Muss eng ëffentlech zougänglech Bild-URL sinn (PNG oder JPG).",
|
||||
"hero_image_url": "Hannergrondbild-URL",
|
||||
"terms_privacy": "AGB & Dateschutz",
|
||||
"terms_conditions": "Allgemeng Geschäftsbedingungen",
|
||||
"privacy_policy_url": "Dateschutzrichtlinn-URL",
|
||||
"program_status": "Programmstatus",
|
||||
"program_active": "Programm aktiv",
|
||||
"program_active_help": "Deaktivéiert kënne Clienten net sammelen oder aléisen",
|
||||
"delete_program": "Programm läschen",
|
||||
"create_program": "Programm erstellen",
|
||||
"save_changes": "Ännerunge späicheren"
|
||||
}
|
||||
},
|
||||
"admin": {
|
||||
"programs": {
|
||||
"title": "Treieprogrammer",
|
||||
"create_program": "Programm erstellen",
|
||||
"loading": "Treieprogrammer gi gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun den Treieprogrammer",
|
||||
"total_programs": "Programmer insgesamt",
|
||||
"active": "Aktiv",
|
||||
"total_members": "Memberen insgesamt",
|
||||
"transactions_30d": "Transaktiounen (30D)",
|
||||
"search_placeholder": "No Händlernumm sichen...",
|
||||
"all_status": "All Status",
|
||||
"table_merchant": "Händler",
|
||||
"table_program_type": "Programmtyp",
|
||||
"table_members": "Memberen",
|
||||
"table_points_issued": "Punkten vergi",
|
||||
"table_status": "Status",
|
||||
"table_created": "Erstellt",
|
||||
"table_actions": "Aktiounen",
|
||||
"no_programs": "Keng Treieprogrammer fonnt",
|
||||
"adjust_filters": "Probéiert Är Sich oder Filter unzepassen",
|
||||
"no_merchants_yet": "Nach kee Händler huet en Treieprogramm erstellt",
|
||||
"x_active": "({count} aktiv)",
|
||||
"x_redeemed": "{count} agelées",
|
||||
"pt_per_eur": "Pkt/EUR",
|
||||
"delete_title": "Treieprogramm läschen",
|
||||
"delete_message": "Treieprogramm fir \"{name}\" läschen? All verbonnen Daten (Kaarten, Transaktiounen, Belounungen) ginn dauerhaft geläscht. Dëst kann net réckgängeg gemaach ginn.",
|
||||
"delete_confirm": "Programm läschen",
|
||||
"create_title": "Treieprogramm erstellen",
|
||||
"create_description": "Wielt en Händler fir en Treieprogramm ze erstellen.",
|
||||
"search_merchant": "Händler sichen",
|
||||
"type_merchant_name": "Händlernumm aginn...",
|
||||
"no_merchants_found": "Keen Händler fonnt",
|
||||
"existing_program_warning": "Dësen Händler huet schonn en Treieprogramm.",
|
||||
"view_edit_existing": "Bestehend Programm kucken / beaarbechten"
|
||||
},
|
||||
"merchant_detail": {
|
||||
"title": "Händler-Treiedetailer",
|
||||
"loading": "Treiedetailer gi gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun der Händlertreie",
|
||||
"program_active": "Treieprogramm aktiv",
|
||||
"no_program_subtitle": "Keen Treieprogramm",
|
||||
"quick_actions": "Schnellaktiounen",
|
||||
"edit_program": "Programm beaarbechten",
|
||||
"admin_policy": "Admin-Richtlinn",
|
||||
"view_merchant": "Händler kucken",
|
||||
"total_members": "Memberen insgesamt",
|
||||
"active_30d": "Aktiv (30D)",
|
||||
"points_issued_30d": "Punkten vergi (30D)",
|
||||
"points_redeemed_30d": "Punkten agelées (30D)",
|
||||
"no_program": "Keen Treieprogramm",
|
||||
"no_program_desc": "Dësen Händler huet nach keen Treieprogramm ageriicht.",
|
||||
"create_program": "Programm erstellen",
|
||||
"delete_title": "Treieprogramm läschen",
|
||||
"delete_message": "D'Treieprogramm an all verbonnen Daten ginn dauerhaft geläscht. Dëst kann net réckgängeg gemaach ginn.",
|
||||
"delete_confirm": "Programm läschen",
|
||||
"location_breakdown": "Opschlësselung no Standuert",
|
||||
"table_location": "Standuert",
|
||||
"table_enrolled": "Ageschriwwen",
|
||||
"table_points_earned": "Punkten verdéngt",
|
||||
"table_points_redeemed": "Punkten agelées",
|
||||
"table_transactions_30d": "Transaktiounen (30D)",
|
||||
"admin_policy_settings": "Admin-Richtlinn-Astellungen",
|
||||
"staff_pin_policy": "Personal-PIN-Richtlinn",
|
||||
"self_enrollment": "Selbstumeldung",
|
||||
"cross_location_redemption": "Standuertiwergreifend Aléisung",
|
||||
"allowed": "Erlaabt",
|
||||
"disabled": "Deaktivéiert",
|
||||
"modify_policy": "Admin-Richtlinn änneren"
|
||||
},
|
||||
"merchant_settings": {
|
||||
"title": "Händler-Treieastelllungen",
|
||||
"loading": "Astellunge gi gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun den Astellungen",
|
||||
"admin_controlled": "Admin-kontrolléiert Astellungen fir d'Treieprogramm vun dësem Händler",
|
||||
"staff_pin_policy": "Personal-PIN-Richtlinn",
|
||||
"staff_pin_description": "Bestëmmt ob Mataarbechter e PIN aginn mussen fir Treietransaktiounen ze veraarbechten.",
|
||||
"required": "Erfuerderlech",
|
||||
"required_desc": "Personal muss säin PIN bei all Transaktioun aginn. Recommandéiert fir d'Sécherheet.",
|
||||
"optional": "Fakultativ",
|
||||
"optional_desc": "Geschäfter kënne wielen ob PINe verlaangt ginn.",
|
||||
"pin_disabled": "Deaktivéiert",
|
||||
"pin_disabled_desc": "Personal-PINe ginn net benotzt. All Mataarbechter kann Transaktiounen veraarbechten.",
|
||||
"pin_lockout_settings": "PIN-Sparrastellungen",
|
||||
"max_failed_attempts": "Max. Fehlversich",
|
||||
"max_failed_attempts_help": "Unzuel vu falschen Versich virun der Spär (3-10)",
|
||||
"lockout_duration": "Spärauer (Minutten)",
|
||||
"lockout_duration_help": "Wéi laang d'Spär no Fehlversich dauert (5-120 Minutten)",
|
||||
"enrollment_settings": "Umeldungsastellungen",
|
||||
"allow_self_enrollment": "Selbstumeldung erlaben",
|
||||
"self_enrollment_desc": "Clienten kënne sech per QR-Code ouni Personal umellen",
|
||||
"transaction_settings": "Transaktiouns-Astellungen",
|
||||
"allow_cross_location": "Standuertiwergreifend Aléisung erlaben",
|
||||
"cross_location_desc": "Clienten kënne Punkten an all Standuerter vum Händler aléisen",
|
||||
"allow_void": "Stornéierungen erlaben",
|
||||
"void_desc": "Personal kann Punkten/Stempelen bei Réckgaben stornéieren",
|
||||
"save_settings": "Astellunge späicheren"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Treie-Analytik",
|
||||
"subtitle": "Plattformwäit Treieprogramm-Statistiken",
|
||||
"loading": "Analytik gëtt gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun der Analytik",
|
||||
"filter_by_merchant": "No Händler filtréieren",
|
||||
"search_merchants_placeholder": "Händler no Numm sichen...",
|
||||
"showing_stats_for": "Statistike fir:",
|
||||
"wallet_status": "Wallet-Integratiounsstatus",
|
||||
"google_wallet": "Google Wallet",
|
||||
"apple_wallet": "Apple Wallet",
|
||||
"connected": "Verbonnen",
|
||||
"error": "Feeler",
|
||||
"not_configured": "Net konfiguréiert",
|
||||
"issuer_id": "Aussteller-ID",
|
||||
"project": "Projet",
|
||||
"wallet_objects": "Wallet-Objeten",
|
||||
"loyalty_classes": "Treieklassen",
|
||||
"pass_type_id": "Pass-Typ-ID",
|
||||
"team_id": "Team-ID",
|
||||
"active_passes": "Aktiv Päss",
|
||||
"quick_actions": "Schnellaktiounen",
|
||||
"view_all_programs": "All Programmer kucken",
|
||||
"manage_merchants": "Händler verwalten"
|
||||
},
|
||||
"program_edit": {
|
||||
"title": "Programmkonfiguratioun",
|
||||
"loading": "Konfiguratioun gëtt gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun der Programmkonfiguratioun",
|
||||
"create_subtitle": "Treieprogramm fir dësen Händler erstellen",
|
||||
"edit_subtitle": "Programmkonfiguratioun beaarbechten",
|
||||
"delete_title": "Treieprogramm läschen",
|
||||
"delete_message": "D'Treieprogramm an all verbonnen Daten (Kaarten, Transaktiounen, Belounungen) ginn dauerhaft geläscht. Dëst kann net réckgängeg gemaach ginn.",
|
||||
"delete_confirm": "Programm läschen"
|
||||
}
|
||||
},
|
||||
"merchant": {
|
||||
"program": {
|
||||
"title": "Treieprogramm",
|
||||
"subtitle": "Är Treieprogramm-Konfiguratioun.",
|
||||
"edit_program": "Programm beaarbechten",
|
||||
"no_program": "Keen Treieprogramm",
|
||||
"no_program_desc": "Ärt Treieprogramm gouf nach net ageriicht. Erstellt eent fir Är Clienten ze belounegen.",
|
||||
"create_program": "Programm erstellen"
|
||||
},
|
||||
"program_edit": {
|
||||
"title": "Treie-Astellungen",
|
||||
"page_title": "Treieprogramm-Astellungen",
|
||||
"subtitle": "Konfiguréiert Ärt Treieprogramm",
|
||||
"loading": "Astellunge gi gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun den Astellungen",
|
||||
"delete_title": "Treieprogramm läschen",
|
||||
"delete_message": "Ärt Treieprogramm an all verbonnen Daten (Kaarten, Transaktiounen, Belounungen) ginn dauerhaft geläscht. Dëst kann net réckgängeg gemaach ginn.",
|
||||
"delete_confirm": "Programm läschen"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Treie-Analytik",
|
||||
"subtitle": "Treieprogramm-Statistiken fir all Är Geschäfter",
|
||||
"loading": "Analytik gëtt gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun der Analytik",
|
||||
"no_program": "Keen Treieprogramm",
|
||||
"no_program_desc": "Riicht en Treieprogramm an fir hei Analytik ze gesinn.",
|
||||
"create_program": "Programm erstellen",
|
||||
"quick_actions": "Schnellaktiounen",
|
||||
"view_program": "Programm kucken",
|
||||
"edit_program": "Programm beaarbechten"
|
||||
}
|
||||
},
|
||||
"store": {
|
||||
"terminal": {
|
||||
"title": "Treie-Terminal",
|
||||
"subtitle": "Treietransaktiounen veraarbechten",
|
||||
"members": "Memberen",
|
||||
"analytics": "Analytik",
|
||||
"loading": "Treie-Terminal gëtt gelueden...",
|
||||
"error_loading": "Feeler beim Luede vum Terminal",
|
||||
"not_setup": "Treieprogramm net ageriicht",
|
||||
"not_setup_desc": "Ären Händler huet nach keen Treieprogramm konfiguréiert.",
|
||||
"setup_program": "Treieprogramm ariichten",
|
||||
"contact_admin": "Kontaktéiert Ären Administrateur fir d'Ariichtung ofzeschléissen.",
|
||||
"find_customer": "Client fannen",
|
||||
"search_placeholder": "E-Mail, Telefon oder Kaartnummer...",
|
||||
"looking_up": "Sich leeft...",
|
||||
"look_up_customer": "Client sichen",
|
||||
"enroll_new_customer": "Neie Client umellen",
|
||||
"customer_found": "Client fonnt",
|
||||
"points_balance": "Punktestand",
|
||||
"stamps": "Stempelen",
|
||||
"x_more_for_reward": "Nach {count} fir d'Belounung",
|
||||
"ready_to_redeem": "Prett fir anzeléisen!",
|
||||
"add_stamp": "Stempel derbäisetzen",
|
||||
"current": "Aktuell:",
|
||||
"cooldown_active": "Waardezäit aktiv",
|
||||
"redeem_stamps": "Stempelen aléisen",
|
||||
"not_enough_stamps": "Nach net genuch Stempelen",
|
||||
"earn_points": "Punkten sammelen",
|
||||
"purchase_amount": "Akafsbetrag",
|
||||
"points_to_award": "Punkten ze verginn:",
|
||||
"award_points": "Punkten verginn",
|
||||
"redeem_reward": "Belounung aléisen",
|
||||
"select_reward": "Belounung wielen",
|
||||
"select_reward_placeholder": "Belounung wielen...",
|
||||
"points_after": "Punkten duerno:",
|
||||
"search_to_process": "Sicht e Client fir eng Transaktioun ze veraarbechten",
|
||||
"recent_transactions": "Rezent Transaktiounen un dësem Standuert",
|
||||
"table_time": "Zäit",
|
||||
"table_customer": "Client",
|
||||
"table_type": "Typ",
|
||||
"table_points": "Punkten",
|
||||
"table_notes": "Notizen",
|
||||
"no_recent_transactions": "Keng rezent Transaktiounen",
|
||||
"enter_staff_pin": "Personal-PIN aginn",
|
||||
"pin_authorize": "Gitt Äre Personal-PIN an fir dës Transaktioun ze autoriséieren.",
|
||||
"clear": "Läschen",
|
||||
"processing": "Veraarbechtung...",
|
||||
"customer_not_found": "Client net fonnt. Dir kënnt hien als neit Member umellen.",
|
||||
"error_lookup": "Feeler bei der Clientesich: {message}",
|
||||
"transaction_failed": "Transaktioun feelgeschloen: {message}",
|
||||
"stamp_added": "Stempel derbäigesat!",
|
||||
"stamps_redeemed": "Stempelen agelées! Belounung kritt.",
|
||||
"x_points_awarded": "{points} Punkten vergi!",
|
||||
"reward_redeemed": "Belounung agelées: {name}"
|
||||
},
|
||||
"cards": {
|
||||
"title": "Treie-Memberen",
|
||||
"subtitle": "Memberen vun Ärem Treieprogramm kucken a verwalten",
|
||||
"enroll_new": "Umellen",
|
||||
"loading": "Membere gi gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun de Memberen",
|
||||
"total_members": "Memberen insgesamt",
|
||||
"active_30d": "Aktiv (30D)",
|
||||
"new_this_month": "Nei dëse Mount",
|
||||
"total_points_balance": "Gesamtpunktestand",
|
||||
"search_placeholder": "No Numm, E-Mail, Telefon oder Kaart sichen...",
|
||||
"all_status": "All Status",
|
||||
"table_member": "Member",
|
||||
"table_card_number": "Kaartnummer",
|
||||
"table_points_balance": "Punktestand",
|
||||
"table_last_activity": "Lescht Aktivitéit",
|
||||
"table_status": "Status",
|
||||
"table_actions": "Aktiounen",
|
||||
"no_members": "Keng Membere fonnt",
|
||||
"adjust_search": "Probéiert Är Sich unzepassen",
|
||||
"enroll_first": "Mellt Ären éischte Client un"
|
||||
},
|
||||
"card_detail": {
|
||||
"title": "Memberdetailer",
|
||||
"loading": "Memberdetailer gi gelueden...",
|
||||
"error_loading": "Feeler beim Luede vum Member",
|
||||
"points_balance": "Punktestand",
|
||||
"total_earned": "Insgesamt verdéngt",
|
||||
"total_redeemed": "Insgesamt agelées",
|
||||
"member_since": "Member zënter",
|
||||
"customer_information": "Clienteninformatioun",
|
||||
"name": "Numm",
|
||||
"email": "E-Mail",
|
||||
"phone": "Telefon",
|
||||
"birthday": "Gebuertsdag",
|
||||
"card_details": "Kaartdetailer",
|
||||
"card_number": "Kaartnummer",
|
||||
"status": "Status",
|
||||
"last_activity": "Lescht Aktivitéit",
|
||||
"enrolled_at": "Ugemellt um",
|
||||
"transaction_history": "Transaktiounsverlaf",
|
||||
"table_date": "Datum",
|
||||
"table_type": "Typ",
|
||||
"table_points": "Punkten",
|
||||
"table_location": "Standuert",
|
||||
"table_notes": "Notizen",
|
||||
"no_transactions": "Nach keng Transaktiounen"
|
||||
},
|
||||
"enroll": {
|
||||
"title": "Client umellen",
|
||||
"page_title": "Neie Client umellen",
|
||||
"subtitle": "Neit Member bei Ärem Treieprogramm derbäisetzen",
|
||||
"loading": "Lueden...",
|
||||
"error_loading": "Feeler beim Luede vum Umeldungsformular",
|
||||
"customer_information": "Clienteninformatioun",
|
||||
"first_name": "Virnumm",
|
||||
"last_name": "Nonumm",
|
||||
"email": "E-Mail",
|
||||
"phone": "Telefon",
|
||||
"birthday": "Gebuertsdag",
|
||||
"birthday_help": "Fir Gebuertsdagsbelounungen (fakultativ)",
|
||||
"communication_preferences": "Kommunikatiounsastellungen",
|
||||
"send_emails": "Promotiounsmaile schécken",
|
||||
"send_sms": "Promotiounssms schécken",
|
||||
"welcome_bonus": "Wëllkommensbonus",
|
||||
"welcome_bonus_desc": "Client kritt {points} Bonuspunkten!",
|
||||
"enroll_customer": "Client umellen",
|
||||
"enrolling": "Umeldung...",
|
||||
"customer_enrolled": "Client ugemellt!",
|
||||
"starting_balance": "Ufankssolde:",
|
||||
"x_points": "{count} Punkten",
|
||||
"back_to_terminal": "Zréck zum Terminal",
|
||||
"enroll_another": "Weider umellen",
|
||||
"enrollment_failed": "Umeldung feelgeschloen: {message}"
|
||||
},
|
||||
"analytics": {
|
||||
"title": "Treie-Analytik",
|
||||
"subtitle": "Verfollegt d'Performance vun Ärem Treieprogramm",
|
||||
"loading": "Analytik gëtt gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun der Analytik",
|
||||
"quick_actions": "Schnellaktiounen",
|
||||
"open_terminal": "Terminal opmaachen",
|
||||
"view_members": "Membere kucken",
|
||||
"view_program": "Programm kucken"
|
||||
},
|
||||
"program": {
|
||||
"title": "Treieprogramm",
|
||||
"subtitle": "Är Treieprogramm-Konfiguratioun",
|
||||
"edit_program": "Programm beaarbechten",
|
||||
"loading": "Programm gëtt gelueden...",
|
||||
"error_loading": "Feeler beim Luede vum Programm",
|
||||
"no_program": "Keen Treieprogramm",
|
||||
"no_program_desc": "Ären Händler huet nach keen Treieprogramm konfiguréiert.",
|
||||
"create_program": "Programm erstellen",
|
||||
"contact_admin": "Kontaktéiert Ären Administrateur fir en Treieprogramm anzerichten."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Treie-Astellungen",
|
||||
"page_title": "Treieprogramm-Astellungen",
|
||||
"subtitle": "Konfiguréiert Ärt Treieprogramm",
|
||||
"back_to_program": "Zréck zum Programm",
|
||||
"loading": "Astellunge gi gelueden...",
|
||||
"error_loading": "Feeler beim Luede vun den Astellungen",
|
||||
"access_restricted": "Zougang ageschränkt",
|
||||
"owner_only": "Nëmmen den Geschäftseigentümer kann d'Treieprogramm-Astellungen verwalten.",
|
||||
"delete_title": "Treieprogramm läschen",
|
||||
"delete_message": "D'Treieprogramm an all verbonnen Daten (Kaarten, Transaktiounen, Belounungen) ginn dauerhaft geläscht. Dëst kann net réckgängeg gemaach ginn.",
|
||||
"delete_confirm": "Programm läschen",
|
||||
"program_created": "Programm erfollegräich erstellt",
|
||||
"program_updated": "Programm erfollegräich aktualiséiert",
|
||||
"program_deleted": "Treieprogramm geläscht",
|
||||
"save_failed": "Späichere feelgeschloen: {message}",
|
||||
"delete_failed": "Läsche feelgeschloen: {message}"
|
||||
}
|
||||
},
|
||||
"storefront": {
|
||||
"dashboard": {
|
||||
"back_to_account": "Zréck zum Kont",
|
||||
"my_loyalty": "Meng Treie",
|
||||
"join_title": "Gitt Member vun eisem Belounungsprogramm!",
|
||||
"join_subtitle": "Sammelt Punkten bei all Akaf an léist se géint Belounungen an.",
|
||||
"join_now": "Elo bäitrieden",
|
||||
"points_balance": "Punktestand",
|
||||
"card_number": "Kaartnummer",
|
||||
"show_card": "Kaart weisen",
|
||||
"total_earned": "Insgesamt verdéngt",
|
||||
"total_redeemed": "Insgesamt agelées",
|
||||
"available_rewards": "Verfügbar Belounungen",
|
||||
"no_rewards_yet": "Nach keng Belounungen verfügbar",
|
||||
"ready_to_redeem": "Prett fir anzeléisen",
|
||||
"x_more_to_go": "Nach {count}",
|
||||
"redeem_hint": "Weist Är Kaart dem Personal fir Belounungen am Geschäft anzeléisen.",
|
||||
"recent_activity": "Rezent Aktivitéit",
|
||||
"view_all": "Alles kucken",
|
||||
"no_transactions": "Nach keng Transaktiounen. Maacht en Akaf fir Punkten ze sammelen!",
|
||||
"earn_redeem_locations": "Sammel- & Aléisungs-Standuerter",
|
||||
"your_loyalty_card": "Är Treiekaart",
|
||||
"show_to_staff": "Weist dëst dem Personal beim Akaf oder beim Aléise vu Belounungen."
|
||||
},
|
||||
"history": {
|
||||
"back_to_loyalty": "Zréck zur Treie",
|
||||
"title": "Transaktiounsverlaf",
|
||||
"subtitle": "All Är Treiepunkttransaktiounen kucken",
|
||||
"current_balance": "Aktuell Solde",
|
||||
"total_earned": "Insgesamt verdéngt",
|
||||
"total_redeemed": "Insgesamt agelées",
|
||||
"no_transactions": "Nach keng Transaktiounen",
|
||||
"balance": "Solde:",
|
||||
"previous": "Zréck",
|
||||
"next": "Weider",
|
||||
"page_x_of_y": "Säit {page} vun {pages}"
|
||||
}
|
||||
},
|
||||
"toasts": {
|
||||
"program_activated": "Programm erfollegräich aktivéiert",
|
||||
"program_deactivated": "Programm erfollegräich deaktivéiert",
|
||||
"activate_failed": "Programm konnt net aktivéiert ginn: {message}",
|
||||
"deactivate_failed": "Programm konnt net deaktivéiert ginn: {message}",
|
||||
"program_deleted": "Programm erfollegräich geläscht",
|
||||
"delete_failed": "Programm konnt net geläscht ginn: {message}",
|
||||
"program_created": "Programm erfollegräich erstellt",
|
||||
"program_updated": "Programm erfollegräich aktualiséiert",
|
||||
"loyalty_program_created": "Treieprogramm erstellt",
|
||||
"loyalty_program_deleted": "Treieprogramm geläscht",
|
||||
"settings_saved": "Astellungen erfollegräich gespäichert",
|
||||
"save_failed": "Späichere feelgeschloen: {message}",
|
||||
"settings_save_failed": "Astellunge konnten net gespäichert ginn: {message}",
|
||||
"create_failed": "Programm konnt net erstellt ginn: {message}",
|
||||
"logo_required": "Logo-URL ass erfuerderlech fir d'Wallet-Integratioun."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,12 +190,12 @@ function adminLoyaltyMerchantDetail() {
|
||||
};
|
||||
const response = await apiClient.post(`/admin/loyalty/merchants/${this.merchantId}/program`, data);
|
||||
this.program = response;
|
||||
Utils.showToast('Loyalty program created', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.program_created'), 'success');
|
||||
loyaltyMerchantDetailLog.info('Program created for merchant', this.merchantId);
|
||||
// Reload stats
|
||||
await this.loadStats();
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to create program: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.create_failed', {message: error.message}), 'error');
|
||||
loyaltyMerchantDetailLog.error('Failed to create program:', error);
|
||||
}
|
||||
},
|
||||
@@ -207,10 +207,10 @@ function adminLoyaltyMerchantDetail() {
|
||||
try {
|
||||
const response = await apiClient.post(`/admin/loyalty/programs/${this.program.id}/${action}`);
|
||||
this.program.is_active = response.is_active;
|
||||
Utils.showToast(`Program ${action}d successfully`, 'success');
|
||||
Utils.showToast(I18n.t(`loyalty.toasts.program_${action}d`), 'success');
|
||||
loyaltyMerchantDetailLog.info(`Program ${action}d`);
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to ${action} program: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t(`loyalty.toasts.${action}_failed`, {message: error.message}), 'error');
|
||||
loyaltyMerchantDetailLog.error(`Failed to ${action} program:`, error);
|
||||
}
|
||||
},
|
||||
@@ -227,12 +227,12 @@ function adminLoyaltyMerchantDetail() {
|
||||
await apiClient.delete(`/admin/loyalty/programs/${this.program.id}`);
|
||||
this.program = null;
|
||||
this.showDeleteModal = false;
|
||||
Utils.showToast('Loyalty program deleted', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.program_deleted'), 'success');
|
||||
loyaltyMerchantDetailLog.info('Program deleted');
|
||||
// Reload stats
|
||||
await this.loadStats();
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to delete program: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.delete_failed', {message: error.message}), 'error');
|
||||
loyaltyMerchantDetailLog.error('Failed to delete program:', error);
|
||||
this.showDeleteModal = false;
|
||||
}
|
||||
|
||||
@@ -150,14 +150,14 @@ function adminLoyaltyMerchantSettings() {
|
||||
|
||||
if (response) {
|
||||
loyaltyMerchantSettingsLog.info('Settings saved successfully');
|
||||
Utils.showToast('Settings saved successfully', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.settings_saved'), 'success');
|
||||
|
||||
// Navigate back to merchant detail
|
||||
window.location.href = this.backUrl;
|
||||
}
|
||||
} catch (error) {
|
||||
loyaltyMerchantSettingsLog.error('Failed to save settings:', error);
|
||||
Utils.showToast(`Failed to save settings: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.settings_save_failed', {message: error.message}), 'error');
|
||||
} finally {
|
||||
this.saving = false;
|
||||
}
|
||||
|
||||
@@ -109,19 +109,19 @@ function adminLoyaltyProgramEdit() {
|
||||
);
|
||||
this.programId = response.id;
|
||||
this.isNewProgram = false;
|
||||
Utils.showToast('Program created successfully', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.program_created'), 'success');
|
||||
} else {
|
||||
await apiClient.patch(
|
||||
`/admin/loyalty/programs/${this.programId}`,
|
||||
payload
|
||||
);
|
||||
Utils.showToast('Program updated successfully', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.program_updated'), 'success');
|
||||
}
|
||||
|
||||
loyaltyProgramEditLog.info('Program saved');
|
||||
window.location.href = this.backUrl;
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to save: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.save_failed', {message: error.message}), 'error');
|
||||
loyaltyProgramEditLog.error('Save failed:', error);
|
||||
} finally {
|
||||
this.saving = false;
|
||||
@@ -134,11 +134,11 @@ function adminLoyaltyProgramEdit() {
|
||||
|
||||
try {
|
||||
await apiClient.delete(`/admin/loyalty/programs/${this.programId}`);
|
||||
Utils.showToast('Program deleted', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.program_deleted'), 'success');
|
||||
loyaltyProgramEditLog.info('Program deleted');
|
||||
window.location.href = this.backUrl;
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to delete: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.delete_failed', {message: error.message}), 'error');
|
||||
loyaltyProgramEditLog.error('Delete failed:', error);
|
||||
} finally {
|
||||
this.deleting = false;
|
||||
|
||||
@@ -249,10 +249,10 @@ function adminLoyaltyPrograms() {
|
||||
try {
|
||||
const response = await apiClient.post(`/admin/loyalty/programs/${program.id}/${action}`);
|
||||
program.is_active = response.is_active;
|
||||
Utils.showToast(`Program ${action}d successfully`, 'success');
|
||||
Utils.showToast(I18n.t(`loyalty.toasts.program_${action}d`), 'success');
|
||||
loyaltyProgramsLog.info(`Program ${program.id} ${action}d`);
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to ${action} program: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t(`loyalty.toasts.${action}_failed`, {message: error.message}), 'error');
|
||||
loyaltyProgramsLog.error(`Failed to ${action} program:`, error);
|
||||
}
|
||||
},
|
||||
@@ -267,13 +267,13 @@ function adminLoyaltyPrograms() {
|
||||
if (!this.deletingProgram) return;
|
||||
try {
|
||||
await apiClient.delete(`/admin/loyalty/programs/${this.deletingProgram.id}`);
|
||||
Utils.showToast('Program deleted successfully', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.program_deleted'), 'success');
|
||||
loyaltyProgramsLog.info('Program deleted:', this.deletingProgram.id);
|
||||
this.showDeleteModal = false;
|
||||
this.deletingProgram = null;
|
||||
await Promise.all([this.loadPrograms(), this.loadStats()]);
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to delete program: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.delete_failed', {message: error.message}), 'error');
|
||||
loyaltyProgramsLog.error('Failed to delete program:', error);
|
||||
this.showDeleteModal = false;
|
||||
this.deletingProgram = null;
|
||||
|
||||
@@ -61,10 +61,10 @@ function merchantLoyaltySettings() {
|
||||
await apiClient.patch('/merchants/loyalty/program', payload);
|
||||
}
|
||||
|
||||
Utils.showToast('Settings saved successfully', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.settings_saved'), 'success');
|
||||
loyaltySettingsLog.info('Settings saved');
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to save: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.save_failed', {message: error.message}), 'error');
|
||||
loyaltySettingsLog.error('Save failed:', error);
|
||||
} finally {
|
||||
this.saving = false;
|
||||
@@ -76,11 +76,11 @@ function merchantLoyaltySettings() {
|
||||
|
||||
try {
|
||||
await apiClient.delete('/merchants/loyalty/program');
|
||||
Utils.showToast('Loyalty program deleted', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.program_deleted'), 'success');
|
||||
loyaltySettingsLog.info('Program deleted');
|
||||
window.location.href = '/merchants/loyalty/program';
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to delete: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.toasts.delete_failed', {message: error.message}), 'error');
|
||||
loyaltySettingsLog.error('Delete failed:', error);
|
||||
} finally {
|
||||
this.deleting = false;
|
||||
|
||||
@@ -99,7 +99,7 @@ function createProgramFormMixin() {
|
||||
if (!payload.card_name) payload.card_name = null;
|
||||
if (!payload.card_secondary_color) payload.card_secondary_color = null;
|
||||
if (!payload.logo_url) {
|
||||
this.error = 'Logo URL is required for wallet integration.';
|
||||
this.error = I18n.t('loyalty.toasts.logo_required');
|
||||
return null;
|
||||
}
|
||||
if (!payload.hero_image_url) payload.hero_image_url = null;
|
||||
|
||||
@@ -80,7 +80,7 @@ function storeLoyaltyEnroll() {
|
||||
loyaltyEnrollLog.info('Customer enrolled successfully:', response.card_number);
|
||||
}
|
||||
} catch (error) {
|
||||
Utils.showToast(`Enrollment failed: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.store.enroll.enrollment_failed', {message: error.message}), 'error');
|
||||
loyaltyEnrollLog.error('Enrollment failed:', error);
|
||||
} finally {
|
||||
this.enrolling = false;
|
||||
|
||||
@@ -87,10 +87,10 @@ function loyaltySettings() {
|
||||
let response;
|
||||
if (this.isNewProgram) {
|
||||
response = await apiClient.post('/store/loyalty/program', payload);
|
||||
Utils.showToast('Program created successfully', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.store.settings.program_created'), 'success');
|
||||
} else {
|
||||
response = await apiClient.put('/store/loyalty/program', payload);
|
||||
Utils.showToast('Program updated successfully', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.store.settings.program_updated'), 'success');
|
||||
}
|
||||
|
||||
this.populateSettings(response);
|
||||
@@ -98,7 +98,7 @@ function loyaltySettings() {
|
||||
|
||||
loyaltySettingsLog.info('Program saved:', response.display_name);
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to save: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.store.settings.save_failed', {message: error.message}), 'error');
|
||||
loyaltySettingsLog.error('Save failed:', error);
|
||||
} finally {
|
||||
this.saving = false;
|
||||
@@ -110,13 +110,13 @@ function loyaltySettings() {
|
||||
|
||||
try {
|
||||
await apiClient.delete('/store/loyalty/program');
|
||||
Utils.showToast('Loyalty program deleted', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.store.settings.program_deleted'), 'success');
|
||||
loyaltySettingsLog.info('Program deleted');
|
||||
// Redirect to terminal page
|
||||
const storeCode = window.location.pathname.split('/')[2];
|
||||
window.location.href = `/store/${storeCode}/loyalty/program`;
|
||||
} catch (error) {
|
||||
Utils.showToast(`Failed to delete: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.store.settings.delete_failed', {message: error.message}), 'error');
|
||||
loyaltySettingsLog.error('Delete failed:', error);
|
||||
} finally {
|
||||
this.deleting = false;
|
||||
|
||||
@@ -136,9 +136,9 @@ function storeLoyaltyTerminal() {
|
||||
}
|
||||
} catch (error) {
|
||||
if (error.status === 404) {
|
||||
Utils.showToast('Customer not found. You can enroll them as a new member.', 'warning');
|
||||
Utils.showToast(I18n.t('loyalty.store.terminal.customer_not_found'), 'warning');
|
||||
} else {
|
||||
Utils.showToast(`Error looking up customer: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.store.terminal.error_lookup', {message: error.message}), 'error');
|
||||
}
|
||||
loyaltyTerminalLog.error('Lookup failed:', error);
|
||||
} finally {
|
||||
@@ -213,7 +213,7 @@ function storeLoyaltyTerminal() {
|
||||
await this.loadRecentTransactions();
|
||||
|
||||
} catch (error) {
|
||||
Utils.showToast(`Transaction failed: ${error.message}`, 'error');
|
||||
Utils.showToast(I18n.t('loyalty.store.terminal.transaction_failed', {message: error.message}), 'error');
|
||||
loyaltyTerminalLog.error('Transaction failed:', error);
|
||||
} finally {
|
||||
this.processing = false;
|
||||
@@ -229,7 +229,7 @@ function storeLoyaltyTerminal() {
|
||||
staff_pin: this.pinDigits
|
||||
});
|
||||
|
||||
Utils.showToast('Stamp added!', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.store.terminal.stamp_added'), 'success');
|
||||
},
|
||||
|
||||
// Redeem stamps
|
||||
@@ -241,7 +241,7 @@ function storeLoyaltyTerminal() {
|
||||
staff_pin: this.pinDigits
|
||||
});
|
||||
|
||||
Utils.showToast('Stamps redeemed! Reward earned.', 'success');
|
||||
Utils.showToast(I18n.t('loyalty.store.terminal.stamps_redeemed'), 'success');
|
||||
},
|
||||
|
||||
// Earn points
|
||||
@@ -255,7 +255,7 @@ function storeLoyaltyTerminal() {
|
||||
});
|
||||
|
||||
const pointsEarned = response.points_earned || Math.floor(this.earnAmount * (this.program?.points_per_euro || 1));
|
||||
Utils.showToast(`${pointsEarned} points awarded!`, 'success');
|
||||
Utils.showToast(I18n.t('loyalty.store.terminal.x_points_awarded', {points: pointsEarned}), 'success');
|
||||
|
||||
this.earnAmount = null;
|
||||
},
|
||||
@@ -273,7 +273,7 @@ function storeLoyaltyTerminal() {
|
||||
staff_pin: this.pinDigits
|
||||
});
|
||||
|
||||
Utils.showToast(`Reward redeemed: ${reward.name}`, 'success');
|
||||
Utils.showToast(I18n.t('loyalty.store.terminal.reward_redeemed', {name: reward.name}), 'success');
|
||||
|
||||
this.selectedReward = '';
|
||||
},
|
||||
@@ -292,21 +292,11 @@ function storeLoyaltyTerminal() {
|
||||
|
||||
// Format number
|
||||
getTransactionLabel(tx) {
|
||||
const labels = {
|
||||
'card_created': 'Enrolled',
|
||||
'welcome_bonus': 'Welcome Bonus',
|
||||
'stamp_earned': 'Stamp Earned',
|
||||
'stamp_redeemed': 'Stamp Redeemed',
|
||||
'stamp_voided': 'Stamp Voided',
|
||||
'stamp_adjustment': 'Stamp Adjusted',
|
||||
'points_earned': 'Points Earned',
|
||||
'points_redeemed': 'Points Redeemed',
|
||||
'points_voided': 'Points Voided',
|
||||
'points_adjustment': 'Points Adjusted',
|
||||
'points_expired': 'Points Expired',
|
||||
'card_deactivated': 'Deactivated',
|
||||
};
|
||||
return labels[tx.transaction_type] || tx.transaction_type?.replace(/_/g, ' ') || 'Unknown';
|
||||
const type = tx.transaction_type;
|
||||
if (type) {
|
||||
return I18n.t('loyalty.transactions.' + type, {defaultValue: type.replace(/_/g, ' ')});
|
||||
}
|
||||
return I18n.t('loyalty.common.unknown');
|
||||
},
|
||||
|
||||
getTransactionColor(tx) {
|
||||
|
||||
@@ -46,7 +46,7 @@ function customerLoyaltyEnroll() {
|
||||
this.program = null;
|
||||
} else {
|
||||
console.error('Failed to load program:', error);
|
||||
this.error = 'Failed to load program information';
|
||||
this.error = I18n.t('loyalty.enrollment.errors.load_failed');
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
@@ -89,9 +89,9 @@ function customerLoyaltyEnroll() {
|
||||
} catch (error) {
|
||||
console.error('Enrollment failed:', error);
|
||||
if (error.message?.includes('already')) {
|
||||
this.error = 'This email is already registered in our loyalty program.';
|
||||
this.error = I18n.t('loyalty.enrollment.errors.email_exists');
|
||||
} else {
|
||||
this.error = error.message || 'Enrollment failed. Please try again.';
|
||||
this.error = error.message || I18n.t('loyalty.enrollment.errors.failed');
|
||||
}
|
||||
} finally {
|
||||
this.enrolling = false;
|
||||
|
||||
@@ -84,16 +84,10 @@ function customerLoyaltyHistory() {
|
||||
|
||||
getTransactionLabel(tx) {
|
||||
const type = tx.transaction_type || '';
|
||||
const labels = {
|
||||
'points_earned': 'Points Earned',
|
||||
'points_redeemed': 'Reward Redeemed',
|
||||
'points_voided': 'Points Voided',
|
||||
'welcome_bonus': 'Welcome Bonus',
|
||||
'points_expired': 'Points Expired',
|
||||
'stamp_earned': 'Stamp Earned',
|
||||
'stamp_redeemed': 'Stamp Redeemed'
|
||||
};
|
||||
return labels[type] || type.replace(/_/g, ' ');
|
||||
if (type) {
|
||||
return I18n.t('loyalty.transactions.' + type, {defaultValue: type.replace(/_/g, ' ')});
|
||||
}
|
||||
return type;
|
||||
},
|
||||
|
||||
formatNumber(num) {
|
||||
|
||||
@@ -4,12 +4,14 @@
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/inputs.html' import search_autocomplete, selected_item_display %}
|
||||
|
||||
{% block title %}Loyalty Analytics{% endblock %}
|
||||
{% block title %}{{ _('loyalty.admin.analytics.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminLoyaltyAnalytics(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call page_header_flex(title='Loyalty Analytics', subtitle='Platform-wide loyalty program statistics') %}
|
||||
{% call page_header_flex(title=_('loyalty.admin.analytics.title'), subtitle=_('loyalty.admin.analytics.subtitle')) %}
|
||||
<div class="flex items-center gap-3">
|
||||
{{ refresh_button(loading_var='loading', onclick='loadStats()', variant='secondary') }}
|
||||
</div>
|
||||
@@ -17,7 +19,7 @@
|
||||
|
||||
<!-- Merchant Filter -->
|
||||
<div class="mb-6 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">Filter by Merchant</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-400 mb-1">{{ _('loyalty.admin.analytics.filter_by_merchant') }}</label>
|
||||
<div x-show="!selectedMerchant">
|
||||
{{ search_autocomplete(
|
||||
search_var='merchantSearch',
|
||||
@@ -28,20 +30,20 @@
|
||||
select_action='selectMerchant(item)',
|
||||
display_field='merchant_name',
|
||||
secondary_field='loyalty_type',
|
||||
placeholder='Search merchants by name...',
|
||||
placeholder=_('loyalty.admin.analytics.search_merchants_placeholder'),
|
||||
) }}
|
||||
</div>
|
||||
{{ selected_item_display(
|
||||
selected_var='selectedMerchant',
|
||||
display_field='merchant_name',
|
||||
clear_action='clearMerchantFilter()',
|
||||
label='Showing stats for:'
|
||||
label=_('loyalty.admin.analytics.showing_stats_for')
|
||||
) }}
|
||||
</div>
|
||||
|
||||
{{ loading_state('Loading analytics...') }}
|
||||
{{ loading_state(_('loyalty.admin.analytics.loading')) }}
|
||||
|
||||
{{ error_state('Error loading analytics') }}
|
||||
{{ error_state(_('loyalty.admin.analytics.error_loading')) }}
|
||||
|
||||
<!-- Analytics Dashboard -->
|
||||
<div x-show="!loading">
|
||||
@@ -54,42 +56,42 @@
|
||||
<div class="mb-6 px-4 py-5 bg-white rounded-lg shadow-md dark:bg-gray-800" x-show="walletStatus">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
<span x-html="$icon('device-phone-mobile', 'w-5 h-5 inline mr-1')"></span>
|
||||
Wallet Integration Status
|
||||
{{ _('loyalty.admin.analytics.wallet_status') }}
|
||||
</h3>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<!-- Google Wallet -->
|
||||
<div class="p-4 border rounded-lg dark:border-gray-700" x-show="walletStatus?.google_wallet">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300">Google Wallet</h4>
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.admin.analytics.google_wallet') }}</h4>
|
||||
<template x-if="walletStatus?.google_wallet?.credentials_valid">
|
||||
<span class="px-2 py-1 text-xs font-medium text-green-700 bg-green-100 rounded-full dark:text-green-300 dark:bg-green-900/30">Connected</span>
|
||||
<span class="px-2 py-1 text-xs font-medium text-green-700 bg-green-100 rounded-full dark:text-green-300 dark:bg-green-900/30">{{ _('loyalty.admin.analytics.connected') }}</span>
|
||||
</template>
|
||||
<template x-if="walletStatus?.google_wallet?.configured && !walletStatus?.google_wallet?.credentials_valid">
|
||||
<span class="px-2 py-1 text-xs font-medium text-red-700 bg-red-100 rounded-full dark:text-red-300 dark:bg-red-900/30">Error</span>
|
||||
<span class="px-2 py-1 text-xs font-medium text-red-700 bg-red-100 rounded-full dark:text-red-300 dark:bg-red-900/30">{{ _('loyalty.admin.analytics.error') }}</span>
|
||||
</template>
|
||||
<template x-if="!walletStatus?.google_wallet?.configured">
|
||||
<span class="px-2 py-1 text-xs font-medium text-gray-500 bg-gray-100 rounded-full dark:text-gray-400 dark:bg-gray-700">Not Configured</span>
|
||||
<span class="px-2 py-1 text-xs font-medium text-gray-500 bg-gray-100 rounded-full dark:text-gray-400 dark:bg-gray-700">{{ _('loyalty.admin.analytics.not_configured') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<template x-if="walletStatus?.google_wallet?.configured">
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">Issuer ID</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.analytics.issuer_id') }}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300" x-text="walletStatus.google_wallet.issuer_id"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">Project</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.analytics.project') }}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300" x-text="walletStatus.google_wallet.project_id || '-'"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">Wallet Objects</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.analytics.wallet_objects') }}</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="walletStatus.google_wallet.total_objects || 0"></span>
|
||||
</div>
|
||||
<!-- Class statuses -->
|
||||
<template x-if="walletStatus.google_wallet.classes?.length > 0">
|
||||
<div class="mt-2 pt-2 border-t dark:border-gray-700">
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Loyalty Classes</p>
|
||||
<p class="text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">{{ _('loyalty.admin.analytics.loyalty_classes') }}</p>
|
||||
<template x-for="cls in walletStatus.google_wallet.classes" :key="cls.class_id">
|
||||
<div class="flex justify-between text-xs py-1">
|
||||
<span class="text-gray-600 dark:text-gray-400" x-text="cls.program_name"></span>
|
||||
@@ -115,29 +117,29 @@
|
||||
<!-- Apple Wallet -->
|
||||
<div class="p-4 border rounded-lg dark:border-gray-700" x-show="walletStatus?.apple_wallet">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300">Apple Wallet</h4>
|
||||
<h4 class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.admin.analytics.apple_wallet') }}</h4>
|
||||
<template x-if="walletStatus?.apple_wallet?.credentials_valid">
|
||||
<span class="px-2 py-1 text-xs font-medium text-green-700 bg-green-100 rounded-full dark:text-green-300 dark:bg-green-900/30">Connected</span>
|
||||
<span class="px-2 py-1 text-xs font-medium text-green-700 bg-green-100 rounded-full dark:text-green-300 dark:bg-green-900/30">{{ _('loyalty.admin.analytics.connected') }}</span>
|
||||
</template>
|
||||
<template x-if="walletStatus?.apple_wallet?.configured && !walletStatus?.apple_wallet?.credentials_valid">
|
||||
<span class="px-2 py-1 text-xs font-medium text-red-700 bg-red-100 rounded-full dark:text-red-300 dark:bg-red-900/30">Error</span>
|
||||
<span class="px-2 py-1 text-xs font-medium text-red-700 bg-red-100 rounded-full dark:text-red-300 dark:bg-red-900/30">{{ _('loyalty.admin.analytics.error') }}</span>
|
||||
</template>
|
||||
<template x-if="!walletStatus?.apple_wallet?.configured">
|
||||
<span class="px-2 py-1 text-xs font-medium text-gray-500 bg-gray-100 rounded-full dark:text-gray-400 dark:bg-gray-700">Not Configured</span>
|
||||
<span class="px-2 py-1 text-xs font-medium text-gray-500 bg-gray-100 rounded-full dark:text-gray-400 dark:bg-gray-700">{{ _('loyalty.admin.analytics.not_configured') }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<template x-if="walletStatus?.apple_wallet?.configured">
|
||||
<div class="space-y-2 text-sm">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">Pass Type ID</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.analytics.pass_type_id') }}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300" x-text="walletStatus.apple_wallet.pass_type_id"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">Team ID</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.analytics.team_id') }}</span>
|
||||
<span class="text-gray-700 dark:text-gray-300" x-text="walletStatus.apple_wallet.team_id"></span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-500 dark:text-gray-400">Active Passes</span>
|
||||
<span class="text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.analytics.active_passes') }}</span>
|
||||
<span class="font-medium text-gray-700 dark:text-gray-300" x-text="walletStatus.apple_wallet.total_passes || 0"></span>
|
||||
</div>
|
||||
<template x-if="walletStatus.apple_wallet.errors?.length > 0">
|
||||
@@ -155,17 +157,17 @@
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<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">Quick Actions</h3>
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">{{ _('loyalty.admin.analytics.quick_actions') }}</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="/admin/loyalty/programs"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-purple-600 bg-purple-100 rounded-lg hover:bg-purple-200 dark:text-purple-300 dark:bg-purple-900/30 dark:hover:bg-purple-900/50">
|
||||
<span x-html="$icon('gift', 'w-4 h-4 mr-2')"></span>
|
||||
View All Programs
|
||||
{{ _('loyalty.admin.analytics.view_all_programs') }}
|
||||
</a>
|
||||
<a href="/admin/merchants"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-blue-600 bg-blue-100 rounded-lg hover:bg-blue-200 dark:text-blue-300 dark:bg-blue-900/30 dark:hover:bg-blue-900/50">
|
||||
<span x-html="$icon('building-office', 'w-4 h-4 mr-2')"></span>
|
||||
Manage Merchants
|
||||
{{ _('loyalty.admin.analytics.manage_merchants') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,44 +5,46 @@
|
||||
{% from 'shared/macros/tables.html' import table_wrapper, table_header %}
|
||||
{% from 'shared/macros/modals.html' import confirm_modal %}
|
||||
|
||||
{% block title %}Merchant Loyalty Details{% endblock %}
|
||||
{% block title %}{{ _('loyalty.admin.merchant_detail.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminLoyaltyMerchantDetail(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call detail_page_header("merchant?.name || 'Merchant Loyalty'", '/admin/loyalty/programs', subtitle_show='merchant') %}
|
||||
<span x-text="program ? 'Loyalty Program Active' : 'No Loyalty Program'"></span>
|
||||
<span x-text="program ? $t('loyalty.admin.merchant_detail.program_active') : $t('loyalty.admin.merchant_detail.no_program_subtitle')"></span>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading merchant loyalty details...') }}
|
||||
{{ loading_state(_('loyalty.admin.merchant_detail.loading')) }}
|
||||
|
||||
{{ error_state('Error loading merchant loyalty') }}
|
||||
{{ error_state(_('loyalty.admin.merchant_detail.error_loading')) }}
|
||||
|
||||
<!-- Merchant Details -->
|
||||
<div x-show="!loading && merchant">
|
||||
<!-- Quick Actions Card -->
|
||||
<div class="px-4 py-3 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">
|
||||
Quick Actions
|
||||
{{ _('loyalty.admin.merchant_detail.quick_actions') }}
|
||||
</h3>
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<a x-show="program"
|
||||
:href="`/admin/loyalty/merchants/${merchantId}/program`"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-white transition-colors duration-150 bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple">
|
||||
<span x-html="$icon('pencil', 'w-4 h-4 mr-2')"></span>
|
||||
Edit Program
|
||||
{{ _('loyalty.admin.merchant_detail.edit_program') }}
|
||||
</a>
|
||||
<a
|
||||
:href="`/admin/loyalty/merchants/${merchantId}/settings`"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-gray-100 border border-gray-300 rounded-lg hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600">
|
||||
<span x-html="$icon('shield-check', 'w-4 h-4 mr-2')"></span>
|
||||
Admin Policy
|
||||
{{ _('loyalty.admin.merchant_detail.admin_policy') }}
|
||||
</a>
|
||||
<a
|
||||
:href="`/admin/merchants/${merchant?.id}`"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium leading-5 text-gray-700 transition-colors duration-150 bg-gray-100 border border-gray-300 rounded-lg hover:bg-gray-200 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600">
|
||||
<span x-html="$icon('building-office', 'w-4 h-4 mr-2')"></span>
|
||||
View Merchant
|
||||
{{ _('loyalty.admin.merchant_detail.view_merchant') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,7 +58,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Total Members
|
||||
{{ _('loyalty.admin.merchant_detail.total_members') }}
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.total_cards)">
|
||||
0
|
||||
@@ -71,7 +73,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Active (30d)
|
||||
{{ _('loyalty.admin.merchant_detail.active_30d') }}
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.active_cards)">
|
||||
0
|
||||
@@ -86,7 +88,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Points Issued (30d)
|
||||
{{ _('loyalty.admin.merchant_detail.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
|
||||
@@ -101,7 +103,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Points Redeemed (30d)
|
||||
{{ _('loyalty.admin.merchant_detail.points_redeemed_30d') }}
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.points_redeemed_30d)">
|
||||
0
|
||||
@@ -120,14 +122,14 @@
|
||||
<div class="flex items-center">
|
||||
<span x-html="$icon('exclamation-triangle', 'w-5 h-5 text-yellow-500 mr-3')"></span>
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-yellow-800 dark:text-yellow-200">No Loyalty Program</h3>
|
||||
<p class="text-sm text-yellow-700 dark:text-yellow-300">This merchant has not set up a loyalty program yet.</p>
|
||||
<h3 class="text-sm font-semibold text-yellow-800 dark:text-yellow-200">{{ _('loyalty.admin.merchant_detail.no_program') }}</h3>
|
||||
<p class="text-sm text-yellow-700 dark:text-yellow-300">{{ _('loyalty.admin.merchant_detail.no_program_desc') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<a :href="`/admin/loyalty/merchants/${merchantId}/program`"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||
Create Program
|
||||
{{ _('loyalty.admin.merchant_detail.create_program') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -135,12 +137,12 @@
|
||||
<!-- Delete Confirmation Modal -->
|
||||
{{ confirm_modal(
|
||||
'deleteProgramModal',
|
||||
'Delete Loyalty Program',
|
||||
'This will permanently delete the loyalty program and all associated data. This action cannot be undone.',
|
||||
_('loyalty.admin.merchant_detail.delete_title'),
|
||||
_('loyalty.admin.merchant_detail.delete_message'),
|
||||
'deleteProgram()',
|
||||
'showDeleteModal',
|
||||
'Delete Program',
|
||||
'Cancel',
|
||||
_('loyalty.admin.merchant_detail.delete_confirm'),
|
||||
_('loyalty.common.cancel'),
|
||||
'danger'
|
||||
) }}
|
||||
|
||||
@@ -148,10 +150,10 @@
|
||||
<div x-show="locations.length > 0" class="px-4 py-3 mb-8 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('map-pin', 'inline w-5 h-5 mr-2')"></span>
|
||||
Location Breakdown (<span x-text="locations.length"></span>)
|
||||
{{ _('loyalty.admin.merchant_detail.location_breakdown') }} (<span x-text="locations.length"></span>)
|
||||
</h3>
|
||||
{% call table_wrapper() %}
|
||||
{{ table_header(['Location', 'Enrolled', 'Points Earned', 'Points Redeemed', 'Transactions (30d)']) }}
|
||||
{{ table_header([_('loyalty.admin.merchant_detail.table_location'), _('loyalty.admin.merchant_detail.table_enrolled'), _('loyalty.admin.merchant_detail.table_points_earned'), _('loyalty.admin.merchant_detail.table_points_redeemed'), _('loyalty.admin.merchant_detail.table_transactions_30d')]) }}
|
||||
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
||||
<template x-for="location in locations" :key="location.store_id">
|
||||
<tr class="text-gray-700 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
@@ -171,7 +173,7 @@
|
||||
</template>
|
||||
<!-- Totals Row -->
|
||||
<tr class="text-gray-900 dark:text-gray-100 font-semibold bg-gray-50 dark:bg-gray-700">
|
||||
<td class="px-4 py-3 text-sm">TOTAL</td>
|
||||
<td class="px-4 py-3 text-sm">{{ _('loyalty.common.total') }}</td>
|
||||
<td class="px-4 py-3 text-sm" x-text="formatNumber(stats.total_cards)">0</td>
|
||||
<td class="px-4 py-3 text-sm" x-text="formatNumber(stats.total_points_issued)">0</td>
|
||||
<td class="px-4 py-3 text-sm" x-text="formatNumber(stats.total_points_redeemed)">0</td>
|
||||
@@ -185,11 +187,11 @@
|
||||
<div class="px-4 py-3 mb-8 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>
|
||||
Admin Policy Settings
|
||||
{{ _('loyalty.admin.merchant_detail.admin_policy_settings') }}
|
||||
</h3>
|
||||
<div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Staff PIN Policy</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">{{ _('loyalty.admin.merchant_detail.staff_pin_policy') }}</p>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold leading-tight rounded-full"
|
||||
:class="{
|
||||
'text-green-700 bg-green-100 dark:bg-green-700 dark:text-green-100': settings?.staff_pin_policy === 'required',
|
||||
@@ -200,17 +202,17 @@
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Self Enrollment</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">{{ _('loyalty.admin.merchant_detail.self_enrollment') }}</p>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold leading-tight rounded-full"
|
||||
:class="settings?.allow_self_enrollment ? '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="settings?.allow_self_enrollment ? 'Allowed' : 'Disabled'"></span>
|
||||
<span x-text="settings?.allow_self_enrollment ? $t('loyalty.admin.merchant_detail.allowed') : $t('loyalty.admin.merchant_detail.disabled')"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">Cross-Location Redemption</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase mb-2">{{ _('loyalty.admin.merchant_detail.cross_location_redemption') }}</p>
|
||||
<span class="inline-flex items-center px-2 py-1 text-xs font-semibold leading-tight rounded-full"
|
||||
:class="settings?.allow_cross_location_redemption !== false ? '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="settings?.allow_cross_location_redemption !== false ? 'Allowed' : 'Disabled'"></span>
|
||||
<span x-text="settings?.allow_cross_location_redemption !== false ? $t('loyalty.admin.merchant_detail.allowed') : $t('loyalty.admin.merchant_detail.disabled')"></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,7 +221,7 @@
|
||||
:href="`/admin/loyalty/merchants/${merchantId}/settings`"
|
||||
class="text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400">
|
||||
<span x-html="$icon('cog', 'inline w-4 h-4 mr-1')"></span>
|
||||
Modify admin policy
|
||||
{{ _('loyalty.admin.merchant_detail.modify_policy') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,18 +4,20 @@
|
||||
{% from 'shared/macros/headers.html' import detail_page_header %}
|
||||
{% from 'shared/macros/forms.html' import form_section, form_actions %}
|
||||
|
||||
{% block title %}Merchant Loyalty Settings{% endblock %}
|
||||
{% block title %}{{ _('loyalty.admin.merchant_settings.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminLoyaltyMerchantSettings(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call detail_page_header("'Admin Policy: ' + (merchant?.name || '')", '/admin/loyalty/merchants/' ~ merchant_id, subtitle_show='merchant') %}
|
||||
Admin-controlled settings for this merchant's loyalty program
|
||||
{{ _('loyalty.admin.merchant_settings.admin_controlled') }}
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading settings...') }}
|
||||
{{ loading_state(_('loyalty.admin.merchant_settings.loading')) }}
|
||||
|
||||
{{ error_state('Error loading settings') }}
|
||||
{{ error_state(_('loyalty.admin.merchant_settings.error_loading')) }}
|
||||
|
||||
<!-- Settings Form -->
|
||||
<div x-show="!loading">
|
||||
@@ -24,10 +26,10 @@
|
||||
<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('key', 'inline w-5 h-5 mr-2')"></span>
|
||||
Staff PIN Policy
|
||||
{{ _('loyalty.admin.merchant_settings.staff_pin_policy') }}
|
||||
</h3>
|
||||
<p class="mb-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
Control whether staff members at this merchant's locations must enter a PIN to process loyalty transactions.
|
||||
{{ _('loyalty.admin.merchant_settings.staff_pin_description') }}
|
||||
</p>
|
||||
|
||||
<div class="space-y-4">
|
||||
@@ -37,8 +39,8 @@
|
||||
x-model="settings.staff_pin_policy"
|
||||
class="mt-1 text-purple-600 form-radio">
|
||||
<div class="ml-3">
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">Required</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Staff must enter their PIN for every transaction. Recommended for security.</p>
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.admin.merchant_settings.required') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.merchant_settings.required_desc') }}</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -48,8 +50,8 @@
|
||||
x-model="settings.staff_pin_policy"
|
||||
class="mt-1 text-purple-600 form-radio">
|
||||
<div class="ml-3">
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">Optional</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Stores can choose whether to require PINs at their locations.</p>
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.admin.merchant_settings.optional') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.merchant_settings.optional_desc') }}</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -59,8 +61,8 @@
|
||||
x-model="settings.staff_pin_policy"
|
||||
class="mt-1 text-purple-600 form-radio">
|
||||
<div class="ml-3">
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">Disabled</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Staff PINs are not used. Any staff member can process transactions.</p>
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.admin.merchant_settings.pin_disabled') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.merchant_settings.pin_disabled_desc') }}</p>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -68,26 +70,26 @@
|
||||
<!-- PIN Lockout Settings -->
|
||||
<div x-show="settings.staff_pin_policy !== 'disabled'" class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<h4 class="mb-4 text-md font-medium text-gray-700 dark:text-gray-300">
|
||||
PIN Lockout Settings
|
||||
{{ _('loyalty.admin.merchant_settings.pin_lockout_settings') }}
|
||||
</h4>
|
||||
<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">
|
||||
Max Failed Attempts
|
||||
{{ _('loyalty.admin.merchant_settings.max_failed_attempts') }}
|
||||
</label>
|
||||
<input type="number" min="3" max="10" {# noqa: FE-008 #}
|
||||
x-model.number="settings.staff_pin_lockout_attempts"
|
||||
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">Number of wrong attempts before lockout (3-10)</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.merchant_settings.max_failed_attempts_help') }}</p>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Lockout Duration (minutes)
|
||||
{{ _('loyalty.admin.merchant_settings.lockout_duration') }}
|
||||
</label>
|
||||
<input type="number" min="5" max="120"
|
||||
x-model.number="settings.staff_pin_lockout_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 dark:text-gray-400">How long to lock out after failed attempts (5-120 minutes)</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.merchant_settings.lockout_duration_help') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,14 +99,14 @@
|
||||
<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-plus', 'inline w-5 h-5 mr-2')"></span>
|
||||
Enrollment Settings
|
||||
{{ _('loyalty.admin.merchant_settings.enrollment_settings') }}
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<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">Allow Self-Service Enrollment</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Customers can sign up via QR code without staff assistance</p>
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.admin.merchant_settings.allow_self_enrollment') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.merchant_settings.self_enrollment_desc') }}</p>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="checkbox" x-model="settings.allow_self_enrollment"
|
||||
@@ -122,14 +124,14 @@
|
||||
<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('arrows-right-left', 'inline w-5 h-5 mr-2')"></span>
|
||||
Transaction Settings
|
||||
{{ _('loyalty.admin.merchant_settings.transaction_settings') }}
|
||||
</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<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">Allow Cross-Location Redemption</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Customers can redeem points at any merchant location</p>
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.admin.merchant_settings.allow_cross_location') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.merchant_settings.cross_location_desc') }}</p>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="checkbox" x-model="settings.allow_cross_location_redemption"
|
||||
@@ -143,8 +145,8 @@
|
||||
|
||||
<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">Allow Void Transactions</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Staff can void points/stamps for returns</p>
|
||||
<p class="font-medium text-gray-700 dark:text-gray-300">{{ _('loyalty.admin.merchant_settings.allow_void') }}</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.admin.merchant_settings.void_desc') }}</p>
|
||||
</div>
|
||||
<div class="relative">
|
||||
<input type="checkbox" x-model="settings.allow_void_transactions"
|
||||
@@ -162,13 +164,13 @@
|
||||
<div class="flex items-center justify-end gap-4">
|
||||
<a :href="backUrl"
|
||||
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-4 py-2 text-sm font-medium text-white bg-purple-600 border border-transparent rounded-lg hover:bg-purple-700 focus:outline-none focus:shadow-outline-purple 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...' : 'Save Settings'"></span>
|
||||
<span x-text="saving ? $t('loyalty.common.saving') : $t('loyalty.admin.merchant_settings.save_settings')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/modals.html' import confirm_modal %}
|
||||
|
||||
{% block title %}Program Configuration{% endblock %}
|
||||
{% block title %}{{ _('loyalty.admin.program_edit.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminLoyaltyProgramEdit(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call detail_page_header("isNewProgram ? 'Create Program: ' + (merchant?.name || '') : 'Edit Program: ' + (merchant?.name || '')", '/admin/loyalty/merchants/' ~ merchant_id, subtitle_show='merchant') %}
|
||||
<span x-text="isNewProgram ? 'Create a loyalty program for this merchant' : 'Edit program configuration'"></span>
|
||||
<span x-text="isNewProgram ? $t('loyalty.admin.program_edit.create_subtitle') : $t('loyalty.admin.program_edit.edit_subtitle')"></span>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading program configuration...') }}
|
||||
{{ error_state('Error loading program configuration') }}
|
||||
{{ loading_state(_('loyalty.admin.program_edit.loading')) }}
|
||||
{{ error_state(_('loyalty.admin.program_edit.error_loading')) }}
|
||||
|
||||
<div x-show="!loading">
|
||||
<form @submit.prevent="saveSettings">
|
||||
@@ -28,12 +30,12 @@
|
||||
<!-- Delete Confirmation Modal -->
|
||||
{{ confirm_modal(
|
||||
'deleteProgramModal',
|
||||
'Delete Loyalty Program',
|
||||
'This will permanently delete the loyalty program and all associated data (cards, transactions, rewards). This action cannot be undone.',
|
||||
_('loyalty.admin.program_edit.delete_title'),
|
||||
_('loyalty.admin.program_edit.delete_message'),
|
||||
'deleteProgram()',
|
||||
'showDeleteModal',
|
||||
'Delete Program',
|
||||
'Cancel',
|
||||
_('loyalty.admin.program_edit.delete_confirm'),
|
||||
_('loyalty.common.cancel'),
|
||||
'danger'
|
||||
) }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -6,16 +6,18 @@
|
||||
{% from 'shared/macros/tables.html' import table_wrapper, table_header %}
|
||||
{% from 'shared/macros/modals.html' import modal, confirm_modal_dynamic %}
|
||||
|
||||
{% block title %}Loyalty Programs{% endblock %}
|
||||
{% block title %}{{ _('loyalty.admin.programs.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}adminLoyaltyPrograms(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ page_header('Loyalty Programs', action_label='Create Program', action_onclick="showCreateModal = true") }}
|
||||
{{ page_header(_('loyalty.admin.programs.title'), action_label=_('loyalty.admin.programs.create_program'), action_onclick="showCreateModal = true") }}
|
||||
|
||||
{{ loading_state('Loading loyalty programs...') }}
|
||||
{{ loading_state(_('loyalty.admin.programs.loading')) }}
|
||||
|
||||
{{ error_state('Error loading loyalty programs') }}
|
||||
{{ error_state(_('loyalty.admin.programs.error_loading')) }}
|
||||
|
||||
<!-- Stats Cards -->
|
||||
<div x-show="!loading" class="grid gap-6 mb-8 md:grid-cols-2 xl:grid-cols-4">
|
||||
@@ -26,7 +28,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Total Programs
|
||||
{{ _('loyalty.admin.programs.total_programs') }}
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.total_programs || 0">
|
||||
0
|
||||
@@ -41,7 +43,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Active
|
||||
{{ _('loyalty.admin.programs.active') }}
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="stats.active_programs || 0">
|
||||
0
|
||||
@@ -56,7 +58,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Total Members
|
||||
{{ _('loyalty.admin.programs.total_members') }}
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.total_cards) || 0">
|
||||
0
|
||||
@@ -71,7 +73,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
Transactions (30d)
|
||||
{{ _('loyalty.admin.programs.transactions_30d') }}
|
||||
</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.transactions_30d) || 0">
|
||||
0
|
||||
@@ -93,7 +95,7 @@
|
||||
type="text"
|
||||
x-model="filters.search"
|
||||
@input="debouncedSearch()"
|
||||
placeholder="Search by merchant name..."
|
||||
placeholder="{{ _('loyalty.admin.programs.search_placeholder') }}"
|
||||
class="w-full pl-10 pr-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>
|
||||
@@ -107,19 +109,19 @@
|
||||
@change="pagination.page = 1; loadPrograms()"
|
||||
class="px-4 py-2 text-sm text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-lg focus:border-purple-400 focus:outline-none"
|
||||
>
|
||||
<option value="">All Status</option>
|
||||
<option value="true">Active</option>
|
||||
<option value="false">Inactive</option>
|
||||
<option value="">{{ _('loyalty.admin.programs.all_status') }}</option>
|
||||
<option value="true">{{ _('loyalty.common.active') }}</option>
|
||||
<option value="false">{{ _('loyalty.common.inactive') }}</option>
|
||||
</select>
|
||||
|
||||
<!-- Refresh Button -->
|
||||
<button
|
||||
@click="loadPrograms(); loadStats()"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 focus:outline-none transition-colors"
|
||||
title="Refresh"
|
||||
title="{{ _('loyalty.common.refresh') }}"
|
||||
>
|
||||
<span x-html="$icon('refresh', 'w-4 h-4 mr-2')"></span>
|
||||
Refresh
|
||||
{{ _('loyalty.common.refresh') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -128,7 +130,7 @@
|
||||
<!-- Programs Table -->
|
||||
<div x-show="!loading">
|
||||
{% call table_wrapper() %}
|
||||
{{ table_header(['Merchant', 'Program Type', 'Members', 'Points Issued', 'Status', 'Created', 'Actions']) }}
|
||||
{{ table_header([_('loyalty.admin.programs.table_merchant'), _('loyalty.admin.programs.table_program_type'), _('loyalty.admin.programs.table_members'), _('loyalty.admin.programs.table_points_issued'), _('loyalty.admin.programs.table_status'), _('loyalty.admin.programs.table_created'), _('loyalty.admin.programs.table_actions')]) }}
|
||||
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
||||
<!-- Empty State -->
|
||||
<template x-if="programs.length === 0">
|
||||
@@ -136,8 +138,8 @@
|
||||
<td colspan="7" class="px-4 py-8 text-center text-gray-600 dark:text-gray-400">
|
||||
<div class="flex flex-col items-center">
|
||||
<span x-html="$icon('gift', 'w-12 h-12 mb-2 text-gray-300')"></span>
|
||||
<p class="font-medium">No loyalty programs found</p>
|
||||
<p class="text-xs mt-1" x-text="filters.search || filters.is_active ? 'Try adjusting your search or filters' : 'No merchants have set up loyalty programs yet'"></p>
|
||||
<p class="font-medium">{{ _('loyalty.admin.programs.no_programs') }}</p>
|
||||
<p class="text-xs mt-1" x-text="filters.search || filters.is_active ? $t('loyalty.admin.programs.adjust_filters') : $t('loyalty.admin.programs.no_merchants_yet')"></p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -175,23 +177,23 @@
|
||||
<span x-text="program.loyalty_type?.charAt(0).toUpperCase() + program.loyalty_type?.slice(1) || 'Unknown'"></span>
|
||||
</span>
|
||||
<p class="text-xs text-gray-500 mt-1" x-show="program.is_points_enabled">
|
||||
<span x-text="program.points_per_euro"></span> pt/EUR
|
||||
<span x-text="program.points_per_euro"></span> <span x-text="$t('loyalty.admin.programs.pt_per_eur')"></span>
|
||||
</p>
|
||||
</td>
|
||||
|
||||
<!-- Members -->
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<span class="font-semibold" x-text="formatNumber(program.total_cards) || 0"></span>
|
||||
<span class="text-xs text-gray-500" x-show="program.active_cards">
|
||||
(<span x-text="formatNumber(program.active_cards)"></span> active)
|
||||
<span class="text-xs text-gray-500" x-show="program.active_cards"
|
||||
x-text="$t('loyalty.admin.programs.x_active', {count: program.active_cards})">
|
||||
</span>
|
||||
</td>
|
||||
|
||||
<!-- Points Issued -->
|
||||
<td class="px-4 py-3 text-sm">
|
||||
<span x-text="formatNumber(program.total_points_issued) || 0"></span>
|
||||
<p class="text-xs text-gray-500" x-show="program.total_points_redeemed">
|
||||
<span x-text="formatNumber(program.total_points_redeemed)"></span> redeemed
|
||||
<p class="text-xs text-gray-500" x-show="program.total_points_redeemed"
|
||||
x-text="$t('loyalty.admin.programs.x_redeemed', {count: formatNumber(program.total_points_redeemed)})">
|
||||
</p>
|
||||
</td>
|
||||
|
||||
@@ -199,7 +201,7 @@
|
||||
<td class="px-4 py-3 text-xs">
|
||||
<span class="inline-flex items-center px-2 py-1 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>
|
||||
</td>
|
||||
|
||||
@@ -213,7 +215,7 @@
|
||||
<a
|
||||
: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"
|
||||
title="View merchant loyalty details"
|
||||
title="{{ _('loyalty.common.view') }}"
|
||||
>
|
||||
<span x-html="$icon('eye', 'w-5 h-5')"></span>
|
||||
</a>
|
||||
@@ -222,7 +224,7 @@
|
||||
<a
|
||||
: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"
|
||||
title="Edit program configuration"
|
||||
title="{{ _('loyalty.common.edit') }}"
|
||||
>
|
||||
<span x-html="$icon('edit', 'w-5 h-5')"></span>
|
||||
</a>
|
||||
@@ -231,7 +233,7 @@
|
||||
<button
|
||||
@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"
|
||||
title="Delete program"
|
||||
title="{{ _('loyalty.common.delete') }}"
|
||||
>
|
||||
<span x-html="$icon('delete', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
@@ -258,24 +260,24 @@
|
||||
<!-- Delete Confirmation Modal -->
|
||||
{{ confirm_modal_dynamic(
|
||||
'deleteProgramModal',
|
||||
'Delete Loyalty Program',
|
||||
"'Delete the loyalty program for \"' + (deletingProgram?.merchant_name || '') + '\"? This will permanently remove all associated data (cards, transactions, rewards). This cannot be undone.'",
|
||||
_('loyalty.admin.programs.delete_title'),
|
||||
"$t('loyalty.admin.programs.delete_message', {name: deletingProgram?.merchant_name || ''})",
|
||||
'deleteProgram()',
|
||||
'showDeleteModal',
|
||||
'Delete Program',
|
||||
'Cancel',
|
||||
_('loyalty.admin.programs.delete_confirm'),
|
||||
_('loyalty.common.cancel'),
|
||||
'danger'
|
||||
) }}
|
||||
|
||||
<!-- Create Program Modal -->
|
||||
{% call modal('createProgramModal', 'Create Loyalty Program', 'showCreateModal', show_footer=false) %}
|
||||
{% call modal('createProgramModal', _('loyalty.admin.programs.create_title'), 'showCreateModal', show_footer=false) %}
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Select a merchant to create a loyalty program for.
|
||||
{{ _('loyalty.admin.programs.create_description') }}
|
||||
</p>
|
||||
|
||||
<!-- Merchant Search -->
|
||||
<div class="mb-4">
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Search Merchant</label>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.admin.programs.search_merchant') }}</label>
|
||||
<div class="relative">
|
||||
<span class="absolute inset-y-0 left-0 flex items-center pl-3">
|
||||
<span x-html="$icon('search', 'w-4 h-4 text-gray-400')"></span>
|
||||
@@ -283,7 +285,7 @@
|
||||
<input type="text"
|
||||
x-model="merchantSearch"
|
||||
@input="searchMerchants()"
|
||||
placeholder="Type merchant name..."
|
||||
placeholder="{{ _('loyalty.admin.programs.type_merchant_name') }}"
|
||||
class="w-full pl-10 pr-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>
|
||||
@@ -304,7 +306,7 @@
|
||||
</div>
|
||||
|
||||
<div x-show="merchantSearch && merchantResults.length === 0 && !searchingMerchants" class="mb-4 text-sm text-gray-500 text-center py-4">
|
||||
No merchants found
|
||||
{{ _('loyalty.admin.programs.no_merchants_found') }}
|
||||
</div>
|
||||
|
||||
<!-- Existing program warning -->
|
||||
@@ -313,11 +315,11 @@
|
||||
<div class="flex items-start">
|
||||
<span x-html="$icon('exclamation-triangle', 'w-5 h-5 text-yellow-500 mr-3 mt-0.5 flex-shrink-0')"></span>
|
||||
<div>
|
||||
<p class="text-sm font-medium text-yellow-800 dark:text-yellow-200">This merchant already has a loyalty program.</p>
|
||||
<p class="text-sm font-medium text-yellow-800 dark:text-yellow-200">{{ _('loyalty.admin.programs.existing_program_warning') }}</p>
|
||||
<a :href="'/admin/loyalty/merchants/' + selectedMerchant.id + '/program'"
|
||||
class="inline-flex items-center mt-1 text-sm text-purple-600 hover:text-purple-700 dark:text-purple-400">
|
||||
<span x-html="$icon('eye', 'w-4 h-4 mr-1')"></span>
|
||||
View / Edit existing program
|
||||
{{ _('loyalty.admin.programs.view_edit_existing') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -327,12 +329,12 @@
|
||||
<div class="flex justify-end gap-3">
|
||||
<button @click="showCreateModal = false; merchantSearch = ''; merchantResults = []; selectedMerchant = null"
|
||||
class="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700">
|
||||
Cancel
|
||||
{{ _('loyalty.common.cancel') }}
|
||||
</button>
|
||||
<button @click="goToCreateProgram()"
|
||||
:disabled="!selectedMerchant || existingProgramForMerchant(selectedMerchant?.id)"
|
||||
class="px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
Continue
|
||||
{{ _('loyalty.common.continue') }}
|
||||
</button>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
@@ -3,32 +3,34 @@
|
||||
{% from 'shared/macros/headers.html' import page_header_flex, refresh_button %}
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
|
||||
{% block title %}Loyalty Analytics{% endblock %}
|
||||
{% block title %}{{ _('loyalty.merchant.analytics.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}merchantLoyaltyAnalytics(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call page_header_flex(title='Loyalty Analytics', subtitle='Loyalty program statistics across all your stores') %}
|
||||
{% call page_header_flex(title=_('loyalty.merchant.analytics.title'), subtitle=_('loyalty.merchant.analytics.subtitle')) %}
|
||||
<div class="flex items-center gap-3">
|
||||
{{ refresh_button(loading_var='loading', onclick='loadStats()', variant='secondary') }}
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading analytics...') }}
|
||||
{{ loading_state(_('loyalty.merchant.analytics.loading')) }}
|
||||
|
||||
{{ error_state('Error loading analytics') }}
|
||||
{{ error_state(_('loyalty.merchant.analytics.error_loading')) }}
|
||||
|
||||
<!-- No Program State -->
|
||||
<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('gift', '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">No Loyalty Program</h3>
|
||||
<p class="mt-1 text-sm text-yellow-700 dark:text-yellow-300">Set up a loyalty program to see analytics here.</p>
|
||||
<h3 class="text-sm font-semibold text-yellow-800 dark:text-yellow-200">{{ _('loyalty.merchant.analytics.no_program') }}</h3>
|
||||
<p class="mt-1 text-sm text-yellow-700 dark:text-yellow-300">{{ _('loyalty.merchant.analytics.no_program_desc') }}</p>
|
||||
<a href="/merchants/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('plus', 'w-4 h-4 mr-2')"></span>
|
||||
Create Program
|
||||
{{ _('loyalty.merchant.analytics.create_program') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -43,17 +45,17 @@
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<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">Quick Actions</h3>
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">{{ _('loyalty.merchant.analytics.quick_actions') }}</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="/merchants/loyalty/program"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-purple-600 bg-purple-100 rounded-lg hover:bg-purple-200 dark:text-purple-300 dark:bg-purple-900/30 dark:hover:bg-purple-900/50">
|
||||
<span x-html="$icon('eye', 'w-4 h-4 mr-2')"></span>
|
||||
View Program
|
||||
{{ _('loyalty.merchant.analytics.view_program') }}
|
||||
</a>
|
||||
<a href="/merchants/loyalty/program/edit"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-blue-600 bg-blue-100 rounded-lg hover:bg-blue-200 dark:text-blue-300 dark:bg-blue-900/30 dark:hover:bg-blue-900/50">
|
||||
<span x-html="$icon('pencil', 'w-4 h-4 mr-2')"></span>
|
||||
Edit Program
|
||||
{{ _('loyalty.merchant.analytics.edit_program') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,15 +4,17 @@
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/modals.html' import confirm_modal %}
|
||||
|
||||
{% block title %}Loyalty Settings{% endblock %}
|
||||
{% block title %}{{ _('loyalty.merchant.program_edit.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}merchantLoyaltySettings(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call page_header_flex(title='Loyalty Program Settings', subtitle='Configure your loyalty program') %}{% endcall %}
|
||||
{% call page_header_flex(title=_('loyalty.merchant.program_edit.page_title'), subtitle=_('loyalty.merchant.program_edit.subtitle')) %}{% endcall %}
|
||||
|
||||
{{ loading_state('Loading settings...') }}
|
||||
{{ error_state('Error loading settings') }}
|
||||
{{ loading_state(_('loyalty.merchant.program_edit.loading')) }}
|
||||
{{ error_state(_('loyalty.merchant.program_edit.error_loading')) }}
|
||||
|
||||
<div x-show="!loading">
|
||||
<form @submit.prevent="saveSettings">
|
||||
@@ -26,12 +28,12 @@
|
||||
<!-- Delete Confirmation Modal -->
|
||||
{{ confirm_modal(
|
||||
'deleteProgramModal',
|
||||
'Delete Loyalty Program',
|
||||
'This will permanently delete your loyalty program and all associated data (cards, transactions, rewards). This action cannot be undone.',
|
||||
_('loyalty.merchant.program_edit.delete_title'),
|
||||
_('loyalty.merchant.program_edit.delete_message'),
|
||||
'deleteProgram()',
|
||||
'showDeleteModal',
|
||||
'Delete Program',
|
||||
'Cancel',
|
||||
_('loyalty.common.delete'),
|
||||
_('loyalty.common.cancel'),
|
||||
'danger'
|
||||
) }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{# app/modules/loyalty/templates/loyalty/merchant/program.html #}
|
||||
{% extends "merchant/base.html" %}
|
||||
|
||||
{% block title %}Loyalty Program{% endblock %}
|
||||
{% block title %}{{ _('loyalty.merchant.program.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div x-data="merchantLoyaltyProgram()">
|
||||
@@ -9,14 +11,14 @@
|
||||
<!-- Page Header -->
|
||||
<div class="mb-8 mt-6 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">Loyalty Program</h2>
|
||||
<p class="mt-1 text-gray-500 dark:text-gray-400">Your loyalty program configuration.</p>
|
||||
<h2 class="text-2xl font-bold text-gray-900 dark:text-gray-100">{{ _('loyalty.merchant.program.title') }}</h2>
|
||||
<p class="mt-1 text-gray-500 dark:text-gray-400">{{ _('loyalty.merchant.program.subtitle') }}</p>
|
||||
</div>
|
||||
<template x-if="stats.program_id">
|
||||
<a href="/merchants/loyalty/program/edit"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
<span x-html="$icon('pencil', 'w-4 h-4 mr-2')"></span>
|
||||
Edit Program
|
||||
{{ _('loyalty.merchant.program.edit_program') }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
@@ -25,14 +27,14 @@
|
||||
<template x-if="!stats.program_id && !loading">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-12 text-center">
|
||||
<span x-html="$icon('gift', 'w-12 h-12 mx-auto text-gray-400')"></span>
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-white">No Loyalty Program</h3>
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-white">{{ _('loyalty.merchant.program.no_program') }}</h3>
|
||||
<p class="mt-2 text-gray-500 dark:text-gray-400">
|
||||
Your loyalty program hasn't been set up yet. Create one to start rewarding your customers.
|
||||
{{ _('loyalty.merchant.program.no_program_desc') }}
|
||||
</p>
|
||||
<a href="/merchants/loyalty/program/edit"
|
||||
class="inline-flex items-center mt-4 px-6 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||
Create Program
|
||||
{{ _('loyalty.merchant.program.create_program') }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -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="'€' + ((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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,35 +3,37 @@
|
||||
{% from 'shared/macros/headers.html' import page_header_flex, refresh_button %}
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
|
||||
{% block title %}Loyalty Analytics{% endblock %}
|
||||
{% block title %}{{ _('loyalty.store.analytics.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}storeLoyaltyAnalytics(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call page_header_flex(title='Loyalty Analytics', subtitle='Track your loyalty program performance') %}
|
||||
{% call page_header_flex(title=_('loyalty.store.analytics.title'), subtitle=_('loyalty.store.analytics.subtitle')) %}
|
||||
<div class="flex items-center gap-3">
|
||||
{{ refresh_button(loading_var='loading', onclick='loadStats()', variant='secondary') }}
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading analytics...') }}
|
||||
{{ error_state('Error loading analytics') }}
|
||||
{{ loading_state(_('loyalty.store.analytics.loading')) }}
|
||||
{{ error_state(_('loyalty.store.analytics.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 Program Not Set Up</h3>
|
||||
<p class="mt-1 text-sm text-yellow-700 dark:text-yellow-300">Your merchant doesn't have a loyalty program configured yet.</p>
|
||||
<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>
|
||||
Set Up Loyalty Program
|
||||
{{ _('loyalty.common.setup_program') }}
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">Contact your administrator to complete the setup.</p>
|
||||
<p class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">{{ _('loyalty.common.contact_admin_setup') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -46,22 +48,22 @@
|
||||
|
||||
<!-- Quick Actions -->
|
||||
<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">Quick Actions</h3>
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">{{ _('loyalty.store.analytics.quick_actions') }}</h3>
|
||||
<div class="flex flex-wrap gap-3">
|
||||
<a href="/store/{{ store_code }}/loyalty/terminal"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-purple-600 bg-purple-100 rounded-lg hover:bg-purple-200 dark:text-purple-300 dark:bg-purple-900/30 dark:hover:bg-purple-900/50">
|
||||
<span x-html="$icon('device-tablet', 'w-4 h-4 mr-2')"></span>
|
||||
Open Terminal
|
||||
{{ _('loyalty.store.analytics.open_terminal') }}
|
||||
</a>
|
||||
<a href="/store/{{ store_code }}/loyalty/cards"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-blue-600 bg-blue-100 rounded-lg hover:bg-blue-200 dark:text-blue-300 dark:bg-blue-900/30 dark:hover:bg-blue-900/50">
|
||||
<span x-html="$icon('users', 'w-4 h-4 mr-2')"></span>
|
||||
View Members
|
||||
{{ _('loyalty.store.analytics.view_members') }}
|
||||
</a>
|
||||
<a href="/store/{{ store_code }}/loyalty/program"
|
||||
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-600 bg-gray-100 rounded-lg hover:bg-gray-200 dark:text-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600">
|
||||
<span x-html="$icon('eye', 'w-4 h-4 mr-2')"></span>
|
||||
View Program
|
||||
{{ _('loyalty.store.analytics.view_program') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,17 +4,19 @@
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/tables.html' import table_wrapper, table_header %}
|
||||
|
||||
{% block title %}Member Details{% endblock %}
|
||||
{% block title %}{{ _('loyalty.store.card_detail.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}storeLoyaltyCardDetail(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call detail_page_header("card?.customer_name || 'Member Details'", '/store/' + store_code + '/loyalty/cards', subtitle_show='card') %}
|
||||
Card: <span x-text="card?.card_number"></span>
|
||||
{% call detail_page_header("card?.customer_name || '" + _('loyalty.store.card_detail.title') + "'", '/store/' + store_code + '/loyalty/cards', subtitle_show='card') %}
|
||||
{{ _('loyalty.store.card_detail.card_label') }}: <span x-text="card?.card_number"></span>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading member details...') }}
|
||||
{{ error_state('Error loading member') }}
|
||||
{{ loading_state(_('loyalty.store.card_detail.loading')) }}
|
||||
{{ error_state(_('loyalty.store.card_detail.error_loading')) }}
|
||||
|
||||
<div x-show="!loading && card">
|
||||
<!-- Quick Stats -->
|
||||
@@ -24,7 +26,7 @@
|
||||
<span x-html="$icon('currency-dollar', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Points Balance</p>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.card_detail.points_balance') }}</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(card?.points_balance)">0</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,7 +35,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">Total Earned</p>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.card_detail.total_earned') }}</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(card?.total_points_earned)">0</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -42,7 +44,7 @@
|
||||
<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 Redeemed</p>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.card_detail.total_redeemed') }}</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(card?.total_points_redeemed)">0</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -51,7 +53,7 @@
|
||||
<span x-html="$icon('calendar', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Member Since</p>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.card_detail.member_since') }}</p>
|
||||
<p class="text-sm font-semibold text-gray-700 dark:text-gray-200" x-text="formatDate(card?.created_at)">-</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,23 +64,23 @@
|
||||
<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('user', 'inline w-5 h-5 mr-2')"></span>
|
||||
Customer Information
|
||||
{{ _('loyalty.store.card_detail.customer_information') }}
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Name</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">{{ _('loyalty.store.card_detail.name') }}</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="card?.customer_name || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Email</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">{{ _('loyalty.store.card_detail.email') }}</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="card?.customer_email || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Phone</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">{{ _('loyalty.store.card_detail.phone') }}</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="card?.customer_phone || '-'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Birthday</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">{{ _('loyalty.store.card_detail.birthday') }}</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="card?.customer_birthday || '-'">-</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,25 +90,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('credit-card', 'inline w-5 h-5 mr-2')"></span>
|
||||
Card Details
|
||||
{{ _('loyalty.store.card_detail.card_details') }}
|
||||
</h3>
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Card Number</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">{{ _('loyalty.store.card_detail.card_number') }}</p>
|
||||
<p class="text-sm font-mono text-gray-700 dark:text-gray-300" x-text="card?.card_number">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Status</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">{{ _('loyalty.store.card_detail.status') }}</p>
|
||||
<span class="px-2 py-1 text-xs font-semibold rounded-full"
|
||||
:class="card?.is_active ? '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'"
|
||||
x-text="card?.is_active ? 'Active' : 'Inactive'"></span>
|
||||
x-text="card?.is_active ? $t('loyalty.common.active') : $t('loyalty.common.inactive')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Last Activity</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">{{ _('loyalty.store.card_detail.last_activity') }}</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="formatDate(card?.last_activity_at) || 'Never'">-</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">Enrolled At</p>
|
||||
<p class="text-xs font-semibold text-gray-600 dark:text-gray-400 uppercase">{{ _('loyalty.store.card_detail.enrolled_at') }}</p>
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300" x-text="card?.enrolled_at_store_name || 'Unknown'">-</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,15 +119,15 @@
|
||||
<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('clock', 'inline w-5 h-5 mr-2')"></span>
|
||||
Transaction History
|
||||
{{ _('loyalty.store.card_detail.transaction_history') }}
|
||||
</h3>
|
||||
{% call table_wrapper() %}
|
||||
{{ table_header(['Date', 'Type', 'Points', 'Location', 'Notes']) }}
|
||||
{{ table_header([_('loyalty.store.card_detail.col_date'), _('loyalty.store.card_detail.col_type'), _('loyalty.store.card_detail.col_points'), _('loyalty.store.card_detail.col_location'), _('loyalty.store.card_detail.col_notes')]) }}
|
||||
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
||||
<template x-if="transactions.length === 0">
|
||||
<tr>
|
||||
<td colspan="5" class="px-4 py-6 text-center text-gray-500 dark:text-gray-400">
|
||||
No transactions yet
|
||||
{{ _('loyalty.store.card_detail.no_transactions') }}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
|
||||
@@ -5,42 +5,44 @@
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/tables.html' import table_wrapper, table_header %}
|
||||
|
||||
{% block title %}Loyalty Members{% endblock %}
|
||||
{% block title %}{{ _('loyalty.store.cards.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}storeLoyaltyCards(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
{% call page_header_flex(title='Loyalty Members', subtitle='View and manage your loyalty program members') %}
|
||||
{% call page_header_flex(title=_('loyalty.store.cards.title'), subtitle=_('loyalty.store.cards.subtitle')) %}
|
||||
<div class="flex items-center gap-3">
|
||||
{{ refresh_button(loading_var='loading', onclick='loadCards()', variant='secondary') }}
|
||||
<a href="/store/{{ store_code }}/loyalty/enroll"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
<span x-html="$icon('user-plus', 'w-4 h-4 mr-2')"></span>
|
||||
Enroll New
|
||||
{{ _('loyalty.store.cards.enroll_new') }}
|
||||
</a>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading members...') }}
|
||||
{{ loading_state(_('loyalty.store.cards.loading')) }}
|
||||
|
||||
{{ error_state('Error loading members') }}
|
||||
{{ error_state(_('loyalty.store.cards.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 Program Not Set Up</h3>
|
||||
<p class="mt-1 text-sm text-yellow-700 dark:text-yellow-300">Your merchant doesn't have a loyalty program configured yet.</p>
|
||||
<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>
|
||||
Set Up Loyalty Program
|
||||
{{ _('loyalty.common.setup_program') }}
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">Contact your administrator to complete the setup.</p>
|
||||
<p class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">{{ _('loyalty.common.contact_admin_setup') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -53,7 +55,7 @@
|
||||
<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.store.cards.total_members') }}</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.total_cards)">0</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,7 +64,7 @@
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Active (30d)</p>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.cards.active_30d') }}</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.active_cards)">0</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -71,7 +73,7 @@
|
||||
<span x-html="$icon('user-plus', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">New This Month</p>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.cards.new_this_month') }}</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.new_this_month)">0</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -80,7 +82,7 @@
|
||||
<span x-html="$icon('currency-dollar', 'w-5 h-5')"></span>
|
||||
</div>
|
||||
<div>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">Total Points Balance</p>
|
||||
<p class="mb-2 text-sm font-medium text-gray-600 dark:text-gray-400">{{ _('loyalty.store.cards.total_points_balance') }}</p>
|
||||
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="formatNumber(stats.total_points_balance)">0</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,15 +99,15 @@
|
||||
<input type="text"
|
||||
x-model="filters.search"
|
||||
@input="debouncedSearch()"
|
||||
placeholder="Search by name, email, phone, or card..."
|
||||
placeholder="{{ _('loyalty.store.cards.search_placeholder') }}"
|
||||
class="w-full pl-10 pr-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>
|
||||
<select x-model="filters.status" @change="applyFilter()"
|
||||
class="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">
|
||||
<option value="">All Status</option>
|
||||
<option value="active">Active</option>
|
||||
<option value="inactive">Inactive</option>
|
||||
<option value="">{{ _('loyalty.store.cards.all_status') }}</option>
|
||||
<option value="active">{{ _('loyalty.common.active') }}</option>
|
||||
<option value="inactive">{{ _('loyalty.common.inactive') }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,15 +115,15 @@
|
||||
<!-- Cards Table -->
|
||||
<div x-show="!loading && program">
|
||||
{% call table_wrapper() %}
|
||||
{{ table_header(['Member', 'Card Number', 'Points Balance', 'Last Activity', 'Status', 'Actions']) }}
|
||||
{{ table_header([_('loyalty.store.cards.col_member'), _('loyalty.store.cards.col_card_number'), _('loyalty.store.cards.col_points_balance'), _('loyalty.store.cards.col_last_activity'), _('loyalty.store.cards.col_status'), _('loyalty.store.cards.col_actions')]) }}
|
||||
<tbody class="bg-white divide-y dark:divide-gray-700 dark:bg-gray-800">
|
||||
<template x-if="cards.length === 0">
|
||||
<tr>
|
||||
<td colspan="6" class="px-4 py-8 text-center text-gray-600 dark:text-gray-400">
|
||||
<div class="flex flex-col items-center">
|
||||
<span x-html="$icon('users', 'w-12 h-12 mb-2 text-gray-300')"></span>
|
||||
<p class="font-medium">No members found</p>
|
||||
<p class="text-xs mt-1" x-text="filters.search ? 'Try adjusting your search' : 'Enroll your first customer to get started'"></p>
|
||||
<p class="font-medium" x-text="$t('loyalty.store.cards.no_members_found')"></p>
|
||||
<p class="text-xs mt-1" x-text="filters.search ? $t('loyalty.store.cards.try_adjusting_search') : $t('loyalty.store.cards.enroll_first_customer')"></p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -148,12 +150,12 @@
|
||||
<td class="px-4 py-3 text-xs">
|
||||
<span class="px-2 py-1 font-semibold leading-tight rounded-full"
|
||||
:class="card.is_active ? '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'"
|
||||
x-text="card.is_active ? 'Active' : 'Inactive'"></span>
|
||||
x-text="card.is_active ? $t('loyalty.common.active') : $t('loyalty.common.inactive')"></span>
|
||||
</td>
|
||||
<td class="px-4 py-3">
|
||||
<a :href="'/store/{{ store_code }}/loyalty/cards/' + card.id"
|
||||
class="text-purple-600 hover:text-purple-700 dark:text-purple-400">
|
||||
View
|
||||
class="text-purple-600 hover:text-purple-700 dark:text-purple-400"
|
||||
x-text="$t('loyalty.common.view')">
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@@ -3,17 +3,19 @@
|
||||
{% 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 title %}{{ _('loyalty.store.enroll.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}storeLoyaltyEnroll(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call detail_page_header("'Enroll New Customer'", '/store/' + store_code + '/loyalty/terminal') %}
|
||||
Add a new member to your loyalty program
|
||||
{% call detail_page_header("'" + _('loyalty.store.enroll.page_title') + "'", '/store/' + store_code + '/loyalty/terminal') %}
|
||||
{{ _('loyalty.store.enroll.subtitle') }}
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading...') }}
|
||||
{{ error_state('Error loading enrollment form') }}
|
||||
{{ loading_state(_('loyalty.common.loading')) }}
|
||||
{{ error_state(_('loyalty.store.enroll.error_loading')) }}
|
||||
|
||||
<div x-show="!loading" class="max-w-2xl">
|
||||
<form @submit.prevent="enrollCustomer">
|
||||
@@ -21,20 +23,20 @@
|
||||
<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
|
||||
{{ _('loyalty.store.enroll.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>
|
||||
{{ _('loyalty.store.enroll.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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.store.enroll.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>
|
||||
@@ -42,23 +44,23 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Email <span class="text-red-500">*</span>
|
||||
{{ _('loyalty.store.enroll.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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.store.enroll.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>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">{{ _('loyalty.store.enroll.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>
|
||||
<p class="mt-1 text-xs text-gray-500">{{ _('loyalty.store.enroll.birthday_help') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,19 +69,19 @@
|
||||
<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
|
||||
{{ _('loyalty.store.enroll.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>
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ _('loyalty.store.enroll.send_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>
|
||||
<span class="ml-2 text-sm text-gray-700 dark:text-gray-300">{{ _('loyalty.store.enroll.send_sms') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,9 +91,9 @@
|
||||
<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 font-medium text-green-800 dark:text-green-200">{{ _('loyalty.store.enroll.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!
|
||||
{{ _('loyalty.store.enroll.welcome_bonus_desc') }} <span class="font-bold" x-text="program?.welcome_bonus_points"></span> {{ _('loyalty.store.enroll.bonus_points') }}!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -101,12 +103,12 @@
|
||||
<div class="flex items-center gap-4">
|
||||
<a href="/store/{{ store_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
|
||||
{{ _('loyalty.common.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>
|
||||
<span x-text="enrolling ? $t('loyalty.store.enroll.enrolling') : $t('loyalty.store.enroll.enroll_customer')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -118,21 +120,21 @@
|
||||
<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>
|
||||
<h3 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">
|
||||
Card Number: <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 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
|
||||
{{ _('loyalty.store.enroll.starting_balance') }}: <span class="font-bold text-purple-600" x-text="enrolledCard?.points_balance"></span> {{ _('loyalty.store.enroll.points') }}
|
||||
</p>
|
||||
<div class="flex gap-3 justify-center">
|
||||
<a href="/store/{{ store_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
|
||||
{{ _('loyalty.store.enroll.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
|
||||
{{ _('loyalty.store.enroll.enroll_another') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,42 +3,44 @@
|
||||
{% from 'shared/macros/headers.html' import page_header_flex %}
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
|
||||
{% block title %}Loyalty Program{% endblock %}
|
||||
{% block title %}{{ _('loyalty.store.program.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}storeLoyaltyProgram(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{% call page_header_flex(title='Loyalty Program', subtitle='Your loyalty program configuration') %}
|
||||
{% call page_header_flex(title=_('loyalty.store.program.title'), subtitle=_('loyalty.store.program.subtitle')) %}
|
||||
<div class="flex items-center gap-3">
|
||||
{% if user.role == 'merchant_owner' %}
|
||||
<a href="/store/{{ store_code }}/loyalty/program/edit"
|
||||
class="flex items-center px-4 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700"
|
||||
x-show="program">
|
||||
<span x-html="$icon('pencil', 'w-4 h-4 mr-2')"></span>
|
||||
Edit Program
|
||||
{{ _('loyalty.store.program.edit_program') }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading program...') }}
|
||||
{{ error_state('Error loading program') }}
|
||||
{{ loading_state(_('loyalty.store.program.loading')) }}
|
||||
{{ error_state(_('loyalty.store.program.error_loading')) }}
|
||||
|
||||
<!-- No Program State -->
|
||||
<div x-show="!loading && !program" class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 p-12 text-center">
|
||||
<span x-html="$icon('gift', 'w-12 h-12 mx-auto text-gray-400')"></span>
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-white">No Loyalty Program</h3>
|
||||
<h3 class="mt-4 text-lg font-medium text-gray-900 dark:text-white">{{ _('loyalty.store.program.no_program') }}</h3>
|
||||
<p class="mt-2 text-gray-500 dark:text-gray-400">
|
||||
Your merchant doesn't have a loyalty program configured yet.
|
||||
{{ _('loyalty.common.program_not_setup_desc') }}
|
||||
</p>
|
||||
{% if user.role == 'merchant_owner' %}
|
||||
<a href="/store/{{ store_code }}/loyalty/program/edit"
|
||||
class="inline-flex items-center mt-4 px-6 py-2 text-sm font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700">
|
||||
<span x-html="$icon('plus', 'w-4 h-4 mr-2')"></span>
|
||||
Create Program
|
||||
{{ _('loyalty.store.program.create_program') }}
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">Contact your administrator to set up a loyalty program.</p>
|
||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.store.program.contact_admin') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -4,33 +4,35 @@
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/modals.html' import confirm_modal %}
|
||||
|
||||
{% block title %}Loyalty Settings{% endblock %}
|
||||
{% block title %}{{ _('loyalty.store.settings.title') }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}loyaltySettings(){% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
{% call page_header_flex(title='Loyalty Program Settings', subtitle='Configure your loyalty program') %}
|
||||
{% call page_header_flex(title=_('loyalty.store.settings.page_title'), subtitle=_('loyalty.store.settings.subtitle')) %}
|
||||
<div class="flex items-center gap-3">
|
||||
<a href="/store/{{ store_code }}/loyalty/program"
|
||||
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('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back to Program
|
||||
{{ _('loyalty.store.settings.back_to_program') }}
|
||||
</a>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading settings...') }}
|
||||
{{ loading_state(_('loyalty.store.settings.loading')) }}
|
||||
|
||||
{{ error_state('Error loading settings') }}
|
||||
{{ error_state(_('loyalty.store.settings.error_loading')) }}
|
||||
|
||||
<!-- Access Denied (non-owner) -->
|
||||
<div x-show="!loading && !isOwner" 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('shield', '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">Access Restricted</h3>
|
||||
<p class="mt-1 text-sm text-yellow-700 dark:text-yellow-300">Only the merchant owner can manage loyalty program settings.</p>
|
||||
<h3 class="text-sm font-semibold text-yellow-800 dark:text-yellow-200">{{ _('loyalty.store.settings.access_restricted') }}</h3>
|
||||
<p class="mt-1 text-sm text-yellow-700 dark:text-yellow-300">{{ _('loyalty.store.settings.access_restricted_desc') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -48,12 +50,12 @@
|
||||
<!-- Delete Confirmation Modal -->
|
||||
{{ confirm_modal(
|
||||
'deleteProgramModal',
|
||||
'Delete Loyalty Program',
|
||||
'This will permanently delete the loyalty program and all associated data (cards, transactions, rewards). This action cannot be undone.',
|
||||
_('loyalty.store.settings.delete_program_title'),
|
||||
_('loyalty.store.settings.delete_program_desc'),
|
||||
'deleteProgram()',
|
||||
'showDeleteModal',
|
||||
'Delete Program',
|
||||
'Cancel',
|
||||
_('loyalty.store.settings.delete_program_confirm'),
|
||||
_('loyalty.common.cancel'),
|
||||
'danger'
|
||||
) }}
|
||||
{% endblock %}
|
||||
|
||||
@@ -4,46 +4,48 @@
|
||||
{% from 'shared/macros/alerts.html' import loading_state, error_state %}
|
||||
{% from 'shared/macros/modals.html' import modal_simple %}
|
||||
|
||||
{% block title %}Loyalty Terminal{% endblock %}
|
||||
{% 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 Terminal', subtitle='Process loyalty transactions') %}
|
||||
{% 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>
|
||||
Members
|
||||
{{ _('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>
|
||||
Analytics
|
||||
{{ _('loyalty.store.terminal.analytics') }}
|
||||
</a>
|
||||
</div>
|
||||
{% endcall %}
|
||||
|
||||
{{ loading_state('Loading loyalty terminal...') }}
|
||||
{{ loading_state(_('loyalty.store.terminal.loading')) }}
|
||||
|
||||
{{ error_state('Error loading terminal') }}
|
||||
{{ 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 Program Not Set Up</h3>
|
||||
<p class="mt-1 text-sm text-yellow-700 dark:text-yellow-300">Your merchant doesn't have a loyalty program configured yet.</p>
|
||||
<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>
|
||||
Set Up Loyalty Program
|
||||
{{ _('loyalty.common.setup_program') }}
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">Contact your administrator to complete the setup.</p>
|
||||
<p class="mt-2 text-sm text-yellow-700 dark:text-yellow-300">{{ _('loyalty.common.contact_admin_setup') }}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,7 +59,7 @@
|
||||
<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>
|
||||
Find Customer
|
||||
{{ _('loyalty.store.terminal.find_customer') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
@@ -70,7 +72,7 @@
|
||||
type="text"
|
||||
x-model="searchQuery"
|
||||
@keyup.enter="lookupCustomer()"
|
||||
placeholder="Email, phone, or card number..."
|
||||
placeholder="{{ _('loyalty.store.terminal.search_placeholder') }}"
|
||||
class="w-full pl-10 pr-4 py-3 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>
|
||||
@@ -80,7 +82,7 @@
|
||||
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 ? 'Looking up...' : 'Look Up Customer'"></span>
|
||||
<span x-text="lookingUp ? $t('loyalty.store.terminal.looking_up') : $t('loyalty.store.terminal.look_up_customer')"></span>
|
||||
</button>
|
||||
|
||||
<!-- Divider -->
|
||||
@@ -89,7 +91,7 @@
|
||||
<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">or</span>
|
||||
<span class="px-3 bg-white text-gray-500 dark:bg-gray-800 dark:text-gray-400">{{ _('loyalty.common.or') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -97,7 +99,7 @@
|
||||
<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>
|
||||
Enroll New Customer
|
||||
{{ _('loyalty.store.terminal.enroll_new_customer') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -107,7 +109,7 @@
|
||||
<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>
|
||||
Customer Found
|
||||
{{ _('loyalty.store.terminal.customer_found') }}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="p-4">
|
||||
@@ -122,7 +124,7 @@
|
||||
<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="'Card: ' + 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>
|
||||
<button @click="clearCustomer()" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
|
||||
<span x-html="$icon('x', 'w-5 h-5')"></span>
|
||||
@@ -135,7 +137,7 @@
|
||||
<!-- 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">Points Balance</p>
|
||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400" x-text="$t('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>
|
||||
@@ -144,12 +146,12 @@
|
||||
<!-- 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">Stamps</p>
|
||||
<p class="text-sm font-medium text-gray-600 dark:text-gray-400" x-text="$t('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 ? (selectedCard.stamps_until_reward + ' more for reward') : 'Ready to redeem!'"></p>
|
||||
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>
|
||||
@@ -162,18 +164,18 @@
|
||||
<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>
|
||||
Add Stamp
|
||||
{{ _('loyalty.store.terminal.add_stamp') }}
|
||||
</h4>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
Current: <span class="font-semibold" x-text="(selectedCard?.stamp_count || 0) + '/' + (program?.stamps_target || 10)"></span>
|
||||
{{ _('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">
|
||||
Add Stamp
|
||||
{{ _('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">Cooldown active</p>
|
||||
<p class="text-xs text-red-500 mt-2" x-text="$t('loyalty.store.terminal.cooldown_active')"></p>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
@@ -182,14 +184,14 @@
|
||||
<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>
|
||||
Redeem Stamps
|
||||
{{ _('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 ? 'Reward: ' + (selectedCard?.stamp_reward_description || program?.stamps_reward_description || 'Free item') : 'Not enough stamps yet'"></p>
|
||||
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">
|
||||
Redeem Stamps
|
||||
{{ _('loyalty.store.terminal.redeem_stamps') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -199,10 +201,10 @@
|
||||
<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>
|
||||
Earn Points
|
||||
{{ _('loyalty.store.terminal.earn_points') }}
|
||||
</h4>
|
||||
<div class="mb-3">
|
||||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">Purchase Amount</label>
|
||||
<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 #}
|
||||
@@ -211,12 +213,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
Points to award: <span class="font-semibold text-green-600" x-text="Math.floor((earnAmount || 0) * (program?.points_per_euro || 1))"></span>
|
||||
{{ _('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">
|
||||
Award Points
|
||||
{{ _('loyalty.store.terminal.award_points') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -225,13 +227,13 @@
|
||||
<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>
|
||||
Redeem Reward
|
||||
{{ _('loyalty.store.terminal.redeem_reward') }}
|
||||
</h4>
|
||||
<div class="mb-3">
|
||||
<label class="block text-xs text-gray-500 dark:text-gray-400 mb-1">Select Reward</label>
|
||||
<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="">Select reward...</option>
|
||||
<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>
|
||||
@@ -240,13 +242,13 @@
|
||||
</div>
|
||||
<template x-if="selectedReward">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||||
Points after: <span class="font-semibold text-orange-600" x-text="formatNumber((selectedCard?.points_balance || 0) - (getSelectedRewardPoints() || 0))"></span>
|
||||
{{ _('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">
|
||||
Redeem Reward
|
||||
{{ _('loyalty.store.terminal.redeem_reward') }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -258,7 +260,7 @@
|
||||
<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">Search for a customer to process a transaction</p>
|
||||
<p class="mt-4 text-gray-500 dark:text-gray-400">{{ _('loyalty.store.terminal.search_empty_state') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -268,25 +270,25 @@
|
||||
<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>
|
||||
Recent Transactions at This Location
|
||||
{{ _('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">Time</th>
|
||||
<th class="px-4 py-3">Customer</th>
|
||||
<th class="px-4 py-3">Type</th>
|
||||
<th class="px-4 py-3 text-right">Points</th>
|
||||
<th class="px-4 py-3">Notes</th>
|
||||
<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">
|
||||
No recent transactions
|
||||
{{ _('loyalty.store.terminal.no_recent_transactions') }}
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
@@ -312,10 +314,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Staff PIN Modal -->
|
||||
{% call modal_simple(id='pinModal', title='Enter Staff PIN', show_var='showPinEntry') %}
|
||||
{% 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">
|
||||
Enter your staff PIN to authorize this transaction.
|
||||
{{ _('loyalty.store.terminal.pin_authorize_text') }}
|
||||
</p>
|
||||
<div class="flex justify-center mb-4">
|
||||
<div class="flex gap-2">
|
||||
@@ -335,7 +337,7 @@
|
||||
</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">
|
||||
Clear
|
||||
{{ _('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">
|
||||
@@ -349,13 +351,13 @@
|
||||
<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">
|
||||
Cancel
|
||||
{{ _('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 ? 'Processing...' : 'Confirm'"></span>
|
||||
<span x-text="processing ? $t('loyalty.store.terminal.processing') : $t('loyalty.store.terminal.confirm')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{# app/modules/loyalty/templates/loyalty/storefront/dashboard.html #}
|
||||
{% extends "storefront/base.html" %}
|
||||
|
||||
{% block title %}My Loyalty - {{ store.name }}{% endblock %}
|
||||
{% block title %}{{ _('loyalty.storefront.dashboard.my_loyalty') }} - {{ store.name }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}customerLoyaltyDashboard(){% endblock %}
|
||||
|
||||
@@ -11,9 +13,9 @@
|
||||
<div class="mb-8">
|
||||
<a href="{{ base_url }}account/dashboard" class="inline-flex items-center text-sm text-gray-600 dark:text-gray-400 hover:text-primary mb-4">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back to Account
|
||||
{{ _('loyalty.storefront.dashboard.back_to_account') }}
|
||||
</a>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">My Loyalty</h1>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">{{ _('loyalty.storefront.dashboard.my_loyalty') }}</h1>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
@@ -24,13 +26,13 @@
|
||||
<!-- No Card State -->
|
||||
<div x-show="!loading && !card" class="text-center py-12">
|
||||
<span x-html="$icon('gift', 'w-16 h-16 mx-auto text-gray-300 dark:text-gray-600')"></span>
|
||||
<h2 class="mt-4 text-xl font-semibold text-gray-900 dark:text-white">Join Our Rewards Program!</h2>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">Earn points on every purchase and redeem for rewards.</p>
|
||||
<h2 class="mt-4 text-xl font-semibold text-gray-900 dark:text-white">{{ _('loyalty.storefront.dashboard.join_title') }}</h2>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">{{ _('loyalty.storefront.dashboard.join_subtitle') }}</p>
|
||||
<a href="{{ base_url }}loyalty/join"
|
||||
class="mt-6 inline-flex items-center px-6 py-3 text-sm font-medium text-white rounded-lg"
|
||||
style="background-color: var(--color-primary)">
|
||||
<span x-html="$icon('plus', 'w-5 h-5 mr-2')"></span>
|
||||
Join Now
|
||||
{{ _('loyalty.storefront.dashboard.join_now') }}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -51,19 +53,19 @@
|
||||
</div>
|
||||
|
||||
<div class="text-center py-4">
|
||||
<p class="text-sm opacity-80">Points Balance</p>
|
||||
<p class="text-sm opacity-80">{{ _('loyalty.storefront.dashboard.points_balance') }}</p>
|
||||
<p class="text-5xl font-bold" x-text="formatNumber(card?.points_balance || 0)"></p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-end mt-6">
|
||||
<div>
|
||||
<p class="text-xs opacity-70">Card Number</p>
|
||||
<p class="text-xs opacity-70">{{ _('loyalty.storefront.dashboard.card_number') }}</p>
|
||||
<p class="font-mono" x-text="card?.card_number"></p>
|
||||
</div>
|
||||
<button @click="showBarcode = true"
|
||||
class="px-4 py-2 bg-white/20 hover:bg-white/30 rounded-lg text-sm font-medium transition-colors">
|
||||
<span x-html="$icon('qr-code', 'w-5 h-5 inline mr-1')"></span>
|
||||
Show Card
|
||||
{{ _('loyalty.storefront.dashboard.show_card') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -72,22 +74,22 @@
|
||||
<!-- Quick Stats -->
|
||||
<div class="grid grid-cols-2 gap-4 mb-8">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border border-gray-200 dark:border-gray-700">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Total Earned</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.storefront.dashboard.total_earned') }}</p>
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-white" x-text="formatNumber(card?.total_points_earned || 0)"></p>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-4 border border-gray-200 dark:border-gray-700">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">Total Redeemed</p>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">{{ _('loyalty.storefront.dashboard.total_redeemed') }}</p>
|
||||
<p class="text-2xl font-bold text-gray-900 dark:text-white" x-text="formatNumber(card?.total_points_redeemed || 0)"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Available Rewards -->
|
||||
<div class="mb-8">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">Available Rewards</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">{{ _('loyalty.storefront.dashboard.available_rewards') }}</h2>
|
||||
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<template x-if="rewards.length === 0">
|
||||
<div class="col-span-full text-center py-8 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700">
|
||||
<p class="text-gray-500 dark:text-gray-400">No rewards available yet</p>
|
||||
<p class="text-gray-500 dark:text-gray-400">{{ _('loyalty.storefront.dashboard.no_rewards_yet') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
<template x-for="reward in rewards" :key="reward.id">
|
||||
@@ -103,12 +105,12 @@
|
||||
<template x-if="(card?.points_balance || 0) >= reward.points_required">
|
||||
<span class="inline-flex items-center text-sm font-medium text-green-600">
|
||||
<span x-html="$icon('check-circle', 'w-4 h-4 mr-1')"></span>
|
||||
Ready to redeem
|
||||
<span x-text="$t('loyalty.storefront.dashboard.ready_to_redeem')"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="(card?.points_balance || 0) < reward.points_required">
|
||||
<span class="text-sm text-gray-500">
|
||||
<span x-text="reward.points_required - (card?.points_balance || 0)"></span> more to go
|
||||
<span class="text-sm text-gray-500"
|
||||
x-text="$t('loyalty.storefront.dashboard.x_more_to_go', {count: reward.points_required - (card?.points_balance || 0)})">
|
||||
</span>
|
||||
</template>
|
||||
</div>
|
||||
@@ -117,23 +119,23 @@
|
||||
</div>
|
||||
<p class="mt-4 text-sm text-gray-500 dark:text-gray-400">
|
||||
<span x-html="$icon('information-circle', 'w-4 h-4 inline mr-1')"></span>
|
||||
Show your card to staff to redeem rewards in-store.
|
||||
{{ _('loyalty.storefront.dashboard.redeem_hint') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Recent Activity -->
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">Recent Activity</h2>
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white">{{ _('loyalty.storefront.dashboard.recent_activity') }}</h2>
|
||||
<a href="{{ base_url }}account/loyalty/history"
|
||||
class="text-sm font-medium hover:underline" style="color: var(--color-primary)">
|
||||
View All
|
||||
{{ _('loyalty.storefront.dashboard.view_all') }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 overflow-hidden">
|
||||
<template x-if="transactions.length === 0">
|
||||
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
|
||||
No transactions yet. Make a purchase to start earning points!
|
||||
{{ _('loyalty.storefront.dashboard.no_transactions') }}
|
||||
</div>
|
||||
</template>
|
||||
<template x-if="transactions.length > 0">
|
||||
@@ -148,7 +150,7 @@
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="font-medium text-gray-900 dark:text-white"
|
||||
x-text="tx.points_delta > 0 ? 'Points Earned' : 'Reward Redeemed'"></p>
|
||||
x-text="$t('loyalty.transactions.' + tx.transaction_type)"></p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400" x-text="formatDate(tx.transaction_at)"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,7 +168,7 @@
|
||||
<div class="mt-8" x-show="locations.length > 0">
|
||||
<h2 class="text-xl font-semibold text-gray-900 dark:text-white mb-4">
|
||||
<span x-html="$icon('map-pin', 'w-5 h-5 inline mr-2')"></span>
|
||||
Earn & Redeem Locations
|
||||
{{ _('loyalty.storefront.dashboard.earn_redeem_locations') }}
|
||||
</h2>
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow border border-gray-200 dark:border-gray-700 p-4">
|
||||
<ul class="space-y-2">
|
||||
@@ -187,7 +189,7 @@
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
|
||||
@click.self="showBarcode = false">
|
||||
<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">Your Loyalty Card</h3>
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">{{ _('loyalty.storefront.dashboard.your_loyalty_card') }}</h3>
|
||||
|
||||
<!-- Barcode Placeholder -->
|
||||
<div class="bg-white p-4 rounded-lg mb-4">
|
||||
@@ -198,7 +200,7 @@
|
||||
</div>
|
||||
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-6">
|
||||
Show this to staff when making a purchase or redeeming rewards.
|
||||
{{ _('loyalty.storefront.dashboard.show_to_staff') }}
|
||||
</p>
|
||||
|
||||
<!-- Wallet Buttons -->
|
||||
@@ -207,21 +209,21 @@
|
||||
<a :href="walletUrls.apple_wallet_url" target="_blank" rel="noopener"
|
||||
class="w-full flex items-center justify-center px-4 py-3 bg-black text-white rounded-lg hover:bg-gray-800 transition-colors">
|
||||
<span x-html="$icon('device-mobile', 'w-5 h-5 mr-2')"></span>
|
||||
Add to Apple Wallet
|
||||
{{ _('loyalty.loyalty.wallet.apple') }}
|
||||
</a>
|
||||
</template>
|
||||
<template x-if="walletUrls.google_wallet_url">
|
||||
<a :href="walletUrls.google_wallet_url" target="_blank" rel="noopener"
|
||||
class="w-full flex items-center justify-center px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
|
||||
<span x-html="$icon('device-mobile', 'w-5 h-5 mr-2')"></span>
|
||||
Add to Google Wallet
|
||||
{{ _('loyalty.loyalty.wallet.google') }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<button @click="showBarcode = false"
|
||||
class="w-full px-4 py-2 text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200">
|
||||
Close
|
||||
{{ _('loyalty.enrollment.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{# app/modules/loyalty/templates/loyalty/storefront/enroll-success.html #}
|
||||
{% extends "storefront/base.html" %}
|
||||
|
||||
{% block title %}Welcome to Rewards! - {{ store.name }}{% endblock %}
|
||||
{% block title %}{{ _('loyalty.enrollment.success.title') }} - {{ store.name }}{% endblock %}
|
||||
|
||||
{% block alpine_data %}customerLoyaltyEnrollSuccess(){% endblock %}
|
||||
|
||||
@@ -16,32 +16,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">Welcome!</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-8">You're now a member of our rewards program.</p>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-2">{{ _('loyalty.enrollment.success.title') }}</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400 mb-8">{{ _('loyalty.enrollment.success.message') }}</p>
|
||||
|
||||
<!-- Card Number Display -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mb-8">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">Your Card Number</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">{{ _('loyalty.enrollment.success.card_number') }}</p>
|
||||
<p class="text-2xl font-mono font-bold text-gray-900 dark:text-white">{{ enrolled_card_number or 'Loading...' }}</p>
|
||||
|
||||
<div x-show="walletUrls.apple_wallet_url || walletUrls.google_wallet_url"
|
||||
class="mt-6 pt-6 border-t border-gray-200 dark:border-gray-700">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
||||
Save your card to your phone for easy access:
|
||||
{{ _('loyalty.enrollment.success.wallet_prompt') }}
|
||||
</p>
|
||||
<div class="space-y-2">
|
||||
<template x-if="walletUrls.apple_wallet_url">
|
||||
<a :href="walletUrls.apple_wallet_url" target="_blank" rel="noopener"
|
||||
class="w-full flex items-center justify-center px-4 py-3 bg-black text-white rounded-lg hover:bg-gray-800 transition-colors">
|
||||
<span x-html="$icon('device-mobile', 'w-5 h-5 mr-2')"></span>
|
||||
Add to Apple Wallet
|
||||
{{ _('loyalty.loyalty.wallet.apple') }}
|
||||
</a>
|
||||
</template>
|
||||
<template x-if="walletUrls.google_wallet_url">
|
||||
<a :href="walletUrls.google_wallet_url" target="_blank" rel="noopener"
|
||||
class="w-full flex items-center justify-center px-4 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
|
||||
<span x-html="$icon('device-mobile', 'w-5 h-5 mr-2')"></span>
|
||||
Add to Google Wallet
|
||||
{{ _('loyalty.loyalty.wallet.google') }}
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
@@ -50,19 +50,19 @@
|
||||
|
||||
<!-- Next Steps -->
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 text-left mb-8">
|
||||
<h2 class="font-semibold text-gray-900 dark:text-white mb-4">What's Next?</h2>
|
||||
<h2 class="font-semibold text-gray-900 dark:text-white mb-4">{{ _('loyalty.enrollment.success.next_steps_title') }}</h2>
|
||||
<ul class="space-y-3 text-sm text-gray-600 dark:text-gray-400">
|
||||
<li class="flex items-start">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5 mr-2 text-green-500 flex-shrink-0')"></span>
|
||||
<span>Show your card number when making purchases to earn points</span>
|
||||
<span>{{ _('loyalty.enrollment.success.step_earn') }}</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5 mr-2 text-green-500 flex-shrink-0')"></span>
|
||||
<span>Check your balance online or in the app</span>
|
||||
<span>{{ _('loyalty.enrollment.success.step_balance') }}</span>
|
||||
</li>
|
||||
<li class="flex items-start">
|
||||
<span x-html="$icon('check-circle', 'w-5 h-5 mr-2 text-green-500 flex-shrink-0')"></span>
|
||||
<span>Redeem points for rewards at any of our locations</span>
|
||||
<span>{{ _('loyalty.enrollment.success.step_redeem') }}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -72,11 +72,11 @@
|
||||
<a href="{{ base_url }}account/loyalty"
|
||||
class="block w-full py-3 px-4 text-white font-semibold rounded-lg transition-colors text-center"
|
||||
style="background-color: var(--color-primary)">
|
||||
View My Loyalty Dashboard
|
||||
{{ _('loyalty.enrollment.success.view_dashboard') }}
|
||||
</a>
|
||||
<a href="{{ base_url }}"
|
||||
class="block w-full py-3 px-4 text-gray-700 dark:text-gray-300 font-medium rounded-lg border border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors text-center">
|
||||
Continue Shopping
|
||||
{{ _('loyalty.enrollment.success.continue_shopping') }}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,6 +89,7 @@ function customerLoyaltyEnrollSuccess() {
|
||||
return {
|
||||
...storefrontLayoutData(),
|
||||
walletUrls: { google_wallet_url: null, apple_wallet_url: null },
|
||||
|
||||
init() {
|
||||
// Read wallet URLs saved during enrollment (no auth needed)
|
||||
try {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{# app/modules/loyalty/templates/loyalty/storefront/enroll.html #}
|
||||
{% extends "storefront/base.html" %}
|
||||
|
||||
{% block title %}Join Loyalty Program - {{ store.name }}{% endblock %}
|
||||
{% block title %}{{ _('loyalty.enrollment.title') }} - {{ store.name }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}customerLoyaltyEnroll(){% endblock %}
|
||||
|
||||
@@ -13,8 +15,8 @@
|
||||
{% if store.logo_url %}
|
||||
<img src="{{ store.logo_url }}" alt="{{ store.name }}" class="h-16 w-auto mx-auto mb-4">
|
||||
{% endif %}
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Join Our Rewards Program!</h1>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400" x-text="'Earn ' + (program?.points_per_euro || 1) + ' point for every EUR you spend'"></p>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">{{ _('loyalty.enrollment.title') }}</h1>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400" x-text="I18n.t('loyalty.enrollment.subtitle', {points: program?.points_per_euro || 1})"></p>
|
||||
</div>
|
||||
|
||||
<!-- Loading -->
|
||||
@@ -25,8 +27,8 @@
|
||||
<!-- No Program Available -->
|
||||
<div x-show="!loading && !program" class="bg-white dark:bg-gray-800 rounded-lg shadow p-8 text-center">
|
||||
<span x-html="$icon('exclamation-circle', 'w-12 h-12 mx-auto text-yellow-500')"></span>
|
||||
<h2 class="mt-4 text-xl font-semibold text-gray-900 dark:text-white">Program Not Available</h2>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">This store doesn't have a loyalty program set up yet.</p>
|
||||
<h2 class="mt-4 text-xl font-semibold text-gray-900 dark:text-white">{{ _('loyalty.enrollment.not_available_title') }}</h2>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">{{ _('loyalty.enrollment.not_available_message') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Enrollment Form -->
|
||||
@@ -36,13 +38,13 @@
|
||||
class="p-4 text-center text-white"
|
||||
:style="'background-color: ' + (program?.card_color || 'var(--color-primary)')">
|
||||
<span x-html="$icon('gift', 'w-6 h-6 inline mr-2')"></span>
|
||||
<span class="font-semibold">Get <span x-text="program?.welcome_bonus_points"></span> bonus points when you join!</span>
|
||||
<span class="font-semibold" x-text="I18n.t('loyalty.enrollment.welcome_bonus', {points: program?.welcome_bonus_points})"></span>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="submitEnrollment" class="p-6 space-y-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Email <span class="text-red-500">*</span>
|
||||
{{ _('loyalty.enrollment.form.email') }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="email" x-model="form.email" required
|
||||
class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-700 dark:text-white"
|
||||
@@ -53,7 +55,7 @@
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
First Name <span class="text-red-500">*</span>
|
||||
{{ _('loyalty.enrollment.form.first_name') }} <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<input type="text" x-model="form.first_name" required
|
||||
class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-700 dark:text-white"
|
||||
@@ -61,7 +63,7 @@
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Last Name
|
||||
{{ _('loyalty.enrollment.form.last_name') }}
|
||||
</label>
|
||||
<input type="text" x-model="form.last_name"
|
||||
class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-700 dark:text-white"
|
||||
@@ -71,7 +73,7 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Phone (optional)
|
||||
{{ _('loyalty.enrollment.form.phone') }}
|
||||
</label>
|
||||
<input type="tel" x-model="form.phone"
|
||||
class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-700 dark:text-white"
|
||||
@@ -80,11 +82,11 @@
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Birthday (optional)
|
||||
{{ _('loyalty.enrollment.form.birthday') }}
|
||||
</label>
|
||||
<input type="date" x-model="form.birthday"
|
||||
class="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-primary focus:border-transparent dark:bg-gray-700 dark:text-white">
|
||||
<p class="mt-1 text-xs text-gray-500">For special birthday rewards</p>
|
||||
<p class="mt-1 text-xs text-gray-500">{{ _('loyalty.enrollment.form.birthday_hint') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-3 pt-2">
|
||||
@@ -93,12 +95,12 @@
|
||||
class="mt-1 w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||
style="color: var(--color-primary)">
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
I agree to the
|
||||
{{ _('loyalty.enrollment.form.terms_agree') }}
|
||||
<template x-if="program?.terms_text">
|
||||
<a href="#" @click.prevent="showTerms = true" class="underline" style="color: var(--color-primary)">Terms & Conditions</a>
|
||||
<a href="#" @click.prevent="showTerms = true" class="underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.form.terms') }}</a>
|
||||
</template>
|
||||
<template x-if="!program?.terms_text">
|
||||
<span class="underline" style="color: var(--color-primary)">Terms & Conditions</span>
|
||||
<span class="underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.form.terms') }}</span>
|
||||
</template>
|
||||
</span>
|
||||
</label>
|
||||
@@ -107,7 +109,7 @@
|
||||
class="mt-1 w-4 h-4 rounded border-gray-300 text-primary focus:ring-primary"
|
||||
style="color: var(--color-primary)">
|
||||
<span class="ml-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
Send me news and special offers
|
||||
{{ _('loyalty.enrollment.form.marketing_consent') }}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
@@ -117,13 +119,13 @@
|
||||
class="w-full py-3 px-4 text-white font-semibold rounded-lg transition-colors disabled:opacity-50"
|
||||
:style="'background-color: ' + (program?.card_color || 'var(--color-primary)')">
|
||||
<span x-show="enrolling" x-html="$icon('spinner', 'w-5 h-5 inline animate-spin mr-2')"></span>
|
||||
<span x-text="enrolling ? 'Joining...' : 'Join & Get ' + (program?.welcome_bonus_points || 0) + ' Points'"></span>
|
||||
<span x-text="enrolling ? I18n.t('loyalty.enrollment.form.joining') : I18n.t('loyalty.enrollment.form.join_button', {points: program?.welcome_bonus_points || 0})"></span>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div class="px-6 pb-6 text-center">
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
Already a member? Your points are linked to your email.
|
||||
{{ _('loyalty.enrollment.already_member') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -145,7 +147,7 @@
|
||||
@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="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">Terms & Conditions</h3>
|
||||
<h3 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">
|
||||
<span x-html="$icon('x-mark', 'w-5 h-5')"></span>
|
||||
</button>
|
||||
@@ -153,14 +155,14 @@
|
||||
<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>
|
||||
<template x-if="program?.privacy_url">
|
||||
<div class="px-4 pb-2">
|
||||
<a :href="program.privacy_url" target="_blank" class="text-sm underline" style="color: var(--color-primary)">Privacy Policy</a>
|
||||
<a :href="program.privacy_url" target="_blank" class="text-sm underline" style="color: var(--color-primary)">{{ _('loyalty.enrollment.privacy_policy') }}</a>
|
||||
</div>
|
||||
</template>
|
||||
<div class="p-4 border-t dark:border-gray-700">
|
||||
<button @click="showTerms = false"
|
||||
class="w-full py-2 px-4 text-white font-medium rounded-lg"
|
||||
:style="'background-color: ' + (program?.card_color || 'var(--color-primary)')">
|
||||
Close
|
||||
{{ _('loyalty.enrollment.close') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{# app/modules/loyalty/templates/loyalty/storefront/history.html #}
|
||||
{% extends "storefront/base.html" %}
|
||||
|
||||
{% block title %}Loyalty History - {{ store.name }}{% endblock %}
|
||||
{% block title %}{{ _('loyalty.storefront.history.title') }} - {{ store.name }}{% endblock %}
|
||||
|
||||
{% block i18n_modules %}['loyalty']{% endblock %}
|
||||
|
||||
{% block alpine_data %}customerLoyaltyHistory(){% endblock %}
|
||||
|
||||
@@ -11,10 +13,10 @@
|
||||
<div class="mb-8">
|
||||
<a href="{{ base_url }}account/loyalty" class="inline-flex items-center text-sm text-gray-600 dark:text-gray-400 hover:text-primary mb-4">
|
||||
<span x-html="$icon('arrow-left', 'w-4 h-4 mr-2')"></span>
|
||||
Back to Loyalty
|
||||
{{ _('loyalty.storefront.history.back_to_loyalty') }}
|
||||
</a>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">Transaction History</h1>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">View all your loyalty point transactions</p>
|
||||
<h1 class="text-3xl font-bold text-gray-900 dark:text-white">{{ _('loyalty.storefront.history.title') }}</h1>
|
||||
<p class="mt-2 text-gray-600 dark:text-gray-400">{{ _('loyalty.storefront.history.subtitle') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
@@ -26,15 +28,15 @@
|
||||
<div x-show="!loading && card" class="bg-white dark:bg-gray-800 rounded-lg shadow p-6 mb-8 border border-gray-200 dark:border-gray-700">
|
||||
<div class="grid grid-cols-3 gap-4 text-center">
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Current Balance</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.storefront.history.current_balance') }}</p>
|
||||
<p class="text-2xl font-bold" style="color: var(--color-primary)" x-text="formatNumber(card?.points_balance || 0)"></p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Total Earned</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.storefront.history.total_earned') }}</p>
|
||||
<p class="text-2xl font-bold text-green-600" x-text="formatNumber(card?.total_points_earned || 0)"></p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Total Redeemed</p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">{{ _('loyalty.storefront.history.total_redeemed') }}</p>
|
||||
<p class="text-2xl font-bold text-orange-600" x-text="formatNumber(card?.total_points_redeemed || 0)"></p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -45,7 +47,7 @@
|
||||
<template x-if="transactions.length === 0">
|
||||
<div class="p-12 text-center">
|
||||
<span x-html="$icon('receipt-refund', 'w-12 h-12 mx-auto text-gray-300 dark:text-gray-600')"></span>
|
||||
<p class="mt-4 text-gray-500 dark:text-gray-400">No transactions yet</p>
|
||||
<p class="mt-4 text-gray-500 dark:text-gray-400">{{ _('loyalty.storefront.history.no_transactions') }}</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -61,7 +63,7 @@
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<p class="font-medium text-gray-900 dark:text-white"
|
||||
x-text="getTransactionLabel(tx)"></p>
|
||||
x-text="$t('loyalty.transactions.' + tx.transaction_type)"></p>
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
<span x-text="formatDateTime(tx.transaction_at)"></span>
|
||||
<span x-show="tx.store_name" class="ml-2">
|
||||
@@ -76,7 +78,7 @@
|
||||
:class="tx.points_delta > 0 ? 'text-green-600' : 'text-orange-600'"
|
||||
x-text="(tx.points_delta > 0 ? '+' : '') + formatNumber(tx.points_delta)"></p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
Balance: <span x-text="formatNumber(tx.balance_after)"></span>
|
||||
{{ _('loyalty.storefront.history.balance') }} <span x-text="formatNumber(tx.balance_after)"></span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -88,14 +90,14 @@
|
||||
<div x-show="pagination.pages > 1" class="px-4 py-3 border-t border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||
<button @click="previousPage()" :disabled="pagination.page <= 1"
|
||||
class="px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50">
|
||||
Previous
|
||||
{{ _('loyalty.storefront.history.previous') }}
|
||||
</button>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
||||
Page <span x-text="pagination.page"></span> of <span x-text="pagination.pages"></span>
|
||||
<span class="text-sm text-gray-500 dark:text-gray-400"
|
||||
x-text="$t('loyalty.storefront.history.page_x_of_y', {page: pagination.page, pages: pagination.pages})">
|
||||
</span>
|
||||
<button @click="nextPage()" :disabled="pagination.page >= pagination.pages"
|
||||
class="px-3 py-1 text-sm border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 disabled:opacity-50">
|
||||
Next
|
||||
{{ _('loyalty.storefront.history.next') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user