[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/messages/nl-NL.json from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "store/pricing.from": "Van",
  "store/pricing.to": "Naar",
  "store/button-label": "Koop",
  "store/sponsored-badge.label": "Gesponsord",
  "admin/editor.productSummary.title": "Productoverzicht",
  "admin/editor.productSummary.description": "Het productoverzicht met de belangrijkste productinformatie",
  "admin/editor.productSummary.isOneClickBuy.title": "Moet doorverwijzen naar kassa na klikken op kopen",
  "admin/editor.productSummary.showBadge.title": "Toon de kortingsbadge",
  "admin/editor.productSummary.badgeText.title": "Tekst van de badge",
  "admin/editor.productSummary.buyButtonText.title": "De tekst van de aangepaste koopknop",
  "admin/editor.productSummary.hideBuyButton.title": "Verbergt de koopknop volledig",
  "admin/editor.productSummary.showCollections.title": "Laat de badges van de collectie zien",
  "admin/editor.productSummary.displayBuyButton.title": "Weergavemodus koopknop",
  "admin/editor.productSummary.displayBuyButton.option.none": "Toon nooit",
  "admin/editor.productSummary.displayBuyButton.option.always": "Altijd tonen",
  "admin/editor.productSummary.displayBuyButton.option.hover": "Weergeven bij aanwijzen (web)",
  "store/productSummary.quantity-error": "Het was niet mogelijk om het opgegeven bedrag te selecteren",
  "store/productSummary.unit": "Eenheid",
  "store/productSummary.attachmentName": "{sign} {quantity}x {name}",
  "store/productSummary.missingOptionName": "Geen {name}",
  "admin/editor.productSummaryPrice.title": "Productoverzicht prijs",
  "admin/editor.productSummaryPrice.description": "Component die de prijs van het product toont in het productoverzicht",
  "admin/editor.productSummaryPrice.showListPrice.title": "Toon lijstprijs",
  "admin/editor.productSummaryPrice.showSellingPriceRange.title": "Toon verkoopprijsbereik",
  "admin/editor.productSummaryPrice.showListPriceRange.title": "Toon lijstprijsbereik",
  "admin/editor.productSummaryPrice.showInstallments.title": "Toon termijnen",
  "admin/editor.productSummaryPrice.showDiscountValue.title": "Toon kortingswaarde",
  "admin/editor.productSummaryPrice.showLabels.title": "Toon labels",
  "admin/editor.productSummaryPrice.labelSellingPrice.title": "Label voor verkoopprijs",
  "admin/editor.productSummaryPrice.labelListPrice.title": "Label voor lijstprijs",
  "admin/editor.productSummaryPrice.showBorders.title": "Randen weergeven",
  "admin/editor.productSummaryImage.title": "Productoverzicht afbeelding",
  "admin/editor.productSummaryImage.description": "De component die de productafbeelding toont",
  "admin/editor.productSummaryImage.hoverImageLabel.title": "Aanwijzen afbeeldingslabel",
  "admin/editor.productSummaryImage.hoverImageLabel.description": "Deze eigenschap is verouderd. Gebruik in plaats daarvan de criteria-eigenschap",
  "admin/editor.productSummaryImage.hoverImage.criteria.title": "Criteria voor de aanwijzing van afbeeldingselectie",
  "admin/editor.productSummaryImage.hoverImage.criteria.label": "Aanwijzen afbeeldingslabel",
  "admin/editor.productSummaryImage.hoverImage.criteria.matchCriteria": "Zoekcriteria voor afbeeldingslabel zweven (exact: vindt de afbeelding die exact overeenkomt met de naam die is ingevuld in het veld 'Afbeeldingslabel zweven' | bevat: vindt de eerste afbeelding die de tekst bevat die is ingevuld in het veld 'Afbeeldingslabel zweven')",
  "admin/editor.productSummaryImage.hoverImage.criteria.index": "Aanwijzen afbeeldingsindex",
  "admin/editor.productSummaryBuyButton.title": "Productoverzicht koopknop",
  "admin/editor.productSummaryName.title": "Productoverzicht naam",
  "admin/editor.product-summary-specification-badges.title": "Productoverzicht specificatiebadges",
  "admin/editor.productSummaryList.title": "Productlijst",
  "admin/editor.productSummaryList.description": "Een productlijst met een collectie",
  "admin/editor.productSummaryList.category.title": "Categorie-id",
  "admin/editor.productSummaryList.category.description": "Gebruik voor subcategorieën \"/\" (bijv. 1/2/3)",
  "admin/editor.productSummaryList.collection.title": "Collectie",
  "admin/editor.productSummaryList.orderBy.title": "Lijst sorteren",
  "admin/editor.productSummaryList.hideUnavailableItems": "Verberg niet-beschikbare items",
  "admin/editor.productSummaryList.maxItems.title": "Max. items",
  "admin/editor.productSummaryList.specificationFilters.title": "Specificatiefilters",
  "admin/editor.productSummaryList.specificationFilters.item.title": "Specificatiefilteritem",
  "admin/editor.productSummaryList.specificationFilters.item.id.title": "Specificatiefilter-id",
  "admin/editor.productSummaryList.specificationFilters.item.value.title": "Specificatiefilterwaarde",
  "admin/editor.productSummaryList.orderType.sales": "Verkoop",
  "admin/editor.productSummaryList.orderType.priceDesc": "Prijs, aflopend",
  "admin/editor.productSummaryList.orderType.priceAsc": "Prijs, oplopend",
  "admin/editor.productSummaryList.orderType.nameAsc": "Naam, oplopend",
  "admin/editor.productSummaryList.orderType.nameDesc": "Naam, aflopend",
  "admin/editor.productSummaryList.orderType.releaseDate": "Release datum",
  "admin/editor.productSummaryList.orderType.discount": "Korting",
  "admin/editor.productSummaryList.orderType.relevance": "Relevantie",
  "admin/editor.productSummaryList.skusFilter.title": "SKUs-filter",
  "admin/editor.productSummaryList.skusFilter.description": "Het instellen van het eerste beschikbare filter kan uw query veel sneller maken!",
  "admin/editor.productSummaryList.skusFilter.none": "Geen",
  "admin/editor.productSummaryList.skusFilter.first-available": "Eerst beschikbaar",
  "admin/editor.productSummaryList.skusFilter.all-available": "Alle beschikbare",
  "admin/editor.productSummaryList.installmentCriteria.title": "Weergegeven termijnen",
  "admin/editor.productSummaryList.installmentCriteria.description": "Kies welk type termijnen getoond moet worden (bijv.: maximaal zonder rente of met rente).",
  "admin/editor.productSummaryList.installmentCriteria.max-without-interest": "Maximaal zonder rente",
  "admin/editor.productSummaryList.installmentCriteria.max-with-interest": "Maximum",
  "admin/editor.productSummaryList.analyticsListName.title": "Lijstnaam in Analytics",
  "admin/editor.productSummary.trackListName.title": "Moet lijstnaam volgen",
  "admin/editor.productSummary.sponsoredBadge.title": "Tekst van de gesponsorde producttag, indien van toepassing",
  "admin/editor.productSummary.sponsoredBadge.position": "Positie van de gesponsorde producttag, indien van toepassing",
  "admin/editor.productSummary.sponsoredBadge.position.titleTop": "Boven de naam van het product",
  "admin/editor.productSummary.sponsoredBadge.position.containerTopLeft": "Bovenop de productcontainer",
  "admin/editor.productSummary.sponsoredBadge.position.none": "Niet laten zien"
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/messages/es.json from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "store/pricing.from": "De",
  "store/pricing.to": "Para",
  "store/button-label": "Comprar",
  "store/sponsored-badge.label": "Patrocinado",
  "admin/editor.productSummary.title": "Resumen del producto",
  "admin/editor.productSummary.description": "El resumen del producto que muestra la información principal del producto",
  "admin/editor.productSummary.isOneClickBuy.title": "Redirigir al checkout después de hacer clic en comprar",
  "admin/editor.productSummary.showBadge.title": "Mostrar la insignia de descuento",
  "admin/editor.productSummary.badgeText.title": "Texto de la insignia",
  "admin/editor.productSummary.buyButtonText.title": "Texto del botón de compra personalizado",
  "admin/editor.productSummary.hideBuyButton.title": "Oculta el botón de comprar por completo",
  "admin/editor.productSummary.showCollections.title": "Mostrar las insignias de las colecciones",
  "admin/editor.productSummary.displayBuyButton.title": "Modo de visualización del botón de compra",
  "admin/editor.productSummary.displayBuyButton.option.none": "Nunca mostrar",
  "admin/editor.productSummary.displayBuyButton.option.always": "Mostrar siempre",
  "admin/editor.productSummary.displayBuyButton.option.hover": "Mostrar al pasar el mouse (web)",
  "store/productSummary.quantity-error": "No se pudo seleccionar la cantidad especificada",
  "store/productSummary.unit": "Unidad",
  "store/productSummary.attachmentName": "{sign} {quantity}x {name}",
  "store/productSummary.missingOptionName": "Sin {name}",
  "admin/editor.productSummaryPrice.title": "Precio del resumen del producto",
  "admin/editor.productSummaryPrice.description": "Componente que muestra el precio del producto dentro del resumen del producto",
  "admin/editor.productSummaryPrice.showListPrice.title": "Mostrar el precio de lista",
  "admin/editor.productSummaryPrice.showSellingPriceRange.title": "Mostrar el rango del precio de venta",
  "admin/editor.productSummaryPrice.showListPriceRange.title": "Mostrar el rango del precio de lista",
  "admin/editor.productSummaryPrice.showInstallments.title": "Mostrar cuotas",
  "admin/editor.productSummaryPrice.showDiscountValue.title": "Mostrar valor de descuento",
  "admin/editor.productSummaryPrice.showLabels.title": "Mostrar etiquetas",
  "admin/editor.productSummaryPrice.labelSellingPrice.title": "Etiqueta para el precio de venta",
  "admin/editor.productSummaryPrice.labelListPrice.title": "Etiqueta para el precio de lista",
  "admin/editor.productSummaryPrice.showBorders.title": "Mostrar bordes",
  "admin/editor.productSummaryImage.title": "Imagen del resumen del producto",
  "admin/editor.productSummaryImage.description": "El componente que muestra la imagen del producto",
  "admin/editor.productSummaryImage.hoverImageLabel.title": "Etiqueta de la imagen activable",
  "admin/editor.productSummaryImage.hoverImageLabel.description": "Esta propiedad está en desuso. Por favor, utiliza la propiedad de criterios.",
  "admin/editor.productSummaryImage.hoverImage.criteria.title": "Criterio para selección de imágenes activables",
  "admin/editor.productSummaryImage.hoverImage.criteria.label": "Etiqueta de la imagen activable",
  "admin/editor.productSummaryImage.hoverImage.criteria.matchCriteria": "Criterios de búsqueda de la etiqueta de la imagen activable (exact: busca la imagen que sea un resultado exacto para el nombre ingresado en el campo 'Etiqueta de la imagen activable' | contains: busca la primera imagen que contenga el texto ingresado en el campo 'Etiqueta de la imagen activable')",
  "admin/editor.productSummaryImage.hoverImage.criteria.index": "Índice de la imagen activable",
  "admin/editor.productSummaryImage.fetchpriority.title": "Sugiera la prioridad de la solicitud",
  "admin/editor.productSummaryImage.fetchpriority.high": "Alta prioridad",
  "admin/editor.productSummaryImage.fetchpriority.low": "Baja prioridad",
  "admin/editor.productSummaryImage.fetchpriority.auto": "Deja que el navegador decida la prioridad",
  "admin/editor.productSummaryImage.fetchpriority.byPosition": "Priorizar según su posición",
  "admin/editor.productSummaryBuyButton.title": "Botón de compra del resumen del producto",
  "admin/editor.productSummaryName.title": "Nombre del resumen del producto",
  "admin/editor.product-summary-specification-badges.title": "Insignias de especificación del resumen del producto",
  "admin/editor.productSummaryList.title": "Lista de productos",
  "admin/editor.productSummaryList.description": "Una lista de productos que tiene una colección",
  "admin/editor.productSummaryList.category.title": "ID de categoría",
  "admin/editor.productSummaryList.category.description": "Para las subcategorías, utiliza \"/\" (por ej.: 1/2/3)",
  "admin/editor.productSummaryList.collection.title": "Colección",
  "admin/editor.productSummaryList.orderBy.title": "Orden de la lista",
  "admin/editor.productSummaryList.hideUnavailableItems": "Ocultar los ítems no disponibles",
  "admin/editor.productSummaryList.maxItems.title": "Máximo de ítems",
  "admin/editor.productSummaryList.specificationFilters.title": "Filtros de especificación",
  "admin/editor.productSummaryList.specificationFilters.item.title": "Ítem del filtro de especificación",
  "admin/editor.productSummaryList.specificationFilters.item.id.title": "ID del filtro de especificación",
  "admin/editor.productSummaryList.specificationFilters.item.value.title": "Valor del filtro de especificación",
  "admin/editor.productSummaryList.orderType.sales": "Ventas",
  "admin/editor.productSummaryList.orderType.priceDesc": "Precio, descendente",
  "admin/editor.productSummaryList.orderType.priceAsc": "Precio, ascendente",
  "admin/editor.productSummaryList.orderType.nameAsc": "Nombre, ascendente",
  "admin/editor.productSummaryList.orderType.nameDesc": "Nombre, descendente",
  "admin/editor.productSummaryList.orderType.releaseDate": "Fecha de lanzamiento",
  "admin/editor.productSummaryList.orderType.discount": "Descuento",
  "admin/editor.productSummaryList.orderType.relevance": "Relevancia",
  "admin/editor.productSummaryList.skusFilter.title": "Filtro de SKU",
  "admin/editor.productSummaryList.skusFilter.description": "¡Establecer el primer filtro disponible puede agilizar tu consulta!",
  "admin/editor.productSummaryList.skusFilter.none": "Ninguno",
  "admin/editor.productSummaryList.skusFilter.first-available": "El primero disponible",
  "admin/editor.productSummaryList.skusFilter.all-available": "Todos los disponibles",
  "admin/editor.productSummaryList.installmentCriteria.title": "Cuotas mostradas",
  "admin/editor.productSummaryList.installmentCriteria.description": "Selecciona los tipos de cuotas que se mostrarán (por ej.: máximo sin interés o con interés).",
  "admin/editor.productSummaryList.installmentCriteria.max-without-interest": "Máximo sin interés",
  "admin/editor.productSummaryList.installmentCriteria.max-with-interest": "Máximo",
  "admin/editor.productSummaryList.analyticsListName.title": "Nombre de la lista en Analytics",
  "admin/editor.productSummary.trackListName.title": "Rastrear el nombre de la lista",
  "admin/editor.productSummary.sponsoredBadge.title": "Texto de la insignia de producto patrocinado, si corresponde",
  "admin/editor.productSummary.sponsoredBadge.position": "Posición de la insignia de producto patrocinado, si corresponde",
  "admin/editor.productSummary.sponsoredBadge.position.titleTop": "Sobre el nombre del producto",
  "admin/editor.productSummary.sponsoredBadge.position.containerTopLeft": "Sobre el container del producto",
  "admin/editor.productSummary.sponsoredBadge.position.none": "No mostrar"
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/docs/README.md from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```markdown
📢 Use this project, [contribute](https://github.com/vtex-apps/product-summary) to it or open issues to help evolve it using [Store Discussion](https://github.com/vtex-apps/store-discussion).

# Product Summary

<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->

Product Summary is an app for summarizing product information (such as name, price, and image) in other store blocks, such as the [Shelf](https://developers.vtex.com/docs/guides/vtex-shelf/) and the [Minicart](https://developers.vtex.com/docs/guides/vtex-minicart/).

![image](https://cdn.jsdelivr.net/gh/vtexdocs/dev-portal-content@main/images/vtex-product-summary-0.png)

## Configurating the product summary

1. Import the `vtex.product-summary` app to your theme's dependencies in the `manifest.json`:

```json
  "dependencies": {
    "vtex.product-summary": "2.x"
  }
```

Now, you can use all blocks exported by the `product-summary` app. See the full list below:

| Block name                                                                                | Description                                                                                |
| ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`list-context.product-list`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummarylist)                 | ![https://img.shields.io/badge/-Mandatory-red](https://img.shields.io/badge/-Mandatory-red) Renders the list of products in the Product Summary component. It fetches product information and provides it to the `product-summary.shelf` block. Then, this block provides its child blocks with the product information.                                                                 |
| `product-summary.shelf`                                                                                | ![https://img.shields.io/badge/-Mandatory-red](https://img.shields.io/badge/-Mandatory-red) Logical block that provides the needed structure for the Product Summary component through its child blocks (listed below).                                                                                |
| [`product-summary-attachment-list`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummaryattachmentlist) | Renders a list for product [attachments](https://help.vtex.com/tutorial/adding-an-attachment--7zHMUpuoQE4cAskqEUWScU).                                                                                |
| [`product-summary-brand`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummarybrand)                    | Renders the product brand.                                                                                |
| [`product-summary-buy-button`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummarybuybutton)           | Renders the Buy button. This block must only be configured if your store uses the [Minicart v1](https://github.com/vtex-apps/minicart/blob/383d7bbd3295f06d1b5854a0add561a872e1515c/docs/README.md). If your store uses the [Minicart v2](https://developers.vtex.com/docs/guides/vtex-minicart), please configure the [**Add to Cart button**](https://developers.vtex.com/docs/guides/vtex-add-to-cart-button) instead.                   |
| [`product-summary-description`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummarydescription)        | Renders the product description.                                                                                |
| [`product-summary-image`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummaryimage)                    | Renders the product image.                                                                                |
| [`product-summary-name`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummaryname)                      | Renders the product name.                                                                                |
| [`product-summary-sku-name`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummaryskuname)               | Renders the selected SKU name.                                                                                |
| `product-summary-price`                                                                                | ![https://img.shields.io/badge/-Deprecated-red](https://img.shields.io/badge/-Deprecated-red) Renders the product price. This block has been deprecated in favor of the [Product Price](https://cdn.jsdelivr.net/gh/vtexdocs/dev-portal-content@main/images/vtex-product-summary-3.png) app. Although support for this block is still available, we strongly recommend using the Product Price app.                                         |
| [`product-summary-sku-selector`](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummaryskuselector)       | Renders the SKU Selector block.                                                                                |
| [`product-specification-badges`](https://developers.vtex.com/vtex-developer-docs/docs/vtex-product-summary-productsummaryspecificationbadges) | Renders badges based on product specifications. |

2. Add the `list-context.product-list` block to a store template of your choice, and declare the `product-summary.shelf` in its block list. For example:

```json
{
  "list-context.product-list": {
    "blocks": ["product-summary.shelf"]
  },
```

> ℹ Although the block name 'product-summary.shelf' alludes to the Shelf component, it is not necessary to use this block in order to create a shelf component. The Product Summary Shelf displays a summary of the product information in other components, such as the [Minicart](https://developers.vtex.com/docs/guides/vtex-minicart) and the [Search Results](https://developers.vtex.com/docs/guides/vtex-search-result) pages.

3. Add the blocks from the list above as children of the `product-summary.shelf`, considering the product information you want to display in the product list. Take the following example in which the product name, description, image, price, SKU selector, and Buy button are all displayed in the Product Summary:

```json
{
  "list-context.product-list": {
    "blocks": ["product-summary.shelf"]
  },

  "product-summary.shelf": {
    "children": [
      "product-summary-name",
      "product-summary-description",
      "product-summary-image",
      "product-summary-price",
      "product-summary-sku-selector",
      "product-summary-buy-button"
    ]
  }
}
```

## Customization

To apply CSS customizations to this and other blocks, follow the instructions given in the recipe on [Using CSS handles for store customization](https://developers.vtex.com/docs/guides/vtex-io-documentation-using-css-handles-for-store-customization).

| CSS handles                |
| -------------------------- |
| `aspectRatio`              |
| `buyButton`                |
| `buyButtonContainer`       |
| `clearLink`                |
| `column`                   |
| `container`                |
| `containerNormal`          |
| `containerSmall`           |
| `containerInline`          |
| `description`              |
| `element`                  |
| `image`                    |
| `imageContainer`           |
| `imagePlaceholder`         |
| `information`              |
| `isHidden`                 |
| `nameContainer`            |
| `priceContainer`           |
| `quantityStepperContainer` |
| `spacer`                   |

<!-- DOCS-IGNORE:start -->

## Contributors ✨

Thanks goes to these wonderful people:

<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
<!-- prettier-ignore-start -->
<!-- markdownlint-disable -->
<table>
  <tr>
    <td align="center"><a href="https://github.com/gustavopvasconcellos"><img src="https://avatars1.githubusercontent.com/u/49173685?v=4" width="100px;" alt=""/><br /><sub><b>gustavopvasconcellos</b></sub></a><br /><a href="https://github.com/vtex-apps/product-summary/commits?author=gustavopvasconcellos" title="Code">💻</a></td>
    <td align="center"><a href="http://imdanielpiva.me"><img src="https://avatars0.githubusercontent.com/u/26178791?v=4" width="100px;" alt=""/><br /><sub><b>Daniel Piva</b></sub></a><br /><a href="https://github.com/vtex-apps/product-summary/commits?author=imdanielpiva" title="Code">💻</a></td>
  </tr>
</table>

<!-- markdownlint-enable -->
<!-- prettier-ignore-end -->
<!-- ALL-CONTRIBUTORS-LIST:END -->

This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind are welcome!
<!-- DOCS-IGNORE:end -->

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummarySKUName.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'
import type { CssHandlesTypes } from 'vtex.css-handles'
import { ProductSummaryContext } from 'vtex.product-summary-context'

const { useProductSummary } = ProductSummaryContext

const CSS_HANDLES = ['skuNameContainer'] as const

interface Props {
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

const ProductSummarySKUName = ({ classes }: Props) => {
  const { product } = useProductSummary()
  const { handles } = useCssHandles(CSS_HANDLES, { classes })
  const skuName: string = product?.sku?.name ?? ''
  const { productName } = product

  if (!skuName || skuName === productName) {
    return null
  }

  return <div className={handles.skuNameContainer}>{skuName}</div>
}

export default ProductSummarySKUName

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryAttachmentList.js from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```javascript
import AttachmentList from './components/ProductSummaryAttachmentList/AttachmentList'

export default AttachmentList

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummarySKUSelector.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { SKUSelector } from 'vtex.store-components'
import { useCssHandles, useCustomClasses } from 'vtex.css-handles'
import type { CssHandlesTypes } from 'vtex.css-handles'
import { ProductSummaryContext } from 'vtex.product-summary-context'
import type { ProductSummaryTypes } from 'vtex.product-summary-context'
import type { ResponsiveValuesTypes } from 'vtex.responsive-values'
import type { ProductTypes } from 'vtex.product-context'

import { getDefaultSeller } from './modules/seller'

const { useProductSummary, useProductSummaryDispatch } = ProductSummaryContext

const CSS_HANDLES = [
  'SKUSelectorContainer',
  'frameAround',
  'seeMoreButton',
  'skuSelectorContainer',
  'skuSelectorInternalBox',
  'skuSelectorItemImageValue',
  'skuSelectorItemImage',
  'skuSelectorItemTextValue',
  'skuSelectorItem',
  'skuSelectorNameContainer',
  'skuSelectorNameSeparator',
  'skuSelectorName',
  'skuSelectorOptionsList',
  'skuSelectorSelectorImageValue',
  'skuSelectorSubcontainer',
  'skuSelectorTextContainer',
  'valueWrapper',
] as const

interface Props {
  skuItems: ProductTypes.Item[]
  skuSelected: ProductTypes.Item | null
  onSKUSelected?: (skuId: string) => void
  maxItems?: number
  visibility?: string
  seeMoreLabel: string
  hideImpossibleCombinations?: boolean
  showValueNameForImageVariation?: boolean
  showValueForVariation?: 'none' | 'image' | 'all'
  imageHeight?: ResponsiveValuesTypes.ResponsiveValue<number>
  imageWidth?: ResponsiveValuesTypes.ResponsiveValue<number>
  thumbnailImage?: string
  visibleVariations?: string[]
  showVariationsLabels?: boolean
  variationsSpacing?: number
  showVariationsErrorMessage?: boolean
  initialSelection?: 'complete' | 'image' | 'empty'
  displayMode?: ResponsiveValuesTypes.ResponsiveValue<
    'select' | 'default' | 'slider'
  >
  sliderDisplayThreshold?: number
  sliderArrowSize?: number
  sliderItemsPerPage?: ResponsiveValuesTypes.ResponsiveValue<number>
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function ProductSummarySKUSelector(props: Props) {
  const { handles } = useCssHandles(CSS_HANDLES, { classes: props.classes })
  const dispatch = useProductSummaryDispatch()
  const { product } = useProductSummary()

  const skuSelectorClasses = useCustomClasses(
    () => ({
      frameAround: [
        {
          name: `${handles.frameAround} vtex-store-components-3-x-frameAround`,
          applyModifiers: true,
        },
      ],
      seeMoreButton: [
        {
          name: `${handles.seeMoreButton} vtex-store-components-3-x-seeMoreButton`,
          applyModifiers: true,
        },
      ],
      skuSelectorContainer: [
        {
          name: `${handles.skuSelectorContainer} vtex-store-components-3-x-skuSelectorContainer`,
          applyModifiers: true,
        },
      ],
      skuSelectorInternalBox: [
        {
          name: `${handles.skuSelectorInternalBox} vtex-store-components-3-x-skuSelectorInternalBox`,
          applyModifiers: true,
        },
      ],
      skuSelectorItemImageValue: [
        {
          name: `${handles.skuSelectorItemImageValue} vtex-store-components-3-x-skuSelectorItemImageValue`,
          applyModifiers: true,
        },
      ],
      skuSelectorItemImage: [
        {
          name: `${handles.skuSelectorItemImage} vtex-store-components-3-x-skuSelectorItemImage`,
          applyModifiers: true,
        },
      ],
      skuSelectorItemTextValue: [
        {
          name: `${handles.skuSelectorItemTextValue} vtex-store-components-3-x-skuSelectorItemTextValue`,
          applyModifiers: true,
        },
      ],
      skuSelectorItem: [
        {
          name: `${handles.skuSelectorItem} vtex-store-components-3-x-skuSelectorItem`,
          applyModifiers: true,
        },
      ],
      skuSelectorNameContainer: [
        {
          name: `${handles.skuSelectorNameContainer} vtex-store-components-3-x-skuSelectorNameContainer`,
          applyModifiers: true,
        },
      ],
      skuSelectorNameSeparator: [
        {
          name: `${handles.skuSelectorNameSeparator} vtex-store-components-3-x-skuSelectorNameSeparator`,
          applyModifiers: true,
        },
      ],
      skuSelectorName: [
        {
          name: `${handles.skuSelectorName} vtex-store-components-3-x-skuSelectorName`,
          applyModifiers: true,
        },
      ],
      skuSelectorOptionsList: [
        {
          name: `${handles.skuSelectorOptionsList} vtex-store-components-3-x-skuSelectorOptionsList`,
          applyModifiers: true,
        },
      ],
      skuSelectorSelectorImageValue: [
        {
          name: `${handles.skuSelectorSelectorImageValue} vtex-store-components-3-x-skuSelectorSelectorImageValue`,
          applyModifiers: true,
        },
      ],
      skuSelectorSubcontainer: [
        {
          name: `${handles.skuSelectorSubcontainer} vtex-store-components-3-x-skuSelectorSubcontainer`,
          applyModifiers: true,
        },
      ],
      skuSelectorTextContainer: [
        {
          name: `${handles.skuSelectorTextContainer} vtex-store-components-3-x-skuSelectorTextContainer`,
          applyModifiers: true,
        },
      ],
      valueWrapper: [
        {
          name: `${handles.valueWrapper} vtex-store-components-3-x-valueWrapper`,
          applyModifiers: true,
        },
      ],
    }),
    [handles]
  )

  const stopBubblingUp: React.MouseEventHandler<HTMLDivElement> = (e) => {
    e.preventDefault()
    e.stopPropagation()
  }

  const handleSKUSelected = (skuId: string | null) => {
    if (skuId == null) {
      dispatch({
        type: 'SET_PRODUCT_QUERY',
        args: { query: '' },
      })

      return
    }

    const selectedItem =
      product.items &&
      (product.items.find(
        (item) => item.itemId === skuId
      ) as ProductSummaryTypes.SKU)

    const sku = {
      ...selectedItem,
      image: selectedItem.images[0],
      seller: getDefaultSeller(
        selectedItem.sellers
      ) as ProductSummaryTypes.Seller,
    }

    const newProduct = {
      ...product,
      selectedItem,
      sku,
    }

    dispatch({
      type: 'SET_PRODUCT',
      args: { product: newProduct },
    })

    dispatch({
      type: 'SET_PRODUCT_QUERY',
      args: { query: `skuId=${skuId}` },
    })
  }

  return (
    // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
    <div onClick={stopBubblingUp} className={handles.SKUSelectorContainer}>
      <SKUSelector
        onSKUSelected={handleSKUSelected}
        {...props}
        classes={skuSelectorClasses}
      />
    </div>
  )
}

export default ProductSummarySKUSelector

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryImage.test.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { render, fireEvent } from '@vtex/test-tools/react'
import { ProductSummaryContext } from 'vtex.product-summary-context'

import ProductImage from './ProductSummaryImage'

test('should show placeholder on error', () => {
  const productName = 'Ball'
  const mock = {
    product: {
      productName,
      sku: { image: { imageUrl: 'foo' } },
    },
  }

  const { getByAltText, getByTestId } = render(
    // @ts-expect-error We are not providing the full context
    <ProductSummaryContext.ProductSummaryProvider value={mock}>
      <ProductImage />
    </ProductSummaryContext.ProductSummaryProvider>
  )

  const image = getByAltText(productName)

  fireEvent.error(image)
  expect(getByTestId('image-placeholder')).toBeDefined()
})

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/Spacer.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'

const CSS_HANDLES = ['spacer'] as const

function Spacer() {
  const { handles } = useCssHandles(CSS_HANDLES)

  return <div className={`${handles.spacer} flex flex-grow-1`} />
}

export default Spacer

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryName.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { ProductName } from 'vtex.store-components'
import { useCssHandles } from 'vtex.css-handles'
import type { CssHandlesTypes } from 'vtex.css-handles'
import { ProductSummaryContext } from 'vtex.product-summary-context'
import { SponsoredBadgePosition } from 'vtex.product-summary-context/react/ProductSummaryTypes'
import { useIntl } from 'react-intl'

import shouldShowSponsoredBadge from './utils/shouldShowSponsoredBadge'

const { useProductSummary } = ProductSummaryContext

const CSS_HANDLES = [
  'nameContainer',
  'nameWrapper',
  'brandName',
  'skuName',
  'productReference',
  'productNameLoader',
] as const

const defaultShowFields = {
  showProductReference: false,
  showBrandName: false,
  showSku: false,
}

interface Props {
  /**
   * Defines the visibility on certain properties.
   * @default { showProductReference: false, showBrandName: false, showSku: false }
   */
  showFieldsProps?: {
    showProductReference: boolean
    showBrandName: boolean
    showSku: boolean
  }
  /**
   * HTML tag used. It can be: `div`, `h1`, `h2`, `h3`.
   * @default "h3"
   */
  tag?: 'div' | 'h1' | 'h2' | 'h3'
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function ProductSummaryName({
  showFieldsProps = defaultShowFields,
  tag = 'h3',
  classes,
}: Props) {
  const { product, sponsoredBadge } = useProductSummary()
  const { handles } = useCssHandles(CSS_HANDLES, { classes })
  const productName = product?.productName
  // TODO: change ProductSummaryContext to have `selectedSku` field instead of `sku`
  const skuName = product?.sku?.name
  const brandName = product?.brand

  const showSponsoredBadge = shouldShowSponsoredBadge(
    product,
    sponsoredBadge?.position as SponsoredBadgePosition,
    'titleTop'
  )

  const containerClasses = `${handles.nameContainer} flex items-start justify-center pv6`
  const wrapperClasses = `${handles.nameWrapper} overflow-hidden c-on-base f5`
  const brandNameClasses = `${handles.brandName} t-body`
  const skuNameClasses = `${handles.skuName} t-small`
  const loaderClasses = `${handles.productNameLoader} pt5 overflow-hidden`
  const intl = useIntl()

  return (
    <div
      className={containerClasses}
      aria-label={intl.formatMessage(
        { id: 'store/product-summary.name.aria-label' },
        { productName: product.productName }
      )}
    >
      <ProductName
        className={wrapperClasses}
        brandNameClass={brandNameClasses}
        skuNameClass={skuNameClasses}
        loaderClass={loaderClasses}
        showSponsoredBadge={showSponsoredBadge}
        sponsoredBadgeLabel={sponsoredBadge?.label}
        productReferenceClass={handles.productReference}
        name={productName}
        skuName={skuName}
        brandName={brandName}
        tag={tag}
        {...showFieldsProps}
      />
    </div>
  )
}

ProductSummaryName.schema = {
  title: 'admin/editor.productSummaryName.title',
  type: 'object',
  properties: {
    showFieldsProps: ProductName.schema,
  },
}

export default ProductSummaryName

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/productSummary.css from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```css
.containerInline .imageContainer {
  max-width: 30%;
}

.containerInline .imageContainer > img {
  max-height: 100%;
}

.container {
}
.containerSmall {
}
.containerInline {
}
.containerInlinePrice {
}
.information {
}
.element {
}
.image {
}
.buyButtonContainer {
}
.buyButton {
}
.description {
}
.quantityStepperContainer {
}
.imageInline {
  max-height: 7rem;
}

.isHidden {
  visibility: hidden;
}

.containerNormal {
}

.nameContainer {
}

.clearLink {
  text-decoration: inherit;
  color: inherit;
}

.aspectRatio {
  padding-top: 100%;
  background: center / contain no-repeat;
}

.priceContainer {
}

.SKUSelectorContainer {
}

.attachmentListContainer {
}

.attachmentItemContainer {
}

.imageNormal {
  max-height: 17.56rem;
}

.addToListBtn {
}

.imagePlaceholder {
}

.attachmentChildrenContainer {
}

.imageStackContainer {
}

.hoverEffect {
}

.hoverEffect:hover .hoverImage {
  display: inline;
}

.hoverEffect:hover .mainImageHovered {
  opacity: 0;
}

.hoverImage {
}

.priceSpinner {
  border: 2px solid rgba(0, 0, 0, 0.1);
  border-left-color: #4d4d4d;
  border-radius: 50%;
  width: 20px;
  height: 20px;
  animation: spin 0.5s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.sponsoredBadgeContainer {
}

.sponsoredBadgeText {
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryCustom.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import classNames from 'classnames'
import type { PropsWithChildren } from 'react'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import type { CssHandlesTypes } from 'vtex.css-handles'
import { useCssHandles } from 'vtex.css-handles'
import { useOnView } from 'vtex.on-view'
import type { ProductTypes } from 'vtex.product-context'
import { ProductContextProvider } from 'vtex.product-context'
import { ProductListContext } from 'vtex.product-list-context'
import type { ProductSummaryTypes } from 'vtex.product-summary-context'
import { ProductSummaryContext } from 'vtex.product-summary-context'
import { SponsoredBadgePosition } from 'vtex.product-summary-context/react/ProductSummaryTypes'
import { Link, useRuntime } from 'vtex.render-runtime'
import { useIntl } from 'react-intl'

import LocalProductSummaryContext from './ProductSummaryContext'
import ProductPriceSimulationWrapper from './components/ProductPriceSimulationWrapper'
import { SponsoredBadge } from './components/SponsoredBadge'
import { mapCatalogProductToProductSummary } from './utils/normalize'
import shouldShowSponsoredBadge from './utils/shouldShowSponsoredBadge'

const {
  ProductSummaryProvider,
  useProductSummaryDispatch,
  useProductSummary,
} = ProductSummaryContext

const PRODUCT_SUMMARY_MAX_WIDTH = 300
const CSS_HANDLES = [
  'container',
  'containerNormal',
  'element',
  'clearLink',
] as const

function ProductSummaryCustom({
  product,
  actionOnClick,
  children,
  href,
  priceBehavior = 'default',
  position,
  classes,
}: PropsWithChildren<Props>) {
  const {
    isLoading,
    isHovering,
    selectedItem,
    listName,
    query,
    inView,
    sponsoredBadge,
  } = useProductSummary()

  const dispatch = useProductSummaryDispatch()
  const { handles } = useCssHandles(CSS_HANDLES, { classes })
  const { getSettings } = useRuntime()

  const settings = getSettings('vtex.store')
  const useSemanticHtml = settings?.advancedSettings?.a11ySemanticHtmlMigration

  const productListDispatch = ProductListContext.useProductListDispatch()

  const productListState = ProductListContext.useProductListState()
  const autocompleteSummary =
    productListState?.listName === 'autocomplete-result-list'

  const inViewRef = useRef<HTMLDivElement | null>(null)
  const onView = useCallback(() => {
    productListDispatch?.({
      type: 'SEND_IMPRESSION',
      args: { product, position },
    })

    dispatch({
      type: 'SET_IN_VIEW',
      args: { inView: true },
    })
  }, [dispatch, productListDispatch, product, position])

  useOnView({
    ref: inViewRef,
    once: true,
    onView,
  })

  useEffect(() => {
    if (product) {
      dispatch({
        type: 'SET_PRODUCT',
        args: { product },
      })
    }
  }, [product, dispatch])

  useEffect(() => {
    if (position) {
      dispatch({
        // @ts-expect-error - Depends on vtex.product-summary-context update on PR: https://github.com/vtex-apps/product-summary-context/pull/25
        type: 'SET_POSITION',
        // @ts-expect-error - Depends on vtex.product-summary-context update on PR: https://github.com/vtex-apps/product-summary-context/pull/25
        args: { position },
      })
    }
  }, [position, dispatch])

  const handleMouseLeave = useCallback(() => {
    dispatch({
      type: 'SET_HOVER',
      args: { isHovering: false },
    })
  }, [dispatch])

  const handleMouseEnter = useCallback(() => {
    dispatch({
      type: 'SET_HOVER',
      args: { isHovering: true },
    })
  }, [dispatch])

  const handleItemsStateUpdate = useCallback(
    (loading) => {
      dispatch({
        type: 'SET_LOADING',
        args: { isLoading: loading },
      })
    },
    [dispatch]
  )

  const oldContextProps = useMemo(
    () => ({
      product,
      isLoading,
      isHovering,
      handleItemsStateUpdate,
    }),
    [product, isLoading, isHovering, handleItemsStateUpdate]
  )

  const containerClasses = classNames(
    handles.container,
    handles.containerNormal,
    'br3 h-100 w-100 flex flex-column justify-between center tc',
    !useSemanticHtml && 'overflow-hidden'
  )

  const summaryClasses = classNames(
    handles.element,
    'pointer pt3 pb4 flex flex-column h-100'
  )

  const linkClasses = classNames(
    handles.clearLink,
    'h-100 flex flex-column',
    useSemanticHtml && 'focus-visible:outline-2 focus-visible:outline-blue-500'
  )

  const skuId = selectedItem?.itemId ?? product?.sku?.itemId

  const linkProps = href
    ? {
        to: href,
        onClick: autocompleteSummary ? actionOnClick : undefined,
        onClickCapture: autocompleteSummary ? undefined : actionOnClick,
        'aria-label': `View product details for ${
          product?.productName || 'product'
        }`,
      }
    : {
        page: 'store.product',
        params: {
          slug: product?.linkText,
          id: product?.productId,
          __listName: listName,
        },
        query,
        onClick: autocompleteSummary ? actionOnClick : undefined,
        onClickCapture: autocompleteSummary ? undefined : actionOnClick,
        'aria-label': `View product details for ${
          product?.productName || 'product'
        }`,
      }

  const eventParameters =
    (product.advertisement as any)?.eventParameters ??
    product.advertisement?.adId

  const showSponsoredBadge = shouldShowSponsoredBadge(
    product,
    sponsoredBadge?.position as SponsoredBadgePosition,
    'containerTopLeft'
  )

  const intl = useIntl()

  return (
    <LocalProductSummaryContext.Provider value={oldContextProps}>
      <ProductContextProvider
        product={(product as unknown) as ProductTypes.Product}
        query={{ skuId }}
      >
        <ProductPriceSimulationWrapper
          product={product}
          inView={inView}
          priceBehavior={priceBehavior}
        >
          <section
            aria-label={intl.formatMessage(
              { id: 'store/product-summary.shelf.aria-label' },
              { productName: product.productName }
            )}
            className={containerClasses}
            onMouseEnter={handleMouseEnter}
            onMouseLeave={handleMouseLeave}
            style={{ maxWidth: PRODUCT_SUMMARY_MAX_WIDTH }}
            ref={inViewRef}
            data-van-aid={eventParameters}
          >
            <Link className={linkClasses} {...linkProps}>
              <article className={summaryClasses}>
                {showSponsoredBadge ? (
                  <SponsoredBadge label={sponsoredBadge?.label} />
                ) : null}
                {children}
              </article>
            </Link>
          </section>
        </ProductPriceSimulationWrapper>
      </ProductContextProvider>
    </LocalProductSummaryContext.Provider>
  )
}

interface Props {
  product: ProductSummaryTypes.Product
  /** Function that is executed when a product is clicked */
  actionOnClick?: () => void
  href?: string
  /**
   * Whether the client will request the simulation API for all sellers ("async"), only for the 1P seller ("asyncOnly1P") or if will not request the API ("default")
   * @default "default"
   */
  priceBehavior?: 'async' | 'asyncOnly1P' | 'default'
  /**
   * Name of the list the Product Summary is in. Should be set by a Shelf or the Search Result gallery.
   */
  listName?: string
  /**
   * Whether the listName should be sent to product pages via querystring
   * @default true
   */
  trackListName?: boolean
  /**
   * The position of the Product Summary in a list of Product Summaries. Used by the
   * ProductImpressions event.
   */
  position?: number
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  /**
   * The predefined position of the sponsored badge, if applicable.
   */
  sponsoredBadgePosition?: SponsoredBadgePosition
  /**
   * The label of the sponsored badge, if applicable.
   */
  sponsoredBadgeLabel?: string
  /**
   * Where this ProductSummary is being shown. Used for analytics. E.g. "search" or "shelf".
   */
  placement?: string
}

function ProductSummaryWrapper({
  product,
  actionOnClick,
  href,
  priceBehavior = 'default',
  trackListName = true,
  listName,
  position,
  sponsoredBadgePosition,
  sponsoredBadgeLabel,
  placement,
  classes,
  children,
}: PropsWithChildren<Props>) {
  const sponsoredBadge = {
    position: sponsoredBadgePosition,
    label: sponsoredBadgeLabel,
  }

  return (
    <ProductSummaryProvider
      product={product}
      listName={trackListName ? listName : undefined}
      sponsoredBadge={sponsoredBadge}
      isPriceLoading={
        priceBehavior === 'async' || priceBehavior === 'asyncOnly1P'
      }
    >
      <ProductSummaryCustom
        product={product}
        href={href}
        actionOnClick={actionOnClick}
        priceBehavior={priceBehavior}
        position={position}
        placement={placement}
        classes={classes}
      >
        {children}
      </ProductSummaryCustom>
    </ProductSummaryProvider>
  )
}

ProductSummaryWrapper.schema = {
  title: 'admin/editor.productSummary.title',
  description: 'admin/editor.productSummary.description',
}

// This function is public available to be used only by vtex.shelf and vtex.search-result.
// We do not garantee this API will not change and might happen breaking change anytime.
ProductSummaryWrapper.mapCatalogProductToProductSummary = mapCatalogProductToProductSummary

export default ProductSummaryWrapper

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryLegacy.js from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```javascript
import ProductSummaryLegacy from './legacy'

export default ProductSummaryLegacy

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryList.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { ComponentType, PropsWithChildren } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { useQuery } from 'react-apollo'
import { usePixel } from 'vtex.pixel-manager'
import { QueryProducts } from 'vtex.store-resources'
import { ProductList as ProductListStructuredData } from 'vtex.structured-data'
// eslint-disable-next-line no-restricted-imports
import { equals } from 'ramda'
import { canUseDOM } from 'vtex.render-runtime'

import ProductSummaryListWithoutQuery, {
  PRODUCT_LIST_PLACEMENT,
} from './ProductSummaryListWithoutQuery'
import useSession from './hooks/useSession'
import { PreferenceType } from './utils/normalize'

const DEFAULT_SHOW_SPONSORED_PRODUCTS = false
const DEFAULT_SPONSORED_COUNT = 2
const DEFAULT_REPEAT_SPONSORED_PRODUCTS = false

const ORDER_BY_OPTIONS = {
  RELEVANCE: {
    name: 'admin/editor.productSummaryList.orderType.relevance',
    value: '',
  },
  TOP_SALE_DESC: {
    name: 'admin/editor.productSummaryList.orderType.sales',
    value: 'OrderByTopSaleDESC',
  },
  PRICE_DESC: {
    name: 'admin/editor.productSummaryList.orderType.priceDesc',
    value: 'OrderByPriceDESC',
  },
  PRICE_ASC: {
    name: 'admin/editor.productSummaryList.orderType.priceAsc',
    value: 'OrderByPriceASC',
  },
  NAME_ASC: {
    name: 'admin/editor.productSummaryList.orderType.nameAsc',
    value: 'OrderByNameASC',
  },
  NAME_DESC: {
    name: 'admin/editor.productSummaryList.orderType.nameDesc',
    value: 'OrderByNameDESC',
  },
  RELEASE_DATE_DESC: {
    name: 'admin/editor.productSummaryList.orderType.releaseDate',
    value: 'OrderByReleaseDateDESC',
  },
  BEST_DISCOUNT_DESC: {
    name: 'admin/editor.productSummaryList.orderType.discount',
    value: 'OrderByBestDiscountDESC',
  },
}

function getCookie(cname: string) {
  if (!canUseDOM) {
    return ''
  }

  const name = `${cname}=`
  const decodedCookie = decodeURIComponent(document.cookie)
  const ca = decodedCookie.split(';')

  for (let i = 0; i < ca.length; i++) {
    let c = ca[i]

    while (c.charAt(0) === ' ') {
      c = c.substring(1)
    }

    if (c.indexOf(name) === 0) {
      return c.substring(name.length, c.length)
    }
  }

  return ''
}

const parseFilters = ({ id, value }: { id: string; value: string }) =>
  `specificationFilter_${id}:${value}`

function getOrdinationProp(attribute: 'name' | 'value') {
  return Object.keys(ORDER_BY_OPTIONS).map(
    (key) => ORDER_BY_OPTIONS[key as keyof typeof ORDER_BY_OPTIONS][attribute]
  )
}

export interface ProductClickParams {
  position: number
}

interface SpecificationFilter {
  id: string
  value: string
}

interface Props {
  /** Category ID of the listed items. For sub-categories, use "/" (e.g. "1/2/3") */
  category?: string
  /** Specification Filters of the listed items. */
  specificationFilters?: SpecificationFilter[]
  /** Filter by collection. */
  collection?: string
  /**
   * Ordination type of the items. Possible values: `''`, `OrderByTopSaleDESC`, `OrderByReleaseDateDESC`, `OrderByBestDiscountDESC`, `OrderByPriceDESC`, `OrderByPriceASC`, `OrderByNameASC`, `OrderByNameDESC`
   * @default ""
   */
  orderBy?:
    | ''
    | 'OrderByTopSaleDESC'
    | 'OrderByPriceDESC'
    | 'OrderByPriceASC'
    | 'OrderByNameASC'
    | 'OrderByNameDESC'
    | 'OrderByReleaseDateDESC'
    | 'OrderByBestDiscountDESC'
  /** Hides items that are unavailable. */
  hideUnavailableItems?: boolean
  /**
   * Maximum items to be fetched.
   * @default 10
   */
  maxItems?: number
  /**
   * Control SKUs returned for each product in the query. The less SKUs needed to be returned, the more performant your shelf query will be.
   * @default "ALL_AVAILABLE"
   */
  skusFilter?: 'ALL_AVAILABLE' | 'ALL' | 'FIRST_AVAILABLE'
  /**
   * Control what price to be shown when price has different installments options.
   * @default "MAX_WITHOUT_INTEREST"
   */
  installmentCriteria?: 'MAX_WITHOUT_INTEREST' | 'MAX_WITH_INTEREST'
  /**
   * Name of the list property on Google Analytics events.
   */
  listName?: string
  /**
   * Logic to enable which SKU will be the selected item
   * */
  preferredSKU?: PreferenceType
  /** Slot of a product summary. */
  ProductSummary: ComponentType<{ product: any; actionOnClick: any }>
  /** Callback on product click. */
  actionOnProductClick?: (product: any) => void
  /** Whether or not to show sponsored products in this list. */
  showSponsoredProducts: boolean
  /** Maximum number of sponsored products to put on top of the regular products. */
  sponsoredCount: number
  /** If true, sponsored and regular products will be repeated on the list. */
  repeatSponsoredProducts: boolean
}

function ProductSummaryList(props: PropsWithChildren<Props>) {
  const {
    category = '',
    collection,
    hideUnavailableItems = false,
    orderBy = ORDER_BY_OPTIONS.RELEVANCE.value,
    specificationFilters = [],
    maxItems = 10,
    skusFilter,
    installmentCriteria,
    children,
    listName: rawListName,
    ProductSummary,
    actionOnProductClick,
    preferredSKU,
    showSponsoredProducts = DEFAULT_SHOW_SPONSORED_PRODUCTS,
    sponsoredCount = DEFAULT_SPONSORED_COUNT,
    repeatSponsoredProducts = DEFAULT_REPEAT_SPONSORED_PRODUCTS,
  } = props

  const [shippingOptions, setShippingOptions] = useState([])

  const { getShippingOptionFromSession } = useSession()

  useEffect(() => {
    async function getShippingFromSession() {
      const result = await getShippingOptionFromSession()

      if (result) {
        setShippingOptions((currentShippingOptions) =>
          equals(currentShippingOptions, result)
            ? currentShippingOptions
            : result
        )
      }
    }

    getShippingFromSession()
  }, [getShippingOptionFromSession])

  const { push } = usePixel()
  const { data, loading, error } = useQuery(QueryProducts, {
    variables: {
      category,
      ...(collection != null
        ? {
            collection,
          }
        : {}),
      specificationFilters: specificationFilters.map(parseFilters),
      orderBy,
      from: 0,
      to: maxItems - 1,
      shippingOptions,
      hideUnavailableItems,
      skusFilter,
      installmentCriteria,
      variant: getCookie('sp-variant'),
      advertisementOptions: {
        showSponsored: showSponsoredProducts,
        sponsoredCount,
        repeatSponsoredProducts,
        advertisementPlacement: PRODUCT_LIST_PLACEMENT,
      },
    },
  })

  const { products } = data ?? {}
  // Not using ?? operator because listName can be ''
  // eslint-disable-next-line no-unneeded-ternary
  const listName = rawListName ? rawListName : 'List of products'

  const productClick = useCallback(
    (product: any, productClickParams?: ProductClickParams) => {
      actionOnProductClick?.(product)

      const { position } = productClickParams ?? {}

      push({
        event: 'productClick',
        list: listName,
        product,
        position,
      })
    },
    [push, actionOnProductClick, listName]
  )

  if (loading || error) {
    return null
  }

  return (
    <ProductSummaryListWithoutQuery
      products={products}
      listName={listName}
      ProductSummary={ProductSummary}
      actionOnProductClick={productClick}
      preferredSKU={preferredSKU}
    >
      <ProductListStructuredData products={products} />
      {children}
    </ProductSummaryListWithoutQuery>
  )
}

ProductSummaryList.schema = {
  title: 'admin/editor.productSummaryList.title',
  description: 'admin/editor.productSummaryList.description',
  type: 'object',
  properties: {
    category: {
      title: 'admin/editor.productSummaryList.category.title',
      description: 'admin/editor.productSummaryList.category.description',
      type: 'string',
      isLayout: false,
    },
    specificationFilters: {
      title: 'admin/editor.productSummaryList.specificationFilters.title',
      type: 'array',
      items: {
        title:
          'admin/editor.productSummaryList.specificationFilters.item.title',
        type: 'object',
        properties: {
          id: {
            type: 'string',
            title:
              'admin/editor.productSummaryList.specificationFilters.item.id.title',
          },
          value: {
            type: 'string',
            title:
              'admin/editor.productSummaryList.specificationFilters.item.value.title',
          },
        },
      },
    },
    collection: {
      title: 'admin/editor.productSummaryList.collection.title',
      type: 'string',
      isLayout: false,
    },
    orderBy: {
      title: 'admin/editor.productSummaryList.orderBy.title',
      type: 'string',
      enum: getOrdinationProp('value'),
      enumNames: getOrdinationProp('name'),
      default: ORDER_BY_OPTIONS.RELEVANCE.value,
      isLayout: false,
    },
    hideUnavailableItems: {
      title: 'admin/editor.productSummaryList.hideUnavailableItems',
      type: 'boolean',
      default: false,
      isLayout: false,
    },
    maxItems: {
      title: 'admin/editor.productSummaryList.maxItems.title',
      type: 'number',
      isLayout: false,
      default: 10,
    },
    skusFilter: {
      title: 'admin/editor.productSummaryList.skusFilter.title',
      description: 'admin/editor.productSummaryList.skusFilter.description',
      type: 'string',
      default: 'ALL_AVAILABLE',
      enum: ['ALL_AVAILABLE', 'ALL', 'FIRST_AVAILABLE'],
      enumNames: [
        'admin/editor.productSummaryList.skusFilter.all-available',
        'admin/editor.productSummaryList.skusFilter.none',
        'admin/editor.productSummaryList.skusFilter.first-available',
      ],
    },
    installmentCriteria: {
      title: 'admin/editor.productSummaryList.installmentCriteria.title',
      description:
        'admin/editor.productSummaryList.installmentCriteria.description',
      type: 'string',
      default: 'MAX_WITHOUT_INTEREST',
      enum: ['MAX_WITHOUT_INTEREST', 'MAX_WITH_INTEREST'],
      enumNames: [
        'admin/editor.productSummaryList.installmentCriteria.max-without-interest',
        'admin/editor.productSummaryList.installmentCriteria.max-with-interest',
      ],
    },
    listName: {
      title: 'admin/editor.productSummaryList.analyticsListName.title',
      type: 'string',
    },
    showSponsoredProducts: {
      title: 'admin/editor.productSummaryList.showSponsoredProducts.title',
      description:
        'admin/editor.productSummaryList.showSponsoredProducts.description',
      type: 'boolean',
      default: DEFAULT_SHOW_SPONSORED_PRODUCTS,
    },
    sponsoredCount: {
      title: 'admin/editor.productSummaryList.sponsoredCount.title',
      description: 'admin/editor.productSummaryList.sponsoredCount.description',
      type: 'number',
      default: DEFAULT_SPONSORED_COUNT,
    },
    repeatSponsoredProducts: {
      title: 'admin/editor.productSummaryList.repeatSponsoredProducts.title',
      description:
        'admin/editor.productSummaryList.repeatSponsoredProducts.description',
      type: 'boolean',
      default: DEFAULT_REPEAT_SPONSORED_PRODUCTS,
    },
  },
}

export default ProductSummaryList

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryListWithoutQuery.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import type { ComponentType, PropsWithChildren } from 'react'
import React, { useMemo } from 'react'
import { ListContextProvider, useListContext } from 'vtex.list-context'
import { ProductListContext } from 'vtex.product-list-context'
import { ExtensionPoint, useTreePath } from 'vtex.render-runtime'

import ProductListEventCaller from './components/ProductListEventCaller'
import type { ProductClickParams } from './ProductSummaryList'
import {
  mapCatalogProductToProductSummary,
  PreferenceType,
} from './utils/normalize'

const { ProductListProvider } = ProductListContext

export const PRODUCT_LIST_PLACEMENT = 'home_shelf'

type Props = PropsWithChildren<{
  /** Array of products. */
  products?: any[]
  /** Slot of product summary. */
  ProductSummary: ComponentType<{
    product: any
    actionOnClick: (
      product: any,
      productClickParams?: ProductClickParams
    ) => void
    listName?: string
    position?: number
    placement?: string
  }>
  /** Name of the list property on Google Analytics events. */
  listName?: string
  /** Callback on product click. */
  actionOnProductClick?: (
    product: any,
    productClickParams?: ProductClickParams
  ) => void
  /** Logic to enable which SKU will be the selected item */
  preferredSKU?: PreferenceType
}>

function List({
  children,
  products,
  ProductSummary,
  listName,
  actionOnProductClick,
  preferredSKU,
}: Props) {
  const { list } = useListContext()
  const { treePath } = useTreePath()

  const newListContextValue = useMemo(() => {
    const componentList = products?.map((product, index) => {
      const normalizedProduct = mapCatalogProductToProductSummary(
        product,
        preferredSKU
      )

      const position = list.length + index + 1

      const handleOnClick = () => {
        if (typeof actionOnProductClick === 'function') {
          actionOnProductClick(normalizedProduct, {
            position,
          })
        }
      }

      if (typeof ProductSummary === 'function') {
        return (
          <ProductSummary
            key={normalizedProduct.cacheId}
            product={normalizedProduct}
            listName={listName}
            actionOnClick={handleOnClick}
            position={position}
            placement={PRODUCT_LIST_PLACEMENT}
          />
        )
      }

      return (
        <ExtensionPoint
          id="product-summary"
          key={product.cacheId}
          treePath={treePath}
          product={normalizedProduct}
          listName={listName}
          actionOnClick={handleOnClick}
          position={position}
          placement={PRODUCT_LIST_PLACEMENT}
        />
      )
    })

    return list.concat(componentList ?? [])
  }, [
    products,
    list,
    preferredSKU,
    ProductSummary,
    treePath,
    listName,
    actionOnProductClick,
  ])

  return (
    <ListContextProvider list={newListContextValue}>
      {children}
    </ListContextProvider>
  )
}

function ProductSummaryListWithoutQuery({
  children,
  products,
  listName,
  ProductSummary,
  actionOnProductClick,
  preferredSKU,
}: Props) {
  return (
    <ProductListProvider listName={listName ?? ''}>
      <List
        preferredSKU={preferredSKU}
        products={products}
        listName={listName}
        ProductSummary={ProductSummary}
        actionOnProductClick={actionOnProductClick}
      >
        {children}
      </List>
      <ProductListEventCaller />
    </ProductListProvider>
  )
}

export default ProductSummaryListWithoutQuery

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummarySpecificationBadges.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { ProductSummaryContext } from 'vtex.product-summary-context'
import { BaseSpecificationBadges } from 'vtex.product-specification-badges'

/**
 * @deprecated Use [vtex.product-specifications](https://github.com/vtex-apps/product-specifications) instead.
 */
function ProductSummarySpecificationBadges({
  specificationGroupName,
  visibleWhen,
  specificationsOptions,
  specificationName,
  displayValue,
  orientation,
}: any) {
  const { product } = ProductSummaryContext.useProductSummary()

  return (
    <BaseSpecificationBadges
      product={product}
      visibleWhen={visibleWhen}
      specificationsOptions={specificationsOptions}
      specificationName={specificationName}
      displayValue={displayValue}
      orientation={orientation}
      specificationGroupName={specificationGroupName}
    />
  )
}

ProductSummarySpecificationBadges.schema = {
  ...BaseSpecificationBadges.schema,
  title: 'admin/editor.product-summary-specification-badges.title',
}

export default ProductSummarySpecificationBadges

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryAddToListButton.js from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```javascript
import ProductSummaryAddToListButton from './components/ProductSummaryAddToListButton/ProductSummaryAddToListButton'

export default ProductSummaryAddToListButton

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/package.json from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "name": "vtex.product-summary",
  "scripts": {
    "test": "vtex-test-tools test",
    "lint": "tsc --noEmit && eslint --quiet --fix --ext ts,tsx ."
  },
  "dependencies": {
    "classnames": "^2.2.6",
    "debounce": "^1.2.0",
    "graphql": "^14.1.1",
    "prop-types": "^15.7.2",
    "ramda": "^0.26.1",
    "react": "^16.8.6",
    "react-apollo": "^3.1.5",
    "react-content-loader": "^3.1.2",
    "react-intersection-observer": "^8.25.2",
    "react-intl": "^3.12.1"
  },
  "devDependencies": {
    "@apollo/react-testing": "^3.1.4",
    "@types/classnames": "^2.2.11",
    "@types/ramda": "^0.27.11",
    "@vtex/test-tools": "^3.3.2",
    "apollo-cache-inmemory": "^1.5.1",
    "apollo-client": "^2.5.1",
    "typescript": "3.9.7",
    "vtex.css-handles": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.css-handles@1.0.0/public/@types/vtex.css-handles",
    "vtex.device-detector": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.device-detector@0.2.6/public/@types/vtex.device-detector",
    "vtex.list-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.list-context@0.2.0/public/@types/vtex.list-context",
    "vtex.native-types": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.native-types@0.10.0/public/@types/vtex.native-types",
    "vtex.on-view": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.on-view@1.0.0/public/@types/vtex.on-view",
    "vtex.pixel-manager": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.pixel-manager@1.9.0/public/@types/vtex.pixel-manager",
    "vtex.product-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-context@0.10.0/public/@types/vtex.product-context",
    "vtex.product-list-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-list-context@0.4.1/public/@types/vtex.product-list-context",
    "vtex.product-specification-badges": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-specification-badges@0.3.0/public/@types/vtex.product-specification-badges",
    "vtex.product-summary": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-summary@2.82.0/public/@types/vtex.product-summary",
    "vtex.product-summary-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-summary-context@0.11.0/public/@types/vtex.product-summary-context",
    "vtex.render-runtime": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.render-runtime@8.132.4/public/@types/vtex.render-runtime",
    "vtex.responsive-values": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.responsive-values@0.4.2/public/@types/vtex.responsive-values",
    "vtex.search-page-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.search-page-context@0.2.0/public/@types/vtex.search-page-context",
    "vtex.store-components": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-components@3.170.0/public/@types/vtex.store-components",
    "vtex.store-resources": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.store-resources@0.90.0/public/@types/vtex.store-resources",
    "vtex.structured-data": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.structured-data@0.11.0/public/@types/vtex.structured-data",
    "vtex.styleguide": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.styleguide@9.146.4/public/@types/vtex.styleguide"
  },
  "vtexScriptsOverride": {
    "srcPath": "."
  },
  "version": "2.91.0"
}

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryContext.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import type { ProductSummaryTypes } from 'vtex.product-summary-context'

interface ProductSummaryContext {
  product: ProductSummaryTypes.Product
  isLoading: boolean
  isHovering: boolean
  handleItemsStateUpdate: (loading: boolean) => void
}

const ProductSummaryContext = React.createContext<
  ProductSummaryContext | undefined
>(undefined)

const OriginalConsumer = ProductSummaryContext.Consumer

// @ts-expect-error we are mutating the Consumer
ProductSummaryContext.Consumer = function WrappedConsumer(props: any) {
  console.error(
    "If you are seeing this, a component it's using ProductSummaryContext from vtex.product-summary, which is deprecated. Please see the issue https://github.com/vtex-apps/store-issues#28 on how to migrate to the new component."
  )

  return <OriginalConsumer {...props} />
}

export default ProductSummaryContext

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryPrice.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import classNames from 'classnames'
// eslint-disable-next-line no-restricted-imports
import { pluck, path, flatten } from 'ramda'
import { ProductPrice } from 'vtex.store-components'
import { ProductSummaryContext } from 'vtex.product-summary-context'
import type { ProductSummaryTypes } from 'vtex.product-summary-context'
import { useCssHandles } from 'vtex.css-handles'
import type { CssHandlesTypes } from 'vtex.css-handles'

import styles from './productSummary.css'

const { useProductSummary } = ProductSummaryContext

const CSS_HANDLES = [
  'priceContainer',
  'productPriceClass',
  'listPriceContainer',
  'listPriceLabel',
  'listPrice',
  'sellingPriceContainer',
  'sellingPriceLabel',
  'sellingPrice',
  'savingsContainer',
  'savings',
  'interestRate',
  'installmentContainer',
  'listPriceRange',
  'sellingPriceRange',
  'priceLoading',
] as const

function getPrices(
  product: ProductSummaryTypes.Product,
  attribute: 'sellingPrice' | 'listPrice'
) {
  if (!product || (!product.items && !product.priceRange)) {
    return []
  }

  if (product.priceRange) {
    const values = product.priceRange[attribute]

    return values ? [values.lowPrice, values.highPrice] : []
  }

  // No priceRange resolver provided, use sku information
  const sellers = flatten(pluck('sellers', product.items))
  const offerAttribute = attribute === 'sellingPrice' ? 'Price' : 'ListPrice'
  const prices = sellers.map(path(['commertialOffer', offerAttribute]))
  const availableProductsPrices = prices.filter((price) => price !== 0)

  return availableProductsPrices
}

interface Props {
  /**
   * Set the product selling price range visibility
   * @default false
   */
  showSellingPriceRange?: boolean
  /**
   * Set the product list price's visibility
   * @default true
   */
  showListPrice?: boolean
  /**
   * Set the product list price range visibility
   * @default false
   */
  showListPriceRange?: boolean
  /**
   * Set pricing labels' visibility
   * @default true
   */
  showLabels?: boolean
  /**
   * Set installments' visibility
   * @default true
   */
  showInstallments?: boolean
  /**
   * Set savings' visibility
   * @default false
   */
  showDiscountValue?: boolean
  /**
   * Text of selling Price's label
   * @default ""
   */
  labelSellingPrice?: string
  /**
   * Text of list Price's label
   * @default ""
   */
  labelListPrice?: string
  /**
   * Whether should show borders
   * @default false
   */
  showBorders?: boolean
  classes: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

/**
 * @deprecated Please use [vtex.product-price](https://github.com/vtex-apps/product-price) instead.
 */
function ProductSummaryPrice({
  showListPrice = true,
  showSellingPriceRange = false,
  showLabels = true,
  showInstallments = true,
  showDiscountValue = false,
  labelSellingPrice = '',
  labelListPrice = '',
  showBorders = false,
  showListPriceRange = false,
  classes,
}: Props) {
  const { product, isLoading, isPriceLoading } = useProductSummary()
  const { handles } = useCssHandles(CSS_HANDLES, { classes })

  // TODO: change ProductSummaryContext to have `selectedSku` field instead of `sku`
  const commertialOffer = product?.sku?.seller?.commertialOffer

  if (isLoading || isPriceLoading) {
    return (
      <div
        className={`${handles.priceLoading} flex items-end justify-end w-100 h1 pr6`}
      >
        <div className={styles.priceSpinner} />
      </div>
    )
  }

  const priceClasses = {
    containerClass: classNames('flex flex-column justify-end items-center', {
      [`${handles.priceContainer} pv5`]: !showBorders,
    }),
    sellingPriceClass: `${handles.sellingPrice} dib ph2 t-body t-heading-5-ns`,
  }

  const sellingPriceList = showSellingPriceRange
    ? getPrices(product, 'sellingPrice')
    : []

  const listPriceList = showListPriceRange
    ? getPrices(product, 'listPrice')
    : []

  const sellingPrice = commertialOffer.Price

  return (
    <div className={priceClasses.containerClass}>
      {sellingPrice !== 0 && (
        <ProductPrice
          className={`${handles.productPriceClass} flex flex-column justify-start`}
          listPriceContainerClass={`${handles.listPriceContainer} pv1 normal c-muted-2`}
          listPriceLabelClass={`${handles.listPriceLabel} dib strike t-small t-mini`}
          listPriceClass={`${handles.listPrice} dib ph2 strike t-small-ns t-mini`}
          sellingPriceContainerClass={`${handles.sellingPriceContainer} pt1 pb3 c-on-base`}
          sellingPriceLabelClass={`${handles.sellingPriceLabel} dib`}
          sellingPriceClass={priceClasses.sellingPriceClass}
          savingsContainerClass={`${handles.savingsContainer} t-small-ns c-muted-2`}
          savingsClass={`${handles.savings} dib`}
          interestRateClass={`${handles.interestRate} dib pl2`}
          installmentContainerClass={`${handles.installmentContainer} t-small-ns c-muted-2`}
          listPrice={commertialOffer.ListPrice}
          sellingPriceList={sellingPriceList}
          listPriceRangeClass={`${handles.listPriceRange} dib ph2 t-small-ns strike`}
          sellingPriceRangeClass={`${handles.sellingPriceRange} dib ph2 t-small-ns`}
          sellingPrice={commertialOffer.Price}
          installments={commertialOffer.Installments}
          showListPrice={showListPrice}
          showSellingPriceRange={showSellingPriceRange}
          showLabels={showLabels}
          showInstallments={showInstallments}
          labelSellingPrice={labelSellingPrice}
          labelListPrice={labelListPrice}
          listPriceList={listPriceList}
          showListPriceRange={showListPriceRange}
          showSavings={showDiscountValue}
        />
      )}
    </div>
  )
}

ProductSummaryPrice.schema = {
  title: 'admin/editor.productSummaryPrice.title',
  description: 'admin/editor.productSummaryPrice.description',
  type: 'object',
  properties: {
    showListPrice: {
      type: 'boolean',
      title: 'admin/editor.productSummaryPrice.showListPrice.title',
      default: true,
      isLayout: true,
    },
    showSellingPriceRange: {
      type: 'boolean',
      title: 'admin/editor.productSummaryPrice.showSellingPriceRange.title',
      default: false,
      isLayout: true,
    },
    showListPriceRange: {
      type: 'boolean',
      title: 'admin/editor.productSummaryPrice.showListPriceRange.title',
      default: false,
      isLayout: true,
    },
    showInstallments: {
      type: 'boolean',
      title: 'admin/editor.productSummaryPrice.showInstallments.title',
      default: true,
      isLayout: true,
    },
    showDiscountValue: {
      type: 'boolean',
      title: 'admin/editor.productSummaryPrice.showDiscountValue.title',
      default: false,
    },
    showLabels: {
      type: 'boolean',
      title: 'admin/editor.productSummaryPrice.showLabels.title',
      default: true,
      isLayout: true,
    },
    showBorders: {
      type: 'boolean',
      title: 'admin/editor.productSummaryPrice.showBorders.title',
      default: false,
      isLayout: true,
    },
  },
}

export default ProductSummaryPrice

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryDescription.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'
import type { CssHandlesTypes } from 'vtex.css-handles'
import { SanitizedHTML } from 'vtex.store-components'
import { ProductSummaryContext } from 'vtex.product-summary-context'

const { useProductSummary } = ProductSummaryContext

const MAX_SIZE_DESCRIPTION = 120
const CSS_HANDLES = ['description'] as const

interface Props {
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  descriptionMaxSize?: number
}

function ProductSummaryDescription({ classes, descriptionMaxSize }: Props) {
  const { handles } = useCssHandles(CSS_HANDLES, { classes })

  const {
    product: { description },
  } = useProductSummary()

  if (!description) {
    return null
  }

  const descriptionClasses = `${handles.description} c-muted-2 t-small`

  const maxSize = descriptionMaxSize ?? MAX_SIZE_DESCRIPTION
  const descriptionTruncated =
    description.length > maxSize
      ? `${description.substring(0, maxSize)}...`
      : description

  return (
    <span className={descriptionClasses}>
      <SanitizedHTML content={descriptionTruncated} />
    </span>
  )
}

export default ProductSummaryDescription

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryBrand.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { ProductBrand } from 'vtex.store-components'
import { ProductSummaryContext } from 'vtex.product-summary-context'

const { useProductSummary } = ProductSummaryContext

interface Props {
  /** Whether it should be displayed as a logo or as a text */
  displayMode?: 'logo' | 'text'
  /** Whether it should display the name of the brand if there is no logo */
  fallbackToText?: boolean
  /** List of brands that should be hidden, if any */
  excludeBrands?: Array<string | number>
  /** Height of the logo */
  height?: number
  /** If the logo should have a link */
  logoWithLink?: boolean
}

/**
 * @deprecated Please use ProductBrand from vtex.store-components instead.
 */
const ProductSummaryBrand = ({
  displayMode,
  fallbackToText,
  height,
  excludeBrands,
  logoWithLink = false,
}: Props) => {
  const { product } = useProductSummary()

  return (
    <ProductBrand
      displayMode={displayMode}
      fallbackToText={fallbackToText}
      height={height}
      excludeBrands={excludeBrands}
      logoWithLink={logoWithLink}
      brandName={product.brand}
      brandId={product.brandId}
    />
  )
}

export default ProductSummaryBrand

```


[System] You are an expert VTEX IO developer. You specialize in building VTEX Store Framework apps, React components, GraphQL APIs, and Node.js services for e-commerce platforms.

[User] Show me the implementation of product-summary/react/ProductSummaryImage.tsx from the product-summary VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { useState } from 'react'
import type { PropsWithChildren } from 'react'
import { CollectionBadges, DiscountBadge } from 'vtex.store-components'
import classNames from 'classnames'
import { useDevice } from 'vtex.device-detector'
import { useResponsiveValues } from 'vtex.responsive-values'
import type { ResponsiveValuesTypes } from 'vtex.responsive-values'
import { useCssHandles } from 'vtex.css-handles'
import type { CssHandlesTypes } from 'vtex.css-handles'
import { useProduct } from 'vtex.product-context'
import {
  ProductSummaryContext,
  ProductSummaryTypes,
} from 'vtex.product-summary-context'
import { useIntl } from 'react-intl'

import ImagePlaceholder from './components/ImagePlaceholder'
import productSummary from './productSummary.css'
import { changeImageUrlSize } from './utils/normalize'
import { imageUrl } from './utils/aspectRatioUtil'

const { useProductSummary } = ProductSummaryContext

const CSS_HANDLES = [
  'image',
  'imageWrapper',
  'imageContainer',
  'product',
  'imagePlaceholder',
  'mainImageHovered',
] as const

const MAX_SIZE = 500
const DEFAULT_SIZE = 300

type ImageLabelMatchCriteria = 'exact' | 'contains'

type MainImageLabel = {
  label?: string
  labelMatchCriteria?: ImageLabelMatchCriteria
}

type HoverImageCriteria = 'label' | 'index'

type HoverImage = {
  label?: string
  index?: number
  criteria?: HoverImageCriteria
  labelMatchCriteria?: ImageLabelMatchCriteria
}

type GetImageSrcParams = {
  src: string
  width: number
  height: number
  dpi: number
  aspectRatio?: string | number
}

function getImageSrc({
  src,
  width,
  height,
  dpi,
  aspectRatio,
}: GetImageSrcParams) {
  if (width || height) {
    return changeImageUrlSize(src, width * dpi, height * dpi)
  }

  if (aspectRatio) {
    return imageUrl(src, DEFAULT_SIZE, MAX_SIZE, aspectRatio)
  }

  return src
}

type GetHoverImageParams = {
  images: ProductSummaryTypes.SKU['images']
  hoverImage?: HoverImage
  hoverImageLabel?: string
}

function findImageByIndex(
  images: ProductSummaryTypes.SKU['images'],
  index: HoverImage['index']
) {
  if (index === null || index === undefined || Number.isNaN(index)) {
    return null
  }

  return images[index]
}

function getHoverImage({
  images,
  hoverImage,
  hoverImageLabel,
}: GetHoverImageParams) {
  const {
    criteria = 'label',
    label = hoverImageLabel,
    labelMatchCriteria = 'exact',
    index,
  } = hoverImage ?? {}

  if (criteria === 'label') {
    return findImageByLabel(images, label, labelMatchCriteria)
  }

  if (criteria === 'index') {
    return findImageByIndex(images, index)
  }

  return null
}

type GetStyleParams = {
  width: number
  height: number
  aspectRatio?: string | number
  maxHeight?: string
}

function getStyle({ width, height, aspectRatio, maxHeight }: GetStyleParams) {
  if (width || height) {
    return {
      width: '100%',
      height,
      objectFit: 'contain',
      maxHeight: 'unset',
      maxWidth: width,
    } as React.CSSProperties
  }

  if (aspectRatio || maxHeight) {
    return {
      width: '100%',
      height: '100%',
      objectFit: 'contain',
      maxHeight: maxHeight ?? 'unset',
    } as React.CSSProperties
  }

  return undefined
}

type CollectionWrapperProps = {
  showCollections: boolean
  productClusters: Array<{ name: string }>
}

function CollectionWrapper({
  showCollections,
  productClusters,
  children,
}: PropsWithChildren<CollectionWrapperProps>) {
  if (!showCollections || !productClusters || productClusters.length === 0) {
    return <>{children}</>
  }

  const collections = productClusters.map((cl) => cl.name)

  return (
    <CollectionBadges collectionBadgesText={collections}>
      {children}
    </CollectionBadges>
  )
}

type BadgeWrapperProps = {
  showBadge: boolean
  commertialOffer: { ListPrice: number; Price: number }
  badgeText?: string
}

function BadgeWrapper({
  showBadge,
  commertialOffer,
  badgeText,
  children,
}: PropsWithChildren<BadgeWrapperProps>) {
  if (!showBadge) {
    return <>{children}</>
  }

  return (
    <DiscountBadge
      listPrice={commertialOffer.ListPrice}
      sellingPrice={commertialOffer.Price}
      label={badgeText}
    >
      {children}
    </DiscountBadge>
  )
}

function findImageByLabel(
  images: ProductSummaryTypes.SKU['images'],
  selectedLabel: string | undefined,
  labelMatchCriteria?: ImageLabelMatchCriteria
) {
  if (!selectedLabel) {
    return null
  }

  if (labelMatchCriteria === 'contains') {
    return images.find(({ imageLabel }) => imageLabel?.includes(selectedLabel))
  }

  return images.find(({ imageLabel }) => imageLabel === selectedLabel)
}

interface ImageProps {
  src: string
  width: number
  height: number
  onError: () => void
  alt: string
  className: string
  aspectRatio?: string | number
  maxHeight?: string
  fetchpriority?: 'high' | 'low' | 'auto'
}

function Image({
  src,
  width,
  height,
  onError,
  alt,
  className,
  aspectRatio,
  maxHeight,
  fetchpriority = 'auto',
}: ImageProps) {
  const { isMobile } = useDevice()

  /** TODO: Previously it was as follows :
   *
  const dpi = window.devicePixelRatio || (isMobile ? 2 : 1)
   *
   * it seems good, because it takes the actual user's screen density
   * into account, but causes images to be re-downloaded if the initial
   * device-based guess was wrong. Has to be looked into */
  const dpi = isMobile ? 2 : 1

  return (
    <img
      src={getImageSrc({ src, width, height, dpi, aspectRatio })}
      style={getStyle({ width, height, aspectRatio, maxHeight })}
      loading={fetchpriority === 'low' ? 'lazy' : 'eager'}
      alt={alt}
      className={className}
      onError={onError}
      // @ts-expect-error This property exists in HTML
      fetchpriority={fetchpriority}
    />
  )
}

interface Props {
  /**
   * Set the discount badge's visibility
   * @default true
   */
  showBadge?: boolean
  /** Text shown on badge */
  badgeText?: string
  /**
   * Display mode of the summary
   * @default "normal"
   */
  displayMode?: 'normal' | 'inline'
  /**
   * @default ""
   */
  mainImageLabel?: string | MainImageLabel
  /**
   * @default ""
   * @deprecated
   * Use hoverImage instead
   */
  hoverImageLabel?: string
  hoverImage?: HoverImage
  /**
   * Defines if the collection badges are shown
   * @default false
   */
  showCollections?: boolean
  placeholder?: string
  width?: ResponsiveValuesTypes.ResponsiveValue<number | string>
  height?: ResponsiveValuesTypes.ResponsiveValue<number | string>
  aspectRatio?: ResponsiveValuesTypes.ResponsiveValue<string | number>
  maxHeight?: ResponsiveValuesTypes.ResponsiveValue<string>
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  fetchpriority?: 'high' | 'low' | 'auto' | 'byPosition'
}

function ProductImage({
  showBadge = true,
  badgeText,
  displayMode = 'normal',
  mainImageLabel = '',
  hoverImageLabel = '',
  hoverImage,
  showCollections = false,
  placeholder,
  width: widthProp,
  height: heightProp,
  aspectRatio: aspectRatioProp,
  maxHeight: maxHeightProp,
  classes,
  fetchpriority = 'byPosition',
}: Props) {
  // @ts-expect-error - Depends on vtex.product-summary-context update on PR: https://github.com/vtex-apps/product-summary-context/pull/25
  const {
    product,
    position,
  }: {
    product: ProductSummaryTypes.Product
    position: number | undefined
  } = useProductSummary()

  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })

  const intl = useIntl()

  const [error, setError] = useState(false)
  const onError = () => setError(true)

  const { isMobile } = useDevice()
  const productContext = useProduct() ?? {}
  const { skuSelector: { selectedImageVariationSKU } = {} } = productContext

  const { productClusters, productName: name } = product ?? {}
  const sku = product?.sku

  const {
    widthProp: responsiveWidth,
    heightProp: responsiveHeight,
    aspectRatioProp: aspectRatio,
    maxHeightProp: maxHeight,
  } = useResponsiveValues({
    widthProp,
    heightProp,
    aspectRatioProp,
    maxHeightProp,
  })

  const imageClassName = classNames(
    // legacy class
    productSummary.imageContainer,
    handles.imageWrapper,
    {
      'db w-100 center': displayMode !== 'inline',
    }
  )

  const [width, height] = [
    // fallsback to the other remaining value, if not defined
    // @ts-expect-error There's no problem passing type number to parseFloat
    parseFloat(responsiveWidth ?? responsiveHeight ?? 0),
    // @ts-expect-error There's no problem passing type number to parseFloat
    parseFloat(responsiveHeight ?? responsiveWidth ?? 0),
  ]

  const legacyContainerClasses = classNames(
    productSummary.imageStackContainer,
    !isMobile && productSummary.hoverEffect
  )

  const containerClassname = classNames(
    'dib relative',
    handles.imageContainer,
    legacyContainerClasses
  )

  let skuImageUrl = sku?.image?.imageUrl ?? ''

  const shouldDisplayPlaceholder = !skuImageUrl || error

  if (!placeholder && shouldDisplayPlaceholder) {
    return (
      <div className={imageClassName}>
        <div className={containerClassname}>
          <ImagePlaceholder cssHandle={handles.imagePlaceholder} />
        </div>
      </div>
    )
  }

  const images = sku?.images ?? []
  const selectedHoverImage = getHoverImage({
    images,
    hoverImage,
    hoverImageLabel,
  })

  if (selectedImageVariationSKU == null && mainImageLabel) {
    const mainImage =
      typeof mainImageLabel === 'string'
        ? findImageByLabel(images, mainImageLabel)
        : findImageByLabel(
            images,
            mainImageLabel.label,
            mainImageLabel.labelMatchCriteria
          )

    if (mainImage) {
      skuImageUrl = mainImage.imageUrl
    }
  }

  const legacyImageClasses = classNames({
    [productSummary.imageNormal]: displayMode !== 'inline',
    [productSummary.imageInline]: displayMode === 'inline',
  })

  // TODO: change ProductSummaryContext to have `selectedSku` field instead of `sku`
  const commertialOffer = product.sku?.seller?.commertialOffer ?? {}

  const imageClassname = classNames(legacyImageClasses, handles.image, {
    [handles.mainImageHovered]: Boolean(selectedHoverImage),
  })

  const hoverImageClassname = classNames(
    'w-100 h-100 dn absolute top-0 left-0 z-999',
    withModifiers('image', 'hover'),
    legacyImageClasses,
    !isMobile && productSummary.hoverImage
  )

  /**
   * Determines the priority ('high' or 'low') based on the isMobile flag and the position value.
   * @param isMobileDevice - A boolean indicating the device type (true for mobile, false for non-mobile).
   * @param positionNumber - The Product Summary's position on a list context or search result, used to determine priority.
   * @returns A string representing the priority: 'high' for high priority, 'low' for low priority.
   */

  const getFetchPriority = (
    isMobileDevice: boolean,
    positionNumber: number | undefined
  ): 'high' | 'low' => {
    if (positionNumber) {
      return isMobileDevice
        ? positionNumber === 1
          ? 'high'
          : 'low'
        : positionNumber < 3
        ? 'high'
        : 'low'
    }

    return 'low'
  }

  return (
    <div
      className={imageClassName}
      aria-label={intl.formatMessage(
        { id: 'store/product-summary.image.aria-label' },
        { productName: product.productName }
      )}
    >
      <CollectionWrapper
        showCollections={showCollections}
        productClusters={productClusters}
      >
        <BadgeWrapper
          showBadge={showBadge}
          commertialOffer={commertialOffer}
          badgeText={badgeText}
        >
          <div className={containerClassname}>
            <Image
              src={
                shouldDisplayPlaceholder ? (placeholder as string) : skuImageUrl
              }
              width={width}
              height={height}
              aspectRatio={aspectRatio}
              maxHeight={maxHeight}
              alt={name}
              className={imageClassname}
              onError={onError}
              fetchpriority={
                fetchpriority === 'byPosition'
                  ? getFetchPriority(isMobile, position)
                  : fetchpriority
              }
            />
            {selectedHoverImage && !isMobile && (
              <Image
                src={selectedHoverImage.imageUrl}
                width={width}
                height={height}
                aspectRatio={aspectRatio}
                maxHeight={maxHeight}
                alt={name}
                className={hoverImageClassname}
                onError={onError}
                fetchpriority={
                  fetchpriority === 'byPosition'
                    ? getFetchPriority(isMobile, position)
                    : fetchpriority
                }
              />
            )}
          </div>
        </BadgeWrapper>
      </CollectionWrapper>
    </div>
  )
}

ProductImage.schema = {
  title: 'admin/editor.productSummaryImage.title',
  description: 'admin/editor.productSummaryImage.description',
  type: 'object',
  properties: {
    showBadge: {
      type: 'boolean',
      title: 'admin/editor.productSummary.showBadge.title',
      default: true,
      isLayout: true,
    },
    showCollections: {
      type: 'boolean',
      title: 'admin/editor.productSummary.showCollections.title',
      default: false,
      isLayout: true,
    },
    displayMode: {
      title: 'admin/editor.productSummary.displayMode.title',
      type: 'string',
      enum: ['normal', 'inline'],
      default: 'normal',
      isLayout: true,
    },
    hoverImageLabel: {
      title: 'admin/editor.productSummaryImage.hoverImageLabel.title',
      description:
        'admin/editor.productSummaryImage.hoverImageLabel.description',
      type: 'string',
      default: '',
      isLayout: false,
    },
    hoverImage: {
      type: 'object',
      properties: {
        criteria: {
          title: 'admin/editor.productSummaryImage.hoverImage.criteria.title',
          enum: ['index', 'label'],
        },
      },
      dependencies: {
        criteria: {
          oneOf: [
            {
              properties: {
                criteria: {
                  enum: ['index'],
                },
                index: {
                  title:
                    'admin/editor.productSummaryImage.hoverImage.criteria.index',
                  type: 'number',
                },
              },
            },
            {
              properties: {
                criteria: {
                  enum: ['label'],
                },
                label: {
                  title:
                    'admin/editor.productSummaryImage.hoverImage.criteria.label',
                  type: 'string',
                },
                labelMatchCriteria: {
                  title:
                    'admin/editor.productSummaryImage.hoverImage.criteria.matchCriteria',
                  widget: {
                    'ui:widget': 'radio',
                  },
                  type: 'string',
                  enum: ['exact', 'contains'],
                  default: 'exact',
                },
              },
            },
          ],
        },
      },
    },
    fetchpriority: {
      title: 'admin/editor.productSummaryImage.fetchpriority.title',
      enum: ['high', 'low', 'auto', 'byPosition'],
      enumNames: [
        'admin/editor.productSummaryImage.fetchpriority.high',
        'admin/editor.productSummaryImage.fetchpriority.low',
        'admin/editor.productSummaryImage.fetchpriority.auto',
        'admin/editor.productSummaryImage.fetchpriority.byPosition',
      ],
      widget: {
        'ui:widget': 'radio',
      },
      default: 'byPosition',
    },
  },
}

export default ProductImage

```