wickes-css2 2.103.0-IC-977-colors-semantic.1 → 2.103.0-RG-1658-apply-dynamic-logic-gift-card.1

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