1/* eslint-disable import/no-cycle */ 2import { store } from './api.js'; 3import { 4 getSignInToken, 5 performCommerceRestMutation, 6 performCommerceRestQuery, 7 getProductData, 8} from '../commerce.js'; 9 10import { getConfigValue, CARTID_STORE } from '../configs.js'; 11import { getCustomer } from '../customer/api.js'; 12import { datalayerCartError } from '../analytics/google-data-layer.js'; 13import { setLoyaltyCardAPI } from '../giftcart/api.js'; 14import { getAuraLocalStorageData } from '../aura/api.js'; 15import { isLoggedInUser } from '../scripts.js'; 16import { getCustomerTier } from '../plp-utils/preaccess.js'; 17 18function fetcheGiftURL(sku) { 19 return `/V1/products?searchCriteria%5BpageSize%5D=5&searchCriteria%5BcurrentPage%5D=1&searchCriteria%5BfilterGroups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bfield%5D=visibility&searchCriteria%5BfilterGroups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5Bvalue%5D=4&searchCriteria%5BfilterGroups%5D%5B0%5D%5Bfilters%5D%5B0%5D%5BconditionType%5D=eq&searchCriteria%5BfilterGroups%5D%5B1%5D%5Bfilters%5D%5B0%5D%5Bfield%5D=status&searchCriteria%5BfilterGroups%5D%5B1%5D%5Bfilters%5D%5B0%5D%5Bvalue%5D=1&searchCriteria%5BfilterGroups%5D%5B1%5D%5Bfilters%5D%5B0%5D%5BconditionType%5D=eq&searchCriteria%5BfilterGroups%5D%5B2%5D%5Bfilters%5D%5B0%5D%5Bfield%5D=sku&searchCriteria%5BfilterGroups%5D%5B2%5D%5Bfilters%5D%5B0%5D%5Bvalue%5D=${sku}&searchCriteria%5BfilterGroups%5D%5B2%5D%5Bfilters%5D%5B0%5D%5BconditionType%5D=eq&searchCriteria%5BfilterGroups%5D%5B3%5D%5Bfilters%5D%5B0%5D%5Bfield%5D=type_id&searchCriteria%5BfilterGroups%5D%5B3%5D%5Bfilters%5D%5B0%5D%5Bvalue%5D=virtual&searchCriteria%5BfilterGroups%5D%5B3%5D%5Bfilters%5D%5B0%5D%5BconditionType%5D=eq`; 20} 21 22async function createUrl(endpoint) { 23 const baseUrl = await getConfigValue('commerce-rest-endpoint'); 24 25 const storeViewCode = await getConfigValue('commerce-store-view-code'); 26 27 const url = `${baseUrl}/${storeViewCode}/V1/${endpoint}`; 28 29 return url; 30} 31 32// TODO: Refactor 33const handleCartErrors = (data) => { 34 if (!data) { 35 return; 36 } 37 // Throw for everything 38 throw new Error(data.message || 'An error occurred'); 39}; 40 41export function waitForCart() { 42 const buttons = document.querySelectorAll('button.nav-cart-button, .minicart-header > .close'); 43 buttons.forEach((button) => { button.disabled = true; }); 44 return () => { 45 buttons.forEach((button) => { button.disabled = false; }); 46 }; 47} 48 49export async function getCart(resetCartStore = false) { 50 const token = getSignInToken(); 51 52 if (!token && !store.getCartId()) { 53 return; 54 } 55 56 const done = waitForCart(); 57 let data; 58 let success; 59 try { 60 let url; 61 62 if (token) { 63 url = await createUrl('carts/mine/getCart'); 64 } else { 65 url = await createUrl(`guest-carts/${store.getCartId()}/getCart`); 66 } 67 68 const response = await performCommerceRestQuery(url, !!token); 69 70 ({ data, success } = response); 71 72 if (!success) { 73 handleCartErrors(data); 74 } 75 76 if (!data) { 77 store.resetCart(); 78 } 79 80 if (data) { 81 window.dispatchEvent(new CustomEvent('updateMiniCartResult', { detail: data })); 82 } 83 84 data.cart.items = data.cart.items.filter((item) => item); 85 const cartObj = { 86 id: data.cart.id, 87 cart: data.cart, 88 totals: data.totals, 89 }; 90 91 if (resetCartStore) { 92 const newCartId = token ? cartObj.id : store.getCartId(); 93 store.resetCart(); 94 await store.setCartId(newCartId); 95 } 96 97 store.setCart(cartObj); 98 } catch (err) { 99 console.error('Could not fetch cart'); 100 } finally { 101 done(); 102 } 103} 104 105export async function createCart() { 106 try { 107 const token = getSignInToken(); 108 109 let url; 110 if (token) { 111 url = await createUrl('carts/mine'); 112 } else { 113 url = await createUrl('guest-carts'); 114 } 115 116 const response = await performCommerceRestMutation(url, {}, !!token); 117 118 console.error(response); 119 120 if (!response.success) { 121 handleCartErrors(response); 122 } 123 const cartId = response.data; 124 await store.setCartId(cartId); 125 const auraData = getAuraLocalStorageData(); 126 const currentTier = await getCustomerTier(); 127 const apcIdentifierNumber = auraData?.aura_membership; 128 const auraLink = auraData?.apc_link ? parseInt(auraData.apc_link, 10) : null; 129 if (apcIdentifierNumber && auraLink 130 && (auraLink === 2 || auraLink === 3 || (!isLoggedInUser() && auraLink === 1))) { 131 // setLoyaltyCard only if aura account is linked 132 await setLoyaltyCardAPI(apcIdentifierNumber); 133 } else if (currentTier === 'member' || currentTier === 'plus') { 134 const custResponse = await getCustomer(); 135 if (custResponse?.custom_attributes) { 136 const apcIdentifierNum = custResponse.custom_attributes.find((attribute) =>
136 attribute.attribute_code === 'apc_identifier_number')?.value; 137 await setLoyaltyCardAPI(apcIdentifierNum); 138 } 139 } 140 console.debug('created empty cart', cartId); 141 } catch (err) { 142 console.error('Could not create empty cart', err); 143 } 144} 145 146function mapOption(option) { 147 const decoded = atob(option); 148 // eslint-disable-next-line no-unused-vars 149 const [optionType, optionId, optionValue] = decoded.split('/'); 150 if (optionType === 'bundle') { 151 return { 152 option_id: optionId, 153 option_selections: [optionValue], 154 }; 155 } 156 return { 157 option_id: optionId, 158 option_value: optionValue, 159 }; 160} 161 162async function getCartInFailure(token, response) { 163 const url = token ? await createUrl('carts/mine/getCart') : await createUrl(`guest-carts/${store.getCartId()}/getCart`); 164 const updatedCartFromApi = await performCommerceRestQuery(url, !!token); 165 const isCartLocked = updatedCartFromApi?.data?.cart?.extension_attributes?.is_locked; 166 if (isCartLocked) { 167 response.errorCode = 'CART_LOCKED_ERROR'; 168 } 169 return response?.errorCode; 170} 171 172export async function addToCart(sku, options, quantity, source = 'product-detail') { 173 let isBundle = false; 174 if (options?.length > 0) { 175 isBundle = options.some((option) => { 176 const decodeData = atob(option); 177 const [optionType] = decodeData.split('/'); 178 return optionType === 'bundle'; 179 }); 180 } 181 182 const done = waitForCart(); 183 let response; 184 let isEGift = false;
185 try { 186 let variables; 187 if (source === 'egift-purchase') { 188 isEGift = true; 189 variables = { 190 cartItem: { 191 quote_id: store.getCartId(), 192 product_type: options.product_type, 193 sku, 194 qty: quantity, 195 product_option: { 196 extension_attributes: { 197 hps_giftcard_item_option: { 198 hps_custom_giftcard_amount: options.custom_giftcard_amount, 199 hps_giftcard_amount: options.giftcard_amount, 200 hps_giftcard_sender_name: options.giftcard_sender_name, 201 hps_giftcard_sender_email: options.giftcard_sender_email, 202 hps_giftcard_recipient_name: options.giftcard_recipient_name, 203 hps_giftcard_recipient_email: options.giftcard_recipient_email, 204 hps_giftcard_message: options.giftcard_message, 205 extension_attributes: {}, 206 }, 207 }, 208 }, 209 }, 210 }; 211 } else if (source === 'product-landing') { 212 variables = { 213 cartItem: { 214 sku, 215 qty: quantity, 216 quote_id: store.getCartId(), 217 product_option: { 218 extension_attributes: {}, 219 }, 220 }, 221 }; 222 223 if (isBundle) { 224 const getOptions = options.map((option) => mapOption(option)); 225 variables.cartItem.product_option.extension_attributes.bundle_options = getOptions; 226 } 227 } else { 228 variables = { 229 cartItem: { 230 sku, 231 qty: quantity, 232 product_option: { 233 extension_attributes: { 234 configurable_item_options: options.map((option) => mapOption(option)), 235 custom_options: [], 236 }, 237 }, 238 quote_id: store.getCartId(), 239 }, 240 }; 241 242 if (isBundle) { 243 delete variables.cartItem.product_option.extension_attributes.configurable_item_options; 244 // Add bundle_options 245 const getOptions = options.map((option) => mapOption(option)); 246 variables.cartItem.product_option.extension_attributes.bundle_options = getOptions; 247 } 248 } 249 250 const token = getSignInToken(); 251 // Check for numeric guest cart ID and refresh if needed 252 if (!token) { 253 const currentCartId = store.getCartId(); 254 if (currentCartId && /^\d+$/.test(currentCartId)) { 255 store.resetCart(); 256 await createCart(); 257 } 258 } 259 let url; 260 261 if (token) { 262 url = await createUrl('carts/mine/items'); 263 } else { 264 url = await createUrl(`guest-carts/${store.getCartId()}/items`); 265 } 266 267 response = await performCommerceRestMutation(url, variables, !!token); 268 269 if (!response.success) { 270 const fieldName = response.data?.parameters?.fieldName; 271 const { message } = response; 272 273 const errorConditions = { 274 customerCartId: 'No such entity with %fieldName = %fieldValue', 275 guestCartId: '"%fieldName" is required. Enter and try again.', 276 }; 277 278 // If carId is not found clean the cart 279 if ((message === errorConditions.customerCartId && fieldName === 'cartId') 280 || (message === errorConditions.guestCartId && fieldName === 'quoteId')) { 281 store.resetCart(); 282 // Handle translation 283 response.message = 'Something went wrong. Please try again.'; 284 response.errorCode = 'GENERIC_ERROR'; 285 window.localStorage.removeItem(CARTID_STORE); 286 } else { 287 response.errorCode = await getCartInFailure(token, response); 288 } 289 290 handleCartErrors(response); 291 } 292 293 const { data: updatedCart } = response; 294 if (updatedCart) { 295 const product = await getProductData(sku); 296 297 await getCart(); 298 299 if (isEGift) { 300 product.iseGift = true; 301 if (window.egiftItems && window.egiftItems.some((item) => item.sku === sku)) { 302 const eGiftItem = window.egiftItems.find((item) => item.sku === sku); 303 product.eGiftImage = eGiftItem.image; 304 } else { 305 const storeCode = await getConfigValue('commerce-store-view-code'); 306 const commerceRestEndpoint = await getConfigValue('commerce-rest-endpoint'); 307 const prefix = storeCode ? `/${storeCode}` : ''; 308 const egiftUrl = fetcheGiftURL(sku); 309 const egiftRestEndpoint = `${commerceRestEndpoint}${prefix}${egiftUrl}`; 310 const data = await performCommerceRestQuery(egiftRestEndpoint, false); 311 product.eGiftImage = data?.data?.items[0]?.custom_attributes?.find((attr) => attr.attribute_code === 'image')?.value; 312 } 313 } 314 document.querySelector('main').dispatchEvent(new CustomEvent('addtobag-updated', { 315 detail: { 316 product, 317 cart: store.getCart(), 318 updatedCart, 319 }, 320 })); 321 } 322 return response; 323 } catch (err) { 324 console.error('Could not add item to cart', err); 325 const errorText = response?.message; 326 const cartId = store.getCartId(); 327 datalayerCartError(errorText, cartId); 328 return response; 329 } finally { 330 done(); 331 } 332} 333 334export async function updateItemInCart(itemId, sku, quantity) { 335 const done = waitForCart(); 336 const variables = { 337 cartItem: { 338 quote_id: store.getCartId(), 339 sku, 340 qty: quantity, 341 item_id: itemId, 342 product_option: {}, 343 }, 344 }; 345 let response; 346 try { 347 const token = getSignInToken(); 348 349 let url; 350 if (token) { 351 url = await createUrl(`carts/mine/items/${itemId}`); 352 } else { 353 url = await createUrl(`guest-carts/${store.getCartId()}/items/${itemId}`); 354 } 355 response = await performCommerceRestMutation(url, variables, !!token, 'PUT'); 356 console.error(response); 357 358 if (!response.success) { 359 response.errorCode = await getCartInFailure(token, response); 360 handleCartErrors(response); 361 } 362 363 await getCart(); 364 365 console.debug('updated items in cart', itemId); 366 367 return response; 368 } catch (err) { 369 console.error('Could not update item in cart', err); 370 return response; 371 } finally { 372 done(); 373 } 374} 375 376export async function removeItemFromCart(itemId) { 377 const done = waitForCart(); 378 let response; 379 try { 380 const token = getSignInToken(); 381 382 let url; 383 if (token) { 384 url = await createUrl(`carts/mine/items/${itemId}`); 385 } else { 386 url = await createUrl(`guest-carts/${store.getCartId()}/items/${itemId}`); 387 } 388 389 response = await performCommerceRestMutation(url, null, !!token, 'DELETE'); 390 391 console.error(response); 392 393 if (!response.success) { 394 response.errorCode = await getCartInFailure(token, response); 395 handleCartErrors(response); 396 } 397 398 const cart = await getCart(); 399 console.debug('removed item from cart', itemId, cart?.id); 400 return response; 401 } catch (err) { 402 console.error('Could not remove item from cart', err); 403 return response; 404 } finally { 405 done(); 406 } 407} 408 409export async function mergeCart() { 410 try { 411 const token = getSignInToken(); 412 413 if (!token) { 414 return; 415 } 416 417 // check if guest exists and has items 418 const guestCart = store.getCart(); 419 420 if (guestCart?.id && guestCart?.cart?.items?.length > 0) { 421 const custResponse = await getCustomer(); 422 423 console.log('mergeCart custResponse', custResponse); 424 425 const { id: customerId, store_id: storeId } = custResponse; 426
427 console.debug('Merging cart for customer', customerId, storeId); 428 429 const url = await createUrl(`cart-merge/customer-id/${customerId}/active-quote/${guestCart.id}/store-id/${storeId}`); 430 431 const response = await performCommerceRestMutation(url, [], true, 'PUT'); 432 433 if (!response.success) { 434 console.error('Error merging cart', response.status, response.message); 435 return; 436 } 437 } 438 439 // load cart anyways 440 const cart = await getCart(true); 441 442 console.debug('Merged cart', cart); 443 } catch (err) { 444 console.error('Could not merge cart', err); 445 } 446} 447 448// Helper function for loading external scripts dynamically. 449function loadScript(src) { 450 const script = document.createElement('script'); 451 script.type = 'text/javascript'; 452 script.src = src; 453 (document.head || document.body).appendChild(script); 454} 455 456// Helper function to get item options. 457function getItemSizeById(items, itemId) { 458 const item = items.find((currentItem) => currentItem.item_id === itemId); 459 if (!item) return null; 460 try { 461 const options = JSON.parse(item.options); 462 return options[0]?.value || null; 463 } catch (error) { 464 console.error('Error parsing item options:', error); 465 return null; 466 } 467} 468 469// Helper function for initialising ShoeSizeShoppingCart. 470async function initialiseShoeSizeShoppingCart(event) { 471 const { cart: cartData, totals } = event.detail; 472 473 if (!cartData?.items?.length || !totals?.items) return; 474 475 try { 476 // Retrieve configuration values for the shopping cart initialization. 477 const [domainProd, domainStage, shopId, shoeAiMode] = await Promise.all([ 478 getConfigValue('domain_production'), 479 getConfigValue('domain_staging'), 480 getConfigValue('shop_id'), 481 getConfigValue('shoe_ai_mode'), 482 ]); 483 // Determine the domain based on the AI mode. 484 const domain = shoeAiMode === 'prod' ? domainProd : domainStage; 485 // Filter cart items to include only shoe products and map them to the desired format. 486 const shoePurchases = cartData.items 487 .filter((item) => item?.name?.toLowerCase().includes('shoes')) 488 .map((item) => ({ 489 shoeID: item.extension_attributes?.parent_product_sku || item.sku, 490 quantity: item.qty, 491 price: item.price, 492 currency: cartData.currency.base_currency_code, 493 size: getItemSizeById(totals.items, item.item_id), 494 scale: 'eu', 495 })); 496 // Exit the function if there are no shoe purchases in the cart. 497 if (shoePurchases.length === 0) return; 498 // Create the ShoeSizeShoppingCart object with shop ID and shoe purchases. 499 const ShoeSizeShoppingCart = { 500 shopID: shopId, 501 purchases: shoePurchases, 502 }; 503 const timestamp = Math.round(Date.now() / 1000); 504 const scriptSrc = `${domain}/plugin/cart.js?shopid=${encodeURIComponent(ShoeSizeShoppingCart.shopID)}&sid=${timestamp}`; 505 // Load the script asynchronously. 506 loadScript(scriptSrc); 507 } catch (error) { 508 console.error('Error initializing ShoeSizeShoppingCart:', error); 509 } 510} 511// Check if the Shoe AI feature is enabled. 512const enableShoeai = (await getConfigValue('enable_shoeai')) === 'true'; 513// Add event listener to initialize ShoeSizeShoppingCart on cart update if feature is enabled. 514if (enableShoeai) { 515 window.addEventListener('updateMiniCartResult', initialiseShoeSizeShoppingCart); 516}
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.