{"version":3,"file":"footer-section.min.js","mappings":"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","sources":["webpack://lange/./sections/footer/trustpilot-badge/index.ts","webpack://lange/./sections/footer/yotpo-badge/index.ts","webpack://lange/./sections/footer/trustpilot-rating-stars/index.ts","webpack://lange/./lib/dom-ready.ts","webpack://lange/./sections/footer/country-selector-modal-cta.ts","webpack://lange/./lib/attentive-service.ts","webpack://lange/./sections/footer/marketing-subscribe.ts","webpack://lange/../node_modules/@integrabeauty/person/dist/src/email.js","webpack://lange/../node_modules/@integrabeauty/assert/src/index.ts","webpack://lange/./lib/decorate-error.ts","webpack://lange/./sections/footer/trustpilot-fetch-business-unit.ts","webpack://lange/./sections/footer/trustpilot-badge-init.ts"],"sourcesContent":["import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders a Trustpilot badge with the provided rating and total number of votes. This component is\n * designed to be used within the footer of the website, but it should not be used on the product\n * reviews page. The badge displays the overall rating, total votes, and a star rating based on the\n * provided data attributes.\n */\nclass TrustpilotBadge extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-rating', 'data-total'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Overall rating\n     */\n    rating: string;\n\n    /**\n     * Total number of votes\n     */\n    total: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.render();\n  }\n\n  public attributeChangedCallback(_name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    const rating = Math.min(parseFloat(this.dataset.rating), 5);\n    if (isNaN(rating)) {\n      return;\n    }\n\n    const total = parseInt(this.dataset.total, 10);\n    if (!total) {\n      return;\n    }\n\n    // Show the element. The html starts it out hidden, and the global rule for the hidden attribute\n    // lives in an inlined stylesheet that comes down with the html, so there is no flash of the\n    // badge while this module loads.\n    this.hidden = false;\n\n    // we are modifying HTML inside of a slot, so there's no guarantee that any of the elements\n    // below exist\n\n    const scoreElement = this.querySelector('.trust-score');\n    if (scoreElement) {\n      scoreElement.textContent = rating.toString();\n    }\n\n    const totalElement = this.querySelector('.trust-total');\n    if (totalElement) {\n      const format = new Intl.NumberFormat('en-US');\n      totalElement.textContent = format.format(total);\n    }\n\n    const starsElement = this.querySelector('trustpilot-rating-stars');\n    if (starsElement) {\n      starsElement.dataset.rating = rating.toString();\n    }\n\n    const starPrefix = this.querySelector('.stars-prefix');\n    if (starPrefix) {\n      let prefix = '';\n      if (rating >= 5) {\n        prefix = 'Excellent';\n      } else if (rating >= 4) {\n        prefix = 'Great';\n      } else if (rating >= 3) {\n        prefix = 'Average';\n      } else if (rating >= 2) {\n        prefix = 'Poor';\n      } else {\n        prefix = 'Bad';\n      }\n\n      starPrefix.textContent = prefix;\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'trustpilot-badge': TrustpilotBadge;\n  }\n}\n\nif (!customElements.get('trustpilot-badge')) {\n  customElements.define('trustpilot-badge', TrustpilotBadge);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders Yotpo site rating badge.\n */\nclass YotpoBadge extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return [\n      'data-total',\n      'data-average-score'\n    ];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Overall rating\n     */\n    averageScore: string;\n\n    /**\n     * URL to badge image in the assets folder\n     */\n    badgeImageSrc: string;\n\n    /**\n     * Total number of votes\n     */\n    total: string;\n\n    /**\n     * URL where the badge click will lead to\n     */\n    url: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.render();\n  }\n\n  public attributeChangedCallback(_name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    const link = this.shadowRoot.querySelector('a');\n    link.href = this.dataset.url || '#';\n\n    const imageElement = this.shadowRoot.querySelector('img');\n    imageElement.src = this.dataset.badgeImageSrc;\n\n    const totalElement = this.shadowRoot.querySelector('.total');\n    const total = parseInt(this.dataset.total, 10) / 1000000;\n\n    const totalText = Number.isNaN(total) ? '' : total.toFixed(1);\n\n    totalElement.textContent = totalText ? totalText + 'M' : '';\n\n    const score = this.dataset.averageScore ? this.dataset.averageScore : '';\n\n    const badge = this.shadowRoot.querySelector('.badge');\n    if (score && totalText) {\n      badge?.setAttribute('aria-label', [\n        score,\n        'average score based on',\n        totalText,\n        'million certified reviews',\n        'powered by Yotpo and Trustpilot.'\n      ].join(' '));\n    } else {\n      badge?.setAttribute('aria-label', 'Certified reviews, powered by Yotpo and Trustpilot.');\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'yotpo-badge': YotpoBadge;\n  }\n}\n\nif (!customElements.get('yotpo-badge')) {\n  customElements.define('yotpo-badge', YotpoBadge);\n}\n","import type { CustomElement } from '@integrabeauty/custom-elements';\nimport html from './index.html';\nimport styles from './index.scss';\n\n/**\n * Renders a row of Trustpilot styled stars filled to a given rating, including a half filled star.\n *\n * It is the shared star visual wherever a Trustpilot rating is shown, such as the footer badge and\n * individual reviews.\n */\nclass TrustpilotRatingStars extends HTMLElement implements CustomElement {\n  public static get observedAttributes() {\n    return ['data-rating'];\n  }\n\n  declare readonly dataset: {\n    /**\n     * Present for a perfect rating, switching the stars to the darker fill via CSS.\n     */\n    dark?: string;\n\n    /**\n     * Rating, how many stars will be filled\n     */\n    rating: string;\n  };\n\n  public connectedCallback() {\n    this.initializeShadowRoot();\n\n    this.render();\n  }\n\n  public attributeChangedCallback(name: string, oldValue: string, newValue: string) {\n    this.initializeShadowRoot();\n\n    if (this.isConnected && name === 'data-rating' && oldValue !== newValue) {\n      this.render();\n    }\n  }\n\n  private initializeShadowRoot() {\n    if (!this.shadowRoot) {\n      this.attachShadow({ mode: 'open' });\n      this.shadowRoot.innerHTML = `<style>${styles}</style>${html}`;\n    }\n  }\n\n  private render() {\n    let rating = parseFloat(this.dataset.rating);\n    rating = rating > 5 ? 5 : rating;\n\n    if (!rating) {\n      return;\n    }\n\n    if (rating === 5) {\n      this.dataset.dark = 'true';\n    }\n\n    const starsElement = this.shadowRoot.querySelectorAll<SVGGElement>('svg g');\n    for (let i = 0; i < starsElement.length; i++) {\n      if (i + 1 <= rating) {\n        starsElement[i].classList.add('filled');\n      }\n\n      if (i + 1.5 > rating) {\n        starsElement[i].classList.add('filled-half');\n        break;\n      }\n    }\n  }\n}\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'trustpilot-rating-stars': TrustpilotRatingStars;\n  }\n}\n\nif (!customElements.get('trustpilot-rating-stars')) {\n  customElements.define('trustpilot-rating-stars', TrustpilotRatingStars);\n}\n","/**\n * Releases the handle returned from the ready() function.\n *\n * This is idempotent. Additional calls are pointless but harmless.\n */\nexport type ReleaseHandle = () => void;\n\n/**\n * A subset of events that pertain to document ready state.\n */\ntype LoadingEventMap = Pick<DocumentEventMap, 'DOMContentLoaded' | 'load'>;\n\n/**\n * Calls the provided callback when the document is ready. This works both when the document is\n * already ready and when it is not, so callers do not have to care. This exists because it ensures\n * that each caller consistently expresses its dependency on document readiness, and to save a bit\n * of boilerplate.\n *\n * A document is considered ready when it has been parsed. This corresponds to the ready state of\n * 'interactive'. When the document has been fully loaded, the ready state is 'complete'. The\n * callback runs either way, so callers do not have to check which one they are in.\n *\n * Ready means the html is parsed and the DOM is there to query. It does not mean the page's other\n * scripts have run. Deferred scripts and module scripts run after the ready state turns\n * 'interactive' and before DOMContentLoaded fires, so a callback here can run before them. When a\n * caller needs some other script to have run already, this is the wrong tool, and it should wait on\n * whatever that script produces instead.\n *\n * That remedy has a catch of its own when the calling bundle is loaded with async. An async bundle\n * can execute after DOMContentLoaded has already fired, so a plain listener for that event never\n * runs and the work silently never happens. Nothing here can detect that, because by the time this\n * file executes the event is long gone. Catching it needs something registered at parse time,\n * before any bundle loads.\n *\n * There is a second trap right next to that one, and it applies only when waiting for\n * DOMContentLoaded. The ready state stays 'interactive' after DOMContentLoaded has fired, all the\n * way until 'complete', so the ready state cannot tell you whether DOMContentLoaded is still coming\n * or has already gone. Waiting for load is not exposed to this, because the ready state turns\n * 'complete' and load fires in the same task, so seeing 'complete' means load has fired too.\n *\n * When the document is already ready, the callback is invoked via setTimeout. This way, if the\n * callback throws, the error surfaces to the global error handler in a way that is similar to what\n * happens when an event listener throws.\n *\n * We might one day want to move this to an NPM library.\n *\n * Beware of a quirk here. setTimeout yields, and the busier the frontend, the longer it takes to\n * fire. Page load is exactly when the frontend is busiest. The timeout is a floor and not a\n * guarantee, so there is no real bound on how long that yield lasts. What this means is that using\n * this library can introduce more delay than you might expect. Compare code that runs on async load\n * and checks ready state and invokes immediately to code that runs after setTimeout. The former\n * runs in the same tick, the latter runs in \"some later tick\". That later tick can be surprisingly\n * much later. That can cause initial paint issues.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/readyState\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Document/DOMContentLoaded_event\n * @see https://html.spec.whatwg.org/multipage/parsing.html#the-end\n */\nexport function ready(callback: (event?: Event) => void,\n  type: keyof LoadingEventMap = 'DOMContentLoaded') {\n  if (document.readyState === 'complete' || (type === 'DOMContentLoaded' &&\n    document.readyState === 'interactive')) {\n    const timer = setTimeout(callback, 0);\n    const handle: ReleaseHandle = () => clearTimeout(timer);\n    return handle;\n  } else {\n    addEventListener(type, callback);\n    const handle: ReleaseHandle = () => removeEventListener(type, callback);\n    return handle;\n  }\n}\n","import { ready } from '../../lib/dom-ready.js';\n\n// The modal-element around the country selector handles the close button, the dismiss gestures, and\n// telling the modal manager when it opens and closes. We only open it.\n\ntype ModalElement = HTMLElementTagNameMap['modal-element'];\n\nfunction onReady() {\n  const links = document.querySelectorAll<HTMLAnchorElement>('.open-country-selector-modal');\n  for (const link of links) {\n    link.addEventListener('click', onShowSelectorClick);\n  }\n}\n\nfunction onShowSelectorClick(event: MouseEvent) {\n  event.preventDefault();\n\n  if (event.currentTarget instanceof HTMLElement) {\n    const focusEvent = new Event('focus-stack-push-requested', { bubbles: true, composed: true });\n    event.currentTarget.dispatchEvent(focusEvent);\n\n    const modal = document.querySelector<ModalElement>('#country-selector-modal');\n    modal.dataset.open = 'true';\n  }\n}\n\nready(onReady);\n","export interface SubscribeInput {\n  email?: string;\n  first_name?: string;\n  phone?: string;\n  shopify_customer_id?: string;\n  sign_up_source_id: string;\n}\n\nexport interface RequestBody {\n  email?: string;\n  first_name?: string;\n  phone?: string;\n  shop_domain?: string;\n  shopify_customer_id?: string;\n  sign_up_source_id?: string;\n}\n\nexport type ResponseBody = Record<string, any>;\n\ninterface ErrorResponseBody {\n  error: string;\n}\n\n/**\n * This uses the no-store cache property because we never want this request to use the cache.\n *\n * @see https://developer.mozilla.org/en-US/docs/Web/API/Request/cache\n */\nexport async function subscribe(input: SubscribeInput) {\n  const url = 'https://langehair.com/apps/attentive-relay/prod/v1/subscribe';\n\n  const requestBody: RequestBody = {\n    email: input.email,\n    first_name: input.first_name,\n    phone: input.phone,\n    shop_domain: 'lange-beauty.myshopify.com',\n    shopify_customer_id: input.shopify_customer_id,\n    sign_up_source_id: input.sign_up_source_id\n  };\n\n  let response;\n  try {\n    response = await fetch(url, {\n      body: JSON.stringify(requestBody),\n      cache: 'no-store',\n      headers: {\n        'Content-Type': 'application/json'\n      },\n      method: 'POST',\n      mode: 'cors'\n    });\n  } catch (error) {\n    const ae = new AttentiveError('There was a problem subscribing', { cause: error });\n    ae.request_body = requestBody;\n    ae.url = url;\n    throw ae;\n  }\n\n  // On success, we have a 204 No Content, so we do not attempt to fetch the response body. Calling\n  // await response.json() can throw for 204s. So we either have a non-204 indicating an error or we\n  // succeeded.\n\n  if (response.status !== 204) {\n    const error = new AttentiveError('There was a problem subscribing');\n    error.request_body = requestBody;\n\n    // If there is an issue in the backend subscribing, then we might have some information about\n    // the error in the response body, so we have to try to extract the message.\n\n    try {\n      const responseBody = await response.json();\n      error.response_body = responseBody;\n\n      if (isErrorResponseBody(responseBody) && typeof responseBody.error === 'string' &&\n        responseBody.error.length > 0) {\n        error.message = responseBody.error;\n      }\n    } catch (error) {\n      // ignore\n    }\n\n    error.status = response.status;\n    error.url = response.url;\n    throw error;\n  }\n}\n\nfunction isErrorResponseBody(value: any): value is ErrorResponseBody {\n  return typeof value?.error === 'string' && value.error.length > 0;\n}\n\nexport class AttentiveError extends Error {\n  public declare request_body: any;\n  public declare response_body: any;\n  public declare status: number;\n  public declare url: string;\n\n  constructor(message?: string, options?: ErrorOptions) {\n    super(message || 'Unknown', options);\n  }\n}\n","import { isValidEmailAddress } from '@integrabeauty/person';\nimport { AttentiveError, type SubscribeInput, subscribe } from '../../lib/attentive-service.js';\nimport { ready } from '../../lib/dom-ready.js';\n\ninterface RequestData {\n  email: string;\n  first_name: string;\n  phone: string | undefined;\n}\n\nfunction isValidPhone(phone: string) {\n  return /^[+]?(1\\s?)?((\\([0-9]{3}\\))|[0-9]{3})[\\s\\-]?[\\0-9]{3}[\\s\\-]?[0-9]{4}$/.test(phone);\n}\n\n// Each input sits in a field group that contains a single message element. These helpers toggle the\n// group's error/success state and message by walking up from the input, so the surrounding markup\n// is reached by structure rather than through per-element handles.\n\nfunction markFieldError(input: HTMLInputElement, message: string) {\n  const field = input.closest('.field-wrapper');\n  field.classList.add('error');\n  field.querySelector('.form-message').textContent = message;\n}\n\nfunction markFieldValid(input: HTMLInputElement) {\n  input.closest('.field-wrapper').classList.add('success');\n}\n\nfunction clearField(input: HTMLInputElement | null) {\n  if (input) {\n    const field = input.closest('.field-wrapper');\n    field.classList.remove('error', 'success');\n    field.querySelector('.form-message').textContent = '';\n  }\n}\n\n/**\n * Validates and submits the newsletter signup form\n */\nfunction onSubmit(event: SubmitEvent) {\n  event.preventDefault();\n\n  // In the theme editor a merchant is not actually subscribing, so do not submit the form. We do\n  // this check here instead of dynamically registering the listener so that we handle all submit\n  // events and ensure that we call preventDefault().\n\n  // This module expects that the Shopify global is correctly declared and initialized. We\n  // intentionally are not being defensive about its presence here.\n\n  if (Shopify.designMode) {\n    return;\n  }\n\n  const form = event.currentTarget;\n  if (!(form instanceof HTMLFormElement)) {\n    return;\n  }\n\n  const submitButton = form.querySelector('button');\n  submitButton.classList.add('submitting');\n  submitButton.textContent = 'Submitting...';\n\n  const emailInput = form.querySelector<HTMLInputElement>('#subscribe-email');\n  const nameInput = form.querySelector<HTMLInputElement>('#subscribe-name');\n  const phoneInput = form.querySelector<HTMLInputElement>('#subscribe-phone-number');\n\n  const requestData: RequestData = {\n    email: emailInput.value,\n    first_name: nameInput.value,\n    phone: phoneInput?.value\n  };\n\n  const nameError = !isValidPersonName(requestData.first_name);\n  const emailError = !isValidEmailAddress(requestData.email);\n\n  let phoneError = false;\n  if (requestData.phone) {\n    phoneError = !isValidPhone(requestData.phone);\n  }\n\n  const termsInputElement = form.querySelector<HTMLInputElement>('#subscribe-terms');\n\n  let termsError = false;\n  if (requestData.phone && termsInputElement) {\n    termsError = !termsInputElement.checked;\n  }\n\n  const customerId = form.querySelector<HTMLInputElement>('#customer-id').value;\n\n  if (nameError) {\n    markFieldError(nameInput, 'Please enter a name');\n  } else {\n    markFieldValid(nameInput);\n  }\n\n  if (emailError) {\n    markFieldError(emailInput, 'Please enter a valid email address');\n  } else {\n    markFieldValid(emailInput);\n  }\n\n  const termsWrapper = form.querySelector('#subscribe-terms-wrapper');\n  if (termsError) {\n    termsWrapper.classList.add('error');\n    termsWrapper.querySelector('.form-message').textContent =\n      'Please accept the Conditions and Terms above';\n  } else {\n    termsWrapper.classList.add('success');\n  }\n\n  if (phoneError) {\n    markFieldError(phoneInput, 'Please enter a valid phone number');\n  } else {\n    markFieldValid(phoneInput);\n  }\n\n  if (nameError || emailError || phoneError || termsError) {\n    submitButton.classList.remove('submitting');\n    submitButton.textContent = 'Submit';\n    return;\n  }\n\n  let currencyCode = 'USD';\n  const currencyCodeInput = form.querySelector<HTMLInputElement>('#currency-code');\n  if (currencyCodeInput) {\n    currencyCode = currencyCodeInput.value;\n  }\n\n  const resultElement = form.querySelector('#subscribe-response');\n  if (!resultElement) {\n    return;\n  }\n\n  const subscribeInput: SubscribeInput = {\n    email: requestData.email,\n    first_name: requestData.first_name,\n    phone: requestData.phone,\n    shopify_customer_id: customerId,\n    sign_up_source_id: '991550'\n  };\n\n  subscribe(subscribeInput)\n    .then(() => onSubscribeSuccess(form, requestData, currencyCode))\n    .catch((error) => onSubscribeError(error, form));\n}\n\nfunction isValidPersonName(name: string) {\n  // Some browsers do not tolerate using an empty space in the character class so we use \\s\n  // explicitly, even though it matches multiple kinds of spaces.\n  return /^[a-z\\s]{2,30}$/i.test(name);\n}\n\nfunction onSubscribeSuccess(form: HTMLFormElement, requestData: RequestData, currencyCode: string) {\n  const resultElement = form.querySelector('#subscribe-response');\n  resultElement.classList.remove('error');\n  resultElement.classList.add('success');\n  resultElement.textContent = 'Thank you for registering!';\n\n  const phoneInput = form.querySelector<HTMLInputElement>('#subscribe-phone-number');\n  if (phoneInput) {\n    phoneInput.value = '';\n  }\n\n  const termsInput = form.querySelector<HTMLInputElement>('#subscribe-terms');\n  if (termsInput) {\n    termsInput.checked = false;\n  }\n\n  const termsWrapper = form.querySelector<HTMLElement>('#subscribe-terms-wrapper');\n  if (termsWrapper) {\n    termsWrapper.hidden = true;\n    termsWrapper.classList.remove('success');\n  }\n\n  const inputs = form.querySelectorAll<HTMLInputElement>('input:not([type=\"hidden\"])');\n  for (const input of inputs) {\n    input.value = '';\n  }\n\n  const nameInput = form.querySelector<HTMLInputElement>('#subscribe-name');\n  clearField(nameInput);\n\n  const emailInput = form.querySelector<HTMLInputElement>('#subscribe-email');\n  clearField(emailInput);\n\n  clearField(phoneInput);\n\n  const submitButton = form.querySelector('button');\n  submitButton.classList.remove('submitting');\n  submitButton.classList.add('success');\n  submitButton.textContent = 'Success!';\n\n  dispatchIdentityCaptured(form, requestData);\n\n  type FormSubmitDetail = WindowEventMap['form-submit']['detail'];\n  const submitEvent = new CustomEvent<FormSubmitDetail>('form-submit', {\n    bubbles: true,\n    detail: {\n      currency_code: currencyCode,\n      email_provided: requestData.email ? true : false,\n      form_name: 'footer-subscribe',\n      form_location: location.href,\n      form_type: 'marketing-subscribe',\n      phone_provided: requestData.phone ? true : false\n    }\n  });\n  form.dispatchEvent(submitEvent);\n}\n\nfunction dispatchIdentityCaptured(form: HTMLFormElement, requestData: RequestData) {\n  if (!requestData.email && !requestData.phone) {\n    return;\n  }\n\n  type Detail = WindowEventMap['identity-captured']['detail'];\n  const detail: Detail = {};\n\n  // Although the input field is labelled first_name, the placeholder text suggests that it can\n  // contain the full name. To accommodate this, we will attempt to parse the first_name field into\n  // first and last name by splitting on space. If there is only one part, we will treat it as the\n  // first name. If there are two parts, we will treat the first part as the first name and the\n  // second part as the last name. If there are more than two parts, we treat the last part as the\n  // last name and the rest as the first name.\n\n  if (requestData.first_name) {\n    const parts = requestData.first_name.split(' ');\n    if (parts.length === 1) {\n      detail.first_name = parts[0];\n    } else if (parts.length === 2) {\n      detail.first_name = parts[0];\n      detail.last_name = parts[1];\n    } else {\n      detail.last_name = parts.pop();\n      detail.first_name = parts.join(' ');\n    }\n  }\n\n  if (requestData.email) {\n    detail.email = requestData.email;\n  }\n\n  if (requestData.phone) {\n    detail.phone = requestData.phone;\n  }\n\n  const event = new CustomEvent<Detail>('identity-captured', {\n    bubbles: true,\n    detail\n  });\n  form.dispatchEvent(event);\n}\n\nfunction onSubscribeError(error: unknown, form: HTMLFormElement) {\n  const resultElement = form.querySelector('#subscribe-response');\n  resultElement.classList.remove('success');\n  resultElement.classList.add('error');\n\n  const submitButton = form.querySelector('button');\n  submitButton.classList.remove('submitting');\n  submitButton.textContent = 'Submit';\n\n  if (error instanceof AttentiveError) {\n    resultElement.innerHTML = error.message ||\n      'There was a problem submitting your request, please try again later';\n  } else {\n    resultElement.innerHTML =\n      'There was a problem submitting your request, please try again later';\n    console.warn(error, 'Failed to subscribe in Attentive');\n  }\n\n  const termsWrapper = form.querySelector<HTMLElement>('#subscribe-terms-wrapper');\n  termsWrapper.hidden = true;\n}\n\nfunction onPhoneFocus(event: FocusEvent) {\n  if (event.currentTarget instanceof HTMLInputElement) {\n    clearField(event.currentTarget);\n\n    const terms = event.currentTarget.form.querySelector<HTMLElement>('#subscribe-terms-wrapper');\n    terms.hidden = false;\n    terms.querySelector('.form-message').textContent = '';\n  }\n}\n\nfunction onPhoneBlur(event: FocusEvent) {\n  if (event.currentTarget instanceof HTMLInputElement) {\n    const terms = event.currentTarget.form.querySelector<HTMLElement>('#subscribe-terms-wrapper');\n    terms.hidden = !event.currentTarget.value;\n  }\n}\n\nfunction onTermsChange(event: Event) {\n  if (event.target instanceof HTMLInputElement && event.target.checked) {\n    const terms = event.target.form.querySelector('#subscribe-terms-wrapper');\n    terms.classList.remove('error');\n    terms.querySelector('.form-message').textContent = '';\n  }\n}\n\nfunction onNameFocus(event: FocusEvent) {\n  if (event.currentTarget instanceof HTMLInputElement) {\n    clearField(event.currentTarget);\n  }\n}\n\nfunction onEmailFocus(event: FocusEvent) {\n  if (event.currentTarget instanceof HTMLInputElement) {\n    clearField(event.currentTarget);\n  }\n}\n\nfunction onReady() {\n  const terms = document.querySelector<HTMLInputElement>('#subscribe-terms');\n  terms?.addEventListener('change', onTermsChange);\n\n  const phone = document.querySelector<HTMLInputElement>('#subscribe-phone-number');\n  phone?.addEventListener('focus', onPhoneFocus);\n  phone?.addEventListener('blur', onPhoneBlur);\n\n  const form = document.querySelector<HTMLFormElement>('form[name=\"subscribe\"]');\n  form?.addEventListener('submit', onSubmit);\n\n  const submitButton = form?.querySelector('button');\n  if (submitButton) {\n    submitButton.disabled = false;\n  }\n\n  const name = document.querySelector<HTMLInputElement>('#subscribe-name');\n  name?.addEventListener('focus', onNameFocus);\n\n  const email = document.querySelector<HTMLInputElement>('#subscribe-email');\n  email?.addEventListener('focus', onEmailFocus);\n}\n\nready(onReady);\n","/**\n * Validates an email address.\n *\n * Based on the regex used by Meta's pixel.\n */\nexport function isValidEmailAddress(email) {\n    // eslint-disable-next-line @stylistic/max-len\n    return /^[\\w!#\\$%&\\'\\*\\+\\/\\=\\?\\^`\\{\\|\\}~\\-]+(:?\\.[\\w!#\\$%&\\'\\*\\+\\/\\=\\?\\^`\\{\\|\\}~\\-]+)*@(?:[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9\\-]*[a-z0-9])?$/i.test(email);\n}\n","/**\n * Throws an assertion error if the given condition is falsy.\n *\n * @param expression any expression, although one that evaluates to true or false is preferred by\n * convention\n * @param message optional error text\n */\nexport function assert(expression: unknown, message?: string) {\n  if (!expression) {\n    throw new AssertionError(message);\n  }\n}\n\nexport class AssertionError extends Error {\n  constructor(message = 'Assertion error') {\n    super(message);\n    this.name = this.constructor.name;\n  }\n}\n","/**\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 { assert } from '@integrabeauty/assert';\nimport type * as TrustPilotTypes from '@integrabeauty/trustpilot-types';\nimport { decorateError } from '../../lib/decorate-error.js';\n\n/**\n * Fetches public information about a business unit from TrustPilot's API.\n *\n * @param apiKey api key\n * @param businessUnitId business unit id\n * @param useCache whether to use Integra's own custom backend cache service in order to avoid the\n * unreliable TrustPilot API\n */\nexport async function fetchBusinessUnit(apiKey: string, businessUnitId: string, useCache = false) {\n  assert(apiKey, 'Invalid api key');\n  assert(businessUnitId, 'Invalid business unit id');\n\n  const path = useCache ?\n    '/apps/trustpilot-cache/lange-business-unit.json' :\n    `/apps/trustpilot-api/v1/business-units/${businessUnitId}`;\n\n  let headers: HeadersInit = {};\n  if (!useCache) {\n    headers = {\n      'Content-Type': 'application/json',\n      'apiKey': apiKey\n    };\n  }\n\n  let response;\n  try {\n    response = await fetch(path, { headers });\n  } catch (error) {\n    decorateError(error, 'url', path);\n    throw error;\n  }\n\n  if (!response.ok) {\n    const error: any = new Error('Trustpilot Site Rating Service Error ' +\n      `${response.status} ${response.statusText} ${response.url}`);\n    error.status = response.status;\n    error.url = response.url;\n    throw error;\n  }\n\n  // We are intentionally not using response.json() here because it obscures errors.\n  let responseText;\n  try {\n    responseText = await response.text();\n  } catch (error) {\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  let body;\n  try {\n    body = JSON.parse(responseText);\n  } catch (error) {\n    decorateError(error, 'body', responseText);\n    decorateError(error, 'status', response.status);\n    decorateError(error, 'url', response.url);\n    throw error;\n  }\n\n  if (!isResponseBody(body)) {\n    const error: any = new Error('Unexpected response body');\n    error.body = body;\n    error.status = response.status;\n    error.url = response.url;\n    throw error;\n  }\n\n  return body;\n}\n\nfunction isResponseBody(value: any): value is TrustPilotTypes.FetchBusinessUnitResponseBody {\n  return Number.isInteger(value?.numberOfReviews?.total);\n}\n","import { ready } from '../../lib/dom-ready.js';\nimport { fetchBusinessUnit } from './trustpilot-fetch-business-unit.js';\n\ninterface CachedRating {\n  score: number;\n  total: number;\n}\n\nasync function fetchBusinessUnitCached(apiKey: string, businessUnitId: string) {\n  const storageKey = 'trustpilot_site_rating';\n\n  let rating: CachedRating;\n\n  try {\n    const value = sessionStorage.getItem(storageKey);\n    if (value) {\n      rating = JSON.parse(value);\n      if (rating) {\n        return rating;\n      }\n    }\n  } catch (error) {\n    console.debug(error);\n  }\n\n  const body = await fetchBusinessUnit(apiKey, businessUnitId, true);\n\n  rating = {\n    score: body.score.trustScore,\n    total: body.numberOfReviews.total\n  };\n\n  try {\n    sessionStorage.setItem(storageKey, JSON.stringify(rating));\n  } catch (error) {\n    console.debug(error);\n  }\n\n  return rating;\n}\n\nfunction onReady() {\n  // init calls out to fetch business unit and otherwise does nothing fallible. the only reason init\n  // fails is because of a failure to fetch. the failure to fetch here is not really indicative of a\n  // programming error. it is in fact routine to experience load failures because of networking\n  // problems (e.g. unreliable cellular data). if something were truly systemic it would be visually\n  // obvious. because the fetch errors are so routine, this does not capture the error at the error\n  // or warning level.\n\n  init().catch(console.debug);\n}\n\nasync function init() {\n  const badges = document.querySelectorAll('trustpilot-badge');\n  if (badges.length === 0) {\n    return;\n  }\n\n  // cspell:disable\n  const apiKey = 'zY69rTwFDPKLgqcVnM2Ai95NZ2V6NeAR';\n  const businessUnitId = '5ccc9833442b4b00014a6eb9';\n  // cspell:enable\n\n  const businessUnit = await fetchBusinessUnitCached(apiKey, businessUnitId);\n  for (const badge of badges) {\n    badge.dataset.rating = `${businessUnit.score}`;\n    badge.dataset.total = `${businessUnit.total}`;\n  }\n}\n\nready(onReady);\n"],"names":["TrustpilotBadge","HTMLElement","observedAttributes","connectedCallback","this","initializeShadowRoot","render","attributeChangedCallback","_name","oldValue","newValue","isConnected","shadowRoot","attachShadow","mode","innerHTML","rating","Math","min","parseFloat","dataset","isNaN","total","parseInt","hidden","scoreElement","querySelector","textContent","toString","totalElement","format","Intl","NumberFormat","starsElement","starPrefix","prefix","customElements","get","define","YotpoBadge","href","url","src","badgeImageSrc","totalText","Number","toFixed","score","averageScore","badge","setAttribute","join","TrustpilotRatingStars","name","dark","querySelectorAll","i","length","classList","add","ready","callback","type","document","readyState","timer","setTimeout","clearTimeout","addEventListener","removeEventListener","onShowSelectorClick","event","preventDefault","currentTarget","focusEvent","Event","bubbles","composed","dispatchEvent","open","links","link","AttentiveError","Error","constructor","message","options","super","markFieldError","input","field","closest","markFieldValid","clearField","remove","onSubmit","Shopify","designMode","form","HTMLFormElement","submitButton","emailInput","nameInput","phoneInput","requestData","email","value","first_name","phone","nameError","test","emailError","phoneError","termsInputElement","termsError","checked","customerId","termsWrapper","currencyCode","currencyCodeInput","async","requestBody","shop_domain","shopify_customer_id","sign_up_source_id","response","fetch","body","JSON","stringify","cache","headers","method","error","ae","cause","request_body","status","responseBody","json","response_body","subscribe","then","resultElement","termsInput","inputs","detail","parts","split","last_name","pop","CustomEvent","dispatchIdentityCaptured","submitEvent","currency_code","email_provided","form_name","form_location","location","form_type","phone_provided","onSubscribeSuccess","catch","console","warn","onSubscribeError","onPhoneFocus","HTMLInputElement","terms","onPhoneBlur","onTermsChange","target","onNameFocus","onEmailFocus","assert","expression","AssertionError","disabled","decorateError","key","assignmentError","reason","log","tag","Object","prototype","call","fetchBusinessUnit","apiKey","businessUnitId","useCache","path","responseText","ok","statusText","text","parse","isInteger","_value$numberOfReview","numberOfReviews","badges","businessUnit","storageKey","sessionStorage","getItem","debug","trustScore","setItem","fetchBusinessUnitCached","init"],"sourceRoot":""}