PageSourceSearch

https://www.lasprecious.design/assets/js/promotion/vouchers.js?v1.3

js lasprecious.design collected 2026-09-29 01:03:13 UTC 12,410 bytes, 363 lines download raw bytes

1document.addEventListener('DOMContentLoaded', () => {
2  class VouchersModal extends HTMLElement {
3    constructor() {
4      super();
5      this.voucherModal = document.getElementById('vouchers-modal');
6      this.voucherModalBackdrop = document.getElementById('vouchers-modal-backdrop');
7      this.onBodyClick = this.handleBodyClick.bind(this);
8
9      this.voucherModal.addEventListener('keyup', (evt) => evt.code === 'Escape' && this.close());
10      this.querySelectorAll('.vouchers-modal__close').forEach((closeButton) =>
11        closeButton.addEventListener('click', this.close.bind(this))
12      );
13
14      this.eventHandlers = []; // Store event handlers to remove them later
15    }
16
17    open() {
18      this.voucherModal.classList.add('animate', 'active');
19      this.voucherModalBackdrop.classList.add('active');
20
21      this.voucherModal.addEventListener('transitionend', () => {
22        this.voucherModal.focus();
23        trapFocus(this.voucherModal);
24      }, { once: true });
25
26      document.body.style.overflow = 'hidden';
27      document.body.addEventListener('click', this.onBodyClick, { capture: true });
28
29      // Render voucher placeholders
30      this.initVoucherWidget();
31    }
32
33    close() {
34      this.voucherModal.classList.remove('active');
35      this.voucherModalBackdrop.classList.remove('active');
36
37      document.body.style.overflow = '';
38      document.body.removeEventListener('click', this.onBodyClick, { capture: true });
39
40      // Clear voucher placeholders
41      const $voucherElements = document.querySelectorAll("vouchers-modal [data-voucher-widget]");
42      $voucherElements.forEach(element => element.innerHTML = "");
43
44      // Remove all event listeners attached to buttons
45      this.eventHandlers.forEach(({ button, handler }) => {
46        button.removeEventListener("click", handler);
47      });
48
49      // Clear stored event handlers
50      this.eventHandlers = [];
51    }
52
53    handleBodyClick(evt) {
54      const target = evt.target;
55      if (target !== this.voucherModal && !target.closest('vouchers-modal')) {
56        this.close();
57      }
58    }
59
60    setActiveElement(element) {
61      this.activeElement = element;
62    }
63
64    // Function to process all voucher placeholders with [data-voucher-widget]
65    initVoucherWidget() {
66      const $voucherElements = $("vouchers-modal [data-voucher-widget]");
67
68      $voucherElements.each((index, element) => {
69        const voucherData = JSON.parse(element.getAttribute("data-voucher-widget"));
70        const voucherWidget = createVoucherWidget(this.formatVoucherData(voucherData));
71
72        element.setAttribute("data-type", voucherData.is_enabled ? "active" : "inactive");
73        if (!voucherData.is_enabled) {
74          element.classList.add("disabled");
75        }
76
77        element.appendChild(voucherWidget);
78
79        // Add event listener to button
80        setTimeout(() => {
81          const button = element.querySelector(".voucher-widget-button");
82          if (button) {
83            const clickHandler = (event) => {
84              this.onButtonClick(event, voucherData);
85            };
86            button.addEventListener("click", clickHandler);
87            // Store reference for removal later
88            this.eventHandlers.push({ button, handler: clickHandler });
89          }
90        }, 1000);
91      });
92    }
93
94    formatVoucherData(data) {
95      this.voucherData = {
96        title: data.title,
97        details: data,
98      }
99
100      return {
101        "discount": data.title,
102        "isShowQuantity": true,
103        "isHoverable": false,
104        "condition": getVoucherCondition(this.voucherData),
105        "badge": getPrerequisiteCondition(this.voucherData, _getPrerequisiteConditionTranslation),
106        "expiry": data.is_enabled ? " " : getVoucherUnavailableText(data, _getVoucherErrorTranslation),
107        "highlightExpiry": {
108          "isHighlightExpiry": data.is_enabled ? false : true,
109          "highlightType": "danger",
110        },
111        "buttonText": _getVoucherButtonTextTranslation.apply,
112        "quantity": data.discount_codes_count || 1,
113        "isShowButton": data.is_enabled,
114        "icon": getVoucherIcon(this.voucherData),
115      };
116    }
117
118    onButtonClick(event, data) {
119      event.preventDefault();
120
121      let code = data?.soonest_expiring_discount_code
122        ?? data.discount_codes[0]
123        ?? "";
124      this.hideErrorMsg();
125
126      const allVouchers = document.querySelectorAll("vouchers-modal [data-voucher-widget]");
127      const button = event.currentTarget;
128
129      if (code && button) {
130        allVouchers.forEach(voucher => voucher.style.pointerEvents = "none");
131        button.classList.add('loading')
132        fetch('/new_cart/voucher', {
133          method: 'POST',
134          headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest", },
135          body: JSON.stringify({
136            category: 'create',
137            voucher_code: code
138          }),
139        })
140          .then(response => response.json())
141          .then((data) => {
142            console.log('/new_cart/voucher', data);
143            button.classList.remove('loading');
144            allVouchers.forEach(voucher => voucher.style.removeProperty("pointer-events"));
145
146            if (data.error && data.error.message) this.renderErrorMsg(data.error.message)
147            if (data.cart_content) {
148              document.querySelector('#cart-template , #CartTemplate').innerHTML = data.cart_content;
149              document.querySelector('vouchers-modal').close()
150            }
151          })
152      }
153
154    }
155
156    renderErrorMsg(html) {
157      this.querySelector('.form__message').classList.remove('hidden')
158      this.querySelector('.js-error-content').innerHTML = html
159    }
160
161    hideErrorMsg() {
162      this.querySelector('.form__message').classList.add('hidden')
163    }
164  }
165  customElements.define('vouchers-modal', VouchersModal);
166
167  // TODO: To be removed
168  class VouchersList extends HTMLElement {
169    constructor() {
170      super();
171      this.radioButtons = this.querySelectorAll('input[name="voucher_code"]');
172      this.button = this.querySelector('button');
173
174      this.button.addEventListener('click', this.onButtonClick.bind(this))
175      this.radioButtons.forEach(radioButton => radioButton.addEventListener('change', (event) => {
176        this.hideErrorMsg()
177      }))
178    }
179
180    onButtonClick(event) {
181      event.preventDefault();
182      this.hideErrorMsg();
183      let radioValue
184      for (const radioButton of this.radioButtons) {
185        if (radioButton.checked) {
186          radioValue = radioButton.value;
187          break;
188        }
189      }
190
191      if (radioValue) {
192        this.button.classList.add('loading', 'btn--loading')
193        fetch('/new_cart/voucher', {
194          method: 'POST',
195          headers: { "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest", },
196          body: JSON.stringify({
197            category: 'create',
198            voucher_code: radioValue
199          }),
200        })
201          .then(response => response.json())
202          .then((data) => {
203            console.log('/new_cart/voucher', data);
204            this.button.classList.remove('loading', 'btn--loading')
205            if (data.error && data.error.message) this.renderErrorMsg(data.error.message)
206            if (data.cart_content) {
207              document.querySelector('#cart-template , #CartTemplate').innerHTML = data.cart_content;
208              document.querySelector('vouchers-modal').close()
209            }
210          })
211      }
212
213    }
214
215    renderErrorMsg(html) {
216      this.querySelector('.form__message').classList.remove('hidden')
217      this.querySelector('.js-error-content').innerHTML = html
218    }
219
220    hideErrorMsg() {
221      this.querySelector('.form__message').classList.add('hidden')
222    }
223
224    clearSelection() {
225      for (let radioButton of this.radioButtons) {
226        if (radioButton.checked) {
227          radioButton.checked = false;
228          break;
229        }
230      }
231    }
232  }
233  customElements.define('vouchers-list', VouchersList);
234
235
236  if (typeof DiscountInput == "undefined") {
237    class DiscountInput extends HTMLElement {
238      constructor() {
239        super();
240        this.input = this.querySelector('input');
241        this.button = this.querySelector('button');
242
243        this.button.addEventListener('click', this.onButtonClick.bind(this))
244        this.input.addEventListener('keypress', (event) => {
245          if (event.key == 'Enter' || event.keyCode == 13) {
246            event.preventDefault()
247            this.onButtonClick(event)
248          }
249          this.hideErrorMsg()
250        })
251      }
252
253      onButtonClick(event) {
254        event.preventDefault();
255        this.hideErrorMsg();
256
257        if (this.input.value != '') {
258          this.button.classList.add('loading', 'btn--loading')
259          fetch('/new_cart/voucher', {
260            method: 'POST',
261            headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest', },
262            body: JSON.stringify({
263              category: 'create',
264              voucher_code: this.input.value
265            }),
266          })
267            .then(response => response.json())
268            .then((data) => {
269              this.button.classList.remove('loading', 'btn--loading')
270              if (data.error && data.error.message) this.renderErrorMsg(data.error.message)
271              if (data.cart_content) {
272                document.querySelector('#cart-template , #CartTemplate').innerHTML = data.cart_content;
273                document.querySelector('vouchers-modal').close()
274              }
275            })
276        }
277      }
278
279      renderErrorMsg(html) {
280        const formMessage = this.querySelector('.form__message');
281        const errorContent = this.querySelector('.js-error-content');
282
283        if (
284          typeof html === "string" &&
285          /log in/i.test(html) &&
286          !/voucher-error__login/.test(html)
287        ) {
288          const loginUrl = "/account/login?redirect_uri=" + encodeURIComponent(window.location.pathname + window.location.search);
289          html = html.replace(/log in/i, `<a href="${loginUrl}" class="voucher-error__login">$&</a>`);
290        }
291
292        formMessage.classList.remove('hidden');
293        errorContent.innerHTML = html;
294      }
295
296      hideErrorMsg() {
297        this.querySelector('.form__message').classList.add('hidden')
298      }
299    }
300    customElements.define('discount-input', DiscountInput);
301  }
302
303  // Fallback trapFocus for old themes that don't define it
304  if (typeof trapFocusHandlers === 'undefined' || typeof trapFocus !== 'function') {
305    var trapFocusHandlers = {};
306
307    function getFocusableElements(container) {
308      return Array.from(
309        container.querySelectorAll(
310          'summary, a[href], button:enabled, [tabindex]:not([tabindex^="-"]), [draggable], area, input:not([type=hidden]):enabled, select:enabled, textarea:enabled, object, iframe'
311        )
312      );
313    }
314
315    function removeTrapFocus(elementToFocus = null) {
316      document.removeEventListener('focusin', trapFocusHandlers.focusin);
317      document.removeEventListener('focusout', trapFocusHandlers.focusout);
318      document.removeEventListener('keydown', trapFocusHandlers.keydown);
319      if (elementToFocus) elementToFocus.focus();
320    }
321
322    function trapFocus(container, elementToFocus = container) {
323      var elements = getFocusableElements(container);
324      var first = elements[0];
325      var last = elements[elements.length - 1];
326
327      removeTrapFocus();
328
329      trapFocusHandlers.focusin = (event) => {
330        if (
331          event.target !== container &&
332          event.target !== last &&
333          event.target !== first
334        )
335          return;
336
337        document.addEventListener('keydown', trapFocusHandlers.keydown);
338      };
339
340      trapFocusHandlers.focusout = function() {
341        document.removeEventListener('keydown', trapFocusHandlers.keydown);
342      };
343
344      trapFocusHandlers.keydown = function(event) {
345        if (event.code.toUpperCase() !== 'TAB') return;
346        if (event.target === last && !event.shiftKey) {
347          event.preventDefault();
348          first.focus();
349        }
350        if ((event.target === container || event.target === first) && event.shiftKey) {
351          event.preventDefault();
352          last.focus();
353        }
354      };
355
356      document.addEventListener('focusout', trapFocusHandlers.focusout);
357      document.addEventListener('focusin', trapFocusHandlers.focusin);
358
359      elementToFocus.focus();
360    }
361  }
362
363})

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.