1/** 2 * Zephyr Promo Code Component 3 * 4 * Alpine.js store and helper functions for the promo code modal 5 */ 6 7// Promo code configuration (set from PHP template) 8var promoCodeConfig = { 9 applyCouponUrl: '', 10 removeCouponUrl: '', 11 formKey: '' 12}; 13 14document.addEventListener('alpine:init', function() { 15 // Promo code store 16 Alpine.store('promo', { 17 isOpen: false, 18 code: '', 19 isLoading: false, 20 error: '', 21 appliedCode: null, 22 success: false, 23 successMessage: '', 24 discount: '', 25 26 open: function() { 27 this.isOpen = true; 28 this.error = ''; 29 this.code = ''; 30 this.success = false; 31 this.successMessage = ''; 32 this.discount = ''; 33 // Focus input after modal opens 34 var self = this; 35 setTimeout(function() { 36 var input = document.getElementById('promo-code-input'); 37 if (input) input.focus(); 38 }, 100); 39 }, 40 41 close: function() { 42 var self = this; 43 this.isOpen = false; 44 // Reset state after modal transition completes 45 setTimeout(function() { 46 self.error = ''; 47 self.code = ''; 48 self.isLoading = false; 49 self.success = false; 50 self.successMessage = ''; 51 self.discount = ''; 52 }, 200); 53 }, 54 55 apply: function() { 56 var self = this; 57 var code = this.code.trim(); 58 59 if (!code) { 60 this.error = 'Please enter a promo code'; 61 return; 62 } 63 64 this.isLoading = true; 65 this.error = ''; 66 67 fetch(promoCodeConfig.applyCouponUrl, { 68 method: 'POST', 69 headers: { 70 'Content-Type': 'application/x-www-form-urlencoded', 71 'X-Requested-With': 'XMLHttpRequest' 72 }, 73 credentials: 'same-origin', 74 body: 'coupon_code=' + encodeURIComponent(code) + '&form_key=' + promoCodeConfig.formKey 75 }) 76 .then(function(response) { 77 return response.json(); 78 }) 79 .then(function(data) { 80 self.isLoading = false; 81 if (data.success) { 82 // Show success state 83 self.appliedCode = code.toUpperCase(); 84 self.success = true; 85 self.successMessage = 'Promo code applied!'; 86 self.discount = data.discount || ''; 87 88 // Refresh cart to show updated totals 89 if (Alpine.store('cart')) { 90 Alpine.store('cart').refresh(); 91 } 92 // Notify the full cart page component to reload its data 93 window.dispatchEvent(new CustomEvent('cart-updated')); 94 } else { 95 self.error = data.error || 'Invalid promo code'; 96 } 97 }) 98 .catch(function(error) { 99 self.isLoading = false; 100 self.error = 'An error occurred. Please try again.'; 101 console.error('Promo code error:', error); 102 }); 103 }, 104 105 remove: function() { 106 var self = this; 107 this.isLoading = true; 108 109 fetch(promoCodeConfig.removeCouponUrl, { 110 method: 'POST', 111 headers: { 112 'Content-Type': 'application/x-www-form-urlencoded', 113 'X-Requested-With': 'XMLHttpRequest' 114 }, 115 credentials: 'same-origin', 116 body: 'form_key=' + promoCodeConfig.formKey 117 }) 118 .then(function(response) { 119 return response.json(); 120 }) 121 .then(function(data) { 122 self.isLoading = false; 123 if (data.success) { 124 self.appliedCode = null; 125 // Refresh cart to show updated totals 126 if (Alpine.store('cart')) { 127 Alpine.store('cart').refresh(); 128 } 129 // Notify the full cart page component to reload its data 130 window.dispatchEvent(new CustomEvent('cart-updated')); 131 } 132 }) 133 .catch(function(error) { 134 self.isLoading = false;
135 console.error('Remove coupon error:', error); 136 }); 137 }, 138 139 // Set applied code from cart data (called when cart refreshes) 140 setAppliedCode: function(code) { 141 this.appliedCode = code ? code.toUpperCase() : null; 142 } 143 }); 144});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.