PageSourceSearch

https://www.bathandbodyworks.com.bh/blocks/product-details/slots/price.js

js bathandbodyworks.com.bh collected 2026-10-08 23:03:06 UTC 38,634 bytes, 906 lines download raw bytes

1import { decorateIcons } from '../../../scripts/aem.js';
2import { getConfigValue, getLanguageAttr } from '../../../scripts/configs.js';
3import { calculateDiscount, getCurrencySymbolFromConfig } from '../../../scripts/utils.js';
4import { isLoggedInUser, hasValue } from '../../../scripts/scripts.js';
5import {
6  getAuraLocalStorageData,
7  isAuraLoggedIn,
8  isEarlyAccessToSaleEnabled,
9  checkBeforeOrDuringEarlyAccess,
10} from '../../../scripts/aura/api.js';
11// eslint-disable-next-line import/no-cycle
12import { formatPrice, compileCurrency } from '../pdp-utils.js';
13import { buildLiveCountdown, createEarlyAccessToSalePopup } from '../../header/header-utils.js';
14import { auraButtonClickEventHandler } from '../../header/aura-header.js';
15import { dataLayerEarlyAccessToSaleEvent } from '../../../scripts/analytics/google-data-layer.js';
16
17const appendPromotionsBelowPriceBlock = await getConfigValue('append-promo-below-price-block');
18
19function priceLabel(placeholders) {
20  const priceDiv = document.querySelector('.pdp-product__prices');
21  const priceTitle = document.createElement('span');
22  priceTitle.classList.add('pdp-price-title');
23  priceDiv.appendChild(priceTitle);
24  priceTitle.textContent = `${placeholders.price || 'Price:'}`;
25}
26
27export async function decorateMemberPrice(
28  ctx,
29  $block,
30  placeholders,
31  // eslint-disable-next-line no-unused-vars
32  blockConfigs,
33  memberPriceProvided = null,
34) {
35  let memberPrice;
36
37  if (memberPriceProvided) {
38    memberPrice = memberPriceProvided;
39  } else {
40    const memberPriceValue = ctx.data?.attributes.find((attr) => attr.name === 'member_price')?.value;
41    if (memberPriceValue) {
42      memberPrice = memberPriceValue;
43    }
44  }
45
46  if (memberPrice) {
47    const priceDiv = document.querySelector('.pdp-product__prices');
48    const memberPriceContainer = document.createElement('div');
49    memberPriceContainer.classList.add('pdp-member-price');
50    priceDiv.classList.add('member-price');
51    if (isLoggedInUser()) {
52      memberPriceContainer.classList.add('logged-in');
53      priceDiv.classList.add('member-price-logged-in');
54    } else {
55      memberPriceContainer.classList.add('guest');
56      priceDiv.classList.add('member-price-guest');
57    }
58
59    const memberPriceElement = document.createElement('div');
60    memberPriceElement.classList.add('pdp-member-price-container');
61
62    const memberPriceText = document.createElement('span');
63    memberPriceText.classList.add('pdp-member-price--text');
64    const currency = ctx.data.prices?.final?.currency || 'AED';
65    memberPriceText.textContent = await formatPrice(
66      currency,
67      memberPrice,
68      blockConfigs?.flags?.countryCode || 'AE',
69      blockConfigs.flags.decimalDigits || 2,
70    );
71
72    const memberPriceLabel = document.createElement('span');
73    memberPriceLabel.classList.add('pdp-member-price--label');
74    memberPriceLabel.textContent = placeholders.memberPriceLabel || 'Member Price';
75
76    const lang = getLanguageAttr();
77
78    if (lang === 'ar') {
79      memberPriceElement.appendChild(memberPriceLabel);
80      memberPriceElement.appendChild(memberPriceText);
81    } else {
82      memberPriceElement.appendChild(memberPriceText);
83      memberPriceElement.appendChild(memberPriceLabel);
84    }
85
86    memberPriceContainer.appendChild(memberPriceElement);
87    priceDiv.insertAdjacentElement('afterend', memberPriceContainer);
88  }
89}
90
91export async function decorateAuraPrice(
92  ctx,
93  $block,
94  placeholders,
95  blockConfigs,
96  auraPriceProvided,
97  selectedVariant = null,
98) {
99  let auraPrice;
100  // Remove any existing aura price elements from the DOM before adding new ones
101  const auraPriceDiv = document.querySelectorAll('.pdp-aura-price');
102  if (auraPriceDiv) {
103    auraPriceDiv.forEach((element) => {
104      element.remove();
105    });
106  }
107  const auraOfferContainer = document.querySelector('.get-aura-offer-container');
108  if (auraOfferContainer) {
109    auraOfferContainer.remove();
110  }
111
112  if (auraPriceProvided) {
113    auraPrice = auraPriceProvided;
114  } else {
115    const auraPriceValue = ctx.data?.attributes.find((attr) => attr.name === 'member_price')?.value;
116    if (auraPriceValue) {
117      auraPrice = auraPriceValue;
118    } else if (ctx?.data?.productTypeName === 'ComplexProductView') {
119      auraPrice = ctx?.data?.variants[0]?.product?.attributes.find((attr) => attr.name === 'member_price')?.value
120        ? parseFloat(ctx?.data?.variants[0]?.product?.attributes.find((attr) => attr.name === 'member_price')?.value) : undefined;
121      if (blockConfigs?.flags?.updateAuraPrice === true) {
122        auraPrice = blockConfigs?.decorations?.updateAuraPrice(ctx);
123      }
124    }
125  }
126
127  const priceDiv = document.querySelector('.pdp-product__prices');
128  const priceSpecialElement = document.querySelector('.pdp-product__price-special.pdp-product__price');
129  const auraPriceContainer = document.createElement('div');
130  auraPriceContainer.classList.add('pdp-aura-price');
131  priceDiv.classList.add('aura-price');
132
133  const auraIcon = document.createElement('span');
134  auraIcon.classList.add('icon', 'icon-aura-icon');
135
136  const auraPriceElement = document.createElement('div');
137  auraPriceElement.classList.add('pdp-aura-price-container');
138
139  const auraPriceText = document.createElement('span');
140  auraPriceText.classList.add('pdp-aura-price--text');
141  const currency = ctx.data.prices?.final?.currency || 'AED';
142  auraPriceText.textContent = await formatPrice(
143    currency,
144    auraPrice,
145    blockConfigs?.flags?.countryCode || 'AE',
146    blockConfigs.flags.decimalDigits || 2,
147  );
148
149  const auraPriceLabel = document.createElement('span');
150  auraPriceLabel.classList.add('pdp-aura-price--label');
151  auraPriceLabel.textContent = placeholders.auraPrice || 'Aura Price';
152
153  auraPriceContainer.appendChild(auraIcon);
154  auraPriceElement.appendChild(auraPriceText);
155  auraPriceContainer.appendChild(auraPriceElement);
156  auraPriceElement.appendChild(auraPriceLabel);
157
158  // extract special price
159  let specialPrice;
160  const finalPrice = selectedVariant?.product?.price?.final?.amount?.value
161    ?? ctx.data?.prices?.final?.amount;
162  const regularPrice = selectedVariant?.product?.price?.regular?.amount?.value
163    ?? ctx.data?.prices?.regular?.amount;
164
165  const fallbackSpecialPrice = ctx.data?.attributes?.find((attr) => attr.name === 'special_price')?.value;
166  const specialPriceValue = finalPrice ?? fallbackSpecialPrice;
167  if (specialPriceValue) {
168    specialPrice = specialPriceValue;
169  }
170
171  /* Special price lower than the aura price hide AURA */
172  if (typeof auraPrice === 'undefined' || parseFloat(auraPrice) === 0 || parseFloat(specialPrice) <= parseFloat(auraPrice)) {
173    // TODO: use class rather than style display none
174    auraPriceContainer.classList.add('hide');
175  }
176  decorateIcons(auraPriceContainer);
177  priceDiv.appendChild(auraPriceContainer);
178
179  ctx.onChange((next) => {
180    if (isAuraLoggedIn()) {
181      auraPriceContainer.classList.add('logged-in');
182      if (hasValue(specialPrice) && hasValue(regularPrice) && hasValue(auraPrice)
183        && parseFloat(specialPrice) !== parseFloat(regularPrice)
184        && parseFloat(specialPrice) > parseFloat(auraPrice)) {
185        priceSpecialElement?.classList.add('hide');
186      }
187
188      let mauraPrice;
189
190      if (auraPriceProvided) {
191        mauraPrice = auraPriceProvided;
192      } else {
193        const auraPriceValue = next.data?.attributes.find((attr) => attr.name === 'member_price')?.value;
194        if (auraPriceValue) {
195          mauraPrice = auraPriceValue;
196        } else if (next?.data?.productTypeName === 'ComplexProductView') {
197          const val = next?.data?.variants?.[0]?.product?.attributes.find((attr) => attr.name === 'member_price')?.value;
198          mauraPrice = val ? parseFloat(val) : undefined;
199        }
200      }
201      if (mauraPrice) {
202        priceDiv.classList.add('aura-price-logged-in');
203      } else {
204        priceDiv.classList.remove('aura-price-logged-in');
205      }
206      const auralogin = document.querySelector('.get-aura-offer-container');
207      if (auralogin) {
208        auralogin.remove();
209      }
210    }
211  });
212
213  const auraCustomerData = getAuraLocalStorageData();
214  if (isAuraLoggedIn()) {
215    auraPriceContainer.classList.add('logged-in');
216    if (auraPrice) {
217      priceDiv.classList.add('aura-price-logged-in');
218    }
219    if (priceSpecialElement && finalPrice !== regularPrice) {
220      if (specialPrice && auraPrice) {
221        if (parseFloat(specialPrice) <= parseFloat(auraPrice)) {
222          priceSpecialElement.classList.remove('hide');
223          priceSpecialElement.classList.add('no-strike');
224        } else {
225          priceSpecialElement.classList.add('hide'); // Hide if both exist
226        }
227      } else {
228        priceSpecialElement.classList.remove('hide'); // Show if only priceSpecialElement exists
229      }
230    } else if (finalPrice === regularPrice) {
231      priceSpecialElement.classList.remove('hide');
232    }
233    const currencySymbol = await getCurrencySymbolFromConfig();
234    if (currencySymbol) {
235      const priceEl = priceSpecialElement.querySelector('.pdp-price-range .dropin-price');
236      priceEl.textContent = priceEl.textContent
237        .replace(/\u00A0|\u200A|\u200F/g, '')
238        .replace(/[^\d.,-]/g, '');
239    }
240  } else {
241    const timer = setTimeout(async () => {
242      const openAuraModal = blockConfigs?.flags?.isAuraEnabled
243        ? await import('../../../scripts/aura/common.js')
244        : {};
245      const auraoffer = document.querySelector('.aura-price');
246      // Remove any existing offer container
247      const auraGetOfferContainer = document.querySelector('.get-aura-offer-container');
248      if (auraGetOfferContainer) {
249        auraGetOfferContainer.remove();
250      }
251
252      if (auraPrice && Number(specialPrice) > Number(auraPrice)) {
253        const joinAuraBannerContainer = document.createElement('div');
254        joinAuraBannerContainer.className = 'get-aura-offer-container';
255
256        const paragraph = document.createElement('p');
257
258        auraoffer.append(joinAuraBannerContainer);
259        joinAuraBannerContainer.appendChild(paragraph);
260
261        let joinAuraLabel;
262        let auraLoginButtonLabel;
263        let linkAuraLabel;
264        const getAuraOfferText = placeholders.getAuraOffer || 'Get this Aura Offer';
265
266        if (isLoggedInUser() && ['1', 1].includes(auraCustomerData?.apc_link)) {
267          linkAuraLabel = placeholders.linkYourAura || 'Link your Aura';
268          auraLoginButtonLabel = ''; // No login link for this case
269          const contentHtmlg = `<div class="get-aura-offer-links">
270            <p>${getAuraOfferText}
271            <span class="aura-underline-text">
272              ${auraLoginButtonLabel ? '' : `<a href="#join" class="linking-link">${linkAuraLabel}</a>`}
273            </span>
274            </p>
275          </div>`;
276          joinAuraBannerContainer.innerHTML = contentHtmlg;
277        } else {
278          // User type #1: Ecom guest and Aura guest
279          joinAuraLabel = placeholders.join || 'Join';
280          auraLoginButtonLabel = placeholders.auraLoginButton || 'Login';
281          const contentHtmlg = `<div class="get-aura-offer-links">
282          <p>${getAuraOfferText}
283          <span class="aura-underline-text">
284            <a href="#login" class="login-link">${auraLoginButtonLabel}</a>
285            ${auraLoginButtonLabel ? `<span>${placeholders.or || 'or'}</span> <a href="#login" class="join-link">${joinAuraLabel}</a>` : ''}
286          </span>
287          </p>
288        </div>`;
289          joinAuraBannerContainer.innerHTML = contentHtmlg;
290        }
291
292        const footerLinks = [
293          { selector: '.join-link', action: 'join' },
294          { selector: '.login-link', action: 'link' },
295          { selector: '.linking-link', action: 'link' },
296        ];
297        footerLinks.forEach(({ selector, action }) => {
298          const link = joinAuraBannerContainer.querySelector(selector);
299          if (link) {
300            link.addEventListener('click', () => openAuraModal.default(action));
301          }
302        });
303      }
304      clearTimeout(timer);
305    }, 100);
306  }
307}
308
309export async function decorateAuraEarlyAccessPrice(
310  ctx,
311  $block,
312  placeholders,
313  blockConfigs,
314  earlyAccessToSale,
315  memberPrice = null,
316  isQuickView = false,
317  selectedVariant = null,
318) {
319  if (blockConfigs?.decorations?.decorateAuraEarlyAccessPrice) {
320    blockConfigs?.decorations?.decorateAuraEarlyAccessPrice(
321      ctx,
322      $block,
323      placeholders,
324      blockConfigs,
325      earlyAccessToSale,
326      memberPrice,
327      isQuickView,
328      selectedVariant,
329    );
330  } else {
331    if (document.querySelector('.aura-early-access-price-container')) {
332      document.querySelectorAll('.aura-early-access-price-container').forEach((el) => el.remove());
333    }
334    let auraPrice = memberPrice || ctx.data?.attributes.find((attr) => attr.name === 'member_price')?.value;
335    if (ctx?.data?.productTypeName === 'ComplexProductView' && !auraPrice && selectedVariant) {
336      auraPrice = selectedVariant?.product?.attributes.find((attr) => attr.name === 'member_price')?.value
337        ? parseFloat(selectedVariant?.product?.attributes.find((attr) => attr.name === 'member_price')?.value) : undefined;
338    } else if (ctx?.data?.productTypeName === 'ComplexProductView' && !auraPrice) {
339      auraPrice = ctx?.data?.variants[0]?.product?.attributes.find((attr) => attr.name === 'member_price')?.value
340        ? parseFloat(ctx?.data?.variants[0]?.product?.attributes.find((attr) => attr.name === 'member_price')?.value) : undefined;
341    }
342    let label = '';
343    const earlyAccessPhase = await checkBeforeOrDuringEarlyAccess();
344    if (earlyAccessPhase === 'before') {
345      label = placeholders.beforeEarlyAccessToSalePdp || 'Early Access to Sale on this Item in';
346    } else if (earlyAccessPhase === 'during') {
347      label = placeholders.duringEarlyAccessToSalePdp || 'Time left for Early Access to Sale';
348    }
349    const salePrice = selectedVariant
350      ? selectedVariant?.product?.price?.final?.amount?.value
351      : ctx.data?.prices?.final?.amount;
352    const regularPrice = selectedVariant
353      ? selectedVariant?.product?.price?.regular?.amount?.value
354      : ctx.data?.prices?.regular?.amount;
355    const currency = ctx.data?.prices?.final?.currency || 'AED';
356    const auraPriceDivs = document.querySelectorAll('.pdp-aura-price');
357    auraPriceDivs.forEach((element) => element.remove());
358    const slotMicroFunctions = {
359      decorateAuraPrice: blockConfigs?.decorations?.decorateAuraPrice || decorateAuraPrice,
360      decorateMemberPrice: blockConfigs?.decorations?.decorateMemberPrice || decorateMemberPrice,
361    };
362    if (parseFloat(auraPrice)
363      && parseFloat(auraPrice) < parseFloat(salePrice) && blockConfigs?.flags?.isAuraEnabled) {
364      document.querySelector('.pdp-product__promotion-labels-container')?.classList.add('hide');
365      if (parseFloat(salePrice) === parseFloat(regularPrice)) {
366        document.querySelector('.pdp-product__price-special')?.classList.remove('hide');
367      }
368      if (parseFloat(salePrice) !== parseFloat(regularPrice)) {
369        document.querySelector('.pdp-product__price-special')?.classList.add('hide');
370        if (!isAuraLoggedIn()) {
371          document.querySelector('.pdp-product__price-regular .dropin-price')?.classList.remove('dropin-price--strikethrough');
372        }
373      }
374      slotMicroFunctions.decorateAuraPrice(
375        ctx,
376        $block,
377        placeholders,
378        blockConfigs,
379        auraPrice,
380        selectedVariant,
381      );
382    } else if (parseFloat(auraPrice)
383      && parseFloat(auraPrice) < parseFloat(salePrice) && !blockConfigs?.flags?.isAuraEnabled) {
384      slotMicroFunctions.decorateMemberPrice(ctx, $block, placeholders, blockConfigs, auraPrice);
385    } else if (parseFloat(salePrice) === parseFloat(regularPrice)) {
386      document.querySelector('.pdp-product__price-special')?.classList.remove('hide');
387    } else {
388      const container = document.createElement('div');
389      container.className = 'container aura-early-access-price-container';
390      // AED Price Line
391      const priceLine = document.createElement('div');
392      priceLine.className = 'sale-prices';
393      const priceText = document.createElement('span');
394      priceText.className = 'price-text';
395      priceText.textContent = formatPrice(currency, salePrice, blockConfigs?.flags?.countryCode || 'AE', blockConfigs.flags.decimalDigits || 2);
396      const customerAuraData = getAuraLocalStorageData();
397
398      const labelText = document.createElement('span');
399      labelText.className = 'label-text';
400      labelText.textContent = placeholders?.earlyAccessPriceLabel || 'Aura Star & VIP Only';
401      if (earlyAccessPhase === 'before' && isQuickView) {
402        labelText.textContent = '';
403      }
404
405      if (earlyAccessPhase === 'during') {
406        priceLine.appendChild(priceText);
407      }
408
409      priceLine.appendChild(labelText);
410      container.appendChild(priceLine);
411
412      const notLinked = ['1', 1].includes(customerAuraData?.apc_link);
413      const isLoggedIn = isLoggedInUser();
414
415      // Subtext
416      if (!isQuickView) {
417        const content = document.createElement('div');
418        content.className = 'timer-content';
419        const subtext = document.createElement('div');
420        subtext.className = 'subtext';
421        subtext.textContent = label;
422
423        content.appendChild(subtext);
424        content.appendChild(buildLiveCountdown(placeholders));
425        container.appendChild(content);
426
427        const promoLinkContainer = document.createElement('div');
428        promoLinkContainer.className = 'promo-link-container';
429        const promoLink = document.createElement('a');
430        promoLink.className = 'promo-link';
431        promoLink.href = '#';
432        promoLink.textContent = placeholders.learnMoreText || 'Learn More';
433        promoLinkContainer.appendChild(promoLink);
434        if (!customerAuraData || !customerAuraData?.aura_membership || (isLoggedIn && notLinked)) {
435          const joinnow = document.createElement('a');
436          joinnow.className = 'join-now promo-link';
437          joinnow.href = '#';
438          const linkText = isLoggedIn && notLinked ? placeholders.linkYourAura || 'Link Your Aura' : placeholders.joinAuraText || 'Join Aura';
439          joinnow.textContent = linkText;
440          auraButtonClickEventHandler(joinnow, notLinked ? 'link' : 'join', 'SignIn_');
441          joinnow.addEventListener('click', (event) => {
442            event.preventDefault();
443            dataLayerEarlyAccessToSaleEvent(ctx.data, 'early_access banner click');
444          });
445          const promoLinkText = document.createElement('span');
446          promoLinkText.className = 'promo-link-text';
447          promoLinkText.textContent = placeholders.or || 'or';
448          promoLinkContainer.appendChild(promoLinkText);
449          promoLinkContainer.appendChild(joinnow);
450        }
451
452        container.appendChild(promoLinkContainer);
453        promoLink.addEventListener('click', (event) => {
454          event.preventDefault();
455          dataLayerEarlyAccessToSaleEvent(ctx.data, 'early_access banner click');
456          createEarlyAccessToSalePopup();
457        });
458      }
459
460      const userEligibilityCheck = (!isLoggedIn || (isLoggedIn && parseInt(customerAuraData?.apc_link, 10) !== 1)) && earlyAccessToSale?.early_access_allow_tier?.includes(cu
460stomerAuraData?.aura_Status) && earlyAccessPhase === 'during';
461      if (document.querySelector('.aura-early-access-price-container')) {
462        document.querySelectorAll('.aura-early-access-price-container').forEach((el) => el.remove());
463      }
464      if (!isQuickView) {
465        document.querySelector('.pdp-product__prices').insertAdjacentElement('afterend', container);
466        document.querySelector('.pdp-product__price-regular .dropin-price')?.classList.remove('dropin-price--strikethrough');
467      } else {
468        if (userEligibilityCheck) {
469          document.querySelector('.modal-content .pdp-product__prices').insertAdjacentElement('beforebegin', container);
470        } else {
471          document.querySelector('.modal-content .pdp-product__prices').insertAdjacentElement('afterend', container);
472        }
473        document.querySelector('.modal-content .pdp-product__price-regular .dropin-price')?.classList.remove('dropin-price--strikethrough');
474      }
475      if (userEligibilityCheck) {
476        document.querySelector('.pdp-product__price-regular .dropin-price')?.classList.add('dropin-price--strikethrough');
477        document.querySelector('.dropin-price--small.dropin-price--bold')?.classList.add('dropin-price--strikethrough');
478        const currencySymbol = await getCurrencySymbolFromConfig();
479        if (currencySymbol) {
480          const priceElement = document.querySelector('.pdp-product__price-regular .dropin-price');
481          if (priceElement) {
482            let priceValue = priceElement.innerText;
483            priceValue = priceValue.replace(currencySymbol, '').trim();
484            priceElement.innerText = priceValue;
485          }
486        }
487        document.querySelector('.pdp-product__price-regular .dropin-price').classList.add('dropin-price--strikethrough');
488      }
489    }
490  }
491}
492
493export async function decorateAuraMemberPrice(
494  ctx,
495  $block,
496  placeholders,
497  blockConfigs,
498  memberPrice = null,
499) {
500  const isQuickView = $block.id === 'product-overview-popup';
501  const earlyAccessToSale = await isEarlyAccessToSaleEnabled();
502  const salePrice = ctx.data?.prices?.final?.amount;
503  const regularPrice = ctx.data?.prices?.regular?.amount;
504  const auraPrice = memberPrice || ctx.data?.attributes.find((attr) => attr.name === 'member_price')?.value;
505  // if hello early access price enabled only for few variants
506  const hmEarlyAccessPrice = ctx.data?.variants?.find((variant) => variant?.product?.attributes?.find((item) => item?.name === 'eats_price')?.value > 0);
507  const hmEarlyAccessPercentage = ctx.data?.variants?.find((variant) => variant?.product?.attributes?.find((item) => item?.name === 'eats_category_percent')?.value > 0);
508  const isHelloMemberBrand = blockConfigs?.flags?.isHelloMemberBrand;
509  const earlyAccessPhase = earlyAccessToSale?.early_access_enable === 1
510    ? await checkBeforeOrDuringEarlyAccess() : 0;
511  if (blockConfigs?.flags?.aurapriceDisabled && earlyAccessPhase === 0) return;
512  const slotMicroFunctions = {
513    decorateAuraPrice: blockConfigs?.decorations?.decorateAuraPrice || decorateAuraPrice,
514    decorateMemberPrice: blockConfigs?.decorations?.decorateMemberPrice || decorateMemberPrice,
515    decorateAuraEarlyAccessPrice: blockConfigs?.decorations?.decorateAuraEarlyAccessPrice
516      || decorateAuraEarlyAccessPrice,
517    decorateMemberEarlyAccessPrice: blockConfigs?.decorations?.decorateMemberEarlyAccessPrice,
518  };
519  if (earlyAccessPhase !== 0 && (hmEarlyAccessPrice || hmEarlyAccessPercentage)
520    && blockConfigs?.decorations?.decorateMemberEarlyAccessPrice) {
521    slotMicroFunctions.decorateMemberEarlyAccessPrice(
522      ctx,
523      placeholders,
524      blockConfigs,
525      earlyAccessToSale,
526    );
527  } else if (earlyAccessPhase !== 0
528    && salePrice && !isHelloMemberBrand
529    && ((auraPrice && salePrice <= auraPrice) || (salePrice !== regularPrice))) {
530    slotMicroFunctions.decorateAuraEarlyAccessPrice(
531      ctx,
532      $block,
533      placeholders,
534      blockConfigs,
535      earlyAccessToSale,
536      auraPrice,
537      isQuickView,
538    );
539  } else if (blockConfigs?.flags?.isAuraEnabled) {
540    slotMicroFunctions.decorateAuraPrice(ctx, $block, placeholders, blockConfigs, memberPrice);
541  } else {
542    slotMicroFunctions.decorateMemberPrice(ctx, $block, placeholders, blockConfigs, memberPrice);
543  }
544}
545
546export async function decorateSpecialPrice(
547  ctx,
548  placeholders,
549  blockConfigs,
550  decimalDigits,
551  countryCode,
552  isVatIncluded = true,
553) {
554  const special = ctx.data.prices.special || ctx.data.prices.final;
555  const { maximumAmount, minimumAmount, amount } = special;
556  let maxPrice;
557  let minPrice;
558  let price;
559
560  const minimumFractionDigits = (decimalDigits);
561  const maximumFractionDigits = (decimalDigits);
562
563  // Use compileCurrency utility to get the best available currency
564  const priceCurrency = await compileCurrency(special.currency, placeholders.currency);
565  if (maximumAmount && minimumAmount) {
566    maxPrice = formatPrice(priceCurrency, maximumAmount, countryCode, maximumFractionDigits);
567    minPrice = formatPrice(priceCurrency, minimumAmount, countryCode, minimumFractionDigits);
568  } else {
569    price = formatPrice(priceCurrency, amount, countryCode, decimalDigits);
570  }
571  if (blockConfigs?.flags?.addPriceLabel) {
572    priceLabel(placeholders);
573  }
574  const specialPriceHtml = document.createElement('div');
575  specialPriceHtml.classList.add('pdp-price-range');
576
577  const specialPriceMin = document.createElement('span');
578  specialPriceMin.classList.add('dropin-price', 'dropin-price--default', 'dropin-price--small', 'dropin-price--bold');
579  specialPriceMin.textContent = minPrice;
580
581  const specialPriceHyphen = document.createElement('span');
582  specialPriceHyphen.classList.add('pdp-price-range__label');
583  specialPriceHyphen.textContent = '-';
584
585  const specialPriceMax = document.createElement('span');
586  specialPriceMax.classList.add('dropin-price', 'dropin-price--default', 'dropin-price--small', 'dropin-price--bold');
587  specialPriceMax.textContent = maxPrice;
588
589  const specialPriceSpan = document.createElement('span');
590  specialPriceSpan.classList.add('dropin-price', 'dropin-price--default', 'dropin-price--small', 'dropin-price--bold');
591  specialPriceSpan.textContent = price;
592
593  if (maximumAmount && minimumAmount) {
594    specialPriceHtml.appendChild(specialPriceMin);
595    specialPriceHtml.appendChild(specialPriceHyphen);
596    specialPriceHtml.appendChild(specialPriceMax);
597    if (blockConfigs?.flags?.hideSpecialPrice) {
598      [specialPriceMin, specialPriceHyphen, specialPriceMax].forEach((priceEl) => priceEl.classList.add('hidden'));
599    }
600  } else {
601    specialPriceHtml.appendChild(specialPriceSpan);
602  }
603
604  ctx.replaceWith(specialPriceHtml);
605  const includingVat = document.createElement('div');
606  if (isVatIncluded) {
607    includingVat.classList.add('pdp-product__price--including-vat');
608    includingVat.textContent = placeholders.priceIncludingVat || 'Inc. VAT';
609    ctx.appendChild(includingVat);
610    if (ctx.data.prices.regular && ctx.data.prices.regular.variant === 'strikethrough') {
611      includingVat?.parentElement?.classList.add('hide');
612    }
613    // KW has no VAT
614  }
615
616  // When customSpecialPriceOnChange is set, the market's block-config owns
617  // all SpecialPrice updates via decorateAdditionalSpecialPrice — skip the
618  // shared onChange handler to avoid stale-data conflicts and double renders.
619  if (!blockConfigs?.flags?.customSpecialPriceOnChange) {
620    ctx.onChange(async (next) => {
621      const newSpecial = next.data.prices.special || next.data.prices.final;
622      const {
623        maximumAmount: newMaximumAmount, minimumAmount: newMinimumAmount, amount: newAmount,
624      } = newSpecial;
625
626      if (newSpecial) {
627        // Use compileCurrency utility to get the best available currency
628        const newPriceCurrency = await compileCurrency(newSpecial.currency, placeholders.currency);
629        price = formatPrice(
630          newPriceCurrency,
631          newAmount,
632          countryCode,
633          decimalDigits,
634        );
635        minPrice = formatPrice(
636          newPriceCurrency,
637          newMinimumAmount,
638          countryCode,
639          minimumFractionDigits,
640        );
641        maxPrice = formatPrice(
642          newPriceCurrency,
643          newMaximumAmount,
644          countryCode,
645          maximumFractionDigits,
646        );
647
648        if (newMaximumAmount && newMinimumAmount) {
649          specialPriceMin.textContent = minPrice;
650          specialPriceMax.textContent = maxPrice;
651          specialPriceHtml.innerHTML = '';
652          specialPriceHtml.appendChild(specialPriceMin);
653          specialPriceHtml.appendChild(specialPriceHyphen);
654          specialPriceHtml.appendChild(specialPriceMax);
655        } else {
656          specialPriceSpan.textContent = price;
657          specialPriceHtml.innerHTML = specialPriceSpan.outerHTML;
658        }
659        if (isVatIncluded) {
660          if (next.data.prices.regular && next.data.prices.regular.variant === 'strikethrough') {
661            includingVat?.parentElement?.classList.add('hide');
662          } else if (!next.data.prices.regular || next.data.prices.regular.variant === 'default') {
663            includingVat?.parentElement?.classList.remove('hide');
664          }
665        }
666      }
667    });
668  }
669}
670
671const earlyAccessPhase = await checkBeforeOrDuringEarlyAccess();
672export function decorateRegularPrice(
673  ctx,
674  placeholders,
675  blockConfigs,
676  decimalDigits,
677  countryCode,
678  isVatIncluded = true,
679) {
680  const { regular } = ctx.data.prices;
681  const formattedPrice = formatPrice(regular.currency, regular.amount, countryCode, decimalDigits);
682  Promise.all([
683    getCurrencySymbolFromConfig(),
684  ]).then(([currencySymbol]) => {
685    const regularPrice = document.createElement('div');
686    regularPrice.classList.add('pdp-price-range');
687    const regularPriceSpan = document.createElement('span');
688    regularPriceSpan.classList.add('dropin-price', 'dropin-price--small', 'dropin-price--bold');
689    regularPriceSpan.textContent = `${formattedPrice}`;
690
691    regularPrice.appendChild(regularPriceSpan);
692
693    if (regular?.variant) {
694      if (currencySymbol) {
695        if (regular?.variant === 'strikethrough') {
696          regularPriceSpan.textContent = formattedPrice.replace(currencySymbol, '').trim();
697        }
698      }
699      regularPriceSpan.classList.add(`dropin-price--${regular.variant}`);
700    }
701    ctx.replaceWith(regularPrice);
702
703    if (isVatIncluded && ctx.data.prices.regular) {
704      const { priceIncludingVat } = placeholders;
705      const includingVat = document.createElement('div');
706      includingVat.classList.add('pdp-product__price--including-vat');
707      includingVat.textContent = priceIncludingVat || 'Inc. VAT';
708      ctx.appendChild(includingVat);
709    }
710    let newFormattedPrice;
711    ctx.onChange((next) => {
712      const { regular: newRegular } = next.data.prices;
713      newFormattedPrice = formatPrice(
714        newRegular.currency,
715        newRegular.amount,
716        countryCode,
717        decimalDigits,
718      );
719      if (currencySymbol) {
720        const pricesEl = document.querySelector('.pdp-product__prices');
721        const hasAuraPrice = pricesEl.classList.contains('aura-price');
722        if (isAuraLoggedIn() && hasAuraPrice) {
723          // Remove currency symbol ONLY for regular price
724          newFormattedPrice = newFormattedPrice.replace(currencySymbol, '').trim();
725        } else if ((earlyAccessPhase !== 'during' && earlyAccessPhase !== 'before' && earlyAccessPhase !== 0) && isAuraLoggedIn() && isLoggedInUser()) {
726          // Remove currency symbol ONLY for regular price
727          newFormattedPrice = newFormattedPrice.replace(currencySymbol, '').trim();
728        } else if (!earlyAccessPhase) {
729          // Remove currency symbol ONLY for regular price
730          newFormattedPrice = newFormattedPrice.replace(currencySymbol, '').trim();
731        }
732      }
733      regularPriceSpan.textContent = `${newFormattedPrice}`;
734    });
735  });
736}
737
738const earlyAccessEnabled = await isEarlyAccessToSaleEnabled();
739const auraCustomerData = getAuraLocalStorageData();
740// eslint-disable-next-line no-unused-vars
741export function decoratePromotions(ctx, $block, placeholders, locale, blockConfigs) {
742  ctx.replaceWith(document.createElement('div'));
743  const lang = getLanguageAttr();
744  const productPromotions = ctx.data.promotions || [];
745
746  const { final, regular } = ctx.data.prices;
747  if (final?.amount < regular?.amount) {
748    const discount = calculateDiscount(regular.amount, final.amount);
749    productPromotions.unshift({
750      label: placeholders.plpSave !== '0' ? `(${placeholders.plpSave} ${discount}%)` : `-${discount}%`,
751      type: 'discount',
752    });
753  } else if (final?.minimumAmount < final?.maximumAmount) {
754    const discount = calculateDiscount(final.maximumAmount, final.minimumAmount);
755    productPromotions.unshift({
756      label: placeholders.plpSave !== '0' ? `(${placeholders.plpSave} ${discount}%)` : `-${discount}%`,
757      type: 'discount',
758    });
759  } else {
760    productPromotions.unshift({
761      label: '',
762      type: 'discount',
763      hide: true,
764    });
765  }
766  const labelsContainer = document.createElement('div');
767  const linksContainer = document.createElement('div');
768  if (productPromotions.length) {
769    const promoWithoutLinks = productPromotions.filter((promotion) => !promotion.url);
770    const promoWithLinks = productPromotions.filter((promotion) => !!promotion.url);
771
772    if (!blockConfigs.flags.discountValuehide) {
773      labelsContainer.classList.add('pdp-product__promotion-labels-container');
774      promoWithoutLinks.forEach((promotion) => {
775        if (promotion?.context?.includes('web') && promotion.label?.trim()) {
776          const promotionLabel = document.createElement('div');
777          const promotionClass = promotion?.type === 'discount' ? 'discount-label' : '';
778          promotionLabel.classList.add('promo-label');
779          if (promotionClass.trim() !== '') {
780            promotionLabel.classList.add(promotionClass);
781          }
782          if (promotion.hide) {
783            promotionLabel.classList.add('hide');
784            labelsContainer.classList.add('hide');
785          }
786
787          promotionLabel.textContent = promotion.label;
788          labelsContainer.appendChild(promotionLabel);
789        }
790      });
791      if (appendPromotionsBelowPriceBlock !== 'true') {
792        ctx.prependSibling(labelsContainer);
793      }
794      if (earlyAccessPhase === 'before'
795        || (earlyAccessPhase !== 0
796        && !earlyAccessEnabled?.early_access_allow_tier.includes(auraCustomerData?.aura_Status))
797        || (earlyAccessPhase === 'during' && ((isLoggedInUser() && [1, '1'].includes(auraCustomerData?.apc_link))))) {
798        labelsContainer.querySelectorAll('.promo-label').forEach((el) => { el.classList.add('hide'); });
799      }
800    }
801
802    const linkContainerClass = 'pdp-product__promotion-links-container';
803
804    if (appendPromotionsBelowPriceBlock) {
805      linksContainer.classList.add(linkContainerClass);
806    }
807
808    if (promoWithLinks.length) {
809      if (!linksContainer.classList.contains(linkContainerClass)) {
810        linksContainer.classList.add(linkContainerClass);
811      }
812      promoWithLinks.forEach((promotion) => {
813        if (promotion?.context?.includes('web') && promotion?.label && promotion?.label?.trim()) {
814          const promotionLabel = document.createElement('div');
815          promotionLabel.classList.add('promo-label');
816          promotionLabel.innerHTML = `<a href="/${lang}/${promotion.url}" alt="${promotion.description}">${promotion.label}</a>`;
817          promotionLabel.addEventListener('click', (event) => {
818            event.preventDefault();
819            localStorage.setItem('categoryListName', `pdp-pl-${promotion.label}`); // Set the pdp promo in local storage
820            window.location.href = `/${lang}/${promotion.url}`;
821          });
822
823          linksContainer.appendChild(promotionLabel);
824        }
825      });
826    }
827  }
828
829  if (appendPromotionsBelowPriceBlock === 'true') {
830    linksContainer.prepend(labelsContainer);
831    const priceBlock = document.querySelector('.pdp-product__prices');
832    if (priceBlock) {
833      priceBlock.insertAdjacentElement('afterend', linksContainer);
834    }
835  } else {
836    ctx.prependSibling(linksContainer);
837  }
838
839  let discountPromotion = labelsContainer?.querySelector('.discount-label');
840  if (!discountPromotion) {
841    discountPromotion = document.createElement('div');
842    discountPromotion.classList.add('promo-label', 'discount-label');
843  }
844
845  ctx.onChange(async (next) => {
846    const { final: finalChanged, regular: regularChanged } = next.data.prices;
847    const isShowPdpDiscountPrice = blockConfigs?.flags?.isShowPdpDiscountPrice;
848    const isShowQuickViewDiscountPrice = blockConfigs?.flags?.isShowQuickViewDiscountPrice;
849
850    const formatDiscountValue = await getConfigValue('plp-show-discount-text-format') || 'false';
851    const formatDiscountPosition = await getConfigValue('plp-show-discount-text-position') || 'false';
852
853    if (discountPromotion) {
854      if (finalChanged?.amount < regularChanged?.amount) {
855        const discountChanged = calculateDiscount(regularChanged.amount, finalChanged.amount);
856        let discountText = '';
857        const isHtmlRtl = document.documentElement.lang;
858        if (placeholders.plpSave !== '0') {
859          if (isShowPdpDiscountPrice) {
860            discountText = `${placeholders.plpSave} ${discountChanged}%`;
861          } else if (isShowQuickViewDiscountPrice) {
862            discountText = `${placeholders.plpSave} ${discountChanged}%`;
863          } else if (blockConfigs?.flags?.isShowMinusDiscountChanged) {
864            discountText = `-${discountChanged}%`;
865          } else {
866            discountText = `(${placeholders.plpSave} ${discountChanged}%)`;
867            // Check if the discount label should be left side of discount value for Arabic
868            if (blockConfigs?.flags?.arabicDiscountLabelLeft && isHtmlRtl === 'ar') {
869              discountText = `( %${discountChanged} ${placeholders.plpSave})`;
870            }
871          }
872
873          if (formatDiscountValue === 'true' && formatDiscountPosition === 'false') {
874            discountText = `${placeholders.plpSave} ${discountChanged}%`;
875            if (isHtmlRtl === 'ar') {
876              discountText = ` ${placeholders.plpSave} %${discountChanged}`;
877            }
878          } else if (formatDiscountPosition === 'true') {
879            discountText = `${discountChanged}% ${placeholders.plpSave}`;
880            if (isHtmlRtl === 'ar') {
881              discountText = `${placeholders.plpSave} %${discountChanged}`;
882            }
883          }
884        } else {
885          discountText = `-${discountChanged}%`;
886          if (blockConfigs?.flags?.enableRtlDiscountReverse && isHtmlRtl) {
887            discountText = `%${discountChanged}-`;
888            discountPromotion.setAttribute('dir', 'ltr');
889          }
890        }
891        discountPromotion.textContent = discountText;
892        discountPromotion.classList.remove('hide');
893        labelsContainer?.appendChild(discountPromotion);
894      } else {
895        discountPromotion.textContent = '';
896        discountPromotion.classList.add('hide');
897      }
898      if (earlyAccessPhase === 'before'
899        || (earlyAccessPhase !== 0
900        && !earlyAccessEnabled?.early_access_allow_tier.includes(auraCustomerData?.aura_Status))
901        || (earlyAccessPhase === 'during' && ((isLoggedInUser() && [1, '1'].includes(auraCustomerData?.apc_link))))) {
902        labelsContainer.querySelectorAll('.promo-label').forEach((el) => { el.classList.add('hide'); });
903      }
904    }
905  });
906}

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.