{"version":3,"file":"product-complementary-section.min.js","mappings":"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","sources":["webpack://lange/./lib/decorate-error.ts","webpack://lange/./sections/product-complementary/index.ts","webpack://lange/./lib/shopify/product-recommendations.ts"],"sourcesContent":["/**\n * Copies a single piece of supplementary context onto an error before it is thrown or reported.\n *\n * Use this when decorating a value the calling code did not construct, such as a caught value or an\n * error handed in by another module. An error the caller just built with new is always writable, so\n * plain assignment is fine there. A value from elsewhere may be a foreign object this code does not\n * own. Such a value can carry a non-writable property, such as a DOMException whose code is a read-\n * only accessor, or be frozen outright, and assigning to it then throws. Attaching context must\n * never itself fail, so a rejected assignment is swallowed and the remaining context is still\n * attempted. Guarding each property in isolation means a single read-only slot, the common case,\n * costs only that one property rather than all of them.\n *\n * In practice the value that triggers this is almost always a DOMException that reached the caller\n * from a fetch the browser cut short, such as an abort when an in-flight request is interrupted.\n * The platform defines a DOMException's code, name, and message as read-only attributes, so\n * assigning a code onto one throws under the strict mode that all bundled module code runs in. It\n * surfaces from Safari far more than elsewhere for two reasons. The reported wording, attempted to\n * assign to readonly property, is JavaScriptCore's phrasing for this failure, so the same bug in\n * another engine is worded differently and groups as a separate report, which makes this one look\n * Safari specific even though the read-only rule is not. And iOS Safari abandons in-flight fetches\n * aggressively when a tab is backgrounded or a connection drops, which is when such a DOMException\n * tends to be produced and handed to code that assumed an ordinary writable error.\n *\n * A swallowed failure is logged at a level low enough that it is recorded as a breadcrumb for\n * monitoring rather than escalated into its own captured event. That keeps the lost context visible\n * alongside the eventual report without adding fresh noise of its own. The breadcrumb names the\n * property that could not be attached, the kind of value that rejected it, and why, which is enough\n * to recognize a read-only or frozen caught value after the fact.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Read-only\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMException\n * @see https://webidl.spec.whatwg.org/#dfn-read-only\n */\nexport function decorateError(error: any, key: string, value: unknown) {\n  try {\n    error[key] = value;\n  } catch (assignmentError) {\n    let reason: string;\n    if (assignmentError instanceof Error) {\n      reason = assignmentError.message;\n    } else {\n      reason = `${assignmentError}`;\n    }\n\n    console.log('Failed to assign error property', {\n      key,\n      name: error?.name,\n      tag: Object.prototype.toString.call(error),\n      reason\n    });\n  }\n}\n","import { fetchProductRecommendations } from '../../lib/shopify/product-recommendations.js';\n\nasync function loadRecommendations() {\n  const staticContent = document.querySelector<HTMLElement>(\n    '.complementary-products .static-content');\n  if (!staticContent) {\n    return;\n  }\n\n  const container = document.querySelector('.complementary-products .result');\n  if (!container) {\n    return;\n  }\n\n  container.innerHTML = await fetchProductRecommendations({\n    intent: 'complementary',\n    product_id: staticContent.dataset.productId,\n    section_id: 'product-complementary',\n    url: staticContent.dataset.productRecommendationsUrl\n  });\n}\n\nfunction onCtaButtonClick(event: MouseEvent) {\n  if (!(event.currentTarget instanceof HTMLElement)) {\n    return;\n  }\n\n  const focusEvent = new Event('focus-stack-push-requested', {\n    bubbles: true,\n    composed: true\n  });\n  event.currentTarget.dispatchEvent(focusEvent);\n\n  const cta = event.currentTarget.closest('.cta');\n  const productElement = cta.querySelector<HTMLElementTagNameMap['product-element']>(\n    'product-element');\n  const product = scrapeProductInfo(productElement);\n\n  type Detail = WindowEventMap['select-option-requested']['detail'];\n  const requestEvent = new CustomEvent<Detail>('select-option-requested', {\n    detail: {\n      country: event.currentTarget.dataset.countryCode,\n      product: product\n    }\n  });\n  dispatchEvent(requestEvent);\n}\n\nfunction scrapeProductInfo(productElement: HTMLElementTagNameMap['product-element']) {\n  const product = <Product>{};\n  product.award_badge_image = productElement.dataset.awardBadgeImage;\n  product.award_tag = productElement.dataset.awardTag;\n  product.available = productElement.dataset.available === 'true';\n  product.badge_type = <BadgeTypeOptions>productElement.dataset.badgeType;\n  product.metafields_discount_code = productElement.dataset.discountCode;\n  product.metafields_discount_percentage = productElement.dataset.discountPercentage ?\n    parseFloat(productElement.dataset.discountPercentage) :\n    0;\n  product.metafields_discount_override = productElement.dataset.discountOverride;\n  product.id = parseInt(productElement.dataset.id, 10);\n  product.lazyload = productElement.dataset.lazyload === 'true';\n  product.main_image = productElement.dataset.mainImage;\n  product.metafields_title = productElement.dataset.metafieldsTitle;\n  product.metafields_subtitle = productElement.dataset.metafieldsSubtitle;\n  product.reviews_average = productElement.dataset.reviewsAverage ?\n    parseFloat(productElement.dataset.reviewsAverage) : 0;\n  product.reviews_count = productElement.dataset.reviewsCount ?\n    parseInt(productElement.dataset.reviewsCount, 10) : 0;\n  product.tags = productElement.dataset.tags;\n  product.title = productElement.dataset.title;\n  product.type = productElement.dataset.type;\n  product.url = productElement.dataset.url;\n  product.selected_variant_id = parseInt(productElement.dataset.selectedVariantId, 10);\n\n  product.option_groups = <Product['option_groups']>[];\n  type ProductOptionGroup = HTMLElementTagNameMap['product-option-group'];\n  const optionGroupElements = productElement.querySelectorAll<ProductOptionGroup>(\n    'product-option-groups product-option-group[data-name]');\n  for (const optionGroupElement of optionGroupElements) {\n    const optionGroup = <Product['option_groups'][0]>{};\n    optionGroup.name = optionGroupElement.dataset.name;\n    optionGroup.selected_option = optionGroupElement.dataset.selectedOption;\n    if (optionGroupElement.dataset.position) {\n      optionGroup.position = parseInt(optionGroupElement.dataset.position, 10);\n    }\n\n    if (optionGroupElement.dataset.useSwatchesForColor) {\n      optionGroup.use_swatches_for_color = optionGroupElement.dataset.useSwatchesForColor;\n    }\n\n    optionGroup.options = [];\n\n    type ProductOption = HTMLElementTagNameMap['product-option'];\n    const productOptionElements = optionGroupElement.querySelectorAll<ProductOption>(\n      'product-option[data-value]');\n\n    for (const productOptionElement of productOptionElements) {\n      const productOption = <Product['option_groups'][0]['options'][0]>{};\n      productOption.value = productOptionElement.dataset.value;\n      if (productOptionElement.dataset.color) {\n        productOption.color = productOptionElement.dataset.color;\n      }\n\n      productOption.variants = [];\n\n      const variantElements = productOptionElement.querySelectorAll<\n        HTMLElementTagNameMap['variant-element']>('variant-element[data-id]');\n\n      for (const variantElement of variantElements) {\n        const variant = <ProductVariant>{};\n        variant.available = variantElement.dataset.available === 'true';\n        variant.backorder = variantElement.dataset.backorder;\n        variant.compare_at_price = variantElement.dataset.compareAtPrice ?\n          parseInt(variantElement.dataset.compareAtPrice, 10) : null;\n        variant.id = parseInt(variantElement.dataset.id, 10);\n        variant.price = parseInt(variantElement.dataset.price, 10);\n        variant.selected = variantElement.dataset.selected === 'true';\n        variant.sku = variantElement.dataset.sku;\n        variant.title = variantElement.dataset.title || productElement.dataset.title || '';\n        variant.sizeDescription = variantElement.dataset.sizeDescription || '';\n        variant.option1 = variantElement.dataset.option1 || '';\n        variant.option2 = variantElement.dataset.option2 || '';\n        variant.option3 = variantElement.dataset.option3 || '';\n\n        productOption.variants.push(variant);\n      }\n\n      optionGroup.options.push(productOption);\n    }\n\n    product.option_groups.push(optionGroup);\n  }\n\n  product.variants = [];\n\n  // We must isolate the query selector to children of the parent <product-element> custom element\n  // to ensure we exclude any <variant-element> child instances in the <product-option-groups>\n  // custom element.\n  const variantElements = productElement.querySelectorAll<HTMLElementTagNameMap['variant-element']>(\n    ':scope > variant-element[data-id]');\n\n  for (const variantElement of variantElements) {\n    const variant = <ProductVariant>{};\n    variant.available = variantElement.dataset.available === 'true';\n    variant.backorder = variantElement.dataset.backorder;\n    variant.compare_at_price = variantElement.dataset.compareAtPrice ?\n      parseInt(variantElement.dataset.compareAtPrice, 10) : null;\n    variant.currency_code = variantElement.dataset.currencyCode;\n    variant.hide_backorder_in_collection = variantElement.dataset.hideBackorderInCollection;\n    variant.id = parseInt(variantElement.dataset.id, 10);\n    variant.image_src = variantElement.dataset.imageSrc || '';\n    variant.metafield_compare_at_price_modal_content =\n      variantElement.dataset.compareAtPriceModalContent || '';\n    variant.option1 = variantElement.dataset.option1;\n    variant.option2 = variantElement.dataset.option2;\n    variant.option3 = variantElement.dataset.option3;\n    variant.price = parseInt(variantElement.dataset.price, 10);\n    variant.selected = variantElement.dataset.selected === 'true';\n    variant.sku = variantElement.dataset.sku;\n    variant.title = variantElement.dataset.title || productElement.dataset.title || '';\n    product.variants.push(variant);\n  }\n\n  return product;\n}\n\nfunction setupCtaButtons() {\n  const container = document.querySelector('.complementary-products .result');\n  if (!container) {\n    return;\n  }\n\n  const buttons = container.querySelectorAll<HTMLButtonElement>('.small-product-card .cta button');\n  for (const button of buttons) {\n    button.addEventListener('click', onCtaButtonClick);\n  }\n}\n\nfunction main() {\n  loadRecommendations()\n    .then(setupCtaButtons)\n    .catch(console.warn);\n}\n\n// we don't need to wait for DOMContentLoaded as this script is loaded after the relevant HTML and\n// we want to start fetching the content as soon as possible\nmain();\n\n/**\n * Represents a Shopify product variant\n */\ninterface ProductVariant {\n  /**\n   * Whether the variant is in stock\n   */\n  available: boolean;\n\n  /**\n   * Whether the variant is on backorder\n   */\n  backorder: string;\n\n  /**\n   * The \"Pre-Order\" deferred payment selling plan ID associated with a backordered variant.\n   */\n  backorder_selling_plan_id: number;\n\n  /**\n   * The MSRP, or original sales price before discounts, per unit\n   */\n  compare_at_price: number | null;\n\n  /**\n   * The currency code associated with the price\n   */\n  currency_code: string;\n\n  /**\n   * Boolean as a string to indicate whether to hide any backorder message if the product card is\n   * being viewed in a collection context.\n   */\n  hide_backorder_in_collection: string;\n\n  /**\n   * Variant id\n   *\n   * @example 12345568943\n   */\n  id: number;\n\n  /**\n   * The URL for the variant image\n   */\n  image_src: string;\n\n  /**\n   * Content to be displayed in the compare at price modal for a particular variant\n   */\n  metafield_compare_at_price_modal_content: string;\n\n  /**\n   * The value of the variant for the first product option. If there's no first product option, then\n   * nil is returned.\n   */\n  option1: string;\n\n  /**\n   * The value of the variant for the second product option. If there's no first product option,\n   * then nil is returned.\n   */\n  option2: string;\n\n  /**\n   * The value of the variant for the third product option. If there's no first product option, then\n   * nil is returned.\n   */\n  option3: string;\n\n  /**\n   * The price of the variant represented in cents\n   */\n  price: number;\n\n  /**\n   * Whether this variant is the currently selected variant out of all the variants for the product\n   */\n  selected: boolean;\n\n  /**\n   * The short description associated with the variant size option.\n   */\n  sizeDescription: string;\n\n  /**\n   * The Stock Keeping Unit associated with the variant. This is another kind of product id.\n   */\n  sku: string;\n\n  /**\n   * The title of the variant.\n   */\n  title: string;\n}\n\ntype BadgeTypeOptions = '' | 'best-seller' | 'favorite' | 'new' | 'travel-friendly';\ninterface Product {\n  /**\n   * Product inventory availability\n   */\n  available: boolean;\n\n  /**\n   * Product award image\n   */\n  award_badge_image: string;\n\n  /**\n   * Product award tag\n   */\n  award_tag: string;\n\n  /**\n   * Product badge type\n   */\n  badge_type: BadgeTypeOptions;\n\n  /**\n   * Bundles created using the Shopify app use an abstraction product created by the application to\n   * represent the bundle. If a product's variant options are being presented in the context of a\n   * product in a bundle this attribute identifies the product ID for the abstraction product\n   * created by the Shopify Bundle App to represent that bundle.\n   */\n  bundle_product_id: number;\n\n  /**\n   * Shopify product id\n   */\n  id: number;\n\n  /**\n   * Product main image loading method\n   */\n  lazyload: boolean;\n\n  /**\n   * Product main image\n   */\n  main_image: string;\n\n  /**\n   * Product metafields discount code (used to promote public discount codes)\n   */\n  metafields_discount_code: string;\n\n  /**\n   * Product metafields discount override to use one-off messages instead of generated text\n   */\n  metafields_discount_override: string;\n\n  /**\n   * Product metafields discount percentage (used to render public discount code prices)\n   */\n  metafields_discount_percentage: number;\n\n  /**\n   * Product metafields subtitle\n   */\n  metafields_subtitle: string;\n\n  /**\n   * Product metafields title\n   */\n  metafields_title: string;\n\n  option_groups: {\n    name: string;\n    options: {\n      color?: string;\n      value: string;\n      variants: Partial<{\n        /**\n         * Whether the variant has inventory in stock and is available for sale.\n         */\n        available: boolean;\n\n        backorder_message: string;\n\n        compare_at_price: number;\n\n        customAttributes: {\n          /**\n           * Key or name of the attribute.\n           */\n          key: string;\n\n          /**\n           * Value of the attribute.\n           */\n          value: string;\n        }[];\n\n        hideBackorderInCollection: string;\n\n        /**\n         * Variant id\n         */\n        id: number;\n\n        price: number;\n\n        sku: string;\n\n        title: string;\n      }>[];\n    }[];\n    position?: number;\n    selected_option: string;\n    use_swatches_for_color?: 'false' | 'true';\n  }[];\n\n  /**\n   * Product yotpo reviews average value\n   */\n  reviews_average: number;\n\n  /**\n   * Product yotpo reviews count\n   */\n  reviews_count: number;\n\n  /**\n   * Selected variant id\n   */\n  selected_variant_id: number;\n\n  /**\n   * Product sku\n   */\n  sku: string;\n\n  /**\n   * Product tags\n   */\n  tags: string;\n\n  /**\n   * Product title\n   */\n  title: string;\n\n  /**\n   * Product type\n   *\n   * @example \"hair-care\"\n   */\n  type: string;\n\n  /**\n   * Shopify product url\n   */\n  url: string;\n\n  /**\n   * A list of the product's variants with some information about variant\n   */\n  variants: ProductVariant[];\n}\n","import { decorateError } from '../decorate-error.js';\n\n/**\n * Input to fetching a rendered recommendations section.\n */\nexport interface Input {\n  /**\n   * Whether we want related products or complementary products\n   *\n   * Shopify defaults to related when we leave this out.\n   */\n  intent?: 'complementary' | 'related';\n\n  /**\n   * Shopify id of the product the recommendations are for, as a string of digits\n   *\n   * Shopify documents this as required and answers with a 422 when it is left out, so we throw\n   * before sending a request without a valid one.\n   */\n  product_id: string;\n\n  /**\n   * Id of the section Shopify renders the recommendations into\n   *\n   * Shopify documents this as required for the html version of the endpoint and answers with a 422\n   * when it is left out.\n   */\n  section_id?: string;\n\n  /**\n   * Url of the recommendations endpoint, which is best passed as an absolute url but may be\n   * relative\n   *\n   * This is meant to be the value of `routes.product_recommendations_url` from Liquid with the\n   * store's own domain in front of it. The section renders it into a data attribute, and the\n   * section's script reads that attribute and passes it here. The route on its own is a path such\n   * as `/recommendations/products`, with the locale prefix in front of it when another locale is\n   * active.\n   *\n   * We take this instead of building it ourselves. Shopify only puts the site root on the page for\n   * javascript to read, not the recommendations route, so building it here would mean hardcoding\n   * the route and hoping Shopify never moves it.\n   */\n  url: string;\n}\n\n/**\n * Fetch the html of a recommendations section that Shopify rendered on the server.\n *\n * Shopify serves recommendations in two versions. The url ending in `.json` answers with the\n * recommended products as json and takes no section id. The url without `.json` renders the section\n * named by the section id with the recommendations filled in and answers with its html. This uses\n * the html version, since the url callers pass is the route Liquid gives us, which has no `.json`\n * on the end.\n *\n * Callers have their own preferences about how to parse the html, or whether to parse it at all, so\n * we hand back the raw text and let them decide.\n *\n * @param input which product and section to ask for\n *\n * @see https://shopify.dev/docs/api/ajax/reference/product-recommendations\n */\nexport async function fetchProductRecommendations(input: Input) {\n  if (!/^\\d+$/.test(input.product_id)) {\n    const error: any = new Error('Invalid product id fetching recommendations');\n    error.input = input;\n    throw error;\n  }\n\n  // Callers pass an absolute url, and when the url has its own host the base below is ignored. We\n  // keep the base so a relative url still works. A relative url picks up whatever host the page\n  // reports, though, and a page is not always running on the store's own domain. For example, a\n  // tool can load our html while reporting some other site as the page url, and then the request\n  // goes to that site instead of the store. That is why callers pass an absolute url.\n  const url = new URL(input.url, location.origin);\n  url.searchParams.set('product_id', input.product_id);\n\n  if (input.section_id) {\n    url.searchParams.set('section_id', input.section_id);\n  }\n\n  if (input.intent) {\n    url.searchParams.set('intent', input.intent);\n  }\n\n  let response;\n  try {\n    response = await fetch(url.href);\n  } catch (error) {\n    decorateError(error, 'input', input);\n    decorateError(error, 'url', url.href);\n    throw error;\n  }\n\n  if (!response.ok) {\n    // Some of these statuses are routine enough that Sentry filters them out by message, so take\n    // care when rewording this.\n    const error: any = new Error(`Received status ${response.status} fetching recommendations`);\n    error.code = response.status;\n    error.input = input;\n    error.url = response.url;\n    throw error;\n  }\n\n  let responseText;\n  try {\n    responseText = await response.text();\n  } catch (error) {\n    decorateError(error, 'input', input);\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  return responseText;\n}\n"],"names":["decorateError","error","key","value","assignmentError","reason","Error","message","console","log","name","tag","Object","prototype","toString","call","async","loadRecommendations","staticContent","document","querySelector","container","innerHTML","input","test","product_id","url","URL","location","origin","response","responseText","searchParams","set","section_id","intent","fetch","href","ok","status","code","text","fetchProductRecommendations","dataset","productId","productRecommendationsUrl","onCtaButtonClick","event","currentTarget","HTMLElement","focusEvent","Event","bubbles","composed","dispatchEvent","product","productElement","award_badge_image","awardBadgeImage","award_tag","awardTag","available","badge_type","badgeType","metafields_discount_code","discountCode","metafields_discount_percentage","discountPercentage","parseFloat","metafields_discount_override","discountOverride","id","parseInt","lazyload","main_image","mainImage","metafields_title","metafieldsTitle","metafields_subtitle","metafieldsSubtitle","reviews_average","reviewsAverage","reviews_count","reviewsCount","tags","title","type","selected_variant_id","selectedVariantId","option_groups","optionGroupElements","querySelectorAll","optionGroupElement","optionGroup","selected_option","selectedOption","position","useSwatchesForColor","use_swatches_for_color","options","productOptionElements","productOptionElement","productOption","color","variants","variantElements","variantElement","variant","backorder","compare_at_price","compareAtPrice","price","selected","sku","sizeDescription","option1","option2","option3","push","currency_code","currencyCode","hide_backorder_in_collection","hideBackorderInCollection","image_src","imageSrc","metafield_compare_at_price_modal_content","compareAtPriceModalContent","scrapeProductInfo","closest","requestEvent","CustomEvent","detail","country","countryCode","setupCtaButtons","buttons","button","addEventListener","then","catch","warn"],"sourceRoot":""}