From 5dce82388d5893d279f227fe915648d6b57e8071 Mon Sep 17 00:00:00 2001
From: glo82145 <glo82145@adobe.com>
Date: Tue, 15 Apr 2025 18:55:04 +0530
Subject: [PATCH 1/4] PWA-3318::Prex Compatility

---
 .../lib/components/Gallery/Gallery.js         |  59 +++
 .../lib/components/Gallery/gallery.css        |  24 ++
 .../lib/components/Gallery/index.js           |   1 +
 .../lib/components/Gallery/item.css           |   3 +
 .../ProductRecommendations.css                |  11 +
 .../VeniaProductRecommendations.js            | 123 ++++++
 .../VeniaProductRecommendations/index.js      |   1 +
 .../lib/constants/index.js                    |   2 +
 .../lib/constants/localStorageConstants.js    |   4 +
 .../lib/constants/pageTypes.js                |   8 +
 .../lib/hooks/useObserver.js                  |  41 ++
 .../lib/hooks/useRecsData.js                  | 105 +++++
 .../lib/hooks/useRecsTrackingProps.js         |  34 ++
 .../lib/hooks/useSyncMSEToLocalStorage.js     | 209 +++++++++
 .../lib/index.js                              |  20 +
 .../lib/queries/getCart.gql.js                |  59 +++
 .../lib/targets/intercept.js                  |  23 +
 .../lib/wrappers/wrapUseApp.js                |   7 +
 .../lib/wrappers/wrapUseGalleryItem.js        |  12 +
 .../package.json                              |  24 ++
 packages/venia-concept/package.json           |   5 +
 yarn.lock                                     | 397 ++++++++++--------
 22 files changed, 995 insertions(+), 177 deletions(-)
 create mode 100644 packages/extensions/venia-product-recommendations/lib/components/Gallery/Gallery.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/components/Gallery/gallery.css
 create mode 100644 packages/extensions/venia-product-recommendations/lib/components/Gallery/index.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/components/Gallery/item.css
 create mode 100644 packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/ProductRecommendations.css
 create mode 100644 packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/index.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/constants/index.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/constants/localStorageConstants.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/constants/pageTypes.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/hooks/useObserver.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/hooks/useRecsData.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/hooks/useRecsTrackingProps.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/hooks/useSyncMSEToLocalStorage.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/index.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/queries/getCart.gql.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/targets/intercept.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseApp.js
 create mode 100644 packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseGalleryItem.js
 create mode 100644 packages/extensions/venia-product-recommendations/package.json

diff --git a/packages/extensions/venia-product-recommendations/lib/components/Gallery/Gallery.js b/packages/extensions/venia-product-recommendations/lib/components/Gallery/Gallery.js
new file mode 100644
index 0000000000..f66d7dc3d5
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/components/Gallery/Gallery.js
@@ -0,0 +1,59 @@
+import React from 'react';
+import { string, shape, array } from 'prop-types';
+import { mergeClasses } from '@magento/venia-ui/lib/classify';
+import GalleryItem from '@magento/venia-ui/lib/components/Gallery/item';
+// inline loading of the css is janky, but the webpack loader gets blown out in local environment.
+import defaultGalleryClasses from '!!style-loader!css-loader?modules!./gallery.css';
+import defaultItemClasses from '!!style-loader!css-loader?modules!./item.css';
+
+/**
+ * Renders a Gallery of items. If items is an array of nulls Gallery will render
+ * a placeholder item for each.
+ *
+ * @params {Array} props.items an array of items to render
+ */
+export const Gallery = props => {
+  const galleryClasses = mergeClasses(
+    defaultGalleryClasses,
+    props.galleryClasses,
+  );
+  const itemClasses = mergeClasses(defaultItemClasses, props.itemClasses);
+
+  const { items } = props;
+
+  const galleryItems = items.map((item, index) => {
+    if (item === null) {
+      return <GalleryItem key={index} />;
+    }
+    return <GalleryItem key={item.id} item={item} classes={itemClasses} />;
+  });
+
+  return (
+    <div className={galleryClasses.root}>
+      <div className={galleryClasses.items}>{galleryItems}</div>
+    </div>
+  );
+};
+
+Gallery.propTypes = {
+  galleryClasses: shape({
+    filters: string,
+    items: string,
+    root: string,
+  }),
+  itemClasses: shape({
+    image: string,
+    imageContainer: string,
+    imagePlaceholder: string,
+    image_pending: string,
+    images: string,
+    images_pending: string,
+    name: string,
+    name_pending: string,
+    price: string,
+    price_pending: string,
+    root: string,
+    root_pending: string,
+  }),
+  items: array.isRequired,
+};
diff --git a/packages/extensions/venia-product-recommendations/lib/components/Gallery/gallery.css b/packages/extensions/venia-product-recommendations/lib/components/Gallery/gallery.css
new file mode 100644
index 0000000000..abd4f12a39
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/components/Gallery/gallery.css
@@ -0,0 +1,24 @@
+.root {
+  display: grid;
+  grid-template-areas:
+    'actions'
+    'items';
+  grid-template-columns: 1fr;
+  line-height: 1;
+}
+
+.items {
+  grid-template-columns: repeat(5, 1fr);
+  margin-left: 2em;
+  margin-right: 2em;
+  margin-bottom: 60px;
+  display: grid;
+  grid-area: items;
+  grid-gap: 1rem;
+}
+
+@media (max-width: 640px) {
+  .items {
+    grid-template-columns: repeat(2, 1fr);
+  }
+}
diff --git a/packages/extensions/venia-product-recommendations/lib/components/Gallery/index.js b/packages/extensions/venia-product-recommendations/lib/components/Gallery/index.js
new file mode 100644
index 0000000000..18bb67f844
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/components/Gallery/index.js
@@ -0,0 +1 @@
+export * from './Gallery';
diff --git a/packages/extensions/venia-product-recommendations/lib/components/Gallery/item.css b/packages/extensions/venia-product-recommendations/lib/components/Gallery/item.css
new file mode 100644
index 0000000000..5a4fae3f24
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/components/Gallery/item.css
@@ -0,0 +1,3 @@
+.name {
+  font-weight: bold;
+}
diff --git a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/ProductRecommendations.css b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/ProductRecommendations.css
new file mode 100644
index 0000000000..c2e0d84a7d
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/ProductRecommendations.css
@@ -0,0 +1,11 @@
+.unitTitle {
+  text-align: center;
+  margin: 1.5em;
+  font-weight: 700;
+  font-size: 2.25rem;
+  font-family: 'Source Serif Pro';
+}
+
+.root a {
+  text-decoration: none;
+}
diff --git a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
new file mode 100644
index 0000000000..dc29c1f52b
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
@@ -0,0 +1,123 @@
+import React, { useRef } from 'react';
+import { string, shape } from 'prop-types';
+
+import useRecsTrackingProps from '../../hooks/useRecsTrackingProps';
+import { Gallery } from '../Gallery/Gallery';
+import { mergeClasses } from '@magento/venia-ui/lib/classify';
+// inline loading of the css is janky, but the webpack loader gets blown  out in local environment.
+import defaultClasses from '!!style-loader!css-loader?modules!./ProductRecommendations.css';
+import useObserver from '../../hooks/useObserver';
+import { mse } from '@magento/venia-data-collector';
+
+export const VeniaProductRecommendations = props => {
+  const rendered = useRef([]);
+  const { units } = useRecsTrackingProps(props);
+  const { observeUnit } = useObserver();
+
+  const classes = mergeClasses(defaultClasses, props.classes);
+  const galleryClasses = mergeClasses(defaultClasses, props.galleryClasses);
+  const itemClasses = mergeClasses(defaultClasses, props.itemClasses);
+
+  let galleryUnits = units.map(recommendationUnit => {
+    if (recommendationUnit.totalProducts < 1) {
+      return null;
+    }
+
+    const items = recommendationUnit.products.map(shapeItem);
+    return (
+      <div
+        key={recommendationUnit.unitId}
+        data-unit-id={recommendationUnit.unitId}
+        className={classes.root}
+        ref={element => observeUnit(recommendationUnit, element)}
+      >
+        <div className={classes.unitTitle}>
+          {recommendationUnit.storefrontLabel}
+        </div>
+        <Gallery
+          galleryClasses={galleryClasses}
+          itemClasses={itemClasses}
+          items={items}
+        />
+      </div>
+    );
+  });
+
+  if (units && units.length > 0) {
+    units.forEach(recUnit => {
+      if (
+        recUnit.totalProducts > 0 &&
+        !rendered.current.includes(recUnit.unitId)
+      ) {
+        mse.publish.recsUnitRender(recUnit.unitId);
+        rendered.current.push(recUnit.unitId);
+      }
+    });
+
+    return <div>{galleryUnits}</div>;
+  } else {
+    return null;
+  }
+};
+
+VeniaProductRecommendations.propTypes = {
+  galleryClasses: shape({
+    filters: string,
+    items: string,
+    root: string,
+  }),
+  itemClasses: shape({
+    image: string,
+    imageContainer: string,
+    imagePlaceholder: string,
+    image_pending: string,
+    images: string,
+    images_pending: string,
+    name: string,
+    name_pending: string,
+    price: string,
+    price_pending: string,
+    root: string,
+    root_pending: string,
+  }),
+  classes: shape({
+    unitTitle: string,
+    root: string,
+  }),
+  pageType: string.isRequired,
+};
+
+// format data for GalleryItem, exported for testing
+export const shapeItem = item => {
+  if (item) {
+    const { url, image, prices, productId, currency, type } = item;
+
+    // derive the url_key and url_suffix from the url
+    // example url --> https://magento.com/blah/blah/url_key.url_suffix
+    const urlArray = String(url).split('/').splice(-1)[0].split('.');
+    const url_key = urlArray[0];
+    const url_suffix = `.${urlArray[1]}`;
+
+    const price = {
+      regularPrice: {
+        amount: {
+          value: prices.minimum.regular,
+          currency,
+        },
+      },
+    };
+
+    return {
+      ...item,
+      id: productId,
+      small_image: image,
+      url_key,
+      url_suffix,
+      price,
+      // use inStock when the recs service provides it, use the commented out line below:
+      // stock_status: inStock ? "IN_STOCK" : "OUT_OF_STOCK";
+      stock_status: 'IN_STOCK',
+      type_id: type,
+    };
+  } else return null;
+};
diff --git a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/index.js b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/index.js
new file mode 100644
index 0000000000..4f40fdb4bf
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/index.js
@@ -0,0 +1 @@
+export * from './ProductRecommendations';
diff --git a/packages/extensions/venia-product-recommendations/lib/constants/index.js b/packages/extensions/venia-product-recommendations/lib/constants/index.js
new file mode 100644
index 0000000000..41750d5493
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/constants/index.js
@@ -0,0 +1,2 @@
+export * from './localStorageConstants';
+export * from './pageTypes';
diff --git a/packages/extensions/venia-product-recommendations/lib/constants/localStorageConstants.js b/packages/extensions/venia-product-recommendations/lib/constants/localStorageConstants.js
new file mode 100644
index 0000000000..a9baa56c99
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/constants/localStorageConstants.js
@@ -0,0 +1,4 @@
+export const USER_VIEW_HISTORY_TIME_DECAY_KEY = 'ds-view-history-time-decay';
+export const USER_VIEW_HISTORY_KEY = 'ds-view-history';
+export const PURCHASE_HISTORY_KEY = 'ds-purchase-history';
+export const CART_CONTENTS_KEY = 'ds-cart';
diff --git a/packages/extensions/venia-product-recommendations/lib/constants/pageTypes.js b/packages/extensions/venia-product-recommendations/lib/constants/pageTypes.js
new file mode 100644
index 0000000000..c02a7983a3
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/constants/pageTypes.js
@@ -0,0 +1,8 @@
+export const CMS = 'CMS';
+export const CART = 'Cart';
+export const CATEGORY = 'Category';
+export const PRODUCT = 'Product';
+export const CHECKOUT = 'Checkout';
+export const PAGEBUILDER = 'PageBuilder';
+
+export const PageTypes = [CMS, CART, CATEGORY, CHECKOUT, PRODUCT, PAGEBUILDER];
diff --git a/packages/extensions/venia-product-recommendations/lib/hooks/useObserver.js b/packages/extensions/venia-product-recommendations/lib/hooks/useObserver.js
new file mode 100644
index 0000000000..e027612742
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/hooks/useObserver.js
@@ -0,0 +1,41 @@
+import { mse } from '@magento/venia-data-collector';
+
+let cleared = {};
+
+// oddly, these functions error when not wrapped in a hook. 🤷
+const useObserver = () => {
+  const meetThreshold = (entries, unit) => {
+    entries.forEach(entry => {
+      const { isIntersecting, intersectionRatio } = entry;
+      const { unitId } = unit;
+
+      if (!isIntersecting) {
+        cleared[unitId] = true;
+      }
+      if (cleared[unitId] !== false && intersectionRatio >= 0.5) {
+        cleared[unitId] = false;
+        mse.publish.recsUnitView(unit.unitId);
+      }
+    });
+  };
+
+  const observeUnit = (unit, element) => {
+    if (element) {
+      const options = {
+        threshold: [0.0, 0.5],
+      };
+      const observer = new IntersectionObserver(
+        entries => meetThreshold(entries, unit),
+        options,
+      );
+      observer.observe(element);
+    } else {
+      console.warn(
+        'VeniaProductRecommendations IntersectionObserver: Element is either null or undefined.',
+      );
+    }
+  };
+  return { observeUnit };
+};
+
+export default useObserver;
diff --git a/packages/extensions/venia-product-recommendations/lib/hooks/useRecsData.js b/packages/extensions/venia-product-recommendations/lib/hooks/useRecsData.js
new file mode 100644
index 0000000000..a29868540b
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/hooks/useRecsData.js
@@ -0,0 +1,105 @@
+import RecommendationsClient from '@magento/recommendations-js-sdk';
+import { useEffect, useState, useRef } from 'react';
+import { PageTypes, PRODUCT } from '../constants';
+import { mse } from '@magento/venia-data-collector';
+
+const useRecsData = props => {
+  const [recs, setRecs] = useState(null);
+  const [isLoading, setIsLoading] = useState(false);
+  const [error, setError] = useState(null);
+  const fired = useRef(false);
+  const stale = useRef(false);
+  const [currentProduct, setCurrentProduct] = useState(null);
+
+  if (
+    (process.env.NODE_ENV === 'development' ||
+      process.env.NODE_ENV === 'test') &&
+    (!props || !props.pageType)
+  ) {
+    throw new Error(
+      'Headless Recommendations: PageType is required to fetch recommendations.',
+    );
+  } else if (props.pageType && !PageTypes.includes(props.pageType)) {
+    throw new Error(
+      `Headless Recommendations: ${
+        props.pageType
+      } is not a valid pagetype. Valid types include ${JSON.stringify(
+        PageTypes,
+      )}`,
+    );
+  }
+  const { pageType } = props;
+  const storefrontContext = mse.context.getStorefrontInstance();
+  const product = mse.context.getProduct();
+
+  useEffect(() => {
+    const fetchRecs = async () => {
+      const storefront = { ...storefrontContext, pageType };
+
+      const client = new RecommendationsClient(storefront);
+
+      let currentSku;
+      if (pageType === PRODUCT) {
+        currentSku = product.sku;
+      }
+
+      const fetchProps = {
+        ...props,
+        currentSku,
+      };
+      let res;
+
+      try {
+        setIsLoading(true);
+        fired.current = true;
+        stale.current = false;
+        mse.publish.recsRequestSent({ pageContext: { pageType } });
+
+        res = await client.fetchPreconfigured(fetchProps);
+      } catch (e) {
+        console.error(e);
+        setIsLoading(false);
+        setError(e);
+      }
+      if (res) {
+        const { data } = res;
+        mse.context.setRecommendations({ units: data.results });
+        mse.publish.recsResponseReceived();
+        setIsLoading(false);
+        setRecs(data);
+      }
+    };
+    if (
+      ((!fired.current && !recs) || stale.current) &&
+      PageTypes.includes(pageType) &&
+      storefrontContext !== undefined &&
+      storefrontContext.environmentId &&
+      ((pageType === PRODUCT &&
+        product !== undefined &&
+        product.sku !== undefined) ||
+        pageType !== PRODUCT)
+    ) {
+      fetchRecs();
+    }
+  }, [pageType, props, recs, storefrontContext, product]);
+
+  useEffect(() => {
+    if (
+      product &&
+      product.sku &&
+      (!currentProduct || product.sku !== currentProduct.sku)
+    ) {
+      setCurrentProduct(product);
+    }
+  }, [product,currentProduct]);
+
+  useEffect(() => {
+    if (currentProduct && recs && fired.current === true) {
+      stale.current = true;
+    }
+  }, [currentProduct,recs]);
+
+  return { data: recs, isLoading, error };
+};
+
+export default useRecsData;
diff --git a/packages/extensions/venia-product-recommendations/lib/hooks/useRecsTrackingProps.js b/packages/extensions/venia-product-recommendations/lib/hooks/useRecsTrackingProps.js
new file mode 100644
index 0000000000..ba3f81f365
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/hooks/useRecsTrackingProps.js
@@ -0,0 +1,34 @@
+import { useState, useEffect } from 'react';
+import useRecsData from './useRecsData';
+import { mse } from '@magento/venia-data-collector';
+
+const useRecsTrackingProps = props => {
+  const [units, setUnits] = useState([]);
+  const { data, isLoading, error } = useRecsData(props);
+
+  useEffect(() => {
+    if (data && data.results) {
+      let tmpUnits = data.results.map(unit => {
+        const newUnit = {
+          ...unit,
+          pageType: props.pageType,
+        };
+
+        const products = unit.products.map(product => {
+          const newProduct = { ...product, unit: newUnit };
+          const onClick = () => {
+            const { unit, productId } = newProduct;
+            mse.publish.recsItemClick(unit.unitId, productId);
+          };
+          return { ...newProduct, onClick };
+        });
+        return { ...newUnit, products };
+      });
+
+      setUnits(tmpUnits);
+    }
+  }, [data, props.pageType]);
+  return { units, isLoading, error };
+};
+
+export default useRecsTrackingProps;
diff --git a/packages/extensions/venia-product-recommendations/lib/hooks/useSyncMSEToLocalStorage.js b/packages/extensions/venia-product-recommendations/lib/hooks/useSyncMSEToLocalStorage.js
new file mode 100644
index 0000000000..ba2edf8a05
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/hooks/useSyncMSEToLocalStorage.js
@@ -0,0 +1,209 @@
+import { useEffect, useRef } from 'react';
+import {
+  CART_CONTENTS_KEY,
+  PURCHASE_HISTORY_KEY,
+  USER_VIEW_HISTORY_KEY,
+  USER_VIEW_HISTORY_TIME_DECAY_KEY,
+} from '../constants';
+import makeUrl from '@magento/venia-ui/lib/util/makeUrl';
+import useShoppingCartQuery from '@magento/venia-data-collector/lib/hooks/useShoppingCartQuery';
+import { mse } from '@magento/venia-data-collector';
+
+export default () => {
+  const firstLoad = useRef(true);
+  const { data } = useShoppingCartQuery({
+    fetchPolicy: 'cache-first',
+    skip: !firstLoad.current,
+  });
+
+  const cartEventHandler = () => {
+    const shoppingCartContext = mse.context.getShoppingCart();
+    const dsCart = transformData(shoppingCartContext);
+    localStorage.setItem(CART_CONTENTS_KEY, JSON.stringify(dsCart));
+  };
+
+  const handleProductPageView = () => {
+    const product = mse.context.getProduct();
+
+    if (product && product.sku) {
+      const productPageViewContext = {
+        date: new Date().toISOString(),
+        sku: product.sku,
+      };
+      // if sku is not in viewHistorySkus
+      // write to view_history_decay
+      try {
+        let viewHistory = JSON.parse(
+          localStorage.getItem(USER_VIEW_HISTORY_TIME_DECAY_KEY),
+        );
+
+        if (!viewHistory) {
+          const updatedViewHistory = [productPageViewContext];
+          localStorage.setItem(
+            USER_VIEW_HISTORY_TIME_DECAY_KEY,
+            JSON.stringify(updatedViewHistory),
+          );
+        } else {
+          const productIndex = viewHistory.findIndex(
+            viewedProduct => viewedProduct.sku === product.sku,
+          );
+          if (productIndex === -1) {
+            const updatedViewHistory = [...viewHistory, productPageViewContext];
+            localStorage.setItem(
+              USER_VIEW_HISTORY_TIME_DECAY_KEY,
+              JSON.stringify(updatedViewHistory),
+            );
+            // has been viewed before
+          } else if (productIndex >= 0) {
+            // remove current value in viewHistory,
+            // and add the new value
+            viewHistory.splice(productIndex, 1, productPageViewContext);
+            localStorage.setItem(
+              USER_VIEW_HISTORY_TIME_DECAY_KEY,
+              JSON.stringify(viewHistory),
+            );
+          }
+        }
+      } catch (e) {
+        console.error(e);
+      }
+
+      //write to view_history
+      try {
+        let viewHistory = JSON.parse(
+          localStorage.getItem(USER_VIEW_HISTORY_KEY),
+        );
+
+        if (!viewHistory) {
+          const updatedViewHistory = { skus: [product.sku] };
+          localStorage.setItem(
+            USER_VIEW_HISTORY_KEY,
+            JSON.stringify(updatedViewHistory),
+          );
+        } else {
+          const productIndex = viewHistory.skus.findIndex(
+            viewedProduct => viewedProduct === product.sku,
+          );
+          if (productIndex === -1) {
+            const updatedViewHistory = {
+              skus: [...viewHistory.skus, product.sku],
+            };
+            localStorage.setItem(
+              USER_VIEW_HISTORY_KEY,
+              JSON.stringify(updatedViewHistory),
+            );
+            // has been viewed before
+          } else if (productIndex >= 0) {
+            // remove current value in viewHistory,
+            // and add the new value
+            viewHistory.skus.splice(productIndex, 1, product.sku);
+            localStorage.setItem(
+              USER_VIEW_HISTORY_KEY,
+              JSON.stringify(viewHistory),
+            );
+          }
+        }
+      } catch (e) {
+        console.error(e);
+      }
+    }
+  };
+
+  const handlePlaceOrder = event => {
+    if (
+      event.eventInfo &&
+      event.eventInfo.shoppingCartContext &&
+      event.eventInfo.shoppingCartContext.items
+    ) {
+      let { items } = event.eventInfo.shoppingCartContext;
+      items = items.map(item => {
+        return item.product.sku;
+      });
+      const additionalPurchaseHistory = {
+        date: new Date().toISOString(),
+        items,
+      };
+      try {
+        const currentPurchaseHistory = JSON.parse(
+          localStorage.getItem(PURCHASE_HISTORY_KEY),
+        );
+        const newPurchaseHistory = [
+          ...(currentPurchaseHistory ? currentPurchaseHistory : []),
+          additionalPurchaseHistory,
+        ];
+        localStorage.setItem(
+          PURCHASE_HISTORY_KEY,
+          JSON.stringify(newPurchaseHistory),
+        );
+      } catch (e) {
+        console.error(e);
+      }
+    }
+  };
+
+  useEffect(() => {
+    mse.subscribe.removeFromCart(cartEventHandler);
+    mse.subscribe.addToCart(cartEventHandler);
+    mse.subscribe.productPageView(handleProductPageView);
+    mse.subscribe.placeOrder(handlePlaceOrder);
+
+    return () => {
+      mse.unsubscribe.removeFromCart(cartEventHandler);
+      mse.unsubscribe.addToCart(cartEventHandler);
+      mse.unsubscribe.productPageView(handleProductPageView);
+      mse.unsubscribe.placeOrder(handlePlaceOrder);
+    };
+  }, []);
+
+  useEffect(() => {
+    if (data && firstLoad.current) {
+      firstLoad.current = false;
+      const firstLoadCart = transformData(data.cart, 'firstLoad');
+      localStorage.setItem(CART_CONTENTS_KEY, JSON.stringify(firstLoadCart));
+    }
+  }, [data, firstLoad]);
+};
+
+const productTypesMap = new Map([
+  ['SimpleProduct', 'simple'],
+  ['ConfigurableProduct', 'configurable'],
+]);
+
+const transformData = shoppingCart => {
+  let dsCart;
+  if (shoppingCart && shoppingCart.items && shoppingCart.items.length > 0) {
+    dsCart = {
+      cart: {
+        items: shoppingCart.items.map(item => {
+          const { product, prices } = item;
+          return {
+            product_type: productTypesMap.get(product.__typename),
+            item_id: item.id,
+            qty: item.quantity,
+            product_id: product.id,
+            product_name: product.name,
+            product_sku: product.sku,
+            product_url: makeUrl(
+              `${window.location.origin}/${product.url_key}${
+                product.url_suffix
+              }`,
+            ),
+            product_price_value: prices.price.value,
+            product_image: {
+              src: product.thumbnail.url,
+              alt: product.thumbnail.label || '',
+            },
+          };
+        }),
+      },
+    };
+  } else {
+    dsCart = {
+      cart: {
+        items: [],
+      },
+    };
+  }
+
+  return dsCart;
+};
diff --git a/packages/extensions/venia-product-recommendations/lib/index.js b/packages/extensions/venia-product-recommendations/lib/index.js
new file mode 100644
index 0000000000..6a7adb5916
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/index.js
@@ -0,0 +1,20 @@
+export * from './components/VeniaProductRecommendations/VeniaProductRecommendations';
+export * from './hooks/useRecsData';
+
+import {
+  CMS,
+  PRODUCT,
+  PAGEBUILDER,
+  CART,
+  CATEGORY,
+  CHECKOUT,
+} from './constants/pageTypes';
+
+export const PageTypes = {
+  CMS,
+  PRODUCT,
+  PAGEBUILDER,
+  CART,
+  CATEGORY,
+  CHECKOUT,
+};
diff --git a/packages/extensions/venia-product-recommendations/lib/queries/getCart.gql.js b/packages/extensions/venia-product-recommendations/lib/queries/getCart.gql.js
new file mode 100644
index 0000000000..a3ffca24a0
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/queries/getCart.gql.js
@@ -0,0 +1,59 @@
+import { gql } from '@apollo/client';
+
+export const ProductRecommendationsFragment = gql`
+  fragment ProductRecommendationsFragment on Cart {
+    total_quantity
+    prices {
+      subtotal_excluding_tax {
+        value
+      }
+      subtotal_including_tax {
+        value
+      }
+    }
+    id
+    items {
+      id
+      uid
+      ... on ConfigurableCartItem {
+        configured_variant {
+          uid
+          sku
+        }
+      }
+      product {
+        id
+        name
+        url_key
+        url_suffix
+        sku
+        image {
+          url
+        }
+        thumbnail {
+          url
+          label
+        }
+      }
+      prices {
+        price {
+          currency
+          value
+        }
+      }
+      quantity
+    }
+  }
+`;
+
+const GET_CART_QUERY = gql`
+  query GetCart($cartId: String!) {
+    cart(cart_id: $cartId) @connection(key: "Cart") {
+      id
+      ...ProductRecommendationsFragment
+    }
+  }
+  ${ProductRecommendationsFragment}
+`;
+
+export default GET_CART_QUERY;
diff --git a/packages/extensions/venia-product-recommendations/lib/targets/intercept.js b/packages/extensions/venia-product-recommendations/lib/targets/intercept.js
new file mode 100644
index 0000000000..fc0f56fda9
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/targets/intercept.js
@@ -0,0 +1,23 @@
+const myName = '@magento/venia-product-recommendations';
+
+module.exports = targets => {
+  console.log(myName);
+  const builtins = targets.of('@magento/pwa-buildpack');
+  builtins.specialFeatures.tap(flags => {
+    flags[targets.name] = {
+      esModules: true,
+      graphqlQueries: true,
+      cssModules: true,
+    };
+  });
+
+  builtins.envVarDefinitions.tap(() => {
+    targets.of('@magento/peregrine').talons.tap(talons => {
+      talons.App.useApp.wrapWith(`${myName}/lib/wrappers/wrapUseApp`);
+      talons.Gallery.useGalleryItem.wrapWith(
+        `${myName}/lib/wrappers/wrapUseGalleryItem`,
+      );
+      return talons;
+    });
+  });
+};
diff --git a/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseApp.js b/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseApp.js
new file mode 100644
index 0000000000..60255de056
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseApp.js
@@ -0,0 +1,7 @@
+import useSyncMSEToLocalStorage from '../hooks/useSyncMSEToLocalStorage';
+export default function wrapUseApp(origUseApp) {
+  return function(props) {
+    useSyncMSEToLocalStorage();
+    return origUseApp(props);
+  };
+}
diff --git a/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseGalleryItem.js b/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseGalleryItem.js
new file mode 100644
index 0000000000..8e04f16abb
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseGalleryItem.js
@@ -0,0 +1,12 @@
+export default function wrapUseGalleryItem(origUseGalleryItem) {
+  return function(props) {
+    const orig = origUseGalleryItem(props);
+    const handleLinkClick = props.item.onClick
+      ? props.item.onClick
+      : orig.handleLinkClick;
+    return {
+      ...orig,
+      handleLinkClick,
+    };
+  };
+}
diff --git a/packages/extensions/venia-product-recommendations/package.json b/packages/extensions/venia-product-recommendations/package.json
new file mode 100644
index 0000000000..397f332a8b
--- /dev/null
+++ b/packages/extensions/venia-product-recommendations/package.json
@@ -0,0 +1,24 @@
+{
+  "name": "@magento/venia-product-recommendations",
+  "version": "1.0.2",
+  "main": "./lib/index.js",
+  "license": "MIT",
+  "pwa-studio": {
+    "targets": {
+      "intercept": "./lib/targets/intercept.js"
+    }
+  },
+  "files": [
+    "/lib"
+  ],
+  "scripts": {
+    "cm": "cz"
+  },
+ 
+  "dependencies": {
+    "@magento/recommendations-js-sdk": "~2.0.7",
+    "@magento/venia-data-collector": "^1.0.7",
+    "prop-types": "^15.7.2"
+  }
+  
+}
diff --git a/packages/venia-concept/package.json b/packages/venia-concept/package.json
index 06d9067abb..20f7339cdb 100644
--- a/packages/venia-concept/package.json
+++ b/packages/venia-concept/package.json
@@ -35,11 +35,13 @@
   },
   "homepage": "https://github.com/magento/pwa-studio/tree/main/packages/venia-concept#readme",
   "dependencies": {
+    "@magento/experience-platform-connector": "~1.0.9",
     "@magento/pwa-buildpack": "~11.5.3",
     "dompurify": "~3.2.4"
   },
   "devDependencies": {
     "@adobe/apollo-link-mutation-queue": "~1.0.2",
+    "@adobe/magento-storefront-events-sdk": "~1.13.0",
     "@apollo/client": "~3.5.0",
     "@babel/core": "~7.15.0",
     "@babel/plugin-proposal-class-properties": "~7.14.5",
@@ -56,7 +58,10 @@
     "@magento/pagebuilder": "~9.3.3",
     "@magento/peregrine": "~14.5.1",
     "@magento/pwa-theme-venia": "~2.4.0",
+    "@magento/recommendations-js-sdk": "~2.0.7",
     "@magento/upward-security-headers": "~1.0.14",
+    "@magento/venia-data-collector": "~1.0.7",
+    "@magento/venia-product-recommendations": "~1.0.1",
     "@magento/venia-ui": "~11.6.0",
     "@pmmmwh/react-refresh-webpack-plugin": "0.4.1",
     "@storybook/react": "~6.3.7",
diff --git a/yarn.lock b/yarn.lock
index 8ee6efba9a..3e5d575efa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -15,10 +15,6 @@
   integrity sha512-y5B1LcLo1xbUtRZLe4FRGiburzLu6kgY2VgLutgjoz0bpsKFxb21mqJ1axemsTfpJawYEvuP23+No1ud1ZsP2A==
 
 "@adobe/alloy@^2.9.0":
-  version "2.21.1"
-  resolved "https://registry.yarnpkg.com/@adobe/alloy/-/alloy-2.21.1.tgz#fc5fa5e857a94c23ae3d89973944c445a0ff4b6c"
-  integrity sha512-uTrHlf02eRMEDm69o3bSliwxBV4You/5EI6BtmXJEDhCNycy3xu0CfkYTmA0kNVdfNfZ/FYQrPEaajjyhROSfA==
-
   version "2.23.0"
   resolved "https://registry.yarnpkg.com/@adobe/alloy/-/alloy-2.23.0.tgz#9e8baa35a717afd80c09007a0c2a431f927377d4"
   integrity sha512-UFPEtGqlST11kAIxwb8armSBp2urU8G7v+mHa/a2lM5bg/3KI34hbWe0uI69LOgo22a5ErHtRo855Njut7obaA==
@@ -47,7 +43,6 @@
   optionalDependencies:
     "@rollup/rollup-linux-x64-gnu" "^4.21.0"
 
-
 "@adobe/apollo-link-mutation-queue@~1.0.2":
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/@adobe/apollo-link-mutation-queue/-/apollo-link-mutation-queue-1.0.2.tgz#0c589ffb970b9917ba52c38812740c613c0a40da"
@@ -55,6 +50,18 @@
   dependencies:
     apollo-link "~1.2.13"
 
+"@adobe/magento-storefront-event-collector@1.5.3":
+  version "1.5.3"
+  resolved "https://registry.yarnpkg.com/@adobe/magento-storefront-event-collector/-/magento-storefront-event-collector-1.5.3.tgz#3eeb02b1a0fb09db25f2bff1417b4a9a0df86f3f"
+  integrity sha512-XhkqRWGX0L8qSpSbvFNw2IGs317KBNtu/GfsqTEw+j0CKVidT30w7i9M1tpxPxwqvEhGRR4tSHjHBxTvz6BU9Q==
+  dependencies:
+    "@adobe/adobe-client-data-layer" "^2.0.2"
+    "@adobe/alloy" "^2.9.0"
+    "@adobe/magento-storefront-events-sdk" "*"
+    "@snowplow/browser-plugin-link-click-tracking" "^3.14.0"
+    "@snowplow/browser-plugin-performance-timing" "^3.14.0"
+    "@snowplow/browser-tracker" "^3.14.0"
+
 "@adobe/magento-storefront-event-collector@~1.3.1":
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/@adobe/magento-storefront-event-collector/-/magento-storefront-event-collector-1.3.1.tgz#e6aacd90313018738de01e59a2d2ce6a3dffeb2c"
@@ -74,6 +81,20 @@
   dependencies:
     "@adobe/adobe-client-data-layer" "^2.0.2"
 
+"@adobe/magento-storefront-events-sdk@1.5.3":
+  version "1.5.3"
+  resolved "https://registry.yarnpkg.com/@adobe/magento-storefront-events-sdk/-/magento-storefront-events-sdk-1.5.3.tgz#7d7502055b48d80a256a5bf99de8bd3c4befbde4"
+  integrity sha512-RxKZO0MSihepJLqfr6l8y9t7JURwZkykEKmBgKoWiweKZ55jXhSsG3MGfCl7O29G1Fibu7Fh0gouDKBCoSVJFw==
+  dependencies:
+    "@adobe/adobe-client-data-layer" "^2.0.2"
+
+"@adobe/magento-storefront-events-sdk@~1.13.0":
+  version "1.13.0"
+  resolved "https://registry.yarnpkg.com/@adobe/magento-storefront-events-sdk/-/magento-storefront-events-sdk-1.13.0.tgz#14bd1a2f785284f68c09d590599a64f27d01a7ce"
+  integrity sha512-qgdZlFAEWN9dYLq9ZcoNYp2XvDIGyZbh5oHhkAAEUyKq4mjlwYT/rDzSKI/3nOojlI5q7HlDcYxVwD/No9MxDQ==
+  dependencies:
+    "@adobe/adobe-client-data-layer" "^2.0.2"
+
 "@adobe/magento-storefront-events-sdk@~1.3.1":
   version "1.3.1"
   resolved "https://registry.yarnpkg.com/@adobe/magento-storefront-events-sdk/-/magento-storefront-events-sdk-1.3.1.tgz#efe9e5898462ef8d6b360af483919566dfadf69e"
@@ -211,6 +232,7 @@
   dependencies:
     "@babel/highlight" "^7.25.7"
     picocolors "^1.0.0"
+
 "@babel/compat-data@^7.14.7", "@babel/compat-data@^7.16.8", "@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.25.7":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.7.tgz#b8479fe0018ef0ac87b6b7a5c6916fcd67ae2c9c"
@@ -279,7 +301,7 @@
     json5 "^2.1.2"
     semver "^6.3.0"
     source-map "^0.5.0"
-    
+
 "@babel/eslint-parser@^7.25.1":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.25.7.tgz#27b43de786c83cbabbcb328efbb4f099ae85415e"
@@ -405,7 +427,7 @@
   integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==
   dependencies:
     "@babel/types" "^7.24.7"
-    
+
 "@babel/helper-member-expression-to-functions@^7.25.7":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.25.7.tgz#541a33b071f0355a63a0fa4bdf9ac360116b8574"
@@ -489,7 +511,7 @@
   integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==
   dependencies:
     "@babel/types" "^7.24.7"
-    
+
 "@babel/helper-string-parser@^7.25.7":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz#d50e8d37b1176207b4fe9acedec386c565a44a54"
@@ -965,7 +987,7 @@
   integrity sha512-EJN2mKxDwfOUCPxMO6MUI58RN3ganiRAG/MS/S3HfB6QFNjroAMelQo/gybyYq97WerCBAZoyrAoW8Tzdq2jWg==
   dependencies:
     "@babel/helper-plugin-utils" "^7.25.7"
-    
+
 "@babel/plugin-transform-async-generator-functions@^7.25.7":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.7.tgz#af61a02b30d7bff5108c63bd39ac7938403426d7"
@@ -975,7 +997,7 @@
     "@babel/helper-remap-async-to-generator" "^7.25.7"
     "@babel/plugin-syntax-async-generators" "^7.8.4"
     "@babel/traverse" "^7.25.7"
-    
+
 "@babel/plugin-transform-async-to-generator@^7.16.8", "@babel/plugin-transform-async-to-generator@^7.25.7":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.7.tgz#a44c7323f8d4285a6c568dd43c5c361d6367ec52"
@@ -991,7 +1013,7 @@
   integrity sha512-xHttvIM9fvqW+0a3tZlYcZYSBpSWzGBFIt/sYG3tcdSzBB8ZeVgz2gBP7Df+sM0N1850jrviYSSeUuc+135dmQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.25.7"
-    
+
 "@babel/plugin-transform-block-scoping@^7.12.12", "@babel/plugin-transform-block-scoping@^7.16.7", "@babel/plugin-transform-block-scoping@^7.25.7":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.7.tgz#6dab95e98adf780ceef1b1c3ab0e55cd20dd410a"
@@ -1089,7 +1111,7 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.25.7"
     "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
-    
+
 "@babel/plugin-transform-flow-strip-types@^7.25.7":
   version "7.25.7"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.7.tgz#32be871a80e10bbe6d8b1c8a7eeedbbc896d5e80"
@@ -1770,49 +1792,47 @@
   resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
   integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
 
-"@braintree/asset-loader@0.4.4":
-  version "0.4.4"
-  resolved "https://registry.yarnpkg.com/@braintree/asset-loader/-/asset-loader-0.4.4.tgz#9a5eda24c3627bfd5c7f7483cd48f0e411dd2f09"
-  integrity sha512-uVhXC5dydmngmNVuDiKgfXSlz4kv4x5ytIJodI8N5SY16mRh13m/UmbQ7yH+o8DQqp50qPZ45MUHIZkXKPg85w==
-  dependencies:
-    promise-polyfill "^8.1.3"
+"@braintree/asset-loader@2.0.1":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@braintree/asset-loader/-/asset-loader-2.0.1.tgz#7069b5d54e2827b6c1fb4155c4cdc8dc68e00a33"
+  integrity sha512-OGAoBA5MRVsr5qg0sXM6NMJbqHnYZhBudtM6WGgpQnoX42fjUYbE6Y6qFuuerD5z3lsOAjnu80DooBs1VBuh5Q==
 
-"@braintree/browser-detection@1.14.0":
-  version "1.14.0"
-  resolved "https://registry.yarnpkg.com/@braintree/browser-detection/-/browser-detection-1.14.0.tgz#d1b397b00ccbc7cac12f6cec27c0a413d740332a"
-  integrity sha512-OsqU+28RhNvSw8Y5JEiUHUrAyn4OpYazFkjSJe8ZVZfkAaRXQc6hsV38MMEpIlkPMig+A68buk/diY+0O8/dMQ==
+"@braintree/browser-detection@2.0.1":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@braintree/browser-detection/-/browser-detection-2.0.1.tgz#11bc5e1d173c55cd25bda6ad22c6b3ee14ed87a2"
+  integrity sha512-wpRI7AXEUh6o3ILrJbpNOYE7ItfjX/S8JZP7Z5FF66ULngBGYOqE8SeLlLKXG69Nc07HtlL/6nk/h539iz9hcQ==
 
 "@braintree/browser-detection@^1.12.1":
   version "1.17.2"
   resolved "https://registry.yarnpkg.com/@braintree/browser-detection/-/browser-detection-1.17.2.tgz#bf4edf97a90897aaa0956869316e50be0c4fbcb5"
   integrity sha512-DdEX09uYs6kHwGt4cbONlxlta/0hfmrDUncP6EtfZxFVywNF9LeRUyon+2LihJTbqgSnGqz9ZL450hkqBd6oSw==
 
-"@braintree/class-list@0.2.0":
-  version "0.2.0"
-  resolved "https://registry.yarnpkg.com/@braintree/class-list/-/class-list-0.2.0.tgz#4c4352ac19c262f61526f93d07d248244b399ec4"
-  integrity sha512-iLXJT51jnBFuGvyTAQqZ2uwyEVwdyapyz52F5MK1Uoh2ZOiPJ5hoqI0wncyCP2KfqrgyCpOkkEaLMLb/94unGA==
-
 "@braintree/event-emitter@0.4.1":
   version "0.4.1"
   resolved "https://registry.yarnpkg.com/@braintree/event-emitter/-/event-emitter-0.4.1.tgz#204eaad8cf84eb7bf81fb288a359d34eda85a396"
   integrity sha512-X41357O3OXUDlnwMvS1m0GQEn3zB3s3flOBeg2J5OBvLvdJEIAVpPkblABPtsPrlciDSvfv1aSG5ixHPgFH0Zg==
 
-"@braintree/extended-promise@0.4.1":
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/@braintree/extended-promise/-/extended-promise-0.4.1.tgz#b44f8e6236ddb43434be11924f00fa69f8782a36"
-  integrity sha512-00n7m4z+swWHoFQLHLvrIBIEoxnGUBsl3ogvX79ITpcn8CHczDwtxYy5+RhMoAraRdfN3oB+8QIpN3KOxs2Q7w==
+"@braintree/extended-promise@1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@braintree/extended-promise/-/extended-promise-1.0.0.tgz#b9c52cf93b4935c429053b16e1152549df0696a7"
+  integrity sha512-E9529FJNG4OgeeLJ00vNs3TW67+AeSQobJg0hwfsQk29hgK4bVBsvQHVD4nwDuDD1Czon90K88gfQIFadAMs0w==
 
-"@braintree/iframer@1.1.0":
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/@braintree/iframer/-/iframer-1.1.0.tgz#7e59b975c2a48bd92616f653367a5214fc2ddd4b"
-  integrity sha512-tVpr7U6u6bqeQlHreEjYMNtnHX62vLnNWziY2kQLqkWhvusPuY5DfuGEIPpWqsd+V/a1slyTQaxK6HWTlH6A/Q==
+"@braintree/iframer@2.0.0":
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/@braintree/iframer/-/iframer-2.0.0.tgz#bbba9cfd62b701b8cb1f779dffcad6295aed9d23"
+  integrity sha512-x1kHOyIJNDvi4P1s6pVBZhqhBa1hqDG9+yzcsCR1oNVC0LxH9CAP8bKxioT8/auY1sUyy+D8T4Vp/jv7QqSqLQ==
 
-"@braintree/sanitize-url@6.0.0":
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-6.0.0.tgz#fe364f025ba74f6de6c837a84ef44bdb1d61e68f"
-  integrity sha512-mgmE7XBYY/21erpzhexk4Cj1cyTQ9LzvnTxtzM17BJ7ERMNE6W72mQRo0I1Ud8eFJ+RVVIcBNhLFZ3GX4XFz5w==
+"@braintree/sanitize-url@7.0.4":
+  version "7.0.4"
+  resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-7.0.4.tgz#a7ddd6d55dfb89e341f5684c9717ee24fef62301"
+  integrity sha512-hPYRrKFoI+nuckPgDJfyYAkybFvheo4usS0Vw0HNAe+fmGBQA5Az37b/yStO284atBoqqdOUhKJ3d9Zw3PQkcQ==
+
+"@braintree/uuid@1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@braintree/uuid/-/uuid-1.0.0.tgz#4fda394ceae803a8129e673c29ccf6c5e2014360"
+  integrity sha512-AtI5hfttWSuWAgcwLUZdcZ7Fp/8jCCUf9JTs7+Xow9ditU28zuoBovqq083yph2m3SxPYb84lGjOq+cXlXBvJg==
 
-"@braintree/uuid@0.1.0", "@braintree/uuid@^0.1.0":
+"@braintree/uuid@^0.1.0":
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/@braintree/uuid/-/uuid-0.1.0.tgz#ab9355015a7fb0e25cf3c2ff9cd32ece8ea304b0"
   integrity sha512-YvZJdlNcK5EnR+7M8AjgEAf4Qx696+FOSYlPfy5ePn80vODtVAUU0FxHnzKZC0og1VbDNQDDiwhthR65D4Na0g==
@@ -3148,6 +3168,29 @@
   resolved "https://registry.yarnpkg.com/@magento/eslint-config/-/eslint-config-1.5.3.tgz#7a9e54386fc255a9df359176ddfa0ce1f4ba37df"
   integrity sha512-nCmdeIFbadK6gLvQ/g3OXXuK8JBrp0Hxt8CZD1uj+824X51jr6W7xRKGabmeNKXifnYXFk0wfu+eeMq86PA3jA==
 
+"@magento/recommendations-js-sdk@~2.0.7":
+  version "2.0.7"
+  resolved "https://registry.yarnpkg.com/@magento/recommendations-js-sdk/-/recommendations-js-sdk-2.0.7.tgz#8dff681ddd4484b72cd7418aa342c32aa8014237"
+  integrity sha512-e5lfcdHze3+IU7Cei7i0B25OBcpfLDO32zbimAXwG34Auoaw0MYgSt3oGFPqGxil78/qvQxkLSOfQ/nfBBZr6g==
+  dependencies:
+    isomorphic-unfetch "^3.1.0"
+    mustache "^4.1.0"
+    uuid "^8.3.2"
+
+"@magento/upward-security-headers@~1.0.14":
+  version "1.0.17"
+  resolved "https://registry.yarnpkg.com/@magento/upward-security-headers/-/upward-security-headers-1.0.17.tgz#2f82faf570b5d2a520830d636707788b2971aefd"
+  integrity sha512-/9ag9fj/N2WA0guJXxzvdq6gMHo1Vp6JPSsTufYsMVlZvmEMjBuxc+gdBjQkCErjOharJUAWGCnyPRLqWg1pWg==
+
+"@magento/venia-data-collector@^1.0.7", "@magento/venia-data-collector@~1.0.7":
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/@magento/venia-data-collector/-/venia-data-collector-1.0.7.tgz#00f1c9dbe081f2467c65d713edcf53059f0c3e32"
+  integrity sha512-4z/p4gg3dyRs6DTHleM0BzPFNj1gXLFDta642PCJgQQPJZPm8KNmvq1ccRi60gMfpmLtajfujvnQ7vngMILs9w==
+  dependencies:
+    "@adobe/magento-storefront-event-collector" "1.5.3"
+    "@adobe/magento-storefront-events-sdk" "1.5.3"
+    prop-types "^15.7.2"
+
 "@mdx-js/mdx@^1.6.22":
   version "1.6.22"
   resolved "https://registry.yarnpkg.com/@mdx-js/mdx/-/mdx-1.6.22.tgz#8a723157bf90e78f17dc0f27995398e6c731f1ba"
@@ -4304,6 +4347,15 @@
     "@snowplow/tracker-core" "3.24.4"
     tslib "^2.3.1"
 
+"@snowplow/browser-plugin-link-click-tracking@^3.14.0":
+  version "3.24.6"
+  resolved "https://registry.yarnpkg.com/@snowplow/browser-plugin-link-click-tracking/-/browser-plugin-link-click-tracking-3.24.6.tgz#92fb2a189ecdac1ed3cc40e128e51a83c636397d"
+  integrity sha512-suE6e1KpsextQjSVswyq07PyafinJNfSR1IDB0TcwW45EX0gUgEGa8CmEdlYbIZfH83ftha6J6tX+Q4l9a5UFw==
+  dependencies:
+    "@snowplow/browser-tracker-core" "3.24.6"
+    "@snowplow/tracker-core" "3.24.6"
+    tslib "^2.3.1"
+
 "@snowplow/browser-plugin-performance-timing@^3.0.1":
   version "3.24.4"
   resolved "https://registry.yarnpkg.com/@snowplow/browser-plugin-performance-timing/-/browser-plugin-performance-timing-3.24.4.tgz#8c62256105e9d68b8523620c077f30cd205a499a"
@@ -4313,6 +4365,15 @@
     "@snowplow/tracker-core" "3.24.4"
     tslib "^2.3.1"
 
+"@snowplow/browser-plugin-performance-timing@^3.14.0":
+  version "3.24.6"
+  resolved "https://registry.yarnpkg.com/@snowplow/browser-plugin-performance-timing/-/browser-plugin-performance-timing-3.24.6.tgz#f856f7fa4a6b656332913d8b7ee8c6f299a2c61f"
+  integrity sha512-S48nOdEXMy/P0TJTGV/Gw+1eAqdzElFBIU7bk28g6UVYwRxXqu9QP+voH8/RaeMXHP4CoK3nuNoeC2A3TEmb8w==
+  dependencies:
+    "@snowplow/browser-tracker-core" "3.24.6"
+    "@snowplow/tracker-core" "3.24.6"
+    tslib "^2.3.1"
+
 "@snowplow/browser-tracker-core@3.24.4":
   version "3.24.4"
   resolved "https://registry.yarnpkg.com/@snowplow/browser-tracker-core/-/browser-tracker-core-3.24.4.tgz#07134b2334bf97a2a86d27a35bdae7899f661c08"
@@ -4323,6 +4384,16 @@
     tslib "^2.3.1"
     uuid "^3.4.0"
 
+"@snowplow/browser-tracker-core@3.24.6":
+  version "3.24.6"
+  resolved "https://registry.yarnpkg.com/@snowplow/browser-tracker-core/-/browser-tracker-core-3.24.6.tgz#d48a8011313d022233dbe39b19638713ba3418ad"
+  integrity sha512-rXBAmeZJcqRrn/ewm4yPmP8FfZ7uSiQv1E8Dcf41uE4NrY77SuyNMf61c+ppMtV1QyaIvOZnJvVNywwp52i0mQ==
+  dependencies:
+    "@snowplow/tracker-core" "3.24.6"
+    sha1 "^1.1.1"
+    tslib "^2.3.1"
+    uuid "^3.4.0"
+
 "@snowplow/browser-tracker@^3.0.1":
   version "3.24.4"
   resolved "https://registry.yarnpkg.com/@snowplow/browser-tracker/-/browser-tracker-3.24.4.tgz#c1718bea451aab7faba959caad41b634e9cfacb5"
@@ -4332,6 +4403,15 @@
     "@snowplow/tracker-core" "3.24.4"
     tslib "^2.3.1"
 
+"@snowplow/browser-tracker@^3.14.0":
+  version "3.24.6"
+  resolved "https://registry.yarnpkg.com/@snowplow/browser-tracker/-/browser-tracker-3.24.6.tgz#5df62472e90f664466596ca158011a78a5b6f446"
+  integrity sha512-QTqziA9GWi8OU7zzPtJSj4XU85G7ELHsCjUwni+zUUHBf/WSRAYsVqeG6eTEhoEe9LUmc6CaQ+5GrRyez+HZvw==
+  dependencies:
+    "@snowplow/browser-tracker-core" "3.24.6"
+    "@snowplow/tracker-core" "3.24.6"
+    tslib "^2.3.1"
+
 "@snowplow/tracker-core@3.24.4":
   version "3.24.4"
   resolved "https://registry.yarnpkg.com/@snowplow/tracker-core/-/tracker-core-3.24.4.tgz#64a415bf61472d7f42106b5727538feb6fb54e1f"
@@ -4340,6 +4420,14 @@
     tslib "^2.3.1"
     uuid "^3.4.0"
 
+"@snowplow/tracker-core@3.24.6":
+  version "3.24.6"
+  resolved "https://registry.yarnpkg.com/@snowplow/tracker-core/-/tracker-core-3.24.6.tgz#d20f914c3936125afc43018b11ebc89d1405cfca"
+  integrity sha512-nTpRj42OUkrpJQdbg8a74fZmkb3oSWfqjVc6NiOsAN4kcYmhU/mPt2S1C4tpYP/Qm2c77/8JGQq9sZL6eA77vw==
+  dependencies:
+    tslib "^2.3.1"
+    uuid "^3.4.0"
+
 "@storybook/addons@6.3.13":
   version "6.3.13"
   resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.3.13.tgz#b9f7507210327fb54efca235e2ef90b6a652203f"
@@ -5326,7 +5414,7 @@
   resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.0.33.tgz#1073c4bc824754ae3d10cfab88ab0237ba964e4d"
   integrity sha512-gVC1InwyVrO326wbBZw+AO3u2vRXz/iRWq9jYhpG4W8LXyIgDv3ZmcLQ5Q4Gs+gFMyqx+viFoFT+l3p61QFCmQ==
 
-"@types/trusted-types@^2.0.2":
+"@types/trusted-types@^2.0.2", "@types/trusted-types@^2.0.7":
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
   integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==
@@ -6842,38 +6930,35 @@ braces@^3.0.3, braces@~3.0.2:
   dependencies:
     fill-range "^7.1.1"
 
-braintree-web-drop-in@~1.33.1, braintree-web-drop-in@~1.33.3:
-  version "1.33.7"
-  resolved "https://registry.yarnpkg.com/braintree-web-drop-in/-/braintree-web-drop-in-1.33.7.tgz#16cd3b00ab768437e2de91f2f9f307214f83891b"
-  integrity sha512-69wtLGbFh1jbgkUCyES598OwSsTljwLn33f9bsl2JRLlbvcW6emMazdyG95k3Am5kyh04Aa1Qx0RO3Yx81U/Jg==
+braintree-web-drop-in@~1.43.0:
+  version "1.43.0"
+  resolved "https://registry.yarnpkg.com/braintree-web-drop-in/-/braintree-web-drop-in-1.43.0.tgz#d67744fd224401a66abdc87ca8c75998eb458b61"
+  integrity sha512-lkUpQfYXR0CGtR7mPRR17AnZoYkHjhycxVnMGIPcWT6JPagEZcG/7tYyy34iWjYZeGa2wsquLBDV2Xeita962Q==
   dependencies:
-    "@braintree/asset-loader" "0.4.4"
-    "@braintree/browser-detection" "1.14.0"
-    "@braintree/class-list" "0.2.0"
+    "@braintree/asset-loader" "2.0.1"
+    "@braintree/browser-detection" "2.0.1"
     "@braintree/event-emitter" "0.4.1"
-    "@braintree/uuid" "0.1.0"
+    "@braintree/uuid" "1.0.0"
     "@braintree/wrap-promise" "2.1.0"
-    braintree-web "3.88.3"
-    promise-polyfill "8.2.3"
+    braintree-web "3.103.0"
 
-braintree-web@3.88.3:
-  version "3.88.3"
-  resolved "https://registry.yarnpkg.com/braintree-web/-/braintree-web-3.88.3.tgz#17e19b7b77b18de659f18f41b7842f880bdde265"
-  integrity sha512-+RM22/OeErWGJWoB3CYW8Od3QDaLLc3ZBEktKL3byqMabaHgzgJqHGrHVmqKNAy+ML+TCY15IQGM71U0N4pvqw==
+braintree-web@3.103.0:
+  version "3.103.0"
+  resolved "https://registry.yarnpkg.com/braintree-web/-/braintree-web-3.103.0.tgz#2b965d788038d71cb0d9b8897c63e9427ed902df"
+  integrity sha512-gwmC5LSUP5VUC2HmUyaFnEyLjRRAo1iKKHS5eD9KIAZHB7cAQ2il1V1q2f5zdz7+7EE11eSHXznj6n/Qm6jp6w==
   dependencies:
-    "@braintree/asset-loader" "0.4.4"
-    "@braintree/browser-detection" "1.14.0"
-    "@braintree/class-list" "0.2.0"
+    "@braintree/asset-loader" "2.0.1"
+    "@braintree/browser-detection" "2.0.1"
     "@braintree/event-emitter" "0.4.1"
-    "@braintree/extended-promise" "0.4.1"
-    "@braintree/iframer" "1.1.0"
-    "@braintree/sanitize-url" "6.0.0"
-    "@braintree/uuid" "0.1.0"
+    "@braintree/extended-promise" "1.0.0"
+    "@braintree/iframer" "2.0.0"
+    "@braintree/sanitize-url" "7.0.4"
+    "@braintree/uuid" "1.0.0"
     "@braintree/wrap-promise" "2.1.0"
-    card-validator "8.1.1"
-    credit-card-type "9.1.0"
-    framebus "5.2.0"
-    inject-stylesheet "5.0.0"
+    card-validator "10.0.0"
+    credit-card-type "10.0.1"
+    framebus "6.0.0"
+    inject-stylesheet "6.0.1"
     promise-polyfill "8.2.3"
     restricted-input "3.0.5"
 
@@ -7027,7 +7112,7 @@ buffer@^5.5.0, buffer@^5.7.0:
     base64-js "^1.3.1"
     ieee754 "^1.1.13"
 
-builtin-modules@^3.1.0, builtin-modules@^3.3.0:
+builtin-modules@^3.1.0:
   version "3.3.0"
   resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6"
   integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
@@ -7296,10 +7381,10 @@ capture-exit@^2.0.0:
   dependencies:
     rsvp "^4.8.4"
 
-card-validator@8.1.1:
-  version "8.1.1"
-  resolved "https://registry.yarnpkg.com/card-validator/-/card-validator-8.1.1.tgz#418f5f32435553fb9ca2a02634ad413bb38697a9"
-  integrity sha512-cN4FsKwoTfTFnqPwVc7TQLSsH/QMDB3n/gWm0XelcApz4sKipnOQ6k33sa3bWsNnnIpgs7eXOF+mUV2UQAX2Sw==
+card-validator@10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/card-validator/-/card-validator-10.0.0.tgz#288a5525cae765566c4ff7d6e841d8e1ae63cd4c"
+  integrity sha512-2fLyCBOxO7/b56sxoYav8FeJqv9bWpZSyKq8sXKxnpxTGXHnM/0c8WEKG+ZJ+OXFcabnl98pD0EKBtTn+Tql0g==
   dependencies:
     credit-card-type "^9.1.0"
 
@@ -7982,7 +8067,7 @@ copy-webpack-plugin@~6.4.0:
     schema-utils "^3.0.0"
     serialize-javascript "^5.0.1"
     webpack-sources "^1.4.3"
-    
+
 core-js-compat@^3.20.2, core-js-compat@^3.21.0, core-js-compat@^3.38.0, core-js-compat@^3.38.1, core-js-compat@^3.8.1:
   version "3.38.1"
   resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.38.1.tgz#2bc7a298746ca5a7bcb9c164bcb120f2ebc09a09"
@@ -8172,7 +8257,12 @@ create-require@^1.1.0:
   resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333"
   integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
 
-credit-card-type@9.1.0, credit-card-type@^9.1.0:
+credit-card-type@10.0.1:
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/credit-card-type/-/credit-card-type-10.0.1.tgz#3464309395a9942f0f9768481645b696f9f7c58a"
+  integrity sha512-vQOuWmBgsgG1ovGeDi8m6Zeu1JaqH/JncrxKmaqMbv/LunyOQdLiQhPHtOsNlbUI05TocR5nod/Mbs3HYtr6sQ==
+
+credit-card-type@^9.1.0:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/credit-card-type/-/credit-card-type-9.1.0.tgz#54dd96c93b6579623e9c8656e6798fc2b93f5f05"
   integrity sha512-CpNFuLxiPFxuZqhSKml3M+t0K/484pMAnfYWH14JoD7OZMnmC0Lmo+P7JX9SobqFpRoo7ifA18kOHdxJywYPEA==
@@ -8944,6 +9034,13 @@ dompurify@^2.3.8:
   resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.5.8.tgz#2809d89d7e528dc7a071dea440d7376df676f824"
   integrity sha512-o1vSNgrmYMQObbSSvF/1brBYEQPHhV1+gsmrusO7/GXtp1T9rCS8cXFqVxK/9crT1jA6Ccv+5MTSjBNqr7Sovw==
 
+dompurify@~3.2.4:
+  version "3.2.5"
+  resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.5.tgz#11b108656a5fb72b24d916df17a1421663d7129c"
+  integrity sha512-mLPd29uoRe9HpvwP2TxClGQBzGXeEC/we/q+bFlmPPmj2p2Ugl3r6ATu/UU1v77DXNcehiBg9zsr1dREyA/dJQ==
+  optionalDependencies:
+    "@types/trusted-types" "^2.0.7"
+
 domutils@^2.5.2, domutils@^2.8.0:
   version "2.8.0"
   resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
@@ -9649,11 +9746,6 @@ estree-to-babel@^3.1.0:
     "@babel/types" "^7.2.0"
     c8 "^7.6.0"
 
-estree-walker@^0.6.1:
-  version "0.6.1"
-  resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
-  integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
-
 estree-walker@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
@@ -9945,7 +10037,7 @@ fast-glob@^2.2.6:
     merge2 "^1.2.3"
     micromatch "^3.1.10"
 
-fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.5, fast-glob@^3.2.9, fast-glob@^3.3.0:
+fast-glob@^3.1.1, fast-glob@^3.2.4, fast-glob@^3.2.9, fast-glob@^3.3.0:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
   integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@@ -10449,10 +10541,10 @@ fragment-cache@^0.2.1:
   dependencies:
     map-cache "^0.2.2"
 
-framebus@5.2.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/framebus/-/framebus-5.2.0.tgz#a1689e8bbd5abf3ae7af8b1139658bb66d808e62"
-  integrity sha512-hIKt71vBVd/g0emUbuVg8HAeHEjxBwhAE87CKXvxPIy0sCoGWqBulB1k9lWBWUU6ZHXPs0xjXWMwUldWMiqD6A==
+framebus@6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/framebus/-/framebus-6.0.0.tgz#4ebafaf4d78441fdb1f6c55cb9a6ea9f72c55cff"
+  integrity sha512-bL9V68hVaVBCY9rveoWbPFFI9hAXIJtESs51B+9XmzvMt38+wP8b4VdiJsavjMS6NfPZ/afQ/jc2qaHmSGI1kQ==
   dependencies:
     "@braintree/uuid" "^0.1.0"
 
@@ -11827,10 +11919,10 @@ ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
   resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
   integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
 
-inject-stylesheet@5.0.0:
-  version "5.0.0"
-  resolved "https://registry.yarnpkg.com/inject-stylesheet/-/inject-stylesheet-5.0.0.tgz#bb34acf05ca6ed86e5763d886cd6c9b19f360ab1"
-  integrity sha512-GzncrJP8E/pavMQzoO93CXoYCfTttwVm2cX2TyXJdgtVE0cCvWSFCn1/uMsM6ZkEg7LUsOcKuamcLiGWlv2p9A==
+inject-stylesheet@6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/inject-stylesheet/-/inject-stylesheet-6.0.1.tgz#ab724474ac300684875e8980d1285cb4e99b33ae"
+  integrity sha512-2fvune1D4+8mvJoLVo95ncY4HrDkIaYIReRzXv8tkWFgdG9iuc5QuX57gtSDPWTWQI/f5BGwwtH85wxHouzucg==
 
 inline-style-parser@0.1.1:
   version "0.1.1"
@@ -11891,24 +11983,6 @@ inquirer@^8.2.0:
     through "^2.3.6"
     wrap-ansi "^6.0.1"
 
-inquirer@^9.2.23:
-  version "9.3.6"
-  resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.3.6.tgz#670f1e9408743c3ed23df576f94fe5369f353055"
-  integrity sha512-riK/iQB2ctwkpWYgjjWIRv3MBLt2gzb2Sj0JNQNbyTXgyXsLWcDPJ5WS5ZDTCx7BRFnJsARtYh+58fjP5M2Y0Q==
-  dependencies:
-    "@inquirer/figures" "^1.0.3"
-    ansi-escapes "^4.3.2"
-    cli-width "^4.1.0"
-    external-editor "^3.1.0"
-    mute-stream "1.0.0"
-    ora "^5.4.1"
-    run-async "^3.0.0"
-    rxjs "^7.8.1"
-    string-width "^4.2.3"
-    strip-ansi "^6.0.1"
-    wrap-ansi "^6.2.0"
-    yoctocolors-cjs "^2.1.2"
-
 internal-ip@^4.2.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"
@@ -12077,13 +12151,6 @@ is-buffer@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191"
   integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==
 
-is-builtin-module@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169"
-  integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==
-  dependencies:
-    builtin-modules "^3.3.0"
-
 is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
   version "1.2.7"
   resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
@@ -12545,6 +12612,14 @@ isomorphic-fetch@^3.0.0:
     node-fetch "^2.6.1"
     whatwg-fetch "^3.4.1"
 
+isomorphic-unfetch@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz#87341d5f4f7b63843d468438128cb087b7c3e98f"
+  integrity sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==
+  dependencies:
+    node-fetch "^2.6.1"
+    unfetch "^4.2.0"
+
 isomorphic-ws@4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz#55fd4cd6c5e6491e76dc125938dd863f5cd4f2dc"
@@ -12604,17 +12679,6 @@ istanbul-lib-instrument@^5.0.4:
     istanbul-lib-coverage "^3.2.0"
     semver "^6.3.0"
 
-istanbul-lib-instrument@^6.0.1:
-  version "6.0.3"
-  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765"
-  integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==
-  dependencies:
-    "@babel/core" "^7.23.9"
-    "@babel/parser" "^7.23.9"
-    "@istanbuljs/schema" "^0.1.3"
-    istanbul-lib-coverage "^3.2.0"
-    semver "^7.5.4"
-
 istanbul-lib-report@^3.0.0:
   version "3.0.1"
   resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz#908305bac9a5bd175ac6a74489eafd0fc2445a7d"
@@ -14467,6 +14531,11 @@ multicast-dns@^6.0.1:
     dns-packet "^1.3.1"
     thunky "^1.0.2"
 
+mustache@^4.1.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64"
+  integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==
+
 mute-stream@0.0.8:
   version "0.0.8"
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
@@ -16174,11 +16243,6 @@ promise-polyfill@^7.1.1:
   resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-7.1.2.tgz#ab05301d8c28536301622d69227632269a70ca3b"
   integrity sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==
 
-promise-polyfill@^8.1.3:
-  version "8.3.0"
-  resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
-  integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
-
 promise.allsettled@^1.0.0:
   version "1.0.7"
   resolved "https://registry.yarnpkg.com/promise.allsettled/-/promise.allsettled-1.0.7.tgz#b9dd51e9cffe496243f5271515652c468865f2d8"
@@ -17352,13 +17416,6 @@ rollup-plugin-terser@^7.0.0:
     serialize-javascript "^4.0.0"
     terser "^5.0.0"
 
-rollup-pluginutils@^2.8.2:
-  version "2.8.2"
-  resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
-  integrity sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==
-  dependencies:
-    estree-walker "^0.6.1"
-
 rollup@^2.43.1:
   version "2.79.2"
   resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.2.tgz#f150e4a5db4b121a21a747d762f701e5e9f49090"
@@ -17391,31 +17448,6 @@ rollup@^4.21.0:
     "@rollup/rollup-win32-x64-msvc" "4.24.0"
     fsevents "~2.3.2"
 
-rollup@^4.18.0:
-  version "4.20.0"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.20.0.tgz#f9d602161d29e178f0bf1d9f35f0a26f83939492"
-  integrity sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==
-  dependencies:
-    "@types/estree" "1.0.5"
-  optionalDependencies:
-    "@rollup/rollup-android-arm-eabi" "4.20.0"
-    "@rollup/rollup-android-arm64" "4.20.0"
-    "@rollup/rollup-darwin-arm64" "4.20.0"
-    "@rollup/rollup-darwin-x64" "4.20.0"
-    "@rollup/rollup-linux-arm-gnueabihf" "4.20.0"
-    "@rollup/rollup-linux-arm-musleabihf" "4.20.0"
-    "@rollup/rollup-linux-arm64-gnu" "4.20.0"
-    "@rollup/rollup-linux-arm64-musl" "4.20.0"
-    "@rollup/rollup-linux-powerpc64le-gnu" "4.20.0"
-    "@rollup/rollup-linux-riscv64-gnu" "4.20.0"
-    "@rollup/rollup-linux-s390x-gnu" "4.20.0"
-    "@rollup/rollup-linux-x64-gnu" "4.20.0"
-    "@rollup/rollup-linux-x64-musl" "4.20.0"
-    "@rollup/rollup-win32-arm64-msvc" "4.20.0"
-    "@rollup/rollup-win32-ia32-msvc" "4.20.0"
-    "@rollup/rollup-win32-x64-msvc" "4.20.0"
-    fsevents "~2.3.2"
-
 rsvp@^4.8.4:
   version "4.8.5"
   resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
@@ -18171,20 +18203,6 @@ spdx-satisfies@~5.0.1:
     spdx-expression-parse "^3.0.0"
     spdx-ranges "^2.0.0"
 
-spdx-ranges@^2.0.0:
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/spdx-ranges/-/spdx-ranges-2.1.1.tgz#87573927ba51e92b3f4550ab60bfc83dd07bac20"
-  integrity sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==
-
-spdx-satisfies@~5.0.1:
-  version "5.0.1"
-  resolved "https://registry.yarnpkg.com/spdx-satisfies/-/spdx-satisfies-5.0.1.tgz#9feeb2524686c08e5f7933c16248d4fdf07ed6a6"
-  integrity sha512-Nwor6W6gzFp8XX4neaKQ7ChV4wmpSh2sSDemMFSzHxpTw460jxFYeOn+jq4ybnSSw/5sc3pjka9MQPouksQNpw==
-  dependencies:
-    spdx-compare "^1.0.0"
-    spdx-expression-parse "^3.0.0"
-    spdx-ranges "^2.0.0"
-
 spdy-transport@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31"
@@ -18400,7 +18418,7 @@ string-length@^4.0.1:
     char-regex "^1.0.2"
     strip-ansi "^6.0.0"
 
-"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -18418,6 +18436,15 @@ string-width@^1.0.1:
     is-fullwidth-code-point "^1.0.0"
     strip-ansi "^3.0.0"
 
+"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
 string-width@^2.0.0, string-width@^2.1.1:
   version "2.1.1"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
@@ -18533,7 +18560,7 @@ stringify-object@^3.3.0:
     is-obj "^1.0.1"
     is-regexp "^1.0.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -18568,6 +18595,13 @@ strip-ansi@^5.2.0:
   dependencies:
     ansi-regex "^4.1.0"
 
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
 strip-ansi@^7.0.1:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45"
@@ -20684,7 +20718,7 @@ worker-rpc@^0.1.0:
   dependencies:
     microevent.ts "~0.1.1"
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -20710,6 +20744,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0:
     string-width "^4.1.0"
     strip-ansi "^6.0.0"
 
+wrap-ansi@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
 wrap-ansi@^8.1.0:
   version "8.1.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"

From ee210bf73993a101bb480cd039e3e8eaa25d3913 Mon Sep 17 00:00:00 2001
From: glo82145 <glo82145@adobe.com>
Date: Wed, 16 Apr 2025 15:47:24 +0530
Subject: [PATCH 2/4] PWA-3318

---
 .../lib/components/Gallery/Gallery.js         |  74 ++--
 .../lib/components/Gallery/gallery.css        |  32 +-
 .../lib/components/Gallery/item.css           |   2 +-
 .../ProductRecommendations.css                |  12 +-
 .../VeniaProductRecommendations.js            | 193 ++++-----
 .../lib/hooks/useObserver.js                  |  62 +--
 .../lib/hooks/useRecsData.js                  | 172 ++++----
 .../lib/hooks/useRecsTrackingProps.js         |  46 +--
 .../lib/hooks/useSyncMSEToLocalStorage.js     | 374 +++++++++---------
 .../lib/index.js                              |  24 +-
 .../lib/queries/getCart.gql.js                |  90 ++---
 .../lib/targets/intercept.js                  |  34 +-
 .../lib/wrappers/wrapUseApp.js                |   8 +-
 .../lib/wrappers/wrapUseGalleryItem.js        |  18 +-
 .../package.json                              |  17 +-
 15 files changed, 585 insertions(+), 573 deletions(-)

diff --git a/packages/extensions/venia-product-recommendations/lib/components/Gallery/Gallery.js b/packages/extensions/venia-product-recommendations/lib/components/Gallery/Gallery.js
index f66d7dc3d5..b6cdd203e4 100644
--- a/packages/extensions/venia-product-recommendations/lib/components/Gallery/Gallery.js
+++ b/packages/extensions/venia-product-recommendations/lib/components/Gallery/Gallery.js
@@ -13,47 +13,47 @@ import defaultItemClasses from '!!style-loader!css-loader?modules!./item.css';
  * @params {Array} props.items an array of items to render
  */
 export const Gallery = props => {
-  const galleryClasses = mergeClasses(
-    defaultGalleryClasses,
-    props.galleryClasses,
-  );
-  const itemClasses = mergeClasses(defaultItemClasses, props.itemClasses);
+    const galleryClasses = mergeClasses(
+        defaultGalleryClasses,
+        props.galleryClasses
+    );
+    const itemClasses = mergeClasses(defaultItemClasses, props.itemClasses);
 
-  const { items } = props;
+    const { items } = props;
 
-  const galleryItems = items.map((item, index) => {
-    if (item === null) {
-      return <GalleryItem key={index} />;
-    }
-    return <GalleryItem key={item.id} item={item} classes={itemClasses} />;
-  });
+    const galleryItems = items.map((item, index) => {
+        if (item === null) {
+            return <GalleryItem key={index} />;
+        }
+        return <GalleryItem key={item.id} item={item} classes={itemClasses} />;
+    });
 
-  return (
-    <div className={galleryClasses.root}>
-      <div className={galleryClasses.items}>{galleryItems}</div>
-    </div>
-  );
+    return (
+        <div className={galleryClasses.root}>
+            <div className={galleryClasses.items}>{galleryItems}</div>
+        </div>
+    );
 };
 
 Gallery.propTypes = {
-  galleryClasses: shape({
-    filters: string,
-    items: string,
-    root: string,
-  }),
-  itemClasses: shape({
-    image: string,
-    imageContainer: string,
-    imagePlaceholder: string,
-    image_pending: string,
-    images: string,
-    images_pending: string,
-    name: string,
-    name_pending: string,
-    price: string,
-    price_pending: string,
-    root: string,
-    root_pending: string,
-  }),
-  items: array.isRequired,
+    galleryClasses: shape({
+        filters: string,
+        items: string,
+        root: string
+    }),
+    itemClasses: shape({
+        image: string,
+        imageContainer: string,
+        imagePlaceholder: string,
+        image_pending: string,
+        images: string,
+        images_pending: string,
+        name: string,
+        name_pending: string,
+        price: string,
+        price_pending: string,
+        root: string,
+        root_pending: string
+    }),
+    items: array.isRequired
 };
diff --git a/packages/extensions/venia-product-recommendations/lib/components/Gallery/gallery.css b/packages/extensions/venia-product-recommendations/lib/components/Gallery/gallery.css
index abd4f12a39..b6570083a5 100644
--- a/packages/extensions/venia-product-recommendations/lib/components/Gallery/gallery.css
+++ b/packages/extensions/venia-product-recommendations/lib/components/Gallery/gallery.css
@@ -1,24 +1,24 @@
 .root {
-  display: grid;
-  grid-template-areas:
-    'actions'
-    'items';
-  grid-template-columns: 1fr;
-  line-height: 1;
+    display: grid;
+    grid-template-areas:
+        'actions'
+        'items';
+    grid-template-columns: 1fr;
+    line-height: 1;
 }
 
 .items {
-  grid-template-columns: repeat(5, 1fr);
-  margin-left: 2em;
-  margin-right: 2em;
-  margin-bottom: 60px;
-  display: grid;
-  grid-area: items;
-  grid-gap: 1rem;
+    grid-template-columns: repeat(5, 1fr);
+    margin-left: 2em;
+    margin-right: 2em;
+    margin-bottom: 60px;
+    display: grid;
+    grid-area: items;
+    grid-gap: 1rem;
 }
 
 @media (max-width: 640px) {
-  .items {
-    grid-template-columns: repeat(2, 1fr);
-  }
+    .items {
+        grid-template-columns: repeat(2, 1fr);
+    }
 }
diff --git a/packages/extensions/venia-product-recommendations/lib/components/Gallery/item.css b/packages/extensions/venia-product-recommendations/lib/components/Gallery/item.css
index 5a4fae3f24..5de5efda3b 100644
--- a/packages/extensions/venia-product-recommendations/lib/components/Gallery/item.css
+++ b/packages/extensions/venia-product-recommendations/lib/components/Gallery/item.css
@@ -1,3 +1,3 @@
 .name {
-  font-weight: bold;
+    font-weight: bold;
 }
diff --git a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/ProductRecommendations.css b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/ProductRecommendations.css
index c2e0d84a7d..a63f9751f8 100644
--- a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/ProductRecommendations.css
+++ b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/ProductRecommendations.css
@@ -1,11 +1,11 @@
 .unitTitle {
-  text-align: center;
-  margin: 1.5em;
-  font-weight: 700;
-  font-size: 2.25rem;
-  font-family: 'Source Serif Pro';
+    text-align: center;
+    margin: 1.5em;
+    font-weight: 700;
+    font-size: 2.25rem;
+    font-family: 'Source Serif Pro';
 }
 
 .root a {
-  text-decoration: none;
+    text-decoration: none;
 }
diff --git a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
index dc29c1f52b..80e757f7bb 100644
--- a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
+++ b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
@@ -10,114 +10,117 @@ import useObserver from '../../hooks/useObserver';
 import { mse } from '@magento/venia-data-collector';
 
 export const VeniaProductRecommendations = props => {
-  const rendered = useRef([]);
-  const { units } = useRecsTrackingProps(props);
-  const { observeUnit } = useObserver();
+    const rendered = useRef([]);
+    const { units } = useRecsTrackingProps(props);
+    const { observeUnit } = useObserver();
 
-  const classes = mergeClasses(defaultClasses, props.classes);
-  const galleryClasses = mergeClasses(defaultClasses, props.galleryClasses);
-  const itemClasses = mergeClasses(defaultClasses, props.itemClasses);
+    const classes = mergeClasses(defaultClasses, props.classes);
+    const galleryClasses = mergeClasses(defaultClasses, props.galleryClasses);
+    const itemClasses = mergeClasses(defaultClasses, props.itemClasses);
 
-  let galleryUnits = units.map(recommendationUnit => {
-    if (recommendationUnit.totalProducts < 1) {
-      return null;
-    }
-
-    const items = recommendationUnit.products.map(shapeItem);
-    return (
-      <div
-        key={recommendationUnit.unitId}
-        data-unit-id={recommendationUnit.unitId}
-        className={classes.root}
-        ref={element => observeUnit(recommendationUnit, element)}
-      >
-        <div className={classes.unitTitle}>
-          {recommendationUnit.storefrontLabel}
-        </div>
-        <Gallery
-          galleryClasses={galleryClasses}
-          itemClasses={itemClasses}
-          items={items}
-        />
-      </div>
-    );
-  });
+    const galleryUnits = units.map(recommendationUnit => {
+        if (recommendationUnit.totalProducts < 1) {
+            return null;
+        }
 
-  if (units && units.length > 0) {
-    units.forEach(recUnit => {
-      if (
-        recUnit.totalProducts > 0 &&
-        !rendered.current.includes(recUnit.unitId)
-      ) {
-        mse.publish.recsUnitRender(recUnit.unitId);
-        rendered.current.push(recUnit.unitId);
-      }
+        const items = recommendationUnit.products.map(shapeItem);
+        return (
+            <div
+                key={recommendationUnit.unitId}
+                data-unit-id={recommendationUnit.unitId}
+                className={classes.root}
+                ref={element => observeUnit(recommendationUnit, element)}
+            >
+                <div className={classes.unitTitle}>
+                    {recommendationUnit.storefrontLabel}
+                </div>
+                <Gallery
+                    galleryClasses={galleryClasses}
+                    itemClasses={itemClasses}
+                    items={items}
+                />
+            </div>
+        );
     });
 
-    return <div>{galleryUnits}</div>;
-  } else {
-    return null;
-  }
+    if (units && units.length > 0) {
+        units.forEach(recUnit => {
+            if (
+                recUnit.totalProducts > 0 &&
+                !rendered.current.includes(recUnit.unitId)
+            ) {
+                mse.publish.recsUnitRender(recUnit.unitId);
+                rendered.current.push(recUnit.unitId);
+            }
+        });
+
+        return <div>{galleryUnits}</div>;
+    } else {
+        return null;
+    }
 };
 
 VeniaProductRecommendations.propTypes = {
-  galleryClasses: shape({
-    filters: string,
-    items: string,
-    root: string,
-  }),
-  itemClasses: shape({
-    image: string,
-    imageContainer: string,
-    imagePlaceholder: string,
-    image_pending: string,
-    images: string,
-    images_pending: string,
-    name: string,
-    name_pending: string,
-    price: string,
-    price_pending: string,
-    root: string,
-    root_pending: string,
-  }),
-  classes: shape({
-    unitTitle: string,
-    root: string,
-  }),
-  pageType: string.isRequired,
+    galleryClasses: shape({
+        filters: string,
+        items: string,
+        root: string
+    }),
+    itemClasses: shape({
+        image: string,
+        imageContainer: string,
+        imagePlaceholder: string,
+        image_pending: string,
+        images: string,
+        images_pending: string,
+        name: string,
+        name_pending: string,
+        price: string,
+        price_pending: string,
+        root: string,
+        root_pending: string
+    }),
+    classes: shape({
+        unitTitle: string,
+        root: string
+    }),
+    pageType: string.isRequired
 };
 
 // format data for GalleryItem, exported for testing
 export const shapeItem = item => {
-  if (item) {
-    const { url, image, prices, productId, currency, type } = item;
+    if (item) {
+        const { url, image, prices, productId, currency, type } = item;
 
-    // derive the url_key and url_suffix from the url
-    // example url --> https://magento.com/blah/blah/url_key.url_suffix
-    const urlArray = String(url).split('/').splice(-1)[0].split('.');
-    const url_key = urlArray[0];
-    const url_suffix = `.${urlArray[1]}`;
+        // derive the url_key and url_suffix from the url
+        // example url --> https://magento.com/blah/blah/url_key.url_suffix
+        const urlArray = String(url)
+            .split('/')
+            .splice(-1)[0]
+            .split('.');
+        const url_key = urlArray[0];
+        const url_suffix = `.${urlArray[1]}`;
 
-    const price = {
-      regularPrice: {
-        amount: {
-          value: prices.minimum.regular,
-          currency,
-        },
-      },
-    };
+        const price = {
+            regularPrice: {
+                amount: {
+                    value: prices.minimum.regular,
+                    currency
+                }
+            }
+        };
 
-    return {
-      ...item,
-      id: productId,
-      small_image: image,
-      url_key,
-      url_suffix,
-      price,
-      // use inStock when the recs service provides it, use the commented out line below:
-      // stock_status: inStock ? "IN_STOCK" : "OUT_OF_STOCK";
-      stock_status: 'IN_STOCK',
-      type_id: type,
-    };
-  } else return null;
+        return {
+            ...item,
+            id: productId,
+            small_image: image,
+            url_key,
+            url_suffix,
+            price,
+            // use inStock when the recs service provides it, use the commented out line below:
+            // stock_status: inStock ? "IN_STOCK" : "OUT_OF_STOCK";
+            stock_status: 'IN_STOCK',
+            type_id: type
+        };
+    } else return null;
 };
diff --git a/packages/extensions/venia-product-recommendations/lib/hooks/useObserver.js b/packages/extensions/venia-product-recommendations/lib/hooks/useObserver.js
index e027612742..05539b42f9 100644
--- a/packages/extensions/venia-product-recommendations/lib/hooks/useObserver.js
+++ b/packages/extensions/venia-product-recommendations/lib/hooks/useObserver.js
@@ -1,41 +1,41 @@
 import { mse } from '@magento/venia-data-collector';
 
-let cleared = {};
+const cleared = {};
 
 // oddly, these functions error when not wrapped in a hook. 🤷
 const useObserver = () => {
-  const meetThreshold = (entries, unit) => {
-    entries.forEach(entry => {
-      const { isIntersecting, intersectionRatio } = entry;
-      const { unitId } = unit;
+    const meetThreshold = (entries, unit) => {
+        entries.forEach(entry => {
+            const { isIntersecting, intersectionRatio } = entry;
+            const { unitId } = unit;
 
-      if (!isIntersecting) {
-        cleared[unitId] = true;
-      }
-      if (cleared[unitId] !== false && intersectionRatio >= 0.5) {
-        cleared[unitId] = false;
-        mse.publish.recsUnitView(unit.unitId);
-      }
-    });
-  };
+            if (!isIntersecting) {
+                cleared[unitId] = true;
+            }
+            if (cleared[unitId] !== false && intersectionRatio >= 0.5) {
+                cleared[unitId] = false;
+                mse.publish.recsUnitView(unit.unitId);
+            }
+        });
+    };
 
-  const observeUnit = (unit, element) => {
-    if (element) {
-      const options = {
-        threshold: [0.0, 0.5],
-      };
-      const observer = new IntersectionObserver(
-        entries => meetThreshold(entries, unit),
-        options,
-      );
-      observer.observe(element);
-    } else {
-      console.warn(
-        'VeniaProductRecommendations IntersectionObserver: Element is either null or undefined.',
-      );
-    }
-  };
-  return { observeUnit };
+    const observeUnit = (unit, element) => {
+        if (element) {
+            const options = {
+                threshold: [0.0, 0.5]
+            };
+            const observer = new IntersectionObserver(
+                entries => meetThreshold(entries, unit),
+                options
+            );
+            observer.observe(element);
+        } else {
+            console.warn(
+                'VeniaProductRecommendations IntersectionObserver: Element is either null or undefined.'
+            );
+        }
+    };
+    return { observeUnit };
 };
 
 export default useObserver;
diff --git a/packages/extensions/venia-product-recommendations/lib/hooks/useRecsData.js b/packages/extensions/venia-product-recommendations/lib/hooks/useRecsData.js
index a29868540b..65ab082ca4 100644
--- a/packages/extensions/venia-product-recommendations/lib/hooks/useRecsData.js
+++ b/packages/extensions/venia-product-recommendations/lib/hooks/useRecsData.js
@@ -4,102 +4,102 @@ import { PageTypes, PRODUCT } from '../constants';
 import { mse } from '@magento/venia-data-collector';
 
 const useRecsData = props => {
-  const [recs, setRecs] = useState(null);
-  const [isLoading, setIsLoading] = useState(false);
-  const [error, setError] = useState(null);
-  const fired = useRef(false);
-  const stale = useRef(false);
-  const [currentProduct, setCurrentProduct] = useState(null);
+    const [recs, setRecs] = useState(null);
+    const [isLoading, setIsLoading] = useState(false);
+    const [error, setError] = useState(null);
+    const fired = useRef(false);
+    const stale = useRef(false);
+    const [currentProduct, setCurrentProduct] = useState(null);
 
-  if (
-    (process.env.NODE_ENV === 'development' ||
-      process.env.NODE_ENV === 'test') &&
-    (!props || !props.pageType)
-  ) {
-    throw new Error(
-      'Headless Recommendations: PageType is required to fetch recommendations.',
-    );
-  } else if (props.pageType && !PageTypes.includes(props.pageType)) {
-    throw new Error(
-      `Headless Recommendations: ${
-        props.pageType
-      } is not a valid pagetype. Valid types include ${JSON.stringify(
-        PageTypes,
-      )}`,
-    );
-  }
-  const { pageType } = props;
-  const storefrontContext = mse.context.getStorefrontInstance();
-  const product = mse.context.getProduct();
+    if (
+        (process.env.NODE_ENV === 'development' ||
+            process.env.NODE_ENV === 'test') &&
+        (!props || !props.pageType)
+    ) {
+        throw new Error(
+            'Headless Recommendations: PageType is required to fetch recommendations.'
+        );
+    } else if (props.pageType && !PageTypes.includes(props.pageType)) {
+        throw new Error(
+            `Headless Recommendations: ${
+                props.pageType
+            } is not a valid pagetype. Valid types include ${JSON.stringify(
+                PageTypes
+            )}`
+        );
+    }
+    const { pageType } = props;
+    const storefrontContext = mse.context.getStorefrontInstance();
+    const product = mse.context.getProduct();
 
-  useEffect(() => {
-    const fetchRecs = async () => {
-      const storefront = { ...storefrontContext, pageType };
+    useEffect(() => {
+        const fetchRecs = async () => {
+            const storefront = { ...storefrontContext, pageType };
 
-      const client = new RecommendationsClient(storefront);
+            const client = new RecommendationsClient(storefront);
 
-      let currentSku;
-      if (pageType === PRODUCT) {
-        currentSku = product.sku;
-      }
+            let currentSku;
+            if (pageType === PRODUCT) {
+                currentSku = product.sku;
+            }
 
-      const fetchProps = {
-        ...props,
-        currentSku,
-      };
-      let res;
+            const fetchProps = {
+                ...props,
+                currentSku
+            };
+            let res;
 
-      try {
-        setIsLoading(true);
-        fired.current = true;
-        stale.current = false;
-        mse.publish.recsRequestSent({ pageContext: { pageType } });
+            try {
+                setIsLoading(true);
+                fired.current = true;
+                stale.current = false;
+                mse.publish.recsRequestSent({ pageContext: { pageType } });
 
-        res = await client.fetchPreconfigured(fetchProps);
-      } catch (e) {
-        console.error(e);
-        setIsLoading(false);
-        setError(e);
-      }
-      if (res) {
-        const { data } = res;
-        mse.context.setRecommendations({ units: data.results });
-        mse.publish.recsResponseReceived();
-        setIsLoading(false);
-        setRecs(data);
-      }
-    };
-    if (
-      ((!fired.current && !recs) || stale.current) &&
-      PageTypes.includes(pageType) &&
-      storefrontContext !== undefined &&
-      storefrontContext.environmentId &&
-      ((pageType === PRODUCT &&
-        product !== undefined &&
-        product.sku !== undefined) ||
-        pageType !== PRODUCT)
-    ) {
-      fetchRecs();
-    }
-  }, [pageType, props, recs, storefrontContext, product]);
+                res = await client.fetchPreconfigured(fetchProps);
+            } catch (e) {
+                console.error(e);
+                setIsLoading(false);
+                setError(e);
+            }
+            if (res) {
+                const { data } = res;
+                mse.context.setRecommendations({ units: data.results });
+                mse.publish.recsResponseReceived();
+                setIsLoading(false);
+                setRecs(data);
+            }
+        };
+        if (
+            ((!fired.current && !recs) || stale.current) &&
+            PageTypes.includes(pageType) &&
+            storefrontContext !== undefined &&
+            storefrontContext.environmentId &&
+            ((pageType === PRODUCT &&
+                product !== undefined &&
+                product.sku !== undefined) ||
+                pageType !== PRODUCT)
+        ) {
+            fetchRecs();
+        }
+    }, [pageType, props, recs, storefrontContext, product]);
 
-  useEffect(() => {
-    if (
-      product &&
-      product.sku &&
-      (!currentProduct || product.sku !== currentProduct.sku)
-    ) {
-      setCurrentProduct(product);
-    }
-  }, [product,currentProduct]);
+    useEffect(() => {
+        if (
+            product &&
+            product.sku &&
+            (!currentProduct || product.sku !== currentProduct.sku)
+        ) {
+            setCurrentProduct(product);
+        }
+    }, [product, currentProduct]);
 
-  useEffect(() => {
-    if (currentProduct && recs && fired.current === true) {
-      stale.current = true;
-    }
-  }, [currentProduct,recs]);
+    useEffect(() => {
+        if (currentProduct && recs && fired.current === true) {
+            stale.current = true;
+        }
+    }, [currentProduct, recs]);
 
-  return { data: recs, isLoading, error };
+    return { data: recs, isLoading, error };
 };
 
 export default useRecsData;
diff --git a/packages/extensions/venia-product-recommendations/lib/hooks/useRecsTrackingProps.js b/packages/extensions/venia-product-recommendations/lib/hooks/useRecsTrackingProps.js
index ba3f81f365..2eeb9472a3 100644
--- a/packages/extensions/venia-product-recommendations/lib/hooks/useRecsTrackingProps.js
+++ b/packages/extensions/venia-product-recommendations/lib/hooks/useRecsTrackingProps.js
@@ -3,32 +3,32 @@ import useRecsData from './useRecsData';
 import { mse } from '@magento/venia-data-collector';
 
 const useRecsTrackingProps = props => {
-  const [units, setUnits] = useState([]);
-  const { data, isLoading, error } = useRecsData(props);
+    const [units, setUnits] = useState([]);
+    const { data, isLoading, error } = useRecsData(props);
 
-  useEffect(() => {
-    if (data && data.results) {
-      let tmpUnits = data.results.map(unit => {
-        const newUnit = {
-          ...unit,
-          pageType: props.pageType,
-        };
+    useEffect(() => {
+        if (data && data.results) {
+            const tmpUnits = data.results.map(unit => {
+                const newUnit = {
+                    ...unit,
+                    pageType: props.pageType
+                };
 
-        const products = unit.products.map(product => {
-          const newProduct = { ...product, unit: newUnit };
-          const onClick = () => {
-            const { unit, productId } = newProduct;
-            mse.publish.recsItemClick(unit.unitId, productId);
-          };
-          return { ...newProduct, onClick };
-        });
-        return { ...newUnit, products };
-      });
+                const products = unit.products.map(product => {
+                    const newProduct = { ...product, unit: newUnit };
+                    const onClick = () => {
+                        const { unit, productId } = newProduct;
+                        mse.publish.recsItemClick(unit.unitId, productId);
+                    };
+                    return { ...newProduct, onClick };
+                });
+                return { ...newUnit, products };
+            });
 
-      setUnits(tmpUnits);
-    }
-  }, [data, props.pageType]);
-  return { units, isLoading, error };
+            setUnits(tmpUnits);
+        }
+    }, [data, props.pageType]);
+    return { units, isLoading, error };
 };
 
 export default useRecsTrackingProps;
diff --git a/packages/extensions/venia-product-recommendations/lib/hooks/useSyncMSEToLocalStorage.js b/packages/extensions/venia-product-recommendations/lib/hooks/useSyncMSEToLocalStorage.js
index ba2edf8a05..4870b95ead 100644
--- a/packages/extensions/venia-product-recommendations/lib/hooks/useSyncMSEToLocalStorage.js
+++ b/packages/extensions/venia-product-recommendations/lib/hooks/useSyncMSEToLocalStorage.js
@@ -1,209 +1,219 @@
 import { useEffect, useRef } from 'react';
 import {
-  CART_CONTENTS_KEY,
-  PURCHASE_HISTORY_KEY,
-  USER_VIEW_HISTORY_KEY,
-  USER_VIEW_HISTORY_TIME_DECAY_KEY,
+    CART_CONTENTS_KEY,
+    PURCHASE_HISTORY_KEY,
+    USER_VIEW_HISTORY_KEY,
+    USER_VIEW_HISTORY_TIME_DECAY_KEY
 } from '../constants';
 import makeUrl from '@magento/venia-ui/lib/util/makeUrl';
 import useShoppingCartQuery from '@magento/venia-data-collector/lib/hooks/useShoppingCartQuery';
 import { mse } from '@magento/venia-data-collector';
 
 export default () => {
-  const firstLoad = useRef(true);
-  const { data } = useShoppingCartQuery({
-    fetchPolicy: 'cache-first',
-    skip: !firstLoad.current,
-  });
+    const firstLoad = useRef(true);
+    const { data } = useShoppingCartQuery({
+        fetchPolicy: 'cache-first',
+        skip: !firstLoad.current
+    });
 
-  const cartEventHandler = () => {
-    const shoppingCartContext = mse.context.getShoppingCart();
-    const dsCart = transformData(shoppingCartContext);
-    localStorage.setItem(CART_CONTENTS_KEY, JSON.stringify(dsCart));
-  };
+    const cartEventHandler = () => {
+        const shoppingCartContext = mse.context.getShoppingCart();
+        const dsCart = transformData(shoppingCartContext);
+        localStorage.setItem(CART_CONTENTS_KEY, JSON.stringify(dsCart));
+    };
 
-  const handleProductPageView = () => {
-    const product = mse.context.getProduct();
+    const handleProductPageView = () => {
+        const product = mse.context.getProduct();
 
-    if (product && product.sku) {
-      const productPageViewContext = {
-        date: new Date().toISOString(),
-        sku: product.sku,
-      };
-      // if sku is not in viewHistorySkus
-      // write to view_history_decay
-      try {
-        let viewHistory = JSON.parse(
-          localStorage.getItem(USER_VIEW_HISTORY_TIME_DECAY_KEY),
-        );
+        if (product && product.sku) {
+            const productPageViewContext = {
+                date: new Date().toISOString(),
+                sku: product.sku
+            };
+            // if sku is not in viewHistorySkus
+            // write to view_history_decay
+            try {
+                const viewHistory = JSON.parse(
+                    localStorage.getItem(USER_VIEW_HISTORY_TIME_DECAY_KEY)
+                );
 
-        if (!viewHistory) {
-          const updatedViewHistory = [productPageViewContext];
-          localStorage.setItem(
-            USER_VIEW_HISTORY_TIME_DECAY_KEY,
-            JSON.stringify(updatedViewHistory),
-          );
-        } else {
-          const productIndex = viewHistory.findIndex(
-            viewedProduct => viewedProduct.sku === product.sku,
-          );
-          if (productIndex === -1) {
-            const updatedViewHistory = [...viewHistory, productPageViewContext];
-            localStorage.setItem(
-              USER_VIEW_HISTORY_TIME_DECAY_KEY,
-              JSON.stringify(updatedViewHistory),
-            );
-            // has been viewed before
-          } else if (productIndex >= 0) {
-            // remove current value in viewHistory,
-            // and add the new value
-            viewHistory.splice(productIndex, 1, productPageViewContext);
-            localStorage.setItem(
-              USER_VIEW_HISTORY_TIME_DECAY_KEY,
-              JSON.stringify(viewHistory),
-            );
-          }
-        }
-      } catch (e) {
-        console.error(e);
-      }
+                if (!viewHistory) {
+                    const updatedViewHistory = [productPageViewContext];
+                    localStorage.setItem(
+                        USER_VIEW_HISTORY_TIME_DECAY_KEY,
+                        JSON.stringify(updatedViewHistory)
+                    );
+                } else {
+                    const productIndex = viewHistory.findIndex(
+                        viewedProduct => viewedProduct.sku === product.sku
+                    );
+                    if (productIndex === -1) {
+                        const updatedViewHistory = [
+                            ...viewHistory,
+                            productPageViewContext
+                        ];
+                        localStorage.setItem(
+                            USER_VIEW_HISTORY_TIME_DECAY_KEY,
+                            JSON.stringify(updatedViewHistory)
+                        );
+                        // has been viewed before
+                    } else if (productIndex >= 0) {
+                        // remove current value in viewHistory,
+                        // and add the new value
+                        viewHistory.splice(
+                            productIndex,
+                            1,
+                            productPageViewContext
+                        );
+                        localStorage.setItem(
+                            USER_VIEW_HISTORY_TIME_DECAY_KEY,
+                            JSON.stringify(viewHistory)
+                        );
+                    }
+                }
+            } catch (e) {
+                console.error(e);
+            }
 
-      //write to view_history
-      try {
-        let viewHistory = JSON.parse(
-          localStorage.getItem(USER_VIEW_HISTORY_KEY),
-        );
+            //write to view_history
+            try {
+                const viewHistory = JSON.parse(
+                    localStorage.getItem(USER_VIEW_HISTORY_KEY)
+                );
 
-        if (!viewHistory) {
-          const updatedViewHistory = { skus: [product.sku] };
-          localStorage.setItem(
-            USER_VIEW_HISTORY_KEY,
-            JSON.stringify(updatedViewHistory),
-          );
-        } else {
-          const productIndex = viewHistory.skus.findIndex(
-            viewedProduct => viewedProduct === product.sku,
-          );
-          if (productIndex === -1) {
-            const updatedViewHistory = {
-              skus: [...viewHistory.skus, product.sku],
-            };
-            localStorage.setItem(
-              USER_VIEW_HISTORY_KEY,
-              JSON.stringify(updatedViewHistory),
-            );
-            // has been viewed before
-          } else if (productIndex >= 0) {
-            // remove current value in viewHistory,
-            // and add the new value
-            viewHistory.skus.splice(productIndex, 1, product.sku);
-            localStorage.setItem(
-              USER_VIEW_HISTORY_KEY,
-              JSON.stringify(viewHistory),
-            );
-          }
+                if (!viewHistory) {
+                    const updatedViewHistory = { skus: [product.sku] };
+                    localStorage.setItem(
+                        USER_VIEW_HISTORY_KEY,
+                        JSON.stringify(updatedViewHistory)
+                    );
+                } else {
+                    const productIndex = viewHistory.skus.findIndex(
+                        viewedProduct => viewedProduct === product.sku
+                    );
+                    if (productIndex === -1) {
+                        const updatedViewHistory = {
+                            skus: [...viewHistory.skus, product.sku]
+                        };
+                        localStorage.setItem(
+                            USER_VIEW_HISTORY_KEY,
+                            JSON.stringify(updatedViewHistory)
+                        );
+                        // has been viewed before
+                    } else if (productIndex >= 0) {
+                        // remove current value in viewHistory,
+                        // and add the new value
+                        viewHistory.skus.splice(productIndex, 1, product.sku);
+                        localStorage.setItem(
+                            USER_VIEW_HISTORY_KEY,
+                            JSON.stringify(viewHistory)
+                        );
+                    }
+                }
+            } catch (e) {
+                console.error(e);
+            }
         }
-      } catch (e) {
-        console.error(e);
-      }
-    }
-  };
+    };
 
-  const handlePlaceOrder = event => {
-    if (
-      event.eventInfo &&
-      event.eventInfo.shoppingCartContext &&
-      event.eventInfo.shoppingCartContext.items
-    ) {
-      let { items } = event.eventInfo.shoppingCartContext;
-      items = items.map(item => {
-        return item.product.sku;
-      });
-      const additionalPurchaseHistory = {
-        date: new Date().toISOString(),
-        items,
-      };
-      try {
-        const currentPurchaseHistory = JSON.parse(
-          localStorage.getItem(PURCHASE_HISTORY_KEY),
-        );
-        const newPurchaseHistory = [
-          ...(currentPurchaseHistory ? currentPurchaseHistory : []),
-          additionalPurchaseHistory,
-        ];
-        localStorage.setItem(
-          PURCHASE_HISTORY_KEY,
-          JSON.stringify(newPurchaseHistory),
-        );
-      } catch (e) {
-        console.error(e);
-      }
-    }
-  };
+    const handlePlaceOrder = event => {
+        if (
+            event.eventInfo &&
+            event.eventInfo.shoppingCartContext &&
+            event.eventInfo.shoppingCartContext.items
+        ) {
+            let { items } = event.eventInfo.shoppingCartContext;
+            items = items.map(item => {
+                return item.product.sku;
+            });
+            const additionalPurchaseHistory = {
+                date: new Date().toISOString(),
+                items
+            };
+            try {
+                const currentPurchaseHistory = JSON.parse(
+                    localStorage.getItem(PURCHASE_HISTORY_KEY)
+                );
+                const newPurchaseHistory = [
+                    ...(currentPurchaseHistory ? currentPurchaseHistory : []),
+                    additionalPurchaseHistory
+                ];
+                localStorage.setItem(
+                    PURCHASE_HISTORY_KEY,
+                    JSON.stringify(newPurchaseHistory)
+                );
+            } catch (e) {
+                console.error(e);
+            }
+        }
+    };
 
-  useEffect(() => {
-    mse.subscribe.removeFromCart(cartEventHandler);
-    mse.subscribe.addToCart(cartEventHandler);
-    mse.subscribe.productPageView(handleProductPageView);
-    mse.subscribe.placeOrder(handlePlaceOrder);
+    useEffect(() => {
+        mse.subscribe.removeFromCart(cartEventHandler);
+        mse.subscribe.addToCart(cartEventHandler);
+        mse.subscribe.productPageView(handleProductPageView);
+        mse.subscribe.placeOrder(handlePlaceOrder);
 
-    return () => {
-      mse.unsubscribe.removeFromCart(cartEventHandler);
-      mse.unsubscribe.addToCart(cartEventHandler);
-      mse.unsubscribe.productPageView(handleProductPageView);
-      mse.unsubscribe.placeOrder(handlePlaceOrder);
-    };
-  }, []);
+        return () => {
+            mse.unsubscribe.removeFromCart(cartEventHandler);
+            mse.unsubscribe.addToCart(cartEventHandler);
+            mse.unsubscribe.productPageView(handleProductPageView);
+            mse.unsubscribe.placeOrder(handlePlaceOrder);
+        };
+    }, []);
 
-  useEffect(() => {
-    if (data && firstLoad.current) {
-      firstLoad.current = false;
-      const firstLoadCart = transformData(data.cart, 'firstLoad');
-      localStorage.setItem(CART_CONTENTS_KEY, JSON.stringify(firstLoadCart));
-    }
-  }, [data, firstLoad]);
+    useEffect(() => {
+        if (data && firstLoad.current) {
+            firstLoad.current = false;
+            const firstLoadCart = transformData(data.cart, 'firstLoad');
+            localStorage.setItem(
+                CART_CONTENTS_KEY,
+                JSON.stringify(firstLoadCart)
+            );
+        }
+    }, [data, firstLoad]);
 };
 
 const productTypesMap = new Map([
-  ['SimpleProduct', 'simple'],
-  ['ConfigurableProduct', 'configurable'],
+    ['SimpleProduct', 'simple'],
+    ['ConfigurableProduct', 'configurable']
 ]);
 
 const transformData = shoppingCart => {
-  let dsCart;
-  if (shoppingCart && shoppingCart.items && shoppingCart.items.length > 0) {
-    dsCart = {
-      cart: {
-        items: shoppingCart.items.map(item => {
-          const { product, prices } = item;
-          return {
-            product_type: productTypesMap.get(product.__typename),
-            item_id: item.id,
-            qty: item.quantity,
-            product_id: product.id,
-            product_name: product.name,
-            product_sku: product.sku,
-            product_url: makeUrl(
-              `${window.location.origin}/${product.url_key}${
-                product.url_suffix
-              }`,
-            ),
-            product_price_value: prices.price.value,
-            product_image: {
-              src: product.thumbnail.url,
-              alt: product.thumbnail.label || '',
-            },
-          };
-        }),
-      },
-    };
-  } else {
-    dsCart = {
-      cart: {
-        items: [],
-      },
-    };
-  }
+    let dsCart;
+    if (shoppingCart && shoppingCart.items && shoppingCart.items.length > 0) {
+        dsCart = {
+            cart: {
+                items: shoppingCart.items.map(item => {
+                    const { product, prices } = item;
+                    return {
+                        product_type: productTypesMap.get(product.__typename),
+                        item_id: item.id,
+                        qty: item.quantity,
+                        product_id: product.id,
+                        product_name: product.name,
+                        product_sku: product.sku,
+                        product_url: makeUrl(
+                            `${window.location.origin}/${product.url_key}${
+                                product.url_suffix
+                            }`
+                        ),
+                        product_price_value: prices.price.value,
+                        product_image: {
+                            src: product.thumbnail.url,
+                            alt: product.thumbnail.label || ''
+                        }
+                    };
+                })
+            }
+        };
+    } else {
+        dsCart = {
+            cart: {
+                items: []
+            }
+        };
+    }
 
-  return dsCart;
+    return dsCart;
 };
diff --git a/packages/extensions/venia-product-recommendations/lib/index.js b/packages/extensions/venia-product-recommendations/lib/index.js
index 6a7adb5916..7fbae52900 100644
--- a/packages/extensions/venia-product-recommendations/lib/index.js
+++ b/packages/extensions/venia-product-recommendations/lib/index.js
@@ -2,19 +2,19 @@ export * from './components/VeniaProductRecommendations/VeniaProductRecommendati
 export * from './hooks/useRecsData';
 
 import {
-  CMS,
-  PRODUCT,
-  PAGEBUILDER,
-  CART,
-  CATEGORY,
-  CHECKOUT,
+    CMS,
+    PRODUCT,
+    PAGEBUILDER,
+    CART,
+    CATEGORY,
+    CHECKOUT
 } from './constants/pageTypes';
 
 export const PageTypes = {
-  CMS,
-  PRODUCT,
-  PAGEBUILDER,
-  CART,
-  CATEGORY,
-  CHECKOUT,
+    CMS,
+    PRODUCT,
+    PAGEBUILDER,
+    CART,
+    CATEGORY,
+    CHECKOUT
 };
diff --git a/packages/extensions/venia-product-recommendations/lib/queries/getCart.gql.js b/packages/extensions/venia-product-recommendations/lib/queries/getCart.gql.js
index a3ffca24a0..d6bf634d6a 100644
--- a/packages/extensions/venia-product-recommendations/lib/queries/getCart.gql.js
+++ b/packages/extensions/venia-product-recommendations/lib/queries/getCart.gql.js
@@ -1,59 +1,59 @@
 import { gql } from '@apollo/client';
 
 export const ProductRecommendationsFragment = gql`
-  fragment ProductRecommendationsFragment on Cart {
-    total_quantity
-    prices {
-      subtotal_excluding_tax {
-        value
-      }
-      subtotal_including_tax {
-        value
-      }
-    }
-    id
-    items {
-      id
-      uid
-      ... on ConfigurableCartItem {
-        configured_variant {
-          uid
-          sku
+    fragment ProductRecommendationsFragment on Cart {
+        total_quantity
+        prices {
+            subtotal_excluding_tax {
+                value
+            }
+            subtotal_including_tax {
+                value
+            }
         }
-      }
-      product {
         id
-        name
-        url_key
-        url_suffix
-        sku
-        image {
-          url
-        }
-        thumbnail {
-          url
-          label
+        items {
+            id
+            uid
+            ... on ConfigurableCartItem {
+                configured_variant {
+                    uid
+                    sku
+                }
+            }
+            product {
+                id
+                name
+                url_key
+                url_suffix
+                sku
+                image {
+                    url
+                }
+                thumbnail {
+                    url
+                    label
+                }
+            }
+            prices {
+                price {
+                    currency
+                    value
+                }
+            }
+            quantity
         }
-      }
-      prices {
-        price {
-          currency
-          value
-        }
-      }
-      quantity
     }
-  }
 `;
 
 const GET_CART_QUERY = gql`
-  query GetCart($cartId: String!) {
-    cart(cart_id: $cartId) @connection(key: "Cart") {
-      id
-      ...ProductRecommendationsFragment
+    query GetCart($cartId: String!) {
+        cart(cart_id: $cartId) @connection(key: "Cart") {
+            id
+            ...ProductRecommendationsFragment
+        }
     }
-  }
-  ${ProductRecommendationsFragment}
+    ${ProductRecommendationsFragment}
 `;
 
 export default GET_CART_QUERY;
diff --git a/packages/extensions/venia-product-recommendations/lib/targets/intercept.js b/packages/extensions/venia-product-recommendations/lib/targets/intercept.js
index fc0f56fda9..c0e70c3650 100644
--- a/packages/extensions/venia-product-recommendations/lib/targets/intercept.js
+++ b/packages/extensions/venia-product-recommendations/lib/targets/intercept.js
@@ -1,23 +1,23 @@
 const myName = '@magento/venia-product-recommendations';
 
 module.exports = targets => {
-  console.log(myName);
-  const builtins = targets.of('@magento/pwa-buildpack');
-  builtins.specialFeatures.tap(flags => {
-    flags[targets.name] = {
-      esModules: true,
-      graphqlQueries: true,
-      cssModules: true,
-    };
-  });
+    console.log(myName);
+    const builtins = targets.of('@magento/pwa-buildpack');
+    builtins.specialFeatures.tap(flags => {
+        flags[targets.name] = {
+            esModules: true,
+            graphqlQueries: true,
+            cssModules: true
+        };
+    });
 
-  builtins.envVarDefinitions.tap(() => {
-    targets.of('@magento/peregrine').talons.tap(talons => {
-      talons.App.useApp.wrapWith(`${myName}/lib/wrappers/wrapUseApp`);
-      talons.Gallery.useGalleryItem.wrapWith(
-        `${myName}/lib/wrappers/wrapUseGalleryItem`,
-      );
-      return talons;
+    builtins.envVarDefinitions.tap(() => {
+        targets.of('@magento/peregrine').talons.tap(talons => {
+            talons.App.useApp.wrapWith(`${myName}/lib/wrappers/wrapUseApp`);
+            talons.Gallery.useGalleryItem.wrapWith(
+                `${myName}/lib/wrappers/wrapUseGalleryItem`
+            );
+            return talons;
+        });
     });
-  });
 };
diff --git a/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseApp.js b/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseApp.js
index 60255de056..68ebf46293 100644
--- a/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseApp.js
+++ b/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseApp.js
@@ -1,7 +1,7 @@
 import useSyncMSEToLocalStorage from '../hooks/useSyncMSEToLocalStorage';
 export default function wrapUseApp(origUseApp) {
-  return function(props) {
-    useSyncMSEToLocalStorage();
-    return origUseApp(props);
-  };
+    return function(props) {
+        useSyncMSEToLocalStorage();
+        return origUseApp(props);
+    };
 }
diff --git a/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseGalleryItem.js b/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseGalleryItem.js
index 8e04f16abb..1652459eff 100644
--- a/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseGalleryItem.js
+++ b/packages/extensions/venia-product-recommendations/lib/wrappers/wrapUseGalleryItem.js
@@ -1,12 +1,12 @@
 export default function wrapUseGalleryItem(origUseGalleryItem) {
-  return function(props) {
-    const orig = origUseGalleryItem(props);
-    const handleLinkClick = props.item.onClick
-      ? props.item.onClick
-      : orig.handleLinkClick;
-    return {
-      ...orig,
-      handleLinkClick,
+    return function(props) {
+        const orig = origUseGalleryItem(props);
+        const handleLinkClick = props.item.onClick
+            ? props.item.onClick
+            : orig.handleLinkClick;
+        return {
+            ...orig,
+            handleLinkClick
+        };
     };
-  };
 }
diff --git a/packages/extensions/venia-product-recommendations/package.json b/packages/extensions/venia-product-recommendations/package.json
index 397f332a8b..58756b0bd2 100644
--- a/packages/extensions/venia-product-recommendations/package.json
+++ b/packages/extensions/venia-product-recommendations/package.json
@@ -2,23 +2,22 @@
   "name": "@magento/venia-product-recommendations",
   "version": "1.0.2",
   "main": "./lib/index.js",
-  "license": "MIT",
-  "pwa-studio": {
-    "targets": {
-      "intercept": "./lib/targets/intercept.js"
-    }
-  },
   "files": [
     "/lib"
   ],
   "scripts": {
-    "cm": "cz"
+    "cm": "cz",
+    "clean": " "
   },
- 
+  "license": "MIT",
   "dependencies": {
     "@magento/recommendations-js-sdk": "~2.0.7",
     "@magento/venia-data-collector": "^1.0.7",
     "prop-types": "^15.7.2"
+  },
+  "pwa-studio": {
+    "targets": {
+      "intercept": "./lib/targets/intercept.js"
+    }
   }
-  
 }

From af4feb2eaf1bf6082583189341515640b196f92b Mon Sep 17 00:00:00 2001
From: glo82145 <glo82145@adobe.com>
Date: Mon, 19 May 2025 17:08:01 +0530
Subject: [PATCH 3/4] PWA-3318-v1

---
 .../VeniaProductRecommendations.js            |  4 +-
 .../lib/talons/Gallery/useAddToCartButton.js  | 44 ++++++++++++-------
 2 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
index 80e757f7bb..b4148f10b3 100644
--- a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
+++ b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
@@ -98,9 +98,9 @@ export const shapeItem = item => {
             .split('/')
             .splice(-1)[0]
             .split('.');
-        const url_key = urlArray[0];
+        const url_key = urlArray[0]+`.${urlArray[1]}`;
         const url_suffix = `.${urlArray[1]}`;
-
+        
         const price = {
             regularPrice: {
                 amount: {
diff --git a/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js b/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js
index 11916dfa12..a4376e9456 100644
--- a/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js
+++ b/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js
@@ -62,23 +62,35 @@ export const useAddToCartButton = props => {
                 setIsLoading(true);
 
                 const quantity = 1;
-
-                await addToCart({
-                    variables: {
-                        cartId,
-                        cartItem: {
-                            quantity,
-                            entered_options: [
-                                {
-                                    uid: item.uid,
-                                    value: item.name
-                                }
-                            ],
-                            sku: item.sku
+            
+                if(item.uid) {
+                    await addToCart({
+                        variables: {
+                            cartId,
+                            cartItem: {
+                                quantity,
+                                entered_options: [
+                                    {
+                                        uid: item.uid,
+                                        value: item.name
+                                    }
+                                ],
+                                sku: item.sku
+                            }
                         }
-                    }
-                });
-
+                    });
+                } else {
+                    await addToCart({
+                        variables: {
+                            cartId,
+                            cartItem: {
+                                quantity,
+                                sku: item.sku
+                            }
+                        }
+                    });
+                }
+                
                 dispatch({
                     type: 'CART_ADD_ITEM',
                     payload: {

From aae4a1de1a173aa4f8933f33dea5b14ceb167343 Mon Sep 17 00:00:00 2001
From: glo82145 <glo82145@adobe.com>
Date: Mon, 19 May 2025 20:01:13 +0530
Subject: [PATCH 4/4] PWA-3318-v1

---
 .../VeniaProductRecommendations.js                          | 4 ++--
 .../extensions/venia-product-recommendations/package.json   | 4 ++--
 packages/peregrine/lib/talons/Gallery/useAddToCartButton.js | 6 +++---
 3 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
index b4148f10b3..be0ba25938 100644
--- a/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
+++ b/packages/extensions/venia-product-recommendations/lib/components/VeniaProductRecommendations/VeniaProductRecommendations.js
@@ -98,9 +98,9 @@ export const shapeItem = item => {
             .split('/')
             .splice(-1)[0]
             .split('.');
-        const url_key = urlArray[0]+`.${urlArray[1]}`;
+        const url_key = urlArray[0] + `.${urlArray[1]}`;
         const url_suffix = `.${urlArray[1]}`;
-        
+
         const price = {
             regularPrice: {
                 amount: {
diff --git a/packages/extensions/venia-product-recommendations/package.json b/packages/extensions/venia-product-recommendations/package.json
index 58756b0bd2..94395fd446 100644
--- a/packages/extensions/venia-product-recommendations/package.json
+++ b/packages/extensions/venia-product-recommendations/package.json
@@ -6,8 +6,8 @@
     "/lib"
   ],
   "scripts": {
-    "cm": "cz",
-    "clean": " "
+    "clean": " ",
+    "cm": "cz"
   },
   "license": "MIT",
   "dependencies": {
diff --git a/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js b/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js
index a4376e9456..c72e345dc8 100644
--- a/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js
+++ b/packages/peregrine/lib/talons/Gallery/useAddToCartButton.js
@@ -62,8 +62,8 @@ export const useAddToCartButton = props => {
                 setIsLoading(true);
 
                 const quantity = 1;
-            
-                if(item.uid) {
+
+                if (item.uid) {
                     await addToCart({
                         variables: {
                             cartId,
@@ -90,7 +90,7 @@ export const useAddToCartButton = props => {
                         }
                     });
                 }
-                
+
                 dispatch({
                     type: 'CART_ADD_ITEM',
                     payload: {