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.