PageSourceSearch

https://www.americaneagle.com.kw/scripts/target/target-events.js

js americaneagle.com.kw collected 2026-10-08 17:07:25 UTC 28,519 bytes, 865 lines download raw bytes

1/* eslint-disable import/no-cycle */
2import { getConfigValue, getBrandConfigs, REG_DATE_KEY } from '../configs.js';
3import {
4  getUserAttributes,
5  getPageAttributes,
6  getPageName,
7  setPageName,
8  transformBannerData,
9} from '../analytics/google-data-layer.js';
10import { store } from '../minicart/api.js';
11import { getAuthUserToken } from '../helpers/user-utils.js';
12import { isPDP, isPLP, isSearchPage } from '../utils.js';
13
14const MALE_LIST = ['men', 'male', 'boys'];
15const FEMALE_LIST = ['women', 'female', 'girls'];
16export const EVENT_QUEUE = [];
17const brandConfigs = await getBrandConfigs();
18
19/**
20 * Auxillary function to check gender of the product
21 * based on pre-defined logic from category list
22 * @param {*} categoryList Category list of currently viewed product
23 * @returns gender - male, female or unisex
24 */
25export default function checkGender(categoryList) {
26  const maleRegex = new RegExp(MALE_LIST.join('|'), 'i');
27  const femaleRegex = new RegExp(FEMALE_LIST.join('|'), 'i');
28
29  const hasMaleItem = categoryList.some((item) => maleRegex.test(item));
30  const hasFemaleItem = categoryList.some((item) => femaleRegex.test(item));
31  let gender;
32
33  if (hasFemaleItem) {
34    gender = 'Female';
35  } else if (hasMaleItem && !hasFemaleItem) {
36    gender = 'Male';
37  } else {
38    gender = 'Unisex';
39  }
40
41  return gender;
42}
43
44/**
45 * Replace all null values with an empty string recursively.
46 * @param {*} obj
47 * @returns sanitized object
48 */
49function sanitizePayload(obj) {
50  if (Array.isArray(obj)) {
51    return obj.map((item) => sanitizePayload(item));
52  }
53  if (obj && typeof obj === 'object') {
54    return Object.entries(obj).reduce((acc, [key, value]) => {
55      acc[key] = (value === null) ? '' : sanitizePayload(value);
56      return acc;
57    }, {});
58  }
59  return obj;
60}
61
62let brandEventsPromise = null;
63if (brandConfigs.noconfig || brandConfigs.target) {
64  brandEventsPromise = import(`./${window.axp.brandPath}target-events.js`).catch(() => Promise.resolve);
65}
66
67/* *** Utility Methods *** */
68export function getRecommendationsDataFromQueue(selectedKey) {
69  const eventData = EVENT_QUEUE?.find((el) => el.key === selectedKey)
70    ?.data[0]?.data?.content || null;
71  try {
72    const parsedEventData = JSON.parse(eventData);
73    return parsedEventData?.recommendations || [];
74  } catch (e) {
75    console.error(e);
76    return [];
77  }
78}
79
80/**
81 * Find and return the activity payload attributes from target response meta data
82 * @param {*} targetId decision scope
83 * @returns activity payload attributes
84 */
85function getActivityInfo(targetId) {
86  const { meta } = EVENT_QUEUE?.find((el) => el.key === targetId)?.data?.[0] || {};
87  return {
88    activityId: meta?.['activity.id'],
89    activityName: meta?.['activity.name'],
90    experienceID: meta?.['experience.id'],
91    experienceName: meta?.['experience.name'],
92  };
93}
94
95/**
96* Auxillary function to create listed category object from category list
97* @param {*} categoryList Array of All the current and parent categories
98* @param {*} entity Entity
99* @returns listed category object
100*/
101export function createCategories(categoryList, key) {
102  return {
103    [`${key}majorCategory`]: categoryList?.[0],
104    [`${key}minorCategory`]: categoryList?.[1],
105    [`${key}subCategory`]: categoryList?.[2],
106    [`${key}listingCategory`]: categoryList
107      ?.[categoryList.length - 1],
108  };
109}
110
111/**
112 * Utility to find attribute is array of objects and return it's value
113 * @param {*} arr Array of attributes object
114 * @param {*} attribute Attribute name to find
115 * @returns Attribute value if present else undefined
116 */
117function getValue(arr, attribute) {
118  const value = arr?.find((el) => el.name === attribute)?.value || undefined;
119  if (Array.isArray(value)) {
120    if (value.length === 0) return undefined;
121    return value.join(', ');
122  }
123  return value;
124}
125
126/**
127 * Utility to replace escaped double quotes with HTML entity "
128 * as target system is unable to parse \"
129 * @param {*} obj Object to encode
130 * @returns Object with encoded double quotes in attributes
131 */
132function encodeRecursiveHTMLEntities(obj) {
133  if (typeof obj === 'object') {
134    return Object.keys(obj).reduce((acc, key) => {
135      acc[key] = encodeRecursiveHTMLEntities(obj[key]);
136      return acc;
137    }, {});
138  }
139  return obj?.replace(/"/g, '"');
140}
141
142function formatCategoryIdForPageName(categoryId) {
143  if (!Array.isArray(categoryId)) {
144    return '';
145  }
146  return categoryId.join('|');
147}
148
149/* *** Generic Events *** */
150/**
151 * Common XDM payload for all target calls
152 * @returns XDM payload for authenticated users, empty object otherwise
153 */
154export async function xdmPayload(customXdmPayload = {}) {
155  const isAuthenticated = !!getAuthUserToken();
156  const [{ userID = null, userType, customerType }, brand = null] = await Promise.all([
157    getUserAttributes(),
158    getConfigValue('brand'),
159  ]);
160  const xdmObj = {
161    _mhalshaya: { // when the user is authenticated or un-authenticated
162      customerType,
163      userType,
164    },
165    ...customXdmPayload,
166  };
167  if (isAuthenticated) {
168    xdmObj.identityMap = {
169      customerSourceID: [
170        {
171          authenticatedState: 'authenticated',
172          id: `${userID}-MAGENTO_${brand}`,
173          primary: true,
174        },
175      ],
176    };
177  }
178  return sanitizePayload(xdmObj);
179}
180
181/**
182 * Common payload for all pages where target call is fired
183 * @returns Promise of attributes object
184 */
185export async function pageLoadData() {
186  const [
187    { brand, countryCode, language },
188    environment,
189    { brandPageLoadData = () => {} },
190    { userType, customerType },
191  ] = await Promise.all([
192    getPageAttributes(),
193    getConfigValue('target-environment'),
194    brandEventsPromise,
195    getUserAttributes(),
196  ]);
197  const brandPayload = brandPageLoadData();
198  const market = countryCode?.toLowerCase();
199  const productsInCart = store
200    .getCart()
201    ?.cart?.items?.map(
202      ({ sku, extension_attributes: attribs = {} }) => `${attribs.parent_product_sku || sku}_${market}`,
203    );
204  const currentPath = window.location.pathname;
205  const cartIds = (currentPath.includes('/cart') || currentPath.includes('/checkout'))
206    ? productsInCart?.join(', ') || ''
207    : undefined;
208  const { pageType } = window;
209  return sanitizePayload({
210    channel: 'web',
211    brand,
212    environment,
213    market,
214    language,
215    pageName: getPageName(pageType), // The page/screen name on which the user is on
216    pageType,
217    excludedIds: productsInCart?.join(', ') || '', // List of product IDs that are already in the cart
218    cartIds,
219    userType,
220    customerType,
221    ...brandPayload,
222  });
223}
224
225/**
226 * Send display event to Target on page load for reporting purpose
227 * @param {*} payload Custom scoped Propositions displayed on the page
228 */
229export async function targetDisplayEvent(payload = [], noDisplayScopes = []) {
230  const propositions = payload
231    ?.filter(({ scope, key }) => (
232      !noDisplayScopes.includes(scope || key)
233    ))
234    ?.map(({
235      id = '',
236      scope = '',
237      key = '',
238      scopeDetails = {},
239    }) => ({
240      id,
241      scope: scope || key,
242      scopeDetails,
243    }));
244  if (!window.alloy || propositions.length === 0) return;
245  const [datasetId, pageAttributes, xdm] = await Promise.all([
246    getConfigValue('target-display-dataset-id'),
247    pageLoadData(),
248    xdmPayload(),
249  ]);
250  if (window.axp.targetPageName) {
251    pageAttributes.pageName = window.axp.targetPageName;
252    delete window.axp.targetPageName;
253  }
254  delete pageAttributes.cartIds;
255  try {
256    window.alloy('sendEvent', {
257      xdm: {
258        eventType: 'decisioning.propositionDisplay',
259        _experience: {
260          decisioning: {
261            propositions,
262            propositionEventType: {
263              display: 1,
264            },
265          },
266        },
267        ...xdm,
268      },
269      data: {
270        __adobe: {
271          target: pageAttributes,
272        },
273      },
274      edgeConfigOverrides: {
275        com_adobe_experience_platform: {
276          datasets: {
277            event: {
278              datasetId,
279            },
280          },
281        },
282      },
283    });
284  } catch (e) {
285    console.error(e);
286  }
287}
288
289/**
290 * Create payload for recommendations click event
291 * @param {*} targetId recommendation target id / decision scope
292 * @param {*} recommendationName recommendation name
293 * @returns payload object
294 */
295async function getRecommendationsClickPayload(targetId, recommendationName, clickTarget = 'Tile') {
296  if (!recommendationName) return Promise.resolve({});
297  return {
298    button: `Recommendation ${clickTarget}`,
299    recommendationName,
300    ...getActivityInfo(targetId),
301  };
302}
303
304/**
305 * Create payload for personalized banner / popup cta click event
306 * @param {*} targetId personalized block target id / decision scope
307 * @param {*} personalizationCta cta clicked
308 * @returns payload object
309 */
310async function getPersonalizationCtaPayload(targetId, personalizationCta) {
311  if (!personalizationCta) return Promise.resolve({});
312  return {
313    button: personalizationCta,
314    ...getActivityInfo(targetId),
315  };
316}
317
318/**
319 * Send display event to Target on CTA clicks for reporting purpose
320 * @param {param.key} key clicked entity / decision scope
321 * @param {param.recommendationName} recommendationName Optional.recommendation name
322 * @param {param.personalizationCta} personalizationCta Optional.personalization CTA clicked
323 */
324export async function targetClickTrackingEvent({
325  key,
326  recommendationName,
327  personalizationCta,
328  isQuickView,
329  clickTarget,
330  productName = '',
331  currentProductType = '',
332}) {
333  if (!window.alloy || !key) return;
334  const [
335    recommendationAttribs,
336    personalizationAttribs,
337    pageAttribs,
338    datasetId,
339    xdm,
340  ] = await Promise.all([
341    getRecommendationsClickPayload(key, recommendationName, clickTarget),
342    getPersonalizationCtaPayload(key, personalizationCta),
343    pageLoadData(),
344    getConfigValue('target-display-dataset-id'),
345    xdmPayload(),
346  ]);
347  delete pageAttribs.cartIds;
348  const enableAddToBagonPLP = (await getConfigValue('plp-addedtobag-enable')) === 'true';
349  if (isQuickView) {
350    if (enableAddToBagonPLP) {
351      let targetProductName = productName;
352      if (!productName) {
353        const gtmAttributesString = window?.product?.[2]?.attributes?.find((attr) => attr.name === 'gtm_attributes')?.value || '{}';
354        const gtmAttributes = JSON.parse(gtmAttributesString);
355        targetProductName = gtmAttributes?.name;
356      }
357      if (isPLP() || isSearchPage() || window.pageType === 'cart page' || window.pageType === 'home page' || window.pageType === 'department page' || window.pageType === 'wishlist page') {
358        pageAttribs.pageName += `|${targetProductName}|Quick View`;
359      } else {
360        setPageName(window.pageType, targetProductName);
361        pageAttribs.pageName = `${getPageName(window.pageType)}|Quick View`;
362      }
363    } else {
364      pageAttribs.pageName += '|Quick View';
365    }
366  }
367  /* eslint-disable no-underscore-dangle */
368  if (enableAddToBagonPLP
369    && (window?.product?.[2]?.__typename === 'SimpleProductView' || currentProductType === 'simple')
370    && (isPLP() || isSearchPage() || window.pageType === 'cart page' || window.pageType === 'home page' || window.pageType === 'department page' || window.pageType === 'wishlist page')) {
371    const simpleProductName = window?.product?.[2]?.name || productName || '';
372    pageAttribs.pageName += `|${simpleProductName}`;
373  } else if (enableAddToBagonPLP && clickTarget === 'Tile') {
374    pageAttribs.pageName = getPageName(window.pageType);
375  } else if (enableAddToBagonPLP && clickTarget === 'Add to Bag' && (window?.product?.[2]?.__typename === 'SimpleProductView' || currentProductType === 'simple')) {
376    const simpleProductName = window?.product?.[2]?.name || productName || '';
377    pageAttribs.pageName += `|${simpleProductName}`;
378  } else if (enableAddToBagonPLP && clickTarget === 'Quick View' && window.pageType === 'product detail page') {
379    pageAttribs.pageName = window.pageType;
380  }
381  const data = {
382    __adobe: {
383      target: {
384        clicked: key,
385        ...recommendationAttribs,
386        ...personalizationAttribs,
387        ...pageAttribs,
388      },
389    },
390  };
391  try {
392    await window.alloy('sendEvent', {
393      xdm: {
394        eventType: 'decisioning.propositionDisplay',
395        _experience: {
396          decisioning: {
397            propositions: [
398              {
399                scope: 'mboxClickTracking',
400              },
401            ],
402            propositionEventType: {
403              display: 1,
404            },
405          },
406        },
407        ...xdm,
408      },
409      renderDecisions: false,
410      data,
411      edgeConfigOverrides: {
412        com_adobe_experience_platform: {
413          datasets: {
414            event: {
415              datasetId,
416            },
417          },
418        },
419      },
420    });
421  } catch (e) {
422    console.error(e);
423  }
424}
425
426/* *** Page Specific Events *** */
427/**
428 * Method to create data object for PLP page
429 * @param {*} categoryList Array of All the current and parent categories
430 * @returns plp data object
431 */
432export async function plpLoadData(categoryList = []) {
433  const categoryIdObj = {
434    'user.categoryId': categoryList.join(':'),
435    'entity.categoryId': categoryList.join(':'),
436  };
437  const categoryObj = createCategories(categoryList, 'entity.');
438  setPageName(window.pageType, '', categoryList);
439  const commonPageData = await pageLoadData();
440  const payload = encodeRecursiveHTMLEntities({
441    ...commonPageData,
442    ...categoryIdObj,
443    ...categoryObj,
444  });
445  return payload;
446}
447
448/**
449 * Method to create data object for No results on PLP page
450 * @param {*} categoryList Array of All the current and parent categories
451 * @returns plp data object
452 */
453export async function noResultsPlpLoadData(categoryList = []) {
454  const commonPlpData = await plpLoadData(categoryList);
455  const { brandNoResultsPlpData = () => {} } = await brandEventsPromise;
456  const brandPayload = brandNoResultsPlpData({ categoryList });
457  const payload = {
458    ...commonPlpData,
459    ...brandPayload,
460  };
461  return payload;
462}
463
464/**
465   * Recursively finds a category by its URL path and returns the breadcrumb path of gtm_names.
466   * @param {Array} categories - The list of categories to search in.
467   * @param {string} currentPath - The URL path to find.
468   * @returns {string[]|null} An array of gtm_names representing the path, or null if not found.
469   */
470function findCategoryPath(categories, currentPath, isRootCall = true) {
471  for (const category of categories) { // eslint-disable-line no-restricted-syntax
472    if (category.url_path && category.url_path === currentPath) {
473      return [category.gtm_name];
474    }
475    if (category.children) {
476      const childPath = findCategoryPath(category.children, currentPath, false);
477      if (childPath) {
478        if (category.gtm_name === 'Default Category' && isRootCall) {
479          return childPath;
480        }
481        return [category.gtm_name, ...childPath];
482      }
483    }
484  }
485  return null;
486}
487
488/**
489 * Method to create data object for Department page
490 * @returns Department page data object
491 */
492export async function departmentPageData() {
493  const { fetchCommerceCategories } = await import('../commerce.js');
494  const { items: categoryTree } = await fetchCommerceCategories();
495  const path = window.location.pathname.replace(/^\/(en|ar)\//, '');
496
497  const filteredCategoryList = findCategoryPath(categoryTree, path, true) || [];
498  const departmentPageList = window.location.pathname
499    ?.split('/shop-')
500    ?.flatMap((segment) => segment?.split('/'))
501    ?.filter(Boolean)
502    ?.slice(1) || [];
503  return plpLoadData(filteredCategoryList?.length > 0 ? filteredCategoryList : departmentPageList);
504}
505
506/**
507 * Method to create data object for PDP page
508 * @param {gtm, attributes, options} product Product data from commerce
509 * @returns pdp data object
510 */
511export async function pdpLoadData({
512  gtm,
513  attributes = [],
514  options,
515  sku,
516  variants,
517  priceRange,
518  price,
519}) {
520  const { brandPdpLoadData = () => {} } = await brandEventsPromise;
521  let categoryList = gtm?.category?.split('/')?.filter((x) => x) || [];
522  let defaultCategoryList;
523  const plpCategoryList = localStorage.getItem('categoryListName');
524  if (plpCategoryList?.startsWith('PLP')) {
525    defaultCategoryList = plpCategoryList.split('|').slice(1) || [];
526  }
527  const gtmValue = JSON.parse(getValue(attributes, 'gtm_attributes') || null) || {};
528  let currentPageName = gtm?.name;
529  if (!currentPageName) {
530    currentPageName = gtmValue?.name;
531  }
532  if (!categoryList || categoryList.length === 0) {
533    categoryList = gtmValue?.category?.split('/')?.filter((x) => x) || [];
534  }
535  // Category id structure is created from product category list
536  // or default category list through navigation (if coming from PLP) based on
537  // predefined logic given by target team
538  let categoryId = (defaultCategoryList || categoryList)
539    ?.filter((item) => item)
540    ?.reduce((acc, item) => {
541      const prefix = acc.split(', ').pop();
542      const newItem = prefix ? `${prefix}:${item}` : item;
543      return acc === '' ? newItem : `${acc}, ${newItem}`;
544    }, '') || undefined;
545  if (!categoryId) {
546    categoryId = gtmValue?.category?.split('/')?.[0];
547  }
548  setPageName(
549    window.pageType,
550    currentPageName,
551    [formatCategoryIdForPageName(defaultCategoryList || categoryList)],
552  );
553  const brandPayload = brandPdpLoadData({
554    attributes,
555    options,
556    variants,
557    priceRange,
558    price,
559    categoryList,
560    sku,
561  });
562  const commonPageData = await pageLoadData();
563  const enableAddToBagonPLP = (await getConfigValue('plp-addedtobag-enable')) === 'true';
564  if (enableAddToBagonPLP) {
565    const productType = attributes?.find((a) => a.name === 'type_id')?.value;
566    if (commonPageData.pageType === 'search page' || commonPageData.pageType === 'cart page' || commonPageData.pageType === 'page-not-found' || commonPageData.pageType === 'home page' || commonPageData.pageType === 'wishlist page') {
567      commonPageData.pageName = productType === 'simple'
568        ? `${commonPageData.pageType}|${gtmValue.name || ''}`
569        : `${commonPageData.pageType}|${gtmValue.name || ''}|Quick View`;
570    } else if (commonPageData.pageType === 'product listing page') {
571      commonPageData.pageName += productType === 'simple'
572        ? `|${gtmValue.name || ''}`
573        : `|${gtmValue.name || ''}|Quick View`;
574    } else if (commonPageData.pageType === 'department page') {
575      const { fetchCommerceCategories } = await import('../commerce.js');
576      const { items: categoryTree } = await fetchCommerceCategories();
577      const path = window.location.pathname.replace(/^\/(en|ar)\//, '');
578      const filteredCategoryListItem = findCategoryPath(categoryTree, path, true) || [];
579      commonPageData.pageName = productType === 'simple'
580        ? `${commonPageData.pageType}|${filteredCategoryListItem}|${gtmValue.name || ''}`
581        : `${commonPageData.pageType}|${filteredCategoryListItem}|${gtmValue.name || ''}|Quick View`;
582    } else if (!isPDP()) {
583      commonPageData.pageName += '|Quick View';
584    }
585  }
586  const dataObj = {
587    'entity.id': `${sku}_${commonPageData.market}`, // This is mandatory field
588    'user.categoryId': categoryId,
589    'entity.categoryId': categoryId,
590    'entity.brand': commonPageData.brand,
591    'entity.market': commonPageData.market, // This is mandatory field
592    'entity.name': (enableAddToBagonPLP && gtmValue?.name) ? gtmValue.name : gtm?.name,
593    'entity.is_new': (getValue(attributes, 'is_new') === '1').toString(),
594    'entity.departmentId': categoryList.join(' | ') || undefined,
595  };
596  const categoryObj = createCategories(categoryList, 'entity.');
597  const payload = encodeRecursiveHTMLEntities({
598    ...commonPageData,
599    ...dataObj,
600    ...categoryObj,
601    ...(brandPayload || {}),
602  });
603  return payload;
604}
605
606export async function targetOrderConfirmationLoadData({ purchaseId, totalPrice, products = [] }) {
607  const commonPageData = await pageLoadData();
608  const { market } = commonPageData;
609  const {
610    brandOrderConfirmationLoadData = () => ({
611      data: undefined,
612      xdm: undefined,
613    }),
614  } = await brandEventsPromise;
615  const { data: brandData, xdm: brandXdm } = brandOrderConfirmationLoadData({ market, products });
616  const data = {
617    ...commonPageData,
618    'orderConfirmationMbox.order.purchaseID': purchaseId,
619    'orderConfirmationMbox.order.priceTotal': totalPrice,
620    'orderConfirmationMbox.order.purchases.value': 1, // Only this 
620should be always 1
621    ...(brandData || {}),
622  };
623  const xdm = {
624    commerce: {
625      order: {
626        purchaseID: purchaseId,
627        priceTotal: totalPrice,
628      },
629      purchases: {
630        value: 1, // Only this should be always 1
631      },
632    },
633    ...(brandXdm || {}),
634  };
635  return {
636    data,
637    xdm,
638  };
639}
640
641/**
642 * Method to create data object for QuickView page
643 * @param {gtm, attributes, options} product Product data from commerce
644 * @returns quickview payload data object
645 */
646export async function quickViewLoadData({
647  attributes = [],
648  sku,
649  variants,
650}) {
651  const { brandQuickViewLoadData = () => {} } = await brandEventsPromise;
652  const gtm = JSON.parse(getValue(attributes, 'gtm_attributes') || null) || {};
653  const categoryList = gtm?.category?.split('/')?.filter((x) => x) || [];
654  let defaultCategoryList;
655  const plpCategoryList = localStorage.getItem('categoryListName');
656  if (plpCategoryList?.startsWith('PLP')) {
657    defaultCategoryList = plpCategoryList.split('|').slice(1) || [];
658  }
659  // Category id structure is created from product category list
660  // or default category list through navigation (if coming from PLP) based on
661  // predefined logic given by target team
662  const categoryId = (defaultCategoryList || categoryList)
663    ?.filter((item) => item)
664    ?.reduce((acc, item) => {
665      const prefix = acc.split(', ').pop();
666      const newItem = prefix ? `${prefix}:${item}` : item;
667      return acc === '' ? newItem : `${acc}, ${newItem}`;
668    }, '') || undefined;
669
670  const commonPageData = await pageLoadData();
671  const enableAddToBagonPLP = (await getConfigValue('plp-addedtobag-enable')) === 'true';
672  if (enableAddToBagonPLP) {
673    if (isPLP() || isSearchPage()) {
674      setPageName(window.pageType, '', [formatCategoryIdForPageName(defaultCategoryList || categoryList)]);
675      commonPageData.pageName = `${window.pageName}|${gtm?.name || ''}|Quick View`;
676    } else if (window.pageType === 'cart page' || window.pageType === 'home page' || window.pageType === 'wishlist page') {
677      commonPageData.pageName += `|${gtm?.name || ''}|Quick View`;
678    } else if (window.pageType === 'department page') {
679      const { fetchCommerceCategories } = await import('../commerce.js');
680      const { items: categoryTree } = await fetchCommerceCategories();
681      const path = window.location.pathname.replace(/^\/(en|ar)\//, '');
682      const filteredCategoryListItem = findCategoryPath(categoryTree, path, true) || [];
683      commonPageData.pageName = `${window.pageType}|${filteredCategoryListItem}|${gtm.name || ''}|Quick View`;
684    } else if (window.pageType === 'product detail page') {
685      commonPageData.pageName = `${window.pageType}|${gtm?.name || ''}|Quick View`;
686    } else {
687      commonPageData.pageName += `${gtm?.name || ''}|Quick View`;
688    }
689  } else {
690    commonPageData.pageName += `|${gtm?.name || ''}|Quick View`;
691  }
692
693  const brandPayload = brandQuickViewLoadData({
694    attributes,
695    variants,
696  });
697  const dataObj = {
698    'entity.id': `${sku}_${commonPageData.market}`, // This is mandatory field
699    'user.categoryId': categoryId,
700    'entity.categoryId': categoryId,
701    'entity.brand': commonPageData.brand,
702    'entity.sub_brand': getValue(attributes, 'product_brand'),
703    'entity.market': commonPageData.market,
704    'entity.name': gtm?.name,
705    'entity.departmentId': categoryList.join(' | ') || undefined,
706  };
707  const categoryObj = createCategories(categoryList, 'entity.');
708  const payload = encodeRecursiveHTMLEntities({
709    ...commonPageData,
710    ...dataObj,
711    ...categoryObj,
712    ...(brandPayload || {}),
713  });
714  return payload;
715}
716
717export async function loadBrandConfig() {
718  try {
719    const brandKey = (await getConfigValue('aep-personalization-brand-key')) || '';
720    return { brandKey };
721  } catch (err) {
722    console.error('[loadBrandConfig] config fetch failed:', err);
723    return { brandKey: '' }; // safe fallback
724  }
725}
726
727function getRegistrationDate() {
728  return localStorage.getItem(REG_DATE_KEY);
729}
730
731/**
732 * AEP methods
733 */
734/* eslint no-underscore-dangle: ["error", { "allow": ["_mhalshaya"] }] */
735export async function aepGenericPageLoadData() {
736  const [{
737    userID = '',
738    customerType,
739    userName,
740    userType,
741    userEmailID,
742    userPhone,
743  },
744  { brand, countryCode, language },
745  ] = await Promise.all([
746    getUserAttributes(),
747    getPageAttributes(),
748  ]);
749
750  /* Resolve registration date */
751  let registrationDate = getRegistrationDate();
752  let createdAt;
753  const isAuthenticated = !!getAuthUserToken();
754  if (isAuthenticated && !registrationDate) {
755    const {
756      getCustomer = () => {},
757    } = await import('../customer/api.js');
758    try {
759      // eslint-disable-next-line camelcase
760      const customer = await getCustomer(false);
761      createdAt = customer.created_at;
762      registrationDate = createdAt;
763    } catch (e) {
764      console.error('Could not resolve registration date', e);
765    }
766  }
767  /* Make sure to update brand name in the config file (aep-personalization-brand-key) per brand */
768  const brandConfig = await loadBrandConfig() || '';
769
770  const aepPayload = {
771    _mhalshaya: {
772      pageDetails: {
773        name: getPageName(window.pageType), // The page/screen name on which the user is on
774        URL: document.URL,
775        pageType: window.pageType,
776        brandName: brand,
777        marketName: countryCode?.toLowerCase(),
778        language,
779        channel: 'Web',
780      },
781      userDetails: {
782        email: userEmailID,
783        phoneNumber: userPhone,
784        customerType,
785        userName,
786        userType,
787        loyaltyType: brand === 'HM' ? 'hello' : 'aura',
788      },
789    },
790  };
791
792  if (brandConfig?.brandKey) {
793    aepPayload._mhalshaya[brandConfig.brandKey] = {
794      ...aepPayload._mhalshaya[brandConfig.brandKey],
795      registrationDate,
796    };
797  } else {
798    console.warn('Brand key is not defined in the config file');
799  }
800
801  if (isAuthenticated) {
802    aepPayload.identityMap = {
803      customerSourceID: [
804        {
805          authenticatedState: 'authenticated',
806          id: `${userID}-MAGENTO_${brand}`,
807          primary: true,
808        },
809      ],
810    };
811  }
812  return sanitizePayload(aepPayload);
813}
814
815export async function aepPdpPageLoadData({
816  gtm,
817  name,
818  sku,
819  attributes,
820  options,
821  inStock,
822  variants,
823  price,
824  priceRange,
825}) {
826  const aepPayload = await aepGenericPageLoadData();
827  const currency = await getConfigValue('currency');
828  const itemCategories = gtm?.category?.split('/') || gtm?.gtm_category?.split('/') || [];
829  const categoryData = createCategories(itemCategories, '');
830  categoryData.listingName = categoryData.listingCategory;
831  delete categoryData.listingCategory;
832  const { promotion_name: promotionName = '' } = transformBannerData();
833  aepPayload._mhalshaya.pageDetails = {
834    ...aepPayload._mhalshaya.pageDetails,
835    ...categoryData,
836    promotionName,
837  };
838  const size = options
839    ?.find((el) => el.id === 'size')
840    ?.values?.filter((el) => el.inStock)
841    ?.map((el) => el.value)
842    ?.join(', ');
843  const finalPrice = price?.final?.amount?.value || priceRange?.minimum?.final?.amount?.value;
844  aepPayload._mhalshaya.eCommerce = {
845    currencyCode: currency, // Datatype: String
846    items: [{
847      SKU: sku, // Datatype: String
848      itemName: gtm?.name || gtm?.gtm_name || name || '', // Datatype: String
849      itemPrice: finalPrice, // Datatype: Double
850      itemBrand: getValue(attributes, 'product_brand') || getValue(attributes, 'brand'), // Datatype: String
851      itemCategory1: gtm?.category || gtm?.gtm_category || '', // Datatype: String
852      itemCategory2: itemCategories[0] || '', // Datatype: String
853      itemCategory3: itemCategories[1] || '', // Datatype: String
854      itemCategory4: itemCategories[2] || '', // Datatype: String
855      itemCategory5: itemCategories[3] || '', // Datatype: String
856      inStock: inStock || gtm?.gtm_stock === 'in stock' || null, // Datatype: Boolean
857      itemColor: getValue(
858        variants?.variants?.[0]?.product?.attributes || attributes,
859        'color_label',
860      ), // Datatype: String
861      itemSize: size, // Datatype: String
862    }],
863  };
864  return sanitizePayload(aepPayload);
865}

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.