Categories For You

Shows collection categories personalized from the logged-in customer's past purchases. Ranks collections by purchase frequency, falls back to configured collection handles when logged out or no history, and opens the collection on tap. Hides when empty.

<div class="collection-products-wrapper">
  <h2 id="collectionTitle" class="collection-title">
    Recommended Products 
  </h2>
  <div id="productsCarousel" class="products-carousel"></div>
</div>

.collection-products-wrapper {
  width: 100%;
  /* Max-width added to match the main card perfectly if needed */
  max-width: 354px;
  padding: 16px 0; 
}
.collection-title {
  font-size: 18px;
  font-weight: 700;
  color: #111827;
  /* Removed the 16px side margins to push it flush left */
  margin: 0 0 12px 0; 
}
.products-carousel {
  display: flex;
  gap: 12px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Removed the left padding so the first card perfectly aligns with the title */
  padding: 0 0 8px 0; 
}
.products-carousel::-webkit-scrollbar {
  display: none;
}
.products-carousel::after {
  content: "";
  padding-right: 4px;
}
.product-card {
  flex: 0 0 180px;
  scroll-snap-align: start;
  background: #fff;
  border: 1px solid #E5E7EB;
  border-radius: 12px;
  overflow: hidden;
  cursor: pointer;
}
.product-image {
  width: 100%;
  height: 180px;
  object-fit: cover;
  display: block;
}
.product-content {
  padding: 10px;
}
.product-name {
  font-size: 14px;
  font-weight: 600;
  color: #111827;
  line-height: 1.4;
  min-height: 40px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.product-price {
  margin-top: 8px;
  font-size: 14px;
  font-weight: 700;
  color: #111827;
}
.empty-state {
  padding: 16px;
  text-align: left;
  color: #6B7280;
}
const RECENT_ORDER_COLLECTION_QUERY = `
query getRecentOrderStatusAndProductWithCollection(
  $customerAccessToken: String!
) {
  customer(
    customerAccessToken: $customerAccessToken
  ) {
    orders(first: 1, reverse: true) {
      edges {
        node {
          id
          lineItems(first: 1) {
            edges {
              node {
                variant {
                  product {
                    collections(first: 1) {
                      edges {
                        node {
                          id
                          handle
                          title
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
`;
const COLLECTION_PRODUCTS_QUERY = `
query getCollectionProducts(
  $handle: String!
) {
  collection(handle: $handle) {
    id
    title
    products(first: 20) {
      edges {
        node {
          id
          title
          handle
          onlineStoreUrl
          featuredImage {
            url
          }
          priceRange {
            minVariantPrice {
              amount
              currencyCode
            }
          }
        }
      }
    }
  }
}
`;
function resizeBlock() {
    setTimeout(() => {
        Superfans.actions.resizeBlock();
    }, 100);
}
function getCustomerAccessToken() {
    const customer = Superfans.variables.customer || {};
    return (customer.accessToken || customer.customerAccessToken || customer.shopifyCustomerAccessToken || customer.token || null);
}
function extractCollection(response) {
    return response?.data?.customer?.orders?.edges?.[0]?.node?.lineItems?.edges?.[0]?.node?.variant?.product?.collections?.edges?.[0]?.node;
}
async function fetchLatestOrderCollection() {
    const customerAccessToken = getCustomerAccessToken();
    if (!customerAccessToken) {
        renderEmptyState();
        return;
    }
    try {
        const response = await Superfans.helpers.getStorefrontData({
            query: RECENT_ORDER_COLLECTION_QUERY,
            variables: {
                customerAccessToken
            }
        });
        const collection = extractCollection(response);
        if (!collection?.handle) {
            renderEmptyState();
            return;
        }
        await fetchCollectionProducts(collection.handle);
    } catch (error) {
        console.error(error);
        renderEmptyState();
    }
}
async function fetchCollectionProducts(handle) {
    try {
        const response = await Superfans.helpers.getStorefrontData({
            query: COLLECTION_PRODUCTS_QUERY,
            variables: {
                handle
            }
        });
        const collection = response?.data?.collection;
        if (!collection) {
            renderEmptyState();
            return;
        }
        renderProducts(collection);
    } catch (error) {
        renderEmptyState();
    }
}
function renderProducts(collection) {
    const title = document.getElementById("collectionTitle");
    const carousel = document.getElementById("productsCarousel");
    // title.textContent = collection.title;
    title.textContent = "Recommended Products";
    carousel.innerHTML = "";
    collection.products.edges.forEach(
        ({
            node
        }) => {
            const card = document.createElement("div");
            card.className = "product-card";
            card.innerHTML = `
        <img
          class="product-image"
          src="${
            node.featuredImage?.url ||
            ""
          }"
          alt="${node.title}"
        />
        <div class="product-content">
          <div class="product-name">
            ${node.title}
          </div>
          <div class="product-price">
            ${Number(
              node.priceRange
                .minVariantPrice.amount
            ).toFixed(2)}
            ${
              node.priceRange
                .minVariantPrice.currencyCode
            }
          </div>
        </div>
      `;
            card.addEventListener("click", async () => {
                if (!node.handle) {
                    return;
                }
                try {
                    await Superfans.actions.openUrl({
                        // path: node.onlineStoreUrl
                        path: `/products/${node.handle}`
                    });
                } catch (error) {
                    console.error(error);
                }
            });
            carousel.appendChild(card);
        });
    resizeBlock();
}
function renderEmptyState() {
    const carousel = document.getElementById("productsCarousel");
    carousel.innerHTML = `
    <div class="empty-state">
      No recommendations available.
    </div>
  `;
    resizeBlock();
}
document.addEventListener("DOMContentLoaded",
    () => {
        fetchLatestOrderCollection();
    });
Superfans.listeners.onLoginStatusChanged(
    () => {
        setTimeout(fetchLatestOrderCollection, 300);
    });