wickes-css2 2.103.0-gift-cards.1 → 2.103.0-gift-cards.4

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 (41) hide show
  1. package/build/css/components/checkout-payment-details-v2.css +1 -1
  2. package/build/css/pages/page_checkout_delivery-new.css +1 -1
  3. package/build/js/account-members.min.js +1 -1
  4. package/build/js/add-project-list-id.min.js +1 -1
  5. package/build/js/address-book.min.js +1 -1
  6. package/build/js/basket.min.js +2 -2
  7. package/build/js/bundle.min.js +1 -1
  8. package/build/js/change-password.min.js +1 -1
  9. package/build/js/checkout.min.js +2 -2
  10. package/build/js/emulation.min.js +204 -2
  11. package/build/js/general.bundle.min.js +1 -1
  12. package/build/js/gift-cards.min.js +1 -1
  13. package/build/js/merged-checkout.min.js +2 -2
  14. package/build/js/page/components/gift-cards.js +505 -238
  15. package/build/js/page/components/order-summary.js +42 -25
  16. package/build/js/page/utils/gift-cards-utils.js +117 -16
  17. package/build/js/page/utils/input-handling.js +92 -0
  18. package/build/js/page/utils/validation.js +46 -1
  19. package/build/js/pdp.bundle.min.js +1 -1
  20. package/build/js/personal-details.min.js +1 -1
  21. package/build/js/plp.bundle.min.js +1 -1
  22. package/build/js/project-list.min.js +44 -1
  23. package/build/js/quiz.min.js +1 -1
  24. package/build/js/track-my-order.min.js +1 -1
  25. package/package.json +1 -1
  26. package/src/components/checkout-payment-details-v2.hbs +1 -1
  27. package/src/components/gift-cards.hbs +2 -1
  28. package/src/components/giftcard-chip.hbs +3 -3
  29. package/src/components/giftcard-summary.hbs +6 -0
  30. package/src/js/components/general/notification.js +2 -1
  31. package/src/js/emulation/checkout-data.js +2 -2
  32. package/src/js/emulation/gift-cards.js +201 -0
  33. package/src/js/page/components/gift-cards.js +505 -238
  34. package/src/js/page/components/order-summary.js +42 -25
  35. package/src/js/page/utils/gift-cards-utils.js +117 -16
  36. package/src/js/page/utils/input-handling.js +92 -0
  37. package/src/js/page/utils/validation.js +46 -1
  38. package/src/layouts/checkout.hbs +1 -5
  39. package/src/page_payment-details-with-gift-card.html +5 -3
  40. package/src/scss/components/_gift-cards.scss +5 -1
  41. package/src/scss/pages/page_checkout_delivery-new.scss +7 -0
@@ -13,31 +13,46 @@ const osEl = {
13
13
  }
14
14
 
15
15
  export function updateOrderSummary(data, equalMap, isVoucher) {
16
- equalMap.forEach((obj) => {
17
- if ($(obj.item).length) {
18
- $(obj.item).show();
19
- } else {
20
- if ($(osEl.checkoutWidgetDetailsDiscount).length) {
21
- $(osEl.checkoutWidgetDetailsDiscount).before(createCheckoutWidgetItem(obj))
22
- } else {
23
- $(osEl.checkoutWidgetDetails).append(createCheckoutWidgetItem(obj))
24
- }
25
- }
16
+ equalMap.forEach((obj) => {
17
+ if (typeof obj.dataValue === 'undefined') return;
26
18
 
27
- $(obj.item).find(obj.itemValue).text(obj.dataValue);
28
- if (isVoucher ? obj.dataValue : obj.value && obj.dataValue) {
29
- $(obj.item).find(obj.itemValue).show();
30
- } else {
31
- $(obj.item).hide();
32
- }
33
- })
19
+ if ($(obj.item).length) {
20
+ $(obj.item).show();
21
+ } else {
22
+ if ($(osEl.checkoutWidgetDetailsDiscount).length) {
23
+ $(osEl.checkoutWidgetDetailsDiscount).before(createCheckoutWidgetItem(obj));
24
+ } else {
25
+ $(osEl.checkoutWidgetDetails).append(createCheckoutWidgetItem(obj));
26
+ }
27
+ }
34
28
 
35
- const value = data.totalPrice?.formattedValue || data.total;
29
+ $(obj.item).find(obj.itemValue).text(obj.dataValue);
30
+ if (isVoucher ? obj.dataValue : obj.value && obj.dataValue) {
31
+ $(obj.item).find(obj.itemValue).show();
32
+ } else {
33
+ $(obj.item).hide();
34
+ }
35
+ });
36
36
 
37
+ const keys = new Set(Object.keys(data || {}));
38
+ const hasAny = (...arr) => arr.some(k => keys.has(k));
39
+
40
+ const value = data.totalPrice?.formattedValue || data.total;
41
+ if (typeof value !== 'undefined' && hasAny('total', 'totalPrice')) {
42
+ updateSummaryMobileBarTotal(value);
43
+ }
44
+
45
+ if (hasAny('promotionsInfo')) {
37
46
  updateDiscount(data, isVoucher);
47
+ }
48
+
49
+ if (hasAny('deliveryOrder', 'clickAndCollectOrder', 'clickAndCollectOnly', 'deliveryItemsQuantity', 'pickupItemsQuantity')) {
38
50
  hideUnusedDeliveryTypeCost(data, isVoucher);
51
+ }
52
+
53
+ if (hasAny('freeDelivery', 'messageForFreeDelivery', 'clickAndCollectOnly')) {
39
54
  calculateFreeDelivery(data);
40
- updateSummaryMobileBarTotal(value);
55
+ }
41
56
  }
42
57
 
43
58
  function createCheckoutWidgetItem(obj) {
@@ -53,12 +68,14 @@ function hideUnusedDeliveryTypeCost(data, isVoucher) {
53
68
  $(osEl.delivery).show()
54
69
  $(osEl.cc).show()
55
70
  if (isVoucher) {
56
- if (!data.deliveryOrder) {
57
- $(osEl.delivery).hide()
58
- } else if (!data.clickAndCollectOrder) {
59
- $(osEl.cc).hide()
60
- }
61
- } else {
71
+ if ('deliveryOrder' in data && data.deliveryOrder === false) {
72
+ $(osEl.delivery).hide();
73
+ }
74
+
75
+ if ('clickAndCollectOrder' in data && data.clickAndCollectOrder === false) {
76
+ $(osEl.cc).hide();
77
+ }
78
+ } else {
62
79
  if (data.clickAndCollectOnly) {
63
80
  $(osEl.delivery).hide()
64
81
  } else if (data.deliveryItemsQuantity > 0 && data.pickupItemsQuantity === 0) {
@@ -1,3 +1,5 @@
1
+ import { showLoader, hideLoader } from "./loader";
2
+
1
3
  const SUMMARY_SELECTORS = {
2
4
  subTotal: { item: '.checkout-widget__item-bold', value: '.checkout-widget__item-value', title: 'Items subtotal:' },
3
5
  vat: { item: '.checkout-widget__item-vat', value: '.checkout-widget__item-value', title: 'VAT:' },
@@ -5,17 +7,75 @@ const SUMMARY_SELECTORS = {
5
7
  deliveryCost: { item: '.checkout-widget__item-delivery', value: '.checkout-widget__item-value', title: 'Delivery:' },
6
8
  charityPrice: { item: '.checkout-widget__item-charity', value: '.checkout-widget__item-value', title: 'Charity donation:' },
7
9
  discountTotal: { item: '.checkout-widget__details-discount', value: '.checkout-widget__detail-value' },
10
+ giftCardApplied: { item: '.checkout-widget__gift-card', value: '.checkout-widget__item-value', title: 'Gift Card:' },
8
11
  total: { item: '.checkout-widget__total', value: '.checkout-widget__total-value' },
9
- giftCardApplied: { item: '.checkout-widget__gift-card', value: '.checkout-widget__total-value', title: 'Gift Card:' },
10
12
  };
11
13
 
14
+ const CURRENCY_KEYS = new Set([
15
+ 'subTotal','vat','clickAndCollectCost','deliveryCost',
16
+ 'charityPrice','discountTotal','total','giftCardApplied'
17
+ ]);
18
+
19
+ export const OK_CODE = 0;
20
+
21
+ export const ZERO_BALANCE_MESSAGE =
22
+ 'Insufficient funds available, please try another Gift Card or alternate payment method.';
23
+
24
+ export const MESSAGE_BY_CODE = {
25
+ 10: 'Gift Card number and / or PIN not recognised. A Gift Card will be locked following three unsuccessful PIN attempts.',
26
+ 16: 'Card locked due to three unsuccessful PIN entries. For support click here.',
27
+ 20: 'Unable to use Gift Card at this time, please try later or click here for support.',
28
+ 40: 'Unable to use Gift Card at this time, please try later or click here for support.',
29
+ 60: 'The entered Gift Card has expired. For support click here.',
30
+ 89: 'Gift Card already added, please use a different card.',
31
+ 99: 'The entered Gift Card is invalid. For support click here.',
32
+ [-1]: 'Gift Card is unavailable at this time, please try later. For support click here.',
33
+ };
34
+
35
+ export const PAY_MESSAGE_BY_CODE = {
36
+ 101: 'Unable to use Gift Cards at this time, please try later or click here for support.',
37
+ 102: 'Unfortunately there was a problem with requesting your card details from the payment provider. Available funds on your Gift Card are unchanged.',
38
+ 103: 'Unfortunately there was a problem placing your order with Gift Card(s). Please click here for support.',
39
+ 104: 'Unfortunately there was a problem placing your order with Gift Card(s). Please click here for support.',
40
+ };
41
+
42
+ export const ADD_GIFT_CARD = 'addGiftCard';
43
+ export const REMOVE_GIFT_CARD = 'removeGiftCard';
44
+ export const PAY_WITH_GIFT_CARD = 'payWithGiftCard';
45
+
46
+ export const FAQ_URL = 'https://www.wickes.co.uk/gifts';
47
+
48
+ export const escapeHtml = (s) => $('<div>').text(String(s || '')).html();
49
+
50
+ export const linkifyClickHere = (msg) =>
51
+ escapeHtml(msg).replace(
52
+ /\bclick here\b/gi,
53
+ `<a href="${FAQ_URL}" target="_blank" rel="noopener" class="notification_link">click here</a>`
54
+ );
55
+
12
56
  export function formatGc16(raw) {
13
57
  const digits = String(raw || '').replace(/\D/g, '').slice(0, 16);
14
58
  return digits.replace(/(\d{4})(?=\d)/g, '$1-');
15
59
  }
16
60
 
61
+ function formatGBP(value) {
62
+ if (value === null || typeof value === 'undefined' || String(value).trim() === '') {
63
+ return value;
64
+ }
65
+
66
+ const cleanedValue = String(value).replace(/[^\d.-]/g, '');
67
+ const numberValue = Number(cleanedValue);
68
+
69
+ if (Number.isFinite(numberValue)) {
70
+ return `£${numberValue.toFixed(2)}`;
71
+ }
72
+
73
+ return value;
74
+ }
75
+
17
76
  export function buildHintContext(count, limit) {
18
77
  const max = count >= limit;
78
+
19
79
  return {
20
80
  canAdd: !max,
21
81
  max,
@@ -23,21 +83,62 @@ export function buildHintContext(count, limit) {
23
83
  };
24
84
  }
25
85
 
26
- export function buildChipData($root, numberSelector) {
27
- const digits = String($root.find(numberSelector).val() || '').replace(/\D/g, '');
28
- const last5 = digits.slice(-5);
29
- const id= `giftcard_${Date.now()}`;
30
- const $giftcard = $root.closest('.giftcard');
31
- const currency = $giftcard.data('currency') || '£';
32
- const amount= ($giftcard.data('amount') != null ? $giftcard.data('amount') : '100.00');
33
- return { id, last5, currency, amount };
86
+ export function buildSummaryEqualMap(data) {
87
+ return Object.entries(SUMMARY_SELECTORS).map(([key, d]) => {
88
+ let dataValue = data[key];
89
+
90
+ if (CURRENCY_KEYS.has(key)) {
91
+ dataValue = formatGBP(dataValue);
92
+ }
93
+
94
+ return {
95
+ item: d.item,
96
+ itemValue: d.value,
97
+ dataValue: dataValue,
98
+ ...(d.title ? { title: d.title } : {}),
99
+ };
100
+ });
34
101
  }
35
102
 
36
- export function buildSummaryEqualMap(data) {
37
- return Object.entries(SUMMARY_SELECTORS).map(([key, d]) => ({
38
- item: d.item,
39
- itemValue: d.value,
40
- dataValue: data[key],
41
- ...(d.title ? { title: d.title } : {}),
42
- }));
103
+ export function isZeroAmount(val) {
104
+ const cleaned = String(val ?? '').replace(/[^\d.]/g, '');
105
+ if (!cleaned) {
106
+ return false;
107
+ }
108
+ const num = Number(cleaned);
109
+ return Number.isFinite(num) && num === 0;
110
+ }
111
+
112
+ // --- API/Event-Dispatching Functions ---
113
+
114
+ export function verifyGiftCard($loader, giftCardNumber, pin) {
115
+ showLoader($loader);
116
+ return new Promise((resolve, reject) => {
117
+ const payload = { giftCardNumber, pin };
118
+ const detail = { resolve, reject, payload };
119
+ const evt = createEvent(ADD_GIFT_CARD, payload, detail);
120
+ window.dispatchEvent(evt);
121
+ }).finally(() => {
122
+ hideLoader($loader);
123
+ });
124
+ }
125
+
126
+ export function removeGiftCard($loader, index) {
127
+ showLoader($loader);
128
+ return new Promise((resolve, reject) => {
129
+ const payload = { index };
130
+ const detail = { resolve, reject, payload };
131
+ const evt = createEvent(REMOVE_GIFT_CARD, payload, detail);
132
+ window.dispatchEvent(evt);
133
+ }).finally(() => {
134
+ hideLoader($loader);
135
+ });
136
+ }
137
+
138
+ export function payWithGiftCard() {
139
+ return new Promise((resolve, reject) => {
140
+ const detail = { resolve, reject };
141
+ const evt = createEvent(PAY_WITH_GIFT_CARD, undefined, detail);
142
+ window.dispatchEvent(evt);
143
+ });
43
144
  }
@@ -0,0 +1,92 @@
1
+ import {formatGc16} from './gift-cards-utils';
2
+ import {clearErrorText, showErrorText} from './validation';
3
+
4
+
5
+ export function createGcNumberInputHandler({
6
+ fieldSelector,
7
+ fieldErrorClass,
8
+ errorTextSelector,
9
+ ariaInvalidAttr
10
+ }) {
11
+ return function handleGcNumberInput(e) {
12
+ const input = e.currentTarget;
13
+ const $input = $(input);
14
+
15
+ const canTrack = typeof input.selectionStart === 'number';
16
+ const atEnd = canTrack && input.selectionStart === input.value.length;
17
+
18
+ const hadErr = $input.closest(fieldSelector).hasClass(fieldErrorClass);
19
+
20
+ input.value = formatGc16(input.value);
21
+
22
+ if (hadErr) {
23
+ clearErrorText($input, fieldSelector, errorTextSelector, fieldErrorClass, ariaInvalidAttr);
24
+ }
25
+
26
+ if (atEnd && typeof input.setSelectionRange === 'function') {
27
+ input.setSelectionRange(input.value.length, input.value.length);
28
+ }
29
+ };
30
+ }
31
+
32
+ export function digitsOnlyKeypress(e) {
33
+ if (!/\d/.test(String.fromCharCode(e.which || e.keyCode))) {
34
+ e.preventDefault();
35
+ }
36
+ }
37
+
38
+ export function handleGcNumberBlur(e, {
39
+ fieldSelector,
40
+ errorTextSelector,
41
+ errorClass,
42
+ ariaInvalidAttr,
43
+ numberMessage,
44
+ }) {
45
+ const $input = $(e.currentTarget);
46
+ const len = String($input.val() || '').replace(/\D/g, '').length;
47
+
48
+ clearErrorText($input, fieldSelector, errorTextSelector, errorClass, ariaInvalidAttr);
49
+
50
+ if (len < 16) {
51
+ showErrorText($input, numberMessage, fieldSelector, errorTextSelector, errorClass, ariaInvalidAttr);
52
+ }
53
+ }
54
+
55
+ export function handleGcPinInput(e, {
56
+ fieldSelector,
57
+ errorTextSelector,
58
+ errorClass,
59
+ ariaInvalidAttr,
60
+ }) {
61
+ const input = e.currentTarget;
62
+ const $input = $(input);
63
+
64
+ const hadErr = $input.closest(fieldSelector).hasClass(errorClass);
65
+ input.value = String(input.value || '').replace(/\D/g, '').slice(0, 8);
66
+
67
+ if (hadErr) {
68
+ clearErrorText($input, fieldSelector, errorTextSelector, errorClass, ariaInvalidAttr);
69
+ }
70
+ }
71
+
72
+
73
+ export function handleGcPinKeypress(e) {
74
+ if (!/\d/.test(String.fromCharCode(e.which || e.keyCode))) e.preventDefault();
75
+ }
76
+
77
+ export function handleGcPinBlur(e, {
78
+ fieldSelector,
79
+ errorTextSelector,
80
+ errorClass,
81
+ ariaInvalidAttr,
82
+ pinMessage,
83
+ }) {
84
+ const $input = $(e.currentTarget);
85
+ const length = String($input.val() || '').replace(/\D/g, '').length;
86
+
87
+ clearErrorText($input, fieldSelector, errorTextSelector, errorClass, ariaInvalidAttr);
88
+
89
+ if (length < 8) {
90
+ showErrorText($input, pinMessage, fieldSelector, errorTextSelector, errorClass, ariaInvalidAttr);
91
+ }
92
+ }
@@ -257,7 +257,6 @@ export function wrapWithError(element) {
257
257
 
258
258
  }
259
259
 
260
-
261
260
  export function addErrorMessage(element, errorMessage) {
262
261
  if (errorMessage) {
263
262
  $(element).append('<div class="form-row__error">' + errorMessage + '</div>');
@@ -316,3 +315,49 @@ export function validateGuidPattern (value) {
316
315
  export function validateUserFriendlyIdPattern (value) {
317
316
  return patterns.userFriendlyIdPattern.test(value);
318
317
  }
318
+
319
+ export function showErrorText($input, message, fieldSelector, errorTextClass, errorClass, ariaInvalidAttr) {
320
+ const $field = $input.closest(fieldSelector);
321
+ $field.addClass(errorClass);
322
+ $input.attr(ariaInvalidAttr, 'true');
323
+
324
+ let $err = $field.find(errorTextClass);
325
+ if (!$err.length) {
326
+ $err = $(`<div class="${errorTextClass.slice(1)}" />`).appendTo($field);
327
+ }
328
+ $err.text(message);
329
+ }
330
+
331
+ export function clearErrorText($input, fieldSelector, errorTextClass, errorClass, ariaInvalidAttr) {
332
+ const $field = $input.closest(fieldSelector);
333
+ $field.removeClass(errorClass);
334
+ $input.removeAttr(ariaInvalidAttr);
335
+ $field.find(errorTextClass).remove();
336
+ }
337
+
338
+ function digitsOnly(value) {
339
+ return String(value || '').replace(/\D/g, '');
340
+ }
341
+
342
+ function hasExactDigits(value, n) {
343
+ return digitsOnly(value).length === Number(n);
344
+ }
345
+
346
+ function validateByPredicate($input, predicate, message, {
347
+ fieldSelector,
348
+ errorText: errorTextClass,
349
+ fieldError: errorClass,
350
+ ariaInvalidAttr,
351
+ }) {
352
+ const ok = !!predicate(String($input.val() || ''));
353
+ if (!ok) {
354
+ showErrorText($input, message, fieldSelector, errorTextClass, errorClass, ariaInvalidAttr);
355
+ } else {
356
+ clearErrorText($input, fieldSelector, errorTextClass, errorClass, ariaInvalidAttr);
357
+ }
358
+ return ok;
359
+ }
360
+
361
+ export function validateExactDigits($input, n, message, cfg) {
362
+ return validateByPredicate($input, (v) => hasExactDigits(v, n), message, cfg);
363
+ }
@@ -22,14 +22,10 @@
22
22
  {{/block}}
23
23
  <a id="skip-to-content"></a>
24
24
  <main {{#if fullWidth}}class="full-width-content"{{/if}}>
25
- <div class="globalMessages">
26
- <div class="container">
27
- {{#block "notification"}}{{/block}}
28
- </div>
29
- </div>
30
25
  <div class="container">
31
26
  <div class="row">
32
27
  {{#block "steps"}}{{/block}}
28
+ {{#block "notification"}}{{/block}}
33
29
  <div class="content">
34
30
  {{#block "main"}}{{/block}}
35
31
  </div>
@@ -8,13 +8,15 @@
8
8
 
9
9
  <script src="https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js"></script>
10
10
  {{/content}}
11
- {{#content "notification"}}
12
- {{> notifications notification-page.error text="Sorry, there are errors on this page. Please review the information you've entered and update sections marked in red."}}
13
- {{/content}}
14
11
  {{#content "steps"}}
15
12
  {{> steps-three-steps active-step-index="2"}}
16
13
  {{/content}}
17
14
 
15
+ {{#content "notification"}}
16
+ <div class="globalMessages container">
17
+ </div>
18
+ {{/content}}
19
+
18
20
  {{#content "aside"}}
19
21
  {{> order-summary delivery-address-v2.summary no-action=true id=1 basket-id=true clearPayIcon=clearPayIcon billieIcon=true paymentsCheckoutV2=true checkout=true}}
20
22
  {{/content}}
@@ -55,7 +55,7 @@
55
55
  &__header {
56
56
  display: flex;
57
57
  justify-content: space-between;
58
- margin-bottom: 24px;
58
+ margin-bottom: 16px;
59
59
  }
60
60
 
61
61
  &__title {
@@ -92,6 +92,10 @@
92
92
  flex-direction: column;
93
93
  gap: 24px;
94
94
  }
95
+
96
+ .notification {
97
+ margin-bottom: 16px;
98
+ }
95
99
  }
96
100
 
97
101
  .form-row__label {
@@ -418,6 +418,13 @@
418
418
  margin-bottom: 13px;
419
419
  }
420
420
  }
421
+
422
+ .notification {
423
+ &:only-child,
424
+ &:last-child {
425
+ margin-bottom: 30px;
426
+ }
427
+ }
421
428
  }
422
429
 
423
430
  #modalDeliveryEdit,