wickes-css2 2.105.0-develop.2 → 2.105.0-gift-cards.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/Readme.md +4 -2
  2. package/build/css/components/checkout-payment-details-v2.css +1 -1
  3. package/build/css/main.css +1 -1
  4. package/build/css/pages/checkout-new.css +1 -1
  5. package/build/css/pages/page_checkout_delivery-new.css +1 -1
  6. package/build/img/giftcard.svg +28 -0
  7. package/build/js/account-members.min.js +1 -1
  8. package/build/js/add-project-list-id.min.js +1 -1
  9. package/build/js/address-book.min.js +1 -1
  10. package/build/js/basket.min.js +2 -2
  11. package/build/js/bundle.min.js +1 -1
  12. package/build/js/change-password.min.js +1 -1
  13. package/build/js/checkout.min.js +2 -2
  14. package/build/js/emulation.min.js +291 -16
  15. package/build/js/general.bundle.min.js +1 -1
  16. package/build/js/gift-cards.min.js +1 -0
  17. package/build/js/kitchen/kitchen-plp.min.js +1 -1
  18. package/build/js/merged-checkout.min.js +2 -2
  19. package/build/js/page/basket-v2.js +2 -0
  20. package/build/js/page/components/billie-modal.js +31 -0
  21. package/build/js/page/components/gift-cards.js +915 -0
  22. package/build/js/page/components/order-summary.js +42 -25
  23. package/build/js/page/components/toggle-password-visibility.js +22 -0
  24. package/build/js/page/utils/gift-cards-utils.js +188 -0
  25. package/build/js/page/utils/input-handling.js +92 -0
  26. package/build/js/page/utils/show-hide-input.js +28 -0
  27. package/build/js/page/utils/validation.js +46 -1
  28. package/build/js/pdp.bundle.min.js +1 -1
  29. package/build/js/personal-details.min.js +1 -1
  30. package/build/js/plp.bundle.min.js +1 -1
  31. package/build/js/project-list.min.js +44 -1
  32. package/build/js/quiz.min.js +1 -1
  33. package/build/js/track-my-order.min.js +1 -1
  34. package/package.json +5 -3
  35. package/src/components/checkout-payment-details-v2.hbs +3 -2
  36. package/src/components/generate-project-id.hbs +1 -1
  37. package/src/components/gift-cards-hint.hbs +9 -0
  38. package/src/components/gift-cards.hbs +90 -0
  39. package/src/components/giftcard-chip.hbs +23 -0
  40. package/src/components/giftcard-summary.hbs +6 -0
  41. package/src/components/shopping-list-with-share-list-v2.hbs +28 -10
  42. package/src/components/shopping-list.hbs +5 -5
  43. package/src/elements/form-row.hbs +1 -1
  44. package/src/elements/input.hbs +31 -2
  45. package/src/img/giftcard.svg +28 -0
  46. package/src/js/components/general/notification.js +2 -1
  47. package/src/js/components/pdp-billie-modal-scroll-reset.js +53 -0
  48. package/src/js/emulation/billie-modal.js +17 -0
  49. package/src/js/emulation/checkout-data.js +35 -0
  50. package/src/js/emulation/checkout-payment-details.js +25 -16
  51. package/src/js/emulation/forms.js +7 -0
  52. package/src/js/emulation/gift-cards.js +205 -0
  53. package/src/js/page/basket-v2.js +2 -0
  54. package/src/js/page/components/billie-modal.js +31 -0
  55. package/src/js/page/components/gift-cards.js +915 -0
  56. package/src/js/page/components/order-summary.js +42 -25
  57. package/src/js/page/components/toggle-password-visibility.js +22 -0
  58. package/src/js/page/utils/gift-cards-utils.js +188 -0
  59. package/src/js/page/utils/input-handling.js +92 -0
  60. package/src/js/page/utils/show-hide-input.js +28 -0
  61. package/src/js/page/utils/validation.js +46 -1
  62. package/src/layouts/checkout.hbs +1 -5
  63. package/src/page_my-account_change-password.html +1 -0
  64. package/src/page_my-shopping-list-hide-download.html +1 -1
  65. package/src/page_payment-details-with-gift-card.html +8 -5
  66. package/src/page_project-list-with-new-share-popup-android.html +14 -0
  67. package/src/page_project-list-with-new-share-popup-ios.html +14 -0
  68. package/src/scss/components/_gift-cards.scss +360 -0
  69. package/src/scss/components/_shared-shopping-list.scss +22 -0
  70. package/src/scss/components/_shopping-list.scss +18 -8
  71. package/src/scss/components/checkout-payment-details-v2.scss +2 -0
  72. package/src/scss/pages/_checkout-confirmation-new.scss +49 -21
  73. package/src/scss/pages/page_checkout_delivery-new.scss +26 -0
  74. package/src/sitemap.html +9 -1
  75. package/src/js/components/toggle-password-visibility.js +0 -58
@@ -0,0 +1,915 @@
1
+ import {initializeInputToggle} from "../utils/show-hide-input";
2
+ import {updateOrderSummary} from './order-summary';
3
+ import {hideLoader, showLoader, appendLoader} from "../utils/loader";
4
+ import {
5
+ ADD_GIFT_CARD,
6
+ buildHintContext,
7
+ buildSummaryEqualMap,
8
+ formatGc16,
9
+ isZeroAmount,
10
+ linkifyClickHere,
11
+ MESSAGE_BY_CODE,
12
+ OK_CODE,
13
+ PAY_WITH_GIFT_CARD,
14
+ payWithGiftCard,
15
+ REMOVE_GIFT_CARD,
16
+ removeGiftCard,
17
+ verifyGiftCard,
18
+ PAY_MESSAGE_BY_CODE,
19
+ DEFAULT_ERROR_MESSAGE,
20
+ DEFAULT_PAY_ERROR_MESSAGE
21
+ } from "../utils/gift-cards-utils";
22
+
23
+ import { clearErrorText, validateExactDigits } from "../utils/validation";
24
+ import {
25
+ createGcNumberInputHandler,
26
+ digitsOnlyKeypress,
27
+ handleGcNumberBlur,
28
+ handleGcPinInput,
29
+ handleGcPinKeypress,
30
+ handleGcPinBlur,
31
+ } from '../utils/input-handling';
32
+
33
+ const Handlebars = require('hbsfy/runtime');
34
+
35
+ Handlebars.registerPartial('info-icon', require("../../../components/info-icon.hbs"));
36
+ Handlebars.registerPartial('loader', require("../../../elements/loader.hbs"));
37
+
38
+ const giftCardsHint = require("../../../components/gift-cards-hint.hbs");
39
+ const giftCardChip = require("../../../components/giftcard-chip.hbs");
40
+ const giftcardSummary = require("../../../components/giftcard-summary.hbs");
41
+ const notificationTpl = require("../../../elements/notifications.hbs");
42
+
43
+ var Wick = window.Wick || {};
44
+
45
+ Wick.GiftCard = {
46
+ ADD_GIFT_CARD,
47
+ REMOVE_GIFT_CARD,
48
+ PAY_WITH_GIFT_CARD,
49
+ el: {
50
+ $blocks: $('.giftcard'),
51
+ block: '.giftcard',
52
+ toggle: '#giftcard-toggle',
53
+ panel: '#giftcard-inline',
54
+ hint: '.giftcard-hint',
55
+ closeBtn: '.giftcard-inline__close',
56
+ form: '.giftcard-inline__form',
57
+ number: 'input[name="giftcard-number"]',
58
+ pin: 'input[name="giftcard-pin"]',
59
+ note: '.giftcard-note',
60
+ field: '.giftcard__field',
61
+ errorText: '.giftcard__error-text',
62
+ fieldError: 'giftcard-field--error',
63
+ showToggle: '.toggle-show',
64
+ loader: '.giftcard__container .loader-wrapper',
65
+ success: '#giftcard-applied',
66
+ container: '.giftcard__container',
67
+ paymentForm: '.checkout-payment-details__payment-method',
68
+ billieField: '.form-row__field.form-row__field-billie',
69
+ klarnaField: '.form-row__field.form-row__field-klarna',
70
+ clearpayField: '.form-row__field.form-row__field-clearpay',
71
+ appleField: '.form-row__field.form-row__field-apple',
72
+ googleField: '.form-row__field.form-row__field-google',
73
+ paypalField: '.form-row__field.form-row__field-paypal',
74
+ cardField: '.form-row__field.form-row__card-payment',
75
+ summaryHint: '.giftcard-summary__hint',
76
+ chipList: '.giftcard-chip-list',
77
+ summary: '.giftcard-summary',
78
+ chip: '.giftcard-chip',
79
+ giftcardRow: '.giftcard-row',
80
+ checkoutContainer: '.checkout-payment-details',
81
+ formRow: '.form-row',
82
+ iconInfo: '.icon-info-component',
83
+ toggleIcon: 'svg, i',
84
+ iconEye: 'fa-eye',
85
+ iconEyeSlash: 'fa-eye-slash',
86
+ iconSvgEyeSlash: 'eye-slash',
87
+ passwordToggleEvent: 'input.passwordToggle',
88
+ ariaInvalidAttr: 'aria-invalid',
89
+ typeAttr: 'type',
90
+ dataIconAttr: 'data-icon',
91
+ svg: 'svg',
92
+ expandedAttr: 'aria-expanded',
93
+ giftcardBtn: '.giftcard__btn',
94
+ jsGiftcardAdd: '.js-giftcard-add',
95
+ giftcardChipClose: '.giftcard-chip__close',
96
+ body: 'body',
97
+ loaderChip: '.loader-wrapper',
98
+ chipListLoader: '.giftcard-chip-list > .loader-wrapper',
99
+ notification: '.giftcard-inline .notification',
100
+ giftCardInline: '.giftcard-inline',
101
+ notificationRoot: '.notification',
102
+ inlineHeader: '.giftcard-inline__header',
103
+ ctaSelector: '.btn-enter-details',
104
+ btnDetails: 'button',
105
+ btnText: '.btn__text',
106
+ btnEnterDetails: '.btn-enter-details',
107
+ checkoutPaymentFields: '.billing-address .checkout-payment-details__descr',
108
+ pageLoader: '.loader-wrapper.page-loader',
109
+ cardRadio: '#checkout-payment-details-card, #_checkout-payment-details-card_card',
110
+ billieInfo: '.checkout-payment-details__billie',
111
+ klarnaInfo: '.checkout-payment-details__klarna',
112
+ billingAddress: '.billing-address',
113
+ altPaymentRowsAttr: '[data-apple],[data-google],[data-paypal],[data-klarna],[data-billie],[data-clearpay],[data-existing-card]',
114
+ hiddenCard: 'checkout-payment-details__card-details_hidden',
115
+ cardDetails: '.checkout-payment-details__card-details',
116
+ },
117
+
118
+ messages: {
119
+ numberMessage: 'Gift Card should consist of 16 digits',
120
+ pinMessage: 'Gift Card PIN should consist of 8 digits',
121
+ ctaDefaultText: 'Enter card details',
122
+ ctaGiftcardText: 'Pay with Gift Card',
123
+ },
124
+
125
+ maxChips: 3,
126
+ successTimer: null,
127
+ pendingServerGiftCards: null,
128
+ altPaymentInfosLocked: false,
129
+ altPaymentPanelsLocked: false,
130
+ summaryTotal: null,
131
+
132
+ resetPinToggle($root) {
133
+ const {
134
+ pin,
135
+ field,
136
+ showToggle,
137
+ toggleIcon,
138
+ iconEye,
139
+ iconEyeSlash,
140
+ iconSvgEyeSlash,
141
+ passwordToggleEvent,
142
+ typeAttr,
143
+ dataIconAttr,
144
+ svg
145
+ } = this.el;
146
+
147
+ const $pinField = $root.find(pin).closest(field);
148
+ const $pinInput = $pinField.find(pin).first();
149
+
150
+ $pinInput.attr(typeAttr, 'password');
151
+
152
+ const $toggle = $pinField.find(showToggle).first();
153
+ const $icon = $toggle.find(toggleIcon).first();
154
+
155
+ if ($icon.length) {
156
+ $icon.removeClass(`${iconEye} ${iconEyeSlash}`).addClass(iconEyeSlash);
157
+ if ($icon.is(svg) && $icon.attr(dataIconAttr)) {
158
+ $icon.attr(dataIconAttr, iconSvgEyeSlash);
159
+ }
160
+ }
161
+
162
+ const hasValue = ($pinInput.val() || '').length > 0;
163
+ $toggle.toggle(hasValue);
164
+
165
+ $pinInput.trigger(passwordToggleEvent);
166
+ },
167
+
168
+ resetForm($root) {
169
+ const { number, pin, note } = this.el;
170
+ const $num = $root.find(number);
171
+ const $pinInput = $root.find(pin);
172
+
173
+ $num.val('');
174
+ $pinInput.val('');
175
+ clearErrorText($num, this.el.field, this.el.errorText, this.el.fieldError, this.el.ariaInvalidAttr);
176
+ clearErrorText($pinInput, this.el.field, this.el.errorText, this.el.fieldError, this.el.ariaInvalidAttr);
177
+ $root.find(note).remove();
178
+
179
+ this.hideBanner($root);
180
+ this.resetPinToggle($root);
181
+ },
182
+
183
+ open($root) {
184
+ const { toggle, panel, hint, pin, expandedAttr, passwordToggleEvent } = this.el;
185
+ this.hideBanner($root);
186
+ $root.show();
187
+
188
+ $root.find(panel).prop('hidden', false);
189
+ $root.find(toggle).attr(expandedAttr, 'true').hide();
190
+ $root.find(hint).hide();
191
+ $root.find(pin).trigger(passwordToggleEvent);
192
+ },
193
+
194
+ close($root) {
195
+ const { toggle, panel, loader, expandedAttr } = this.el;
196
+ const $toggle = $root.find(toggle);
197
+ const $panel = $root.find(panel);
198
+ const $loader = $root.find(loader);
199
+
200
+ showLoader($loader);
201
+
202
+ setTimeout(() => {
203
+ hideLoader($loader);
204
+
205
+ $panel.prop('hidden', true);
206
+ $toggle.attr(expandedAttr, 'false').show().trigger('focus');
207
+ this.updateHintVisibility($root);
208
+ }, 1000);
209
+ },
210
+
211
+ showSubmitSpinner($root) {
212
+ const { panel, toggle, hint, expandedAttr } = this.el;
213
+ $root.find(panel).prop('hidden', true);
214
+ $root.find(toggle).attr(expandedAttr, 'false').show().trigger('focus');
215
+ $root.find(hint).show();
216
+ this.showSuccess($root);
217
+ },
218
+
219
+ handleToggleClick(e) {
220
+ e.preventDefault();
221
+ this.open($(e.currentTarget).closest(this.el.$blocks));
222
+ },
223
+
224
+ handleCloseClick(e) {
225
+ e.preventDefault();
226
+ const $root = $(e.currentTarget).closest(this.el.$blocks);
227
+ this.resetForm($root);
228
+ this.close($root);
229
+ },
230
+
231
+ finalizeSuccess($root, $success) {
232
+ let { panel, toggle, hint, expandedAttr } = this.el;
233
+
234
+ if ($success && $success.length) {
235
+ $success.attr('hidden', 'hidden').removeClass('fade show').off('transitionend');
236
+ }
237
+
238
+ this.resetForm($root);
239
+ $root.find(panel).prop('hidden', true);
240
+ $root.find(toggle).attr(expandedAttr, 'false').show();
241
+ $root.find(hint).show();
242
+
243
+ if (this.pendingServerGiftCards) {
244
+ this.renderGiftCardsFromList($root, this.pendingServerGiftCards);
245
+ this.pendingServerGiftCards = null;
246
+ }
247
+
248
+ this.updateHintVisibility($root);
249
+ this.successTimer = null;
250
+ },
251
+
252
+ showSuccess($root) {
253
+ const { success} = this.el;
254
+
255
+ if (this.successTimer) {
256
+ clearTimeout(this.successTimer);
257
+ this.successTimer = null;
258
+ }
259
+
260
+ $root.hide();
261
+ const $success = $(success);
262
+ $success.removeAttr('hidden').addClass('fade');
263
+ $success[0] && $success[0].offsetWidth;
264
+ $success.addClass('show');
265
+
266
+ this.successTimer = setTimeout(() => {
267
+ $success.removeClass('show');
268
+ const handler = () => this.finalizeSuccess($root, $success);
269
+
270
+ $success.on('transitionend', handler);
271
+ setTimeout(handler, 200)
272
+ }, 3000);
273
+ },
274
+
275
+ handleFormSubmit(e) {
276
+ const { number, block, giftcardRow, loaderChip } = this.el;
277
+ e.preventDefault();
278
+
279
+ const $root = $(e.currentTarget).closest(block);
280
+ this.hideBanner($root);
281
+
282
+ const $num = $root.find(number);
283
+ $num.val(formatGc16($num.val()));
284
+
285
+ const $numInput = $root.find(this.el.number);
286
+ const $pinInput = $root.find(this.el.pin);
287
+
288
+ const numberBlurOpts = {
289
+ fieldSelector: this.el.field,
290
+ errorTextSelector: this.el.errorText,
291
+ errorClass: this.el.fieldError,
292
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
293
+ numberMessage: this.messages.numberMessage,
294
+ };
295
+
296
+ const pinBlurOpts = {
297
+ fieldSelector: this.el.field,
298
+ errorTextSelector: this.el.errorText,
299
+ errorClass: this.el.fieldError,
300
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
301
+ pinMessage: this.messages.pinMessage,
302
+ };
303
+
304
+ const okNumber = validateExactDigits($numInput, 16, this.messages.numberMessage, this.el);
305
+ const okPin = validateExactDigits($pinInput, 8, this.messages.pinMessage, this.el);
306
+
307
+ if (!(okNumber && okPin)) {
308
+ handleGcNumberBlur({ currentTarget: $numInput[0] }, numberBlurOpts);
309
+ handleGcPinBlur({ currentTarget: $pinInput[0] }, pinBlurOpts);
310
+ (!okNumber ? $numInput : $pinInput).trigger('focus');
311
+ return;
312
+ }
313
+
314
+ const giftCardNumber = this.getNumberValue($root);
315
+ const pin = this.getPinValue($root);
316
+ const $row = $root.closest(giftcardRow);
317
+
318
+ let $loader = $row.children(loaderChip);
319
+
320
+ if (!$loader.length) {
321
+ appendLoader({ wrapper: $row });
322
+ $loader = $row.children(loaderChip).last();
323
+ }
324
+
325
+ verifyGiftCard($loader, giftCardNumber, pin)
326
+ .then((res) => this.handleVerifyResponse($root, res))
327
+ .catch((err) => {
328
+ const msg = this.getErrorMessage(err);
329
+ this.showBannerError($root, msg);
330
+ });
331
+ },
332
+
333
+ handleVerifyResponse($root, res) {
334
+ if (res && res.code === OK_CODE) {
335
+ if (!Array.isArray(res.giftCards)) {
336
+ this.showBannerError($root, DEFAULT_ERROR_MESSAGE);
337
+ return;
338
+ }
339
+
340
+ this.pendingServerGiftCards = res.giftCards.slice();
341
+
342
+ if (res.orderTotal != null || res.giftCardTotal != null) {
343
+ const data = { total: res.orderTotal, giftCardApplied: res.giftCardTotal };
344
+ updateOrderSummary(data, buildSummaryEqualMap(data), true);
345
+ this.summaryTotal = res.orderTotal;
346
+ this.toggleBillingDescrForZero($root);
347
+ this.updateCtaButton($root);
348
+ }
349
+
350
+ this.resetForm($root);
351
+ this.showSubmitSpinner($root);
352
+ return;
353
+ }
354
+
355
+ const msg = (res && MESSAGE_BY_CODE[res.code]) || DEFAULT_ERROR_MESSAGE;
356
+ this.showBannerError($root, msg);
357
+ },
358
+
359
+ updateHintVisibility($root) {
360
+ const {
361
+ chipList, giftcardRow, hint, panel, toggle, iconInfo, expandedAttr,
362
+ summaryHint, chip, block, body
363
+ } = this.el;
364
+
365
+ const $row = $root.closest(giftcardRow);
366
+ const $giftcard = $row.find(block).first();
367
+ const count = $row.find(`${chipList} ${chip}`).length;
368
+ const limit = this.maxChips || 3;
369
+
370
+ const zeroTotal = this.summaryTotal != null ? isZeroAmount(this.summaryTotal) : false;
371
+
372
+ this.toggleAltPayments($giftcard, count === 0);
373
+ this.toggleAltPaymentInfos($giftcard, count === 0);
374
+
375
+ if (count > 0) {
376
+ if (!this.altPaymentPanelsLocked) {
377
+ this.showOnlyBillingAddress($giftcard);
378
+ this.altPaymentPanelsLocked = true;
379
+ } else {
380
+ this.showOnlyBillingAddress($giftcard);
381
+ }
382
+ } else {
383
+ if (!this.altPaymentPanelsLocked) {
384
+ this.toggleAltPayments($giftcard, true);
385
+ this.toggleAltPaymentInfos($giftcard, true);
386
+ } else {
387
+ this.showOnlyBillingAddress($giftcard);
388
+ }
389
+ }
390
+
391
+ const $summary = this.ensureSummaryContainer($giftcard);
392
+
393
+ if (count === 0) {
394
+ $summary.hide();
395
+ $giftcard.show();
396
+ this.resetForm($giftcard);
397
+ $giftcard.find(panel).prop('hidden', true);
398
+ $giftcard.find(toggle).attr(expandedAttr, 'false').show();
399
+ $giftcard.find(hint)[zeroTotal ? 'hide' : 'show']();
400
+ this.updateCtaButton($giftcard);
401
+ return;
402
+ }
403
+
404
+ this.setBillingLabelToCard($giftcard);
405
+ $summary.show();
406
+ $giftcard.hide();
407
+ $giftcard.find(hint).hide();
408
+
409
+ const ctx = buildHintContext(count, limit, zeroTotal);
410
+ const html = giftCardsHint(ctx);
411
+ const $old = $summary.find(summaryHint).first();
412
+ const $new = $(html);
413
+
414
+ if ($old.length) $old.replaceWith($new);
415
+ else $summary.append($new);
416
+
417
+
418
+ if (zeroTotal) {
419
+ $summary.find(summaryHint).hide();
420
+ }
421
+
422
+ this.toggleCardDetailsForTotal($giftcard);
423
+
424
+ if ($.fn.tooltip) {
425
+ $summary.find(summaryHint).find(iconInfo).tooltip({ container: body });
426
+ }
427
+ },
428
+
429
+ renderChipHTML({ id, maskedNumber, currency, amount }) {
430
+ return giftCardChip({ id, maskedNumber, currency, amount });
431
+ },
432
+
433
+ toggleAltPaymentInfos($root, show) {
434
+ const { checkoutContainer, billieInfo, klarnaInfo } = this.el;
435
+ const $container = $root.closest(checkoutContainer);
436
+
437
+ if (this.altPaymentInfosLocked) {
438
+ show = false;
439
+ }
440
+
441
+ [$container.find(billieInfo), $container.find(klarnaInfo)].forEach(($panel) => {
442
+ if ($panel && $panel.length) {
443
+ $panel[show ? 'show' : 'hide']();
444
+ }
445
+ });
446
+
447
+ if (!show) this.altPaymentInfosLocked = true;
448
+ },
449
+
450
+ toggleAltPayments($root, show) {
451
+ const {
452
+ paymentForm,
453
+ billieField,
454
+ klarnaField,
455
+ clearpayField,
456
+ appleField,
457
+ googleField,
458
+ paypalField,
459
+ checkoutContainer,
460
+ formRow,
461
+ cardField,
462
+ } = this.el;
463
+
464
+ const $container = $root.closest(checkoutContainer);
465
+ const $paymentField = $container.find(paymentForm);
466
+ const isTotalZero = this.summaryTotal != null ? isZeroAmount(this.summaryTotal) : false;
467
+
468
+ const paymentSelectors = [
469
+ billieField,
470
+ klarnaField,
471
+ clearpayField,
472
+ appleField,
473
+ googleField,
474
+ paypalField,
475
+ ];
476
+
477
+ paymentSelectors.forEach((sel) => {
478
+ const $row = $paymentField.find(sel).closest(formRow);
479
+ if ($row.length) {
480
+ const shouldShow = show && !isTotalZero;
481
+ $row[shouldShow ? 'show' : 'hide']();
482
+ }
483
+ });
484
+
485
+ const $cardRow = $paymentField.find(cardField).closest(formRow);
486
+ if ($cardRow.length) {
487
+ $cardRow[isTotalZero ? 'hide' : 'show']();
488
+ }
489
+
490
+ if (!show) {
491
+ this.ensureCardPaymentSelected($paymentField);
492
+ }
493
+ },
494
+
495
+ ensureCardPaymentSelected($paymentField) {
496
+ const { cardField, cardRadio, formRow } = this.el;
497
+
498
+ const $cardFieldRow = $paymentField.find(cardField).closest(formRow);
499
+ if (!$cardFieldRow.length) {
500
+ return;
501
+ }
502
+
503
+ const $radio = $cardFieldRow.find(cardRadio);
504
+
505
+ if ($radio.length && !$radio.prop('checked')) {
506
+ $radio.prop('checked', true);
507
+ $radio.trigger('change');
508
+ this.updateCtaButton($cardFieldRow);
509
+ }
510
+ },
511
+
512
+ handleAddAnotherClick(e) {
513
+ e.preventDefault();
514
+ const { giftcardRow, chipList, chip, block, number, summaryHint } = this.el;
515
+
516
+ const $row = $(e.currentTarget).closest(giftcardRow);
517
+ const count = $row.find(`${chipList} ${chip}`).length;
518
+ if (count >= this.maxChips) return;
519
+
520
+ const $giftcard = $row.find(block).first();
521
+ this.resetForm($giftcard);
522
+ this.open($giftcard);
523
+
524
+ $row.find(summaryHint).hide();
525
+
526
+ setTimeout(() => {
527
+ $giftcard.find(number).trigger('focus');
528
+ }, 0);
529
+ },
530
+
531
+ getErrorMessage(payload) {
532
+ const code =
533
+ payload && (typeof payload.code === 'number' || typeof payload.code === 'string')
534
+ ? payload.code
535
+ : undefined;
536
+
537
+ return (code != null && MESSAGE_BY_CODE[code]) || DEFAULT_ERROR_MESSAGE;
538
+ },
539
+
540
+ handleChipCloseClick(e) {
541
+ e.preventDefault();
542
+ const {
543
+ chip,
544
+ giftcardRow,
545
+ block,
546
+ giftcardChipClose,
547
+ chipList,
548
+ loaderChip,
549
+ } = this.el;
550
+
551
+ const $clickedChip = $(e.currentTarget).closest(chip);
552
+ const $row = $clickedChip.closest(giftcardRow);
553
+ const $giftcard = $row.find(block).first();
554
+ const $summary = this.ensureSummaryContainer($giftcard);
555
+
556
+ const $chips = $summary.find(`${chipList} ${chip}`);
557
+ const index = $chips.index($clickedChip);
558
+ if (index < 0) return;
559
+
560
+ let $loader = $row.children(loaderChip);
561
+ if (!$loader.length) {
562
+ appendLoader({ wrapper: $row });
563
+ $loader = $row.children(loaderChip).last();
564
+ }
565
+
566
+ $row
567
+ .find(giftcardChipClose)
568
+ .css('pointer-events', 'none')
569
+ .attr('aria-disabled', 'true');
570
+
571
+ removeGiftCard($loader, index)
572
+ .then((res) => this.handleRemoveResponse($giftcard, res))
573
+ .catch((err) => {
574
+ const msg = this.getErrorMessage(err);
575
+ this.showBannerError($giftcard, msg);
576
+ })
577
+ .finally(() => {
578
+ hideLoader($loader);
579
+ $row.find(giftcardChipClose).css('pointer-events', '').removeAttr('aria-disabled');
580
+ });
581
+ },
582
+
583
+ handleRemoveResponse($giftcard, res) {
584
+ if (res && res.code === OK_CODE) {
585
+ const giftCardsList = Array.isArray(res.giftCards) ? res.giftCards : [];
586
+ const data = { total: res.orderTotal, giftCardApplied: res.giftCardTotal };
587
+ this.summaryTotal = res.orderTotal;
588
+
589
+ this.renderGiftCardsFromList($giftcard, giftCardsList, data);
590
+
591
+ if (res.orderTotal != null || res.giftCardTotal != null) {
592
+ updateOrderSummary(data, buildSummaryEqualMap(data), true);
593
+ }
594
+ this.updateCtaButton($giftcard);
595
+ this.toggleBillingDescrForZero($giftcard);
596
+ return;
597
+ }
598
+
599
+ const msg = (res && MESSAGE_BY_CODE[res.code]) || DEFAULT_ERROR_MESSAGE;
600
+ this.showBannerError($giftcard, msg);
601
+ },
602
+
603
+ ensureSummaryContainer($root) {
604
+ const { summary, giftcardRow } = this.el;
605
+ const $row = $root.closest(giftcardRow);
606
+ let $summary = $row.find(summary).first();
607
+
608
+ if (!$summary.length) {
609
+ $summary = $(giftcardSummary({}));
610
+ $row.append($summary);
611
+ }
612
+
613
+ return $summary;
614
+ },
615
+
616
+ getPageLoader() {
617
+ const $pl = $(this.el.pageLoader);
618
+ if (!$pl.parent().is('body')) $pl.appendTo('body');
619
+ return $pl;
620
+ },
621
+
622
+ showPageLoader() {
623
+ const $pl = this.getPageLoader();
624
+ showLoader($pl);
625
+ },
626
+
627
+ updateCtaButton($scope) {
628
+ const { ctaSelector, btnDetails, btnText, checkoutContainer } = this.el;
629
+ let $cta = $(ctaSelector);
630
+ if (!$cta.length) {
631
+ $cta = $scope.closest(checkoutContainer).find(ctaSelector);
632
+ }
633
+ if (!$cta.length) return;
634
+
635
+ const $wrap = $cta.closest(btnDetails).length ? $cta.closest(btnDetails) : $cta;
636
+ const $text = $wrap.find(btnText).first().length ? $wrap.find(btnText).first() : $wrap;
637
+ const useGiftcard = isZeroAmount(this.summaryTotal);
638
+
639
+ $text.text(useGiftcard ? this.messages.ctaGiftcardText : this.messages.ctaDefaultText);
640
+ $wrap.attr('data-mode', useGiftcard ? 'giftcards' : 'card');
641
+
642
+ if (useGiftcard) {
643
+ $wrap.attr('type', 'button').removeAttr('form');
644
+ } else {
645
+ $wrap.attr('type', 'submit').attr('form', 'hopPaymentAddressPostForm');
646
+ }
647
+ },
648
+
649
+ toggleBillingDescrForZero($scope) {
650
+ const $container = $scope.closest(this.el.checkoutContainer);
651
+ const $descr = $container.find(this.el.checkoutPaymentFields);
652
+ if (!$descr.length) return;
653
+
654
+ const shouldHide = isZeroAmount(this.summaryTotal);
655
+ $descr[shouldHide ? 'hide' : 'show']();
656
+ },
657
+
658
+ getNumberValue($root) {
659
+ const { number } = this.el;
660
+ return String($root.find(number).val() || '').replace(/\D/g, '');
661
+ },
662
+
663
+ getPinValue($root) {
664
+ const { pin } = this.el;
665
+ return String($root.find(pin).val() || '').replace(/\D/g, '');
666
+ },
667
+
668
+ bindGiftcardCtaHandler() {
669
+ const self = this;
670
+
671
+ $(document).off('click.giftcard', this.el.btnEnterDetails);
672
+ $(document).on('click.giftcard', this.el.btnEnterDetails, function (e) {
673
+ const $btn = $(this);
674
+ const mode = $btn.attr('data-mode');
675
+
676
+ if (mode !== 'giftcards') return;
677
+
678
+ e.preventDefault();
679
+ e.stopPropagation();
680
+ e.stopImmediatePropagation();
681
+
682
+ const $scope = $btn.closest(self.el.checkoutContainer).find(self.el.block).first();
683
+ const $root = $scope.length ? $scope : $(self.el.block).first();
684
+
685
+ self.showPageLoader();
686
+
687
+ payWithGiftCard()
688
+ .then(({ url }) => {
689
+ if (typeof url === 'string' && url) {
690
+ window.location.href = url;
691
+ }
692
+ })
693
+ .catch((error) => {
694
+ if (Array.isArray(error?.giftCards)) {
695
+ self.renderGiftCardsFromList($root, error.giftCards);
696
+ }
697
+
698
+ const msg = (error.errorCode && PAY_MESSAGE_BY_CODE[error.errorCode]) || DEFAULT_PAY_ERROR_MESSAGE;
699
+
700
+ if (msg) {
701
+ Wick.Notification.show({
702
+ text: linkifyClickHere(msg),
703
+ container: '.globalMessages .container',
704
+ type: 'error',
705
+ });
706
+ }
707
+ })
708
+ .finally(() => {
709
+ const $pl = self.getPageLoader();
710
+ hideLoader($pl);
711
+ });
712
+ });
713
+ },
714
+
715
+ showBannerError($root, message, title = '') {
716
+ const { notificationRoot, giftCardInline, inlineHeader } = this.el;
717
+ const $inline = $root.find(giftCardInline);
718
+
719
+ $inline.find(notificationRoot).remove();
720
+
721
+ const html = notificationTpl({
722
+ classModifier: 'notification_error',
723
+ error: true,
724
+ withCloseBtn: true,
725
+ title: title || undefined,
726
+ text: linkifyClickHere(message)
727
+ });
728
+
729
+ $inline.find(inlineHeader).after(html);
730
+ },
731
+
732
+
733
+ hideBanner($root) {
734
+ const { notification } = this.el;
735
+ $root.find(notification).remove();
736
+ },
737
+
738
+ mapGiftToChipData($root, gift) {
739
+ const $giftcard = $root.closest(this.el.block);
740
+ const currency = $giftcard.data('currency') || '£';
741
+ const rawAmount = Number(gift.amountToRedeem || 0);
742
+ const amount = Number.isFinite(rawAmount) ? rawAmount.toFixed(2) : '0.00';
743
+ return {
744
+ id: gift.id,
745
+ maskedNumber: String(gift.maskedNumber || ''),
746
+ currency,
747
+ amount,
748
+ };
749
+ },
750
+
751
+ renderGiftCardsFromList($root, giftCards, summaryData) {
752
+ const { chipList, loaderChip, chip } = this.el;
753
+ const $summary = this.ensureSummaryContainer($root);
754
+ const $list = $summary.find(chipList);
755
+ $list.children(`${chip}:not(${loaderChip})`).remove();
756
+
757
+ const html = (giftCards || [])
758
+ .map((g) => this.renderChipHTML(this.mapGiftToChipData($root, g)))
759
+ .join('');
760
+
761
+ if (html) {
762
+ $list.append(html);
763
+ $summary.show();
764
+ }
765
+
766
+ this.updateHintVisibility($root, summaryData);
767
+
768
+ if (Array.isArray(giftCards) && giftCards.length > 0) {
769
+ this.setBillingLabelToCard($root);
770
+ }
771
+ },
772
+
773
+ toggleCardDetailsForTotal($scope) {
774
+ const { checkoutContainer, cardDetails, hiddenCard } = this.el;
775
+ const $container = $scope.closest(checkoutContainer);
776
+ const $cardDetails = $container.find(cardDetails);
777
+ if (!$cardDetails.length) return;
778
+
779
+ const zero = this.summaryTotal != null ? isZeroAmount(this.summaryTotal) : false;
780
+ if (zero) $cardDetails.addClass(hiddenCard);
781
+ },
782
+
783
+ setBillingLabelToCard($scope) {
784
+ const $container = $scope.closest(this.el.checkoutContainer);
785
+ const $billing = $container.find(this.el.billingAddress);
786
+ if (!$billing.length) return;
787
+
788
+ const $label = $billing.find('[for="card-name"]');
789
+ if ($label.length) {
790
+ $label.text('Name on card');
791
+ }
792
+ },
793
+
794
+ showOnlyBillingAddress($root) {
795
+ const { checkoutContainer, billingAddress, altPaymentRowsAttr, hiddenCard } = this.el;
796
+ const $container = $root.closest(checkoutContainer);
797
+ $container.find(billingAddress).removeClass(hiddenCard);
798
+ $container.find(altPaymentRowsAttr).addClass(hiddenCard);
799
+ this.toggleAltPaymentInfos($root, false);
800
+ },
801
+
802
+ useGlobalError($root, message) {
803
+ if (window.Wick && Wick.Notification && typeof Wick.Notification.show === 'function') {
804
+ const text = linkifyClickHere ? linkifyClickHere(message) : String(message || '');
805
+ const containers = ['.globalMessages .container', '.globalMessages.container'];
806
+ const container = containers.find((selector) => $(selector).length);
807
+
808
+ if (!container) {
809
+ return;
810
+ }
811
+
812
+ Wick.Notification.show({
813
+ text,
814
+ container,
815
+ type: 'error',
816
+ });
817
+ }
818
+ },
819
+
820
+ bindSplitPaymentFailedListener() {
821
+ window.addEventListener('cardSplitPaymentFailed', (event) => {
822
+ const code = event?.detail?.externalErrorCode;
823
+ if (!code) {
824
+ return
825
+ }
826
+
827
+ const msg = PAY_MESSAGE_BY_CODE[code] || DEFAULT_PAY_ERROR_MESSAGE;
828
+ this.useGlobalError(this.el.$blocks.first(), msg);
829
+ });
830
+ },
831
+
832
+ bindEvents() {
833
+ const { number, pin, toggle, closeBtn, giftcardBtn, jsGiftcardAdd, giftcardChipClose } = this.el;
834
+
835
+ $(document).off('.giftcard');
836
+
837
+ const handleNumberInput = createGcNumberInputHandler({
838
+ fieldSelector: this.el.field,
839
+ fieldErrorClass: this.el.fieldError,
840
+ errorTextSelector: this.el.errorText,
841
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
842
+ });
843
+
844
+ const onNumberBlur = (e) =>
845
+ handleGcNumberBlur(e, {
846
+ fieldSelector: this.el.field,
847
+ errorTextSelector: this.el.errorText,
848
+ errorClass: this.el.fieldError,
849
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
850
+ numberMessage: this.messages.numberMessage,
851
+ });
852
+
853
+ const onPinInput = (e) =>
854
+ handleGcPinInput(e, {
855
+ fieldSelector: this.el.field,
856
+ errorTextSelector: this.el.errorText,
857
+ errorClass: this.el.fieldError,
858
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
859
+ });
860
+
861
+ const onPinBlur = (e) =>
862
+ handleGcPinBlur(e, {
863
+ fieldSelector: this.el.field,
864
+ errorTextSelector: this.el.errorText,
865
+ errorClass: this.el.fieldError,
866
+ ariaInvalidAttr: this.el.ariaInvalidAttr,
867
+ pinMessage: this.messages.pinMessage,
868
+ });
869
+
870
+
871
+ $(document)
872
+ .on('input.giftcard', number, handleNumberInput)
873
+ .on('keypress.giftcard', number, digitsOnlyKeypress)
874
+ .on('blur.giftcard', number, onNumberBlur);
875
+
876
+ $(document)
877
+ .on('input.giftcard', pin, onPinInput)
878
+ .on('keypress.giftcard', pin, handleGcPinKeypress)
879
+ .on('blur.giftcard', pin, onPinBlur);
880
+
881
+ $(document)
882
+ .on('click.giftcard', toggle, this.handleToggleClick.bind(this))
883
+ .on('click.giftcard', closeBtn, this.handleCloseClick.bind(this))
884
+ .on('click.giftcard', giftcardBtn, this.handleFormSubmit.bind(this))
885
+ .on('click.giftcard', jsGiftcardAdd, this.handleAddAnotherClick.bind(this))
886
+ .on('click.giftcard', giftcardChipClose, this.handleChipCloseClick.bind(this))
887
+
888
+ this.bindGiftcardCtaHandler();
889
+ },
890
+
891
+ init(payload = {}) {
892
+ const { field, pin, $blocks } = this.el;
893
+ this.bindEvents();
894
+ this.bindSplitPaymentFailedListener();
895
+ if ($blocks && $blocks.length) {
896
+ const $firstBlock = $blocks.first();
897
+ if (Array.isArray(payload.giftCards) && payload.giftCards.length) {
898
+ this.renderGiftCardsFromList($firstBlock, payload.giftCards);
899
+ }
900
+
901
+ const code = payload.errorCode;
902
+
903
+ if (Number.isFinite(code) && PAY_MESSAGE_BY_CODE[code]) {
904
+ this.useGlobalError($firstBlock, PAY_MESSAGE_BY_CODE[code]);
905
+ }
906
+ }
907
+
908
+ $blocks.each((index, block) => {
909
+ const $pinField = $(block).find(pin).closest(field);
910
+ if ($pinField.length) {
911
+ initializeInputToggle($pinField);
912
+ }
913
+ });
914
+ }
915
+ };