[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-price/docs/README.md from the product-price VTEX app.

[Assistant]
Here's the implementation:

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

# Product Price

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

The Product Price app exports blocks related to the product price, such as list price, selling price, and savings.

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

> ℹ Currently, **Product Price is the only app that provides product price blocks for your theme**. The [Product Summary Price](https://developers.vtex.com/docs/guides/vtex-product-summary-productsummaryprice) block from the [Product Summary](https://developers.vtex.com/docs/guides/vtex-product-summary/) app and the [Product Price](https://developers.vtex.com/docs/guides/vtex-store-components-productprice/) block from [Store Components](https://developers.vtex.com/docs/guides/vtex-store-components/) have been deprecated.

## Configuration

### Step 1 - Adding the Product Price app to your theme dependencies

In the theme's `manifest.json` file, add the Product Price app as a dependency:

```json
"dependencies": {
  "vtex.product-price": "1.x"
}
```

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

| Block name                       | Description                                                                                |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `product-list-price`             | Renders the product list price. This block will not be rendered if it is equal to or less than the product selling price.                                                                    |
| `product-selling-price`          | Renders the product selling price.                                                                                |
| `product-spot-price`             | Renders the product spot price (if it is equal to the selling price, the block is not rendered). This block finds the spot price by looking for the lowest price of all installment options. |
| `product-installments`           | Renders the product installments. If more than one option is available, the one with the highest number of installments will be displayed by default.                                        |
| `product-installments-list`      | Renders all the installments of the payment system with the most installment options by default.                                                                                |
| `product-installments-list-item` | Renders an installment option from the `product-installments-list-item`.                                                                                |
| `product-price-savings`          | Renders the product price savings, if there are any. It can show the discount percentage or the total amount of the savings.                                                                 |
| `product-spot-price-savings`     | Renders the product spot price savings, if there are any. It can show the discount percentage or the total amount of the savings.                                                            |
| `product-list-price-range`       | Renders the product list price range. It follows the same logic applied to the `ListPrice`: if its value equals the product selling price, this block is not rendered.                       |
| `product-selling-price-range`    | This is the product selling price range.                                                                                |
| `product-seller-name`            | Renders the name of the product seller.                                                                                |
| `product-price-suspense`         | Renders a fallback component when the price is loading and its children block when it is not loading. This block is handy when the store works with asynchronous prices.                     |

All blocks listed above use product price data fetched from the store catalog. To learn more, please read the [Pricing module overview](https://help.vtex.com/tracks/precos-101--6f8pwCns3PJHqMvQSugNfP).

### Step 2 - Adding the Product Price blocks to your theme templates

To add the Product Price blocks to your theme, you just need to declare them as children of the `product-summary.shelf` exported by the [Product Summary](https://vtex.io/docs/components/content-blocks/vtex.product-summary@2.52.3) app, or declare them in the theme's Product template (`store.product`).

Note that these blocks require a Product context to work correctly. Therefore, when declaring them as children of the `product-summary.shelf`, make sure they are in a store template in which this context is available.

For example:

```json
"shelf#home": {
  "blocks": ["product-summary.shelf"]
},

"product-summary.shelf": {
  "children": [
    "product-list-price",
    "product-selling-price#summary",
    "product-price-savings",
    "product-installments"
  ]
},
```

Except for `product-price-suspense`, every block in this app only has three props in common:

| Prop name    | Type       | Description                                                                                | Default value |
| ------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `markers`    | `[string]` | IDs that you define to identify the block's rendered message and customize it using the Site Editor in the Admin. Learn how to use them by reading the documentation on [Using the Markers prop to customize a block's message](https://developers.vtex.com/docs/guides/vtex-io-documentation-using-the-markers-prop-to-customize-a-blocks-message). Note that a block's message can be customized using the `message` prop in the Store Theme source code. | `[]`          |
| `blockClass` | `string`   | Block ID that you define to be used in [CSS customizations](https://developers.vtex.com/docs/guides/vtex-io-documentation-using-css-handles-for-store-customization#using-the-blockclass-property).                                                                                | `undefined`   |
| `message`    | `string`   | Defines the block's default text, i.e., the block message. If you use the Site Editor in the Admin, you can also define the text message that a block will render on the UI.                                                                                | `undefined`   |

For example:

```json
"product-selling-price#summary": {
  "props": {
    "markers": [
      "highlight"
    ],
    "blockClass": "summary",
    "message": "Selling price: {sellingPriceValue}"
  }
},
```

The `product-price-savings` block has two additional props:

| Prop name           | Type                  | Description                                                                                | Default value |
| ------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `percentageStyle`   | `locale` or `compact` | Set to `compact` to remove the white space between the number and the percent sign. It uses the pattern provided by the current locale as default.                        | `locale`      |
| `minimumPercentage` | `number`              | Set the minimum value for the percentage value display. If not configured, it will display always. Example: `10` means savings less than or equal to 10% will not appear. | `0`           |

The following blocks have an additional prop: `product-list-price`, `product-selling-price`, `product-spot-price`, `product-spot-price-savings`, `product-price-savings`, `product-list-price-range`, and `product-selling-price-range`. The prop is:

|  Prop name   |   Type    |                       Description                       | Default value |
|:------------:|:---------:|:-------------------------------------------------------:|:-------------:|
| `alwaysShow` | `boolean` | Renders the block even when the product is unavailable. |    `false`    |

For example:

```json
"product-selling-price#summary": {
  "props": {
    "alwaysShow": true
  }
},
```

The `product-installments-list` block has two additional props:

| Prop name            | Type       | Description                                                                                | Default value |
| -------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
| `paymentSystemName`  | `string`   | Allows you to filter the listed installment options by a particular payment system. If not passed, the installments of the payment system with the most installment options will be rendered.                                                                                | `undefined`   |
| `installmentsToShow` | `number[]` | Determines the installment options you want to show to the user based on the number of installments. For example, if `[1, 3]` is passed as a value for this prop, only the installments options with `NumberOfInstallments` equal to 1 and 3 will be rendered. If a value is not passed, all options will be rendered. | `undefined`   |

The `product-installments` block also has two additional props:

| Prop name                  | Type                                                            | Description                                                                                | Default value  |
| -------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| `installmentsCriteria`     | `max-quantity` or `max-quantity-without-interest`               | When set to `max-quantity`, the block renders the installment plan with the most number of installments. When set to `max-quantity-without-interest`, the block renders the installment plan with the most number of installments and **no interest**. Note that if this prop is set to `max-quantity-without-interest` and no installment plan matches the 'without interest' criterion, the component will fall back to the default behavior. | `max-quantity` |
| `installmentOptionsFilter` | `{ paymentSystemName?: string, installmentsQuantity?: number }` | Allows you to define two filtering rules that will narrow down the possible installment plans the component may render.                                                                                | `undefined`    |

If you are using the asynchronous price feature, you can take advantage of `product-price-suspense` and its props:

| Prop name  |  Type   |                            Description                             |    Default value    |
|:----------:|:-------:|:------------------------------------------------------------------:|:-------------------:|
| `Fallback` | `block` | Name of the block that will be rendered when the price is loading. | `rich-text#loading` |

For example:

```json
{
  "rich-text#loading": {
    "props": {
      "text": "Loading..."
    }
  },
  "product-price-suspense": {
    "props": {
      "Fallback": "rich-text#loading"
    },
    "children": [
      "product-list-price#summary",
      "flex-layout.row#selling-price-savings",
      "product-installments#summary",
      "add-to-cart-button"
    ]
  },
  "product-summary.shelf": {
    "children": [
      "stack-layout#prodsum",
      "product-summary-name",
      "product-rating-inline",
      "product-summary-space",
      "product-price-suspense"
    ]
  }
}
```

![9SOSUdfAVa](https://cdn.jsdelivr.net/gh/vtexdocs/dev-portal-content@main/images/vtex-product-price-1.gif)

### Step 3 - Editing the block's messages

Every Product Price block uses the [ICU Message Format](https://format-message.github.io/icu-message-format-for-translators/), making it possible to fully edit the text message and variables displayed by each block.

The variable values are rendered based on the context wrapping the block, meaning it uses product data to render the price accordingly.

You can, however, define the message texts a block will render on the UI using the `message` prop as explained previously.

The `markers` prop, in turn, needs an extra configuration in the Admin's Site Editor to work correctly. When using it, do not forget to read the [Using the Markers prop to customize a block's message](https://developers.vtex.com/docs/guides/vtex-io-documentation-using-the-markers-prop-to-customize-a-blocks-message/) documentation.

![Product-Price-Site-Editor-gif](https://cdn.jsdelivr.net/gh/vtexdocs/dev-portal-content@main/images/vtex-product-price-2.gif)

In addition, remember to take into account the message variables for each block, since you will need them to edit the desired messages using the Admin's Site Editor:

- **`product-list-price`**

| Message variable              | Type      | Description                                                                                |
| ----------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
| `listPriceValue`              | `string`  | List price value.                                                                                |
| `listPriceWithTax`            | `string`  | List price value with tax.                                                                       |
| `listPriceWithUnitMultiplier` | `string`  | List price multiplied by a unit multiplier.                                                      |
| `listPriceWithTaxWithoutUnitMultiplier` | `string`  | List price with tax without unit multiplier.                                           |
| `taxPercentage`               | `string`  | Tax percentage.                                                                                |
| `taxValue`                    | `string`  | Tax value.                                                                                |
| `hasMeasurementUnit`          | `boolean` | Determines whether the product has a unit of measure (`true`) or not (`false`).                  |
| `measurementUnit`             | `string`  | Unit of measure text.                                                                            |
| `hasUnitMultiplier`           | `boolean` | Determines whether the product has a unit multiplier different from 1 (`true`) or not (`false`). |
| `unitMultiplier`              | `string`  | Value of the unit multiplier.                                                                    |

- **`product-selling-price`**

| Message variable                 | Type      | Description                                                                                |
| -------------------------------- | --------- | ------------------------------------------------------------------------------------------------ |
| `sellingPriceValue`              | `string`  | Selling price value.                                                                             |
| `sellingPriceWithTax`            | `string`  | Selling price with tax.                                                                          |
| `sellingPriceWithUnitMultiplier` | `string`  | Selling price multiplied by a unit multiplier.                                                   |
| `sellingPriceWithUnitMultiplierAndTax` | `string` | Selling price multiplied by a unit multiplier and added tax. |
| `sellingPriceWithTaxWithoutUnitMultiplier` | `string` | Selling price with tax without unit multiplier. |
| `taxPercentage`                  | `string`  | Tax percentage.                                                                                |
| `hasListPrice`                   | `boolean` | Determines whether the product has a list price (`true`) or not (`false`).                       |
| `taxValue`                       | `string`  | Tax value.                                                                                |
| `hasMeasurementUnit`             | `boolean` | Determines whether the product has a unit of measure (`true`) or not (`false`).                  |
| `measurementUnit`                | `string`  | Unit of measure text.                                                                            |
| `hasUnitMultiplier`              | `boolean` | Determines whether the product has a unit multiplier different from 1 (`true`) or not (`false`). |
| `unitMultiplier`                 | `string`  | Value of the unit multiplier.                                                                    |

You can use the `product-selling-price` `sellingPriceWithUnitMultiplier`, `hasMeasurementUnit`, `unitMultiplier`, and `measurementUnit` variables together to render the unit of measure and unit multiplier on products that have it.

For example:

```json
{
  "product-selling-price": {
    "props": {
      "message": "{sellingPriceWithUnitMultiplier}{hasMeasurementUnit, select, true { / {hasUnitMultiplier, select, true {{unitMultiplier}} false {}} {measurementUnit}} false {}}"
    }
  }
}
```

According to the example above, products with a unit of measure and a unit multiplier would be rendered as follows:

`$24.00 / 2 oz`

> ℹ _Note that the unit of measure and unit multiplier would be properly rendered alongside their price._

Still, in the example, products that do not have a unit of measure and a unit multiplier will only render their price:

`$24.00`

- **`product-spot-price`**

| Message variable | Type     | Description       |
| ---------------- | -------- | ----------------- |
| `spotPriceValue` | `string` | Spot price value. |

- **`product-installments` and `product-installments-list-item`**

| Message variables        | Type      | Description                                                                    |
| ------------------------ | --------- | ------------------------------------------------------------------------------ |
| `spotPriceValue`         | `string`  | Spot price value.                                                              |
| `installmentsNumber`     | `string`  | Number of installments.                                                        |
| `installmentValue`       | `string`  | Amount of each installment.                                                    |
| `installmentsTotalValue` | `string`  | Total amount of installments.                                                  |
| `interestRate`           | `string`  | Interest rate.                                                                 |
| `paymentSystemName`      | `string`  | Payment system for the installments.                                           |
| `hasInterest`            | `boolean` | Determines whether the installments have interest (`true`) or not (`false`).   |
| `hasMoreThanOne`         | `boolean` | Determines whether there is more than 1 installment (`true`) or not (`false`). |

- **`product-price-savings`**

| Message variables    | Type     | Description                                                               |
| -------------------- | -------- | ------------------------------------------------------------------------- |
| `previousPriceValue` | `string` | Previous price value (list price).                                        |
| `newPriceValue`      | `string` | New price value (selling price).                                          |
| `savingsValue`       | `string` | Difference between the previous product price and the new one.            |
| `savingsWithTax`     | `string` | Difference between the previous product price and the new one with taxes. |
| `savingsPercentage`  | `string` | Percentage of savings.                                                    |

- **`product-spot-price-savings`**

| Message variables            | Type     | Description                                                                  |
| ---------------------------- | -------- | ---------------------------------------------------------------------------- |
| `previousPriceValue`         | `string` | Previous price value (list price).                                           |
| `newSpotPriceValue`          | `string` | New price value (spot price).                                                |
| `spotPriceSavingsValue`      | `string` | Difference between the previous product price and the spot price.            |
| `spotPriceSavingsWithTax`    | `string` | Difference between the previous product price and the spot price with taxes. |
| `spotPriceSavingsPercentage` | `string` | Percentage of savings.                                                       |

- **`product-list-price-range`**

| Message variables  | Type     | Description                                                 |
| ------------------ | -------- | ----------------------------------------------------------- |
| `minPriceValue`    | `string` | Value of the cheapest list price SKU.                       |
| `maxPriceValue`    | `string` | Value of the most expensive list price SKU.                 |
| `minPriceWithTax`  | `string` | Value of the cheapest list price SKU with tax.              |
| `maxPriceWithTax`  | `string` | Value of the most expensive list price SKU with tax.        |
| `listPriceValue`   | `string` | Value of the list price of the only SKU available.          |
| `listPriceWithTax` | `string` | Value of the list price of the only SKU available with tax. |

- **`product-selling-price-range`**

| Message variables     | Type     | Description                                                    |
| --------------------- | -------- | -------------------------------------------------------------- |
| `minPriceValue`       | `string` | Value of the cheapest selling price SKU.                       |
| `maxPriceValue`       | `string` | Value of the most expensive selling price SKU.                 |
| `minPriceWithTax`     | `string` | Value of the cheapest selling price SKU with tax.              |
| `maxPriceWithTax`     | `string` | Value of the most expensive selling price SKU with tax.        |
| `sellingPriceValue`   | `string` | Value of the selling price of the only SKU available.          |
| `sellingPriceWithTax` | `string` | Value of the selling price of the only SKU available with tax. |

- **`product-seller-name`**

| Message variable | Type     | Description                     |
| ---------------- | -------- | ------------------------------- |
| `sellerName`     | `string` | The name of the product seller. |

In the gif example above, the block first displayed a `Save $224.40` message. By editing the exported message, it now renders a `You are saving: $224.40 (37%)` message thanks to the changes performed through the Site Editor in the Admin:

![product-price-edited-img](https://cdn.jsdelivr.net/gh/vtexdocs/dev-portal-content@main/images/vtex-product-price-3.png)

## Customization

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

| CSS handles                            |
| -------------------------------------- |
| `installmentValue`                     |
| `installmentsListContainer`            |
| `installmentsNumber`                   |
| `installmentsTotalValue`               |
| `installments`                         |
| `interestRate`                         |
| `listPrice'`                           |
| `listPrice--isUnavailable`             |
| `listPriceRangeMaxValue`               |
| `listPriceRangeMaxWithTax`             |
| `listPriceRangeMinValue`               |
| `listPriceRangeMinWithTax`             |
| `listPriceRangeUniqueValue`            |
| `listPriceRangeUniqueWithTax`          |
| `listPriceRange`                       |
| `listPriceRange--isUnavailable`        |
| `listPriceValue`                       |
| `listPriceWithTax`                     |
| `newPriceValue`                        |
| `newSpotPriceValue`                    |
| `paymentSystemName`                    |
| `previousPriceValue`                   |
| `savingsPercentage`                    |
| `savingsValue`                         |
| `savingsWithTax`                       |
| `savings`                              |
| `savings--isUnavailable`               |
| `sellerName`                           |
| `sellerNameContainer`                  |
| `sellerNameContainer--isDefaultSeller` |
| `sellingPrice--hasListPrice`           |
| `sellingPrice--isUnavailable`          |
| `sellingPriceRangeMaxValue`            |
| `sellingPriceRangeMaxWithTax`          |
| `sellingPriceRangeMinValue`            |
| `sellingPriceRangeMinWithTax`          |
| `sellingPriceRangeUniqueValue`         |
| `sellingPriceRangeUniqueWithTax`       |
| `sellingPriceRange`                    |
| `sellingPriceRange--isUnavailable`     |
| `sellingPriceRange--hasListPrice`      |
| `sellingPriceValue`                    |
| `sellingPriceWithTax`                  |
| `sellingPrice`                         |
| `spotPriceSavingsPercentage`           |
| `spotPriceSavingsValue`                |
| `spotPriceSavingsWithTax`              |
| `spotPriceSavings`                     |
| `spotPriceSavings--isUnavailable`      |
| `spotPriceValue`                       |
| `spotPrice`                            |
| `spotPrice--isUnavailable`             |
| `taxPercentage`                        |

<!-- 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://razvanudrea.com"><img src="https://avatars.githubusercontent.com/u/71461884?v=4?s=100" width="100px;" alt=""/><br /><sub><b>razvanudream</b></sub></a><br /><a href="https://github.com/vtex-apps/product-price/commits?author=razvanudream" title="Code">💻</a></td>
    <td align="center"><a href="https://github.com/khrizzcristian"><img src="https://avatars.githubusercontent.com/u/43498488?v=4?s=100" width="100px;" alt=""/><br /><sub><b>khrizzcristian</b></sub></a><br /><a href="https://github.com/vtex-apps/product-price/commits?author=khrizzcristian" title="Code">💻</a></td>
    <td align="center"><a href="https://github.com/Git-the-Sanz"><img src="https://avatars.githubusercontent.com/u/50715158?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sanz</b></sub></a><br /><a href="https://github.com/vtex-apps/product-price/commits?author=Git-the-Sanz" title="Code">💻</a></td>
    <td align="center"><a href="https://www.linkedin.com/in/renan-guerra-ba91aa17b/"><img src="https://avatars.githubusercontent.com/u/69531548?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Renan Guerra</b></sub></a><br /><a href="https://github.com/vtex-apps/product-price/commits?author=renanguerraa1" title="Code">💻</a></td>
  </tr>
</table>

<!-- markdownlint-restore -->
<!-- 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-price/react/InstallmentsListItem.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages } from 'react-intl'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'

import { useInstallments } from './components/InstallmentsContext'
import InstallmentsRenderer, {
  CSS_HANDLES,
} from './components/InstallmentsRenderer'

const messages = defineMessages({
  title: {
    id: 'admin/installments-list-item.title',
  },
  titleMessage: {
    id: 'admin/installments.title',
  },
  description: {
    id: 'admin/installments.description',
  },
  default: {
    id: 'store/installments.default',
  },
})

interface Props {
  message?: string
  markers?: string[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function InstallmentsListItem({
  message = messages.default.id,
  markers = [],
  classes,
}: Props) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const { installment } = useInstallments() ?? {}

  if (!installment) {
    return null
  }

  return (
    <InstallmentsRenderer
      message={message}
      markers={markers}
      installment={installment}
      handles={handles}
      handlesModifierFunction={withModifiers}
    />
  )
}

InstallmentsListItem.schema = {
  title: 'admin/installments-list-item.title',
}

export default InstallmentsListItem

```


[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-price/react/ListPrice.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages, FormattedNumber } from 'react-intl'
import { useProduct } from 'vtex.product-context'
import { FormattedCurrency } from 'vtex.format-currency'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'
import { IOMessageWithMarkers } from 'vtex.native-types'

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

const CSS_HANDLES = [
  'listPrice',
  'listPriceValue',
  'listPriceWithTax',
  'listPriceWithUnitMultiplier',
  'listPriceWithTaxWithoutUnitMultiplier',
  'taxPercentage',
  'taxValue',
  'unitMultiplier',
  'measurementUnit',
] as const

const messages = defineMessages({
  title: {
    id: 'admin/list-price.title',
  },
  description: {
    id: 'admin/list-price.description',
  },
  default: {
    id: 'store/list-price.default',
  },
})

interface Props {
  message?: string
  markers?: string[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  alwaysShow?: boolean
}

function ListPrice({
  message = messages.default.id,
  markers = [],
  classes,
  alwaysShow = false,
}: Props) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const productContextValue = useProduct()

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer ||
    hideProductPrice({
      alwaysShow,
      availableQuantity: commercialOffer.AvailableQuantity,
    })
  ) {
    return null
  }

  const listPriceValue = commercialOffer.ListPrice
  const sellingPriceValue = commercialOffer.Price
  const { taxPercentage } = commercialOffer
  const listPriceWithTax = listPriceValue + listPriceValue * taxPercentage

  const measurementUnit =
    productContextValue?.selectedItem?.measurementUnit ?? ''

  const unitMultiplier = productContextValue?.selectedItem?.unitMultiplier ?? 1
  const listPriceWithUnitMultiplier = commercialOffer.ListPrice * unitMultiplier

  const taxValue = commercialOffer.Tax

  const hasMeasurementUnit = measurementUnit && measurementUnit !== 'un'
  const hasUnitMultiplier = unitMultiplier !== 1

  const listPriceWithTaxWithoutUnitMultiplier = listPriceValue + (listPriceValue * taxPercentage / unitMultiplier)

  if (listPriceValue <= sellingPriceValue) {
    return null
  }

  const containerClasses = withModifiers('listPrice', [
    alwaysShow && commercialOffer.AvailableQuantity <= 0 ? 'isUnavailable' : '',
  ])

  return (
    <span className={containerClasses}>
      <IOMessageWithMarkers
        message={message}
        markers={markers}
        handleBase="listPrice"
        values={{
          hasMeasurementUnit,
          hasUnitMultiplier,
          listPriceValue: (
            <span
              key="listPriceValue"
              className={`${handles.listPriceValue} strike`}
            >
              <FormattedCurrency value={listPriceValue} />
            </span>
          ),
          listPriceWithTax: (
            <span
              key="listPriceWithTax"
              className={`${handles.listPriceWithTax} strike`}
            >
              <FormattedCurrency value={listPriceWithTax} />
            </span>
          ),
          listPriceWithTaxWithoutUnitMultiplier: (
            <span
              key="listPriceWithTaxWithoutUnitMultiplier"
              className={`${handles.listPriceWithTaxWithoutUnitMultiplier} strike`}
            >
              <FormattedCurrency value={listPriceWithTaxWithoutUnitMultiplier} />
            </span>
          ),
          listPriceWithUnitMultiplier: (
            <span
              key="listPriceWithUnitMultiplier"
              className={`${handles.listPriceWithUnitMultiplier} strike`}
            >
              <FormattedCurrency value={listPriceWithUnitMultiplier} />
            </span>
          ),
          taxPercentage: (
            <span key="taxPercentage" className={handles.taxPercentage}>
              <FormattedNumber value={taxPercentage} style="percent" />
            </span>
          ),
          taxValue: (
            <span key="taxValue" className={handles.taxValue}>
              <FormattedCurrency value={taxValue} />
            </span>
          ),
          unitMultiplier: (
            <span key="unitMultiplier" className={handles.unitMultiplier}>
              <FormattedNumber value={unitMultiplier} />
            </span>
          ),
          measurementUnit: (
            <span key="measurementUnit" className={handles.measurementUnit}>
              {measurementUnit}
            </span>
          ),
        }}
      />
    </span>
  )
}

ListPrice.schema = {
  title: messages.title.id,
}

export default ListPrice

```


[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-price/react/SellerName.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages } from 'react-intl'
import { IOMessageWithMarkers } from 'vtex.native-types'
import { useProduct } from 'vtex.product-context'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'

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

const CSS_HANDLES = ['sellerNameContainer', 'sellerName'] as const

const messages = defineMessages({
  title: {
    id: 'admin/seller-name.title',
  },
  description: {
    id: 'admin/seller-name.description',
  },
  default: {
    id: 'store/seller-name.default',
  },
})

interface Props {
  message?: string
  markers?: string[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function ProductSellerName({
  message = messages.default.id,
  markers = [],
  classes,
}: Partial<Props>) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const productContextValue = useProduct()

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  if (!seller) {
    return null
  }

  const containerClasses = withModifiers(
    'sellerNameContainer',
    seller.sellerDefault ? 'isDefaultSeller' : ''
  )

  return (
    <span className={containerClasses}>
      <IOMessageWithMarkers
        message={message}
        markers={markers}
        handleBase="sellerName"
        values={{
          sellerName: (
            <span key="sellerName" className={handles.sellerName}>
              {seller.sellerName}
            </span>
          ),
        }}
      />
    </span>
  )
}

ProductSellerName.schema = {
  title: messages.title.id,
}

export default ProductSellerName

```


[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-price/react/ListPriceRange.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages } from 'react-intl'
import { useProduct } from 'vtex.product-context'
import { FormattedCurrency } from 'vtex.format-currency'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'
import { IOMessageWithMarkers } from 'vtex.native-types'

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

const CSS_HANDLES = [
  'listPriceRange',
  'listPriceRangeMinValue',
  'listPriceRangeMinWithTax',
  'listPriceRangeMaxValue',
  'listPriceRangeMaxWithTax',
  'listPriceRangeUniqueValue',
  'listPriceRangeUniqueWithTax',
] as const

const messages = defineMessages({
  title: {
    id: 'admin/list-price-range.title',
  },
  messageTitle: {
    id: 'admin/list-price-range-message.title',
  },
  messageDescription: {
    id: 'admin/list-price-range-message.description',
  },
  messageDefault: {
    id: 'store/list-price-range-message.default',
  },
  noRangeMessageTitle: {
    id: 'admin/list-price-range-no-range-message.title',
  },
  noRangeMessageDescription: {
    id: 'admin/list-price-range-no-range-message.description',
  },
  noRangeMessageDefault: {
    id: 'store/list-price-range-no-range-message.default',
  },
})

interface Props {
  message?: string
  noRangeMessage?: string
  markers?: string[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  alwaysShow?: boolean
}

function ListPriceRange({
  message = messages.messageDefault.id,
  noRangeMessage = messages.noRangeMessageDefault.id,
  markers = [],
  classes,
  alwaysShow = false,
}: Props) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const productContextValue = useProduct()

  const priceRange = productContextValue?.product?.priceRange

  if (!priceRange) {
    return null
  }

  if (
    priceRange.listPrice.lowPrice === priceRange.sellingPrice.lowPrice &&
    priceRange.listPrice.highPrice === priceRange.sellingPrice.highPrice
  ) {
    return null
  }

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer ||
    hideProductPrice({
      alwaysShow,
      availableQuantity: commercialOffer.AvailableQuantity,
    })
  ) {
    return null
  }

  const minPrice: number = priceRange.listPrice.lowPrice
  const maxPrice: number = priceRange.listPrice.highPrice
  const hasRange = minPrice !== maxPrice
  const minPriceWithTax = minPrice + minPrice * commercialOffer.taxPercentage
  const maxPriceWithTax = maxPrice + maxPrice * commercialOffer.taxPercentage

  const containerClasses = withModifiers('listPriceRange', [
    alwaysShow && commercialOffer.AvailableQuantity <= 0 ? 'isUnavailable' : '',
  ])

  if (hasRange) {
    return (
      <span className={containerClasses}>
        <IOMessageWithMarkers
          message={message}
          markers={markers}
          handleBase="listPriceRange"
          values={{
            minPriceValue: (
              <span
                key="minPriceValue"
                className={`${handles.listPriceRangeMinValue} strike`}
              >
                <FormattedCurrency value={minPrice} />
              </span>
            ),
            maxPriceValue: (
              <span
                key="maxPriceValue"
                className={`${handles.listPriceRangeMaxValue} strike`}
              >
                <FormattedCurrency value={maxPrice} />
              </span>
            ),
            minPriceWithTax: (
              <span
                key="minPriceWithTax"
                className={`${handles.listPriceRangeMinWithTax} strike`}
              >
                <FormattedCurrency value={minPriceWithTax} />
              </span>
            ),
            maxPriceWithTax: (
              <span
                key="maxPriceWithTax"
                className={`${handles.listPriceRangeMaxWithTax} strike`}
              >
                <FormattedCurrency value={maxPriceWithTax} />
              </span>
            ),
          }}
        />
      </span>
    )
  }

  return (
    <span className={containerClasses}>
      <IOMessageWithMarkers
        message={noRangeMessage}
        markers={markers}
        handleBase="listPriceRange"
        values={{
          listPriceValue: (
            <span
              key="listPriceValue"
              className={`${handles.listPriceRangeUniqueValue} strike`}
            >
              <FormattedCurrency value={maxPrice} />
            </span>
          ),
          listPriceWithTax: (
            <span
              key="listPriceWithTax"
              className={`${handles.listPriceRangeUniqueWithTax} strike`}
            >
              <FormattedCurrency value={maxPriceWithTax} />
            </span>
          ),
        }}
      />
    </span>
  )
}

ListPriceRange.schema = {
  title: messages.title.id,
  type: 'object',
  properties: {
    message: {
      title: messages.messageTitle.id,
      description: messages.messageDescription.id,
    },
    noRangeMessage: {
      title: messages.noRangeMessageTitle.id,
      description: messages.noRangeMessageDescription.id,
    },
  },
}

export default ListPriceRange

```


[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-price/react/SellingPriceRange.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages } from 'react-intl'
import { useProduct } from 'vtex.product-context'
import { FormattedCurrency } from 'vtex.format-currency'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'
import { IOMessageWithMarkers } from 'vtex.native-types'

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

const CSS_HANDLES = [
  'sellingPriceRange',
  'sellingPriceRangeMinValue',
  'sellingPriceRangeMinWithTax',
  'sellingPriceRangeMaxValue',
  'sellingPriceRangeMaxWithTax',
  'sellingPriceRangeValue',
  'sellingPriceRangeWithTax',
  'sellingPriceRangeUniqueValue',
  'sellingPriceRangeUniqueWithTax',
] as const

const messages = defineMessages({
  title: {
    id: 'admin/selling-price-range.title',
  },
  messageTitle: {
    id: 'admin/selling-price-range-message.title',
  },
  messageDescription: {
    id: 'admin/selling-price-range-message.description',
  },
  messageDefault: {
    id: 'store/selling-price-range-message.default',
  },
  noRangeMessageTitle: {
    id: 'admin/selling-price-range-no-range-message.title',
  },
  noRangeMessageDescription: {
    id: 'admin/selling-price-range-no-range-message.description',
  },
  noRangeMessageDefault: {
    id: 'store/selling-price-range-no-range-message.default',
  },
})

interface Props {
  message?: string
  noRangeMessage?: string
  markers?: string[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  alwaysShow?: boolean
}

function SellingPriceRange({
  message = messages.messageDefault.id,
  noRangeMessage = messages.noRangeMessageDefault.id,
  markers = [],
  classes,
  alwaysShow = false,
}: Props) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const productContextValue = useProduct()

  const priceRange = productContextValue?.product?.priceRange

  if (!priceRange) {
    return null
  }

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer ||
    hideProductPrice({
      alwaysShow,
      availableQuantity: commercialOffer.AvailableQuantity,
    })
  ) {
    return null
  }

  const sellingPriceValue = commercialOffer.Price
  const listPriceValue = commercialOffer.ListPrice
  const hasListPrice = sellingPriceValue !== listPriceValue
  const minPrice: number = priceRange.sellingPrice.lowPrice
  const maxPrice: number = priceRange.sellingPrice.highPrice
  const hasRange = minPrice !== maxPrice
  const minPriceWithTax = minPrice + minPrice * commercialOffer.taxPercentage
  const maxPriceWithTax = maxPrice + maxPrice * commercialOffer.taxPercentage
  const sellingPrice = commercialOffer.PriceWithoutDiscount
  const sellingPriceWithTax =
    commercialOffer.PriceWithoutDiscount +
    commercialOffer.PriceWithoutDiscount * commercialOffer.taxPercentage

  const containerClasses = withModifiers('sellingPriceRange', [
    hasListPrice ? 'hasListPrice' : '',
    alwaysShow && commercialOffer.AvailableQuantity <= 0 ? 'isUnavailable' : '',
  ])

  if (hasRange) {
    return (
      <span className={containerClasses}>
        <IOMessageWithMarkers
          message={message}
          markers={markers}
          handleBase="sellingPriceRange"
          values={{
            minPriceValue: (
              <span
                key="minPriceValue"
                className={handles.sellingPriceRangeMinValue}
              >
                <FormattedCurrency value={minPrice} />
              </span>
            ),
            maxPriceValue: (
              <span
                key="maxPriceValue"
                className={handles.sellingPriceRangeMaxValue}
              >
                <FormattedCurrency value={maxPrice} />
              </span>
            ),
            minPriceWithTax: (
              <span
                key="minPriceWithTax"
                className={handles.sellingPriceRangeMinWithTax}
              >
                <FormattedCurrency value={minPriceWithTax} />
              </span>
            ),
            maxPriceWithTax: (
              <span
                key="maxPriceWithTax"
                className={handles.sellingPriceRangeMaxWithTax}
              >
                <FormattedCurrency value={maxPriceWithTax} />
              </span>
            ),
            sellingPriceValue: (
              <span
                key="sellingPriceValue"
                className={handles.sellingPriceRangeValue}
              >
                <FormattedCurrency value={sellingPrice} />
              </span>
            ),
            sellingPriceWithTax: (
              <span
                key="sellingPriceWithTax"
                className={handles.sellingPriceRangeWithTax}
              >
                <FormattedCurrency value={sellingPriceWithTax} />
              </span>
            ),
            hasListPrice,
          }}
        />
      </span>
    )
  }

  return (
    <span className={containerClasses}>
      <IOMessageWithMarkers
        message={noRangeMessage}
        markers={markers}
        handleBase="sellingPriceRange"
        values={{
          sellingPriceValue: (
            <span
              key="sellingPriceValue"
              className={handles.sellingPriceRangeUniqueValue}
            >
              <FormattedCurrency value={maxPrice} />
            </span>
          ),
          sellingPriceWithTax: (
            <span
              key="sellingPriceWithTax"
              className={handles.sellingPriceRangeUniqueWithTax}
            >
              <FormattedCurrency value={maxPriceWithTax} />
            </span>
          ),
        }}
      />
    </span>
  )
}

SellingPriceRange.schema = {
  title: messages.title.id,
  type: 'object',
  properties: {
    message: {
      title: messages.messageTitle.id,
      description: messages.messageDescription.id,
    },
    noRangeMessage: {
      title: messages.noRangeMessageTitle.id,
      description: messages.noRangeMessageDescription.id,
    },
  },
}

export default SellingPriceRange

```


[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-price/react/Savings.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages, useIntl, IntlFormatters } from 'react-intl'
import { useProduct } from 'vtex.product-context'
import { FormattedCurrency } from 'vtex.format-currency'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'
import { IOMessageWithMarkers } from 'vtex.native-types'
import { ProductSummaryContext } from 'vtex.product-summary-context'

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

const CSS_HANDLES = [
  'savings',
  'previousPriceValue',
  'newPriceValue',
  'savingsValue',
  'savingsWithTax',
  'savingsPercentage',
] as const

const messages = defineMessages({
  title: {
    id: 'admin/savings.title',
  },
  description: {
    id: 'admin/savings.description',
  },
  default: {
    id: 'store/savings.default',
  },
})

// This is essentially a space char (" ") that doesn't allow line breaks
// It is needed because the formatNumber function returns it that way
const NON_BREAKING_SPACE_CHAR = String.fromCharCode(160)

interface GetFormattedSavingsPercentageParams {
  formatNumber: IntlFormatters['formatNumber']
  savingsPercentage: number
  percentageStyle: Props['percentageStyle']
}

function getFormattedSavingsPercentage({
  formatNumber,
  savingsPercentage,
  percentageStyle,
}: GetFormattedSavingsPercentageParams) {
  const formattedSavingsPercentage = formatNumber(savingsPercentage, {
    style: 'percent',
  })

  if (percentageStyle === 'compact') {
    return formattedSavingsPercentage.replace(
      `${NON_BREAKING_SPACE_CHAR}%`,
      '%'
    )
  }

  return formattedSavingsPercentage
}

interface Props {
  message?: string
  markers?: string[]
  percentageStyle?: 'locale' | 'compact'
  minimumPercentage?: number
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  alwaysShow?: boolean
}

function Savings({
  message = messages.default.id,
  markers = [],
  minimumPercentage = 0,
  percentageStyle = 'locale',
  classes,
  alwaysShow = false,
}: Props) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const { formatNumber } = useIntl()
  const intl = useIntl()
  const productContextValue = useProduct()
  const productSummaryValue = ProductSummaryContext.useProductSummary()

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer ||
    hideProductPrice({
      alwaysShow,
      availableQuantity: commercialOffer.AvailableQuantity,
    }) ||
    productSummaryValue?.isLoading
  ) {
    return null
  }

  const previousPriceValue = commercialOffer.ListPrice
  const newPriceValue = commercialOffer.Price
  const savingsValue = previousPriceValue - newPriceValue
  const savingsWithTax =
    savingsValue + savingsValue * commercialOffer.taxPercentage

  const savingsPercentage = savingsValue / previousPriceValue

  if (savingsValue <= 0 || savingsPercentage < minimumPercentage / 100) {
    return null
  }

  const containerClasses = withModifiers('savings', [
    alwaysShow && commercialOffer.AvailableQuantity <= 0 ? 'isUnavailable' : '',
  ])

  return (
    <span
      className={containerClasses}
      aria-label={intl.formatMessage(
        { id: 'store/product-price.price-savings.aria-label' },
        { previousPriceValue, newPriceValue }
      )}
    >
      <IOMessageWithMarkers
        message={message}
        markers={markers}
        handleBase="savings"
        values={{
          previousPriceValue: (
            <span
              key="previousPriceValue"
              className={handles.previousPriceValue}
            >
              <FormattedCurrency value={previousPriceValue} />
            </span>
          ),
          newPriceValue: (
            <span key="newPriceValue" className={handles.newPriceValue}>
              <FormattedCurrency value={newPriceValue} />
            </span>
          ),
          savingsValue: (
            <span key="savingsValue" className={handles.savingsValue}>
              <FormattedCurrency value={savingsValue} />
            </span>
          ),
          savingsWithTax: (
            <span key="savingsWithTax" className={handles.savingsWithTax}>
              <FormattedCurrency value={savingsWithTax} />
            </span>
          ),
          savingsPercentage: (
            <span key="savingsPercentage" className={handles.savingsPercentage}>
              {getFormattedSavingsPercentage({
                formatNumber,
                savingsPercentage,
                percentageStyle,
              })}
            </span>
          ),
        }}
      />
    </span>
  )
}

Savings.schema = {
  title: messages.title.id,
}

export default Savings

```


[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-price/react/PriceSuspense.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

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

import PriceLoadingSpinner, {
  CSS_HANDLES as LoadingSpinnerHandles,
} from './components/PriceLoadingSpinner'

const CSS_HANDLES = [
  'priceSuspense',
  'priceSuspenseFallbackWrapper',
  'priceSuspenseContentWrapper',
  ...LoadingSpinnerHandles,
] as const

interface PriceSuspenseProps {
  Fallback?: React.ComponentType
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function PriceSuspense({
  children,
  Fallback,
  classes,
}: PropsWithChildren<PriceSuspenseProps>) {
  const { isPriceLoading } = ProductSummaryContext.useProductSummary()
  const { handles } = useCssHandles(CSS_HANDLES, { classes })

  return (
    <div className={`relative ${handles.priceSuspense}`}>
      {isPriceLoading && (
        <div className={handles.priceSuspenseFallbackWrapper}>
          {Fallback ? <Fallback /> : <PriceLoadingSpinner handles={handles} />}
        </div>
      )}
      <div
        className={`${handles.priceSuspenseContentWrapper} ${
          isPriceLoading ? 'dn' : ''
        }`}
      >
        {children}
      </div>
    </div>
  )
}

export default PriceSuspense

```


[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-price/react/SpotPriceSavings.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages, FormattedNumber } from 'react-intl'
import { useProduct } from 'vtex.product-context'
import { FormattedCurrency } from 'vtex.format-currency'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'
import { IOMessageWithMarkers } from 'vtex.native-types'

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

const CSS_HANDLES = [
  'spotPriceSavings',
  'previousPriceValue',
  'newSpotPriceValue',
  'spotPriceSavingsValue',
  'spotPriceSavingsWithTax',
  'spotPriceSavingsPercentage',
] as const

const messages = defineMessages({
  title: {
    id: 'admin/spot-price-savings.title',
  },
  description: {
    id: 'admin/spot-price-savings.description',
  },
  default: {
    id: 'store/spot-price-savings.default',
  },
})

interface Props {
  message?: string
  markers?: string[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  alwaysShow?: boolean
}

function SpotPriceSavings({
  message = messages.default.id,
  markers = [],
  classes,
  alwaysShow = false,
}: Props) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const productContextValue = useProduct()

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer ||
    hideProductPrice({
      alwaysShow,
      availableQuantity: commercialOffer.AvailableQuantity,
    })
  ) {
    return null
  }

  const previousPriceValue = commercialOffer.ListPrice
  const spotPriceValue = commercialOffer.spotPrice

  const spotPriceSavingsValue = previousPriceValue - spotPriceValue
  const spotPriceSavingsWithTax =
    spotPriceSavingsValue +
    spotPriceSavingsValue * commercialOffer.taxPercentage

  const spotPriceSavingsPercentage = spotPriceSavingsValue / previousPriceValue

  if (spotPriceSavingsValue <= 0) {
    return null
  }

  const containerClasses = withModifiers('spotPriceSavings', [
    alwaysShow && commercialOffer.AvailableQuantity <= 0 ? 'isUnavailable' : '',
  ])

  return (
    <span className={containerClasses}>
      <IOMessageWithMarkers
        message={message}
        markers={markers}
        handleBase="savings"
        values={{
          previousPriceValue: (
            <span
              key="previousPriceValue"
              className={handles.previousPriceValue}
            >
              <FormattedCurrency value={previousPriceValue} />
            </span>
          ),
          newSpotPriceValue: (
            <span key="newSpotPriceValue" className={handles.newSpotPriceValue}>
              <FormattedCurrency value={spotPriceValue} />
            </span>
          ),
          spotPriceSavingsValue: (
            <span
              key="spotPriceSavingsValue"
              className={handles.spotPriceSavingsValue}
            >
              <FormattedCurrency value={spotPriceSavingsValue} />
            </span>
          ),
          spotPriceSavingsWithTax: (
            <span
              key="spotPriceSavingsWithTax"
              className={handles.spotPriceSavingsWithTax}
            >
              <FormattedNumber value={spotPriceSavingsWithTax} />
            </span>
          ),
          spotPriceSavingsPercentage: (
            <span
              key="spotPriceSavingsPercentage"
              className={handles.spotPriceSavingsPercentage}
            >
              <FormattedNumber
                value={spotPriceSavingsPercentage}
                style="percent"
              />
            </span>
          ),
        }}
      />
    </span>
  )
}

SpotPriceSavings.schema = {
  title: messages.title.id,
}

export default SpotPriceSavings

```


[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-price/react/package.json from the product-price VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "scripts": {
    "lint": "tsc --noEmit && eslint --quiet --fix --ext ts,tsx .",
    "test": "vtex-test-tools test"
  },
  "dependencies": {
    "apollo-client": "^2.6.8",
    "react": "^16.8.6",
    "react-apollo": "^2.5.8",
    "react-dom": "^16.8.6",
    "react-intl": "^3.11.0",
    "slugify": "^1.5.3"
  },
  "devDependencies": {
    "@apollo/react-testing": "^3.1.3",
    "@types/classnames": "^2.2.11",
    "@types/graphql": "^14.5.0",
    "@types/jest": "^24.9.0",
    "@types/node": "^13.1.8",
    "@types/react": "^16.9.19",
    "@types/react-intl": "^3.0.0",
    "@vtex/test-tools": "^3.0.1",
    "@vtex/tsconfig": "^0.2.0",
    "apollo-cache-inmemory": "^1.6.5",
    "graphql": "^14.5.8",
    "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.format-currency": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.format-currency@0.3.0/public/@types/vtex.format-currency",
    "vtex.native-types": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.native-types@0.7.5/public/@types/vtex.native-types",
    "vtex.product-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-context@0.9.9/public/@types/vtex.product-context",
    "vtex.product-price": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-price@1.21.0/public/@types/vtex.product-price",
    "vtex.product-summary-context": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.product-summary-context@0.7.1/public/@types/vtex.product-summary-context",
    "vtex.render-runtime": "http://vtex.vtexassets.com/_v/public/typings/v1/vtex.render-runtime@8.128.4/public/@types/vtex.render-runtime"
  },
  "version": "1.33.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-price/react/Installments.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages } from 'react-intl'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'
import { useProduct } from 'vtex.product-context'

import InstallmentsRenderer, {
  CSS_HANDLES,
} from './components/InstallmentsRenderer'
import {
  pickMaxInstallmentsOption,
  pickMaxInstallmentsOptionWithoutInterest,
} from './modules/pickInstallments'
import { getDefaultSeller } from './modules/seller'

const messages = defineMessages({
  title: {
    id: 'admin/installments.title',
  },
  description: {
    id: 'admin/installments.description',
  },
  default: {
    id: 'store/installments.default',
  },
})

interface Props {
  message?: string
  markers?: string[]
  installmentsCriteria?: 'max-quantity' | 'max-quantity-without-interest'
  installmentOptionsFilter?: {
    paymentSystemName?: string
    installmentsQuantity?: number
  }
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function Installments({
  message = messages.default.id,
  markers = [],
  installmentsCriteria = 'max-quantity',
  installmentOptionsFilter,
  classes,
}: Props) {
  const productContextValue = useProduct()
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer?.Installments ||
    commercialOffer?.Installments?.length === 0
  ) {
    return null
  }

  let [installmentsOption] = commercialOffer.Installments

  switch (installmentsCriteria) {
    case 'max-quantity-without-interest': {
      installmentsOption = pickMaxInstallmentsOptionWithoutInterest(
        commercialOffer.Installments,
        installmentOptionsFilter
      )

      break
    }

    default: {
      installmentsOption = pickMaxInstallmentsOption(
        commercialOffer.Installments,
        installmentOptionsFilter
      )

      break
    }
  }

  return (
    <InstallmentsRenderer
      message={message}
      markers={markers}
      installment={installmentsOption}
      handles={handles}
      handlesModifierFunction={withModifiers}
    />
  )
}

Installments.schema = {
  title: messages.title.id,
}

export default Installments

```


[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-price/react/SpotPrice.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages } from 'react-intl'
import { useProduct } from 'vtex.product-context'
import { FormattedCurrency } from 'vtex.format-currency'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'
import { IOMessageWithMarkers } from 'vtex.native-types'

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

const CSS_HANDLES = ['spotPrice', 'spotPriceValue'] as const

const messages = defineMessages({
  title: {
    id: 'admin/spot-price.title',
  },
  description: {
    id: 'admin/spot-price.description',
  },
  default: {
    id: 'store/spot-price.default',
  },
})

interface Props {
  message?: string
  markers?: string[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  alwaysShow?: boolean
}

function SpotPrice({
  message = messages.default.id,
  markers = [],
  classes,
  alwaysShow = false,
}: Props) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, { classes })
  const productContextValue = useProduct()

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer ||
    hideProductPrice({
      alwaysShow,
      availableQuantity: commercialOffer.AvailableQuantity,
    })
  ) {
    return null
  }

  const spotPriceValue = commercialOffer.spotPrice
  const sellingPriceValue = commercialOffer.SellingPrice

  if (spotPriceValue === sellingPriceValue) {
    return null
  }

  const containerClasses = withModifiers('spotPrice', [
    alwaysShow && commercialOffer.AvailableQuantity <= 0 ? 'isUnavailable' : '',
  ])

  return (
    <span className={containerClasses}>
      <IOMessageWithMarkers
        message={message}
        markers={markers}
        handleBase="spotPrice"
        values={{
          spotPriceValue: (
            <span key="spotPriceValue" className={handles.spotPriceValue}>
              <FormattedCurrency value={spotPriceValue} />
            </span>
          ),
        }}
      />
    </span>
  )
}

SpotPrice.schema = {
  title: messages.title.id,
}

export default SpotPrice

```


[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-price/react/SellingPrice.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { defineMessages, FormattedNumber } from 'react-intl'
import { useProduct } from 'vtex.product-context'
import { FormattedCurrency } from 'vtex.format-currency'
import { IOMessageWithMarkers } from 'vtex.native-types'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'

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

const CSS_HANDLES = [
  'sellingPrice',
  'sellingPriceValue',
  'sellingPriceWithTax',
  'sellingPriceWithUnitMultiplier',
  'sellingPriceWithUnitMultiplierAndTax',
  'sellingPriceWithTaxWithoutUnitMultiplier',
  'taxPercentage',
  'taxValue',
  'measurementUnit',
  'unitMultiplier',
] as const

const messages = defineMessages({
  title: {
    id: 'admin/selling-price.title',
  },
  description: {
    id: 'admin/selling-price.description',
  },
  default: {
    id: 'store/selling-price.default',
  },
})

interface Props {
  message?: string
  markers?: string[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
  alwaysShow?: boolean
}

function SellingPrice({
  message = messages.default.id,
  markers = [],
  classes,
  alwaysShow = false,
}: Props) {
  const { handles, withModifiers } = useCssHandles(CSS_HANDLES, {
    classes,
  })

  const productContextValue = useProduct()

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer ||
    hideProductPrice({
      alwaysShow,
      availableQuantity: commercialOffer.AvailableQuantity,
    })
  ) {
    return null
  }

  const sellingPriceValue = commercialOffer.Price
  const listPriceValue = commercialOffer.ListPrice
  const { taxPercentage } = commercialOffer
  const sellingPriceWithTax =
    sellingPriceValue + sellingPriceValue * taxPercentage

  const measurementUnit =
    productContextValue?.selectedItem?.measurementUnit ?? ''

  const unitMultiplier = productContextValue?.selectedItem?.unitMultiplier ?? 1
  const sellingPriceWithUnitMultiplier = sellingPriceValue * unitMultiplier

  const sellingPriceWithUnitMultiplierAndTax =
  sellingPriceValue * (unitMultiplier + taxPercentage)

  const sellingPriceWithTaxWithoutUnitMultiplier =
  sellingPriceValue + (sellingPriceValue * taxPercentage / unitMultiplier)

  const taxValue = commercialOffer.Tax

  const hasListPrice = sellingPriceValue !== listPriceValue
  const hasMeasurementUnit = measurementUnit && measurementUnit !== 'un'
  const hasUnitMultiplier = unitMultiplier !== 1

  const containerClasses = withModifiers('sellingPrice', [
    hasListPrice ? 'hasListPrice' : '',
    hasMeasurementUnit ? 'hasMeasurementUnit' : '',
    hasUnitMultiplier ? 'hasUnitMultiplier' : '',
    alwaysShow && commercialOffer.AvailableQuantity <= 0 ? 'isUnavailable' : '',
  ])

  return (
    <span className={containerClasses}>
      <IOMessageWithMarkers
        message={message}
        markers={markers}
        handleBase="sellingPrice"
        values={{
          sellingPriceValue: (
            <span key="sellingPriceValue" className={handles.sellingPriceValue}>
              <FormattedCurrency value={sellingPriceValue} />
            </span>
          ),
          sellingPriceWithTax: (
            <span
              key="sellingPriceWithTax"
              className={handles.sellingPriceWithTax}
            >
              <FormattedCurrency value={sellingPriceWithTax} />
            </span>
          ),
          sellingPriceWithUnitMultiplier: (
            <span
              key="sellingPriceWithUnitMultiplier"
              className={handles.sellingPriceWithUnitMultiplier}
            >
              <FormattedCurrency value={sellingPriceWithUnitMultiplier} />
            </span>
          ),
          sellingPriceWithUnitMultiplierAndTax: (
            <span
              key="sellingPriceWithUnitMultiplierAndTax"
              className={handles.sellingPriceWithUnitMultiplierAndTax}
            >
              <FormattedCurrency value={sellingPriceWithUnitMultiplierAndTax} />
            </span>
          ),
          sellingPriceWithTaxWithoutUnitMultiplier: (
            <span
              key="sellingPriceWithTaxWithoutUnitMultiplier"
              className={handles.sellingPriceWithTaxWithoutUnitMultiplier}
            >
              <FormattedCurrency value={sellingPriceWithTaxWithoutUnitMultiplier} />
            </span>
          ),
          taxPercentage: (
            <span key="taxPercentage" className={handles.taxPercentage}>
              <FormattedNumber value={taxPercentage} style="percent" />
            </span>
          ),
          taxValue: (
            <span key="taxValue" className={handles.taxValue}>
              <FormattedCurrency value={taxValue} />
            </span>
          ),
          hasMeasurementUnit,
          hasListPrice,
          hasUnitMultiplier,
          unitMultiplier: (
            <span key="unitMultiplier" className={handles.unitMultiplier}>
              <FormattedNumber value={unitMultiplier} />
            </span>
          ),
          measurementUnit: (
            <span key="measurementUnit" className={handles.measurementUnit}>
              {measurementUnit}
            </span>
          ),
        }}
      />
    </span>
  )
}

SellingPrice.schema = {
  title: messages.title.id,
}

export default SellingPrice

```


[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-price/react/InstallmentsList.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React, { PropsWithChildren, useMemo } from 'react'
import { useCssHandles, CssHandlesTypes } from 'vtex.css-handles'
import { ProductTypes, useProduct } from 'vtex.product-context'
import { Installment } from 'vtex.product-context/react/ProductTypes'

import { InstallmentsContextProvider } from './components/InstallmentsContext'
import pickInstallmentsList, {
  clusterInstallments,
} from './modules/pickInstallments'
import { getDefaultSeller } from './modules/seller'

const CSS_HANDLES = ['installmentsListContainer'] as const

interface Props {
  paymentSystemName?: string
  installmentsToShow?: number[]
  /** Used to override default CSS handles */
  classes?: CssHandlesTypes.CustomClasses<typeof CSS_HANDLES>
}

function InstallmentsList({
  paymentSystemName,
  installmentsToShow,
  classes,
  children,
}: PropsWithChildren<Props>) {
  const { handles } = useCssHandles(CSS_HANDLES, { classes })
  const productContextValue = useProduct()

  const seller = getDefaultSeller(productContextValue?.selectedItem?.sellers)

  const commercialOffer = seller?.commertialOffer

  if (
    !commercialOffer?.Installments ||
    commercialOffer.Installments?.length === 0
  ) {
    return null
  }

  let pickedInstallments: Installment[] = []

  if (paymentSystemName) {
    const installmentsByPaymentSystem = clusterInstallments(
      commercialOffer.Installments,
      'PaymentSystemName'
    )

    if (!installmentsByPaymentSystem[paymentSystemName]) {
      return null
    }

    pickedInstallments = installmentsByPaymentSystem[paymentSystemName]
  } else {
    pickedInstallments = pickInstallmentsList(
      commercialOffer.Installments,
      'PaymentSystemName'
    )
  }

  const filteredInstallments = installmentsToShow
    ? pickedInstallments.filter(option =>
        installmentsToShow.includes(option.NumberOfInstallments)
      )
    : pickedInstallments

  return (
    <div className={handles.installmentsListContainer}>
      {filteredInstallments.map((inst, i) => {
        return (
          <InstallmentsItem installment={inst} key={i}>
            {children}
          </InstallmentsItem>
        )
      })}
    </div>
  )
}

interface ItemProps {
  installment: ProductTypes.Installment
}

function InstallmentsItem(props: PropsWithChildren<ItemProps>) {
  const { installment, children } = props
  const contextValue = useMemo(() => ({ installment }), [installment])

  return (
    <InstallmentsContextProvider value={contextValue}>
      {children}
    </InstallmentsContextProvider>
  )
}

export default InstallmentsList

```


[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-price/store/contentSchemas.json from the product-price VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "definitions": {
    "ListPrice": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/list-price.title",
          "description": "admin/list-price.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/list-price.default"
        }
      }
    },
    "SellingPrice": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/selling-price.title",
          "description": "admin/selling-price.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/selling-price.default"
        }
      }
    },
    "SpotPrice": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/spot-price.title",
          "description": "admin/spot-price.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/spot-price.default"
        }
      }
    },
    "SpotPriceSavings": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/spot-price-savings.title",
          "description": "admin/spot-price-savings.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/spot-price-savings.default"
        }
      }
    },
    "Savings": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/savings.title",
          "description": "admin/savings.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/savings.default"
        }
      }
    },
    "Installments": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/installments.title",
          "description": "admin/installments.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/installments.default"
        }
      }
    },
    "InstallmentsListItem": {
      "title": "admin/installments-list-item.title",
      "type": "object",
      "properties": {
        "$ref": "app:vtex.product-price#/definitions/Installments/properties"
      }
    },
    "ListPriceRange": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/list-price-range-message.title",
          "description": "admin/list-price-range-message.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/list-price-range-message.default"
        },
        "noRangeMessage": {
          "title": "admin/list-price-range-no-range-message.title",
          "description": "admin/list-price-range-no-range-message.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/list-price-range-no-range-message.default"
        }
      }
    },
    "SellingPriceRange": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/selling-price-range-message.title",
          "description": "admin/selling-price-range-message.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/selling-price-range-message.default"
        },
        "noRangeMessage": {
          "title": "admin/selling-price-range-no-range-message.title",
          "description": "admin/selling-price-range-no-range-message.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/selling-price-range-no-range-message.default"
        }
      }
    },
    "SellerName": {
      "type": "object",
      "properties": {
        "message": {
          "title": "admin/seller-name.title",
          "description": "admin/seller-name.description",
          "$ref": "app:vtex.native-types#/definitions/text",
          "default": "store/seller-name.default"
        }
      }
    }
  }
}

```


[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-price/store/interfaces.json from the product-price VTEX app.

[Assistant]
Here's the implementation:

```json
{
  "product-list-price": {
    "component": "ListPrice",
    "composition": "children",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/ListPrice"
    }
  },
  "product-selling-price": {
    "component": "SellingPrice",
    "composition": "children",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/SellingPrice"
    }
  },
  "product-spot-price": {
    "component": "SpotPrice",
    "composition": "children",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/SpotPrice"
    }
  },
  "product-spot-price-savings": {
    "component": "SpotPriceSavings",
    "composition": "children",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/SpotPriceSavings"
    }
  },
  "product-price-savings": {
    "component": "Savings",
    "composition": "children",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/Savings"
    }
  },
  "product-installments": {
    "component": "Installments",
    "composition": "children",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/Installments"
    }
  },
  "product-installments-list": {
    "component": "InstallmentsList",
    "composition": "children"
  },
  "product-installments-list-item": {
    "component": "InstallmentsListItem",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/InstallmentsListItem"
    }
  },
  "product-list-price-range": {
    "component": "ListPriceRange",
    "composition": "children",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/ListPriceRange"
    }
  },
  "product-selling-price-range": {
    "component": "SellingPriceRange",
    "composition": "children",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/SellingPriceRange"
    }
  },
  "product-seller-name": {
    "component": "SellerName",
    "content": {
      "$ref": "app:vtex.product-price#/definitions/SellerName"
    }
  },
  "product-price-suspense": {
    "component": "PriceSuspense",
    "composition": "children"
  }
}

```


[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-price/react/typings/installments-list-mock.d.ts from the product-price VTEX app.

[Assistant]
Here's the implementation:

```typescript
declare module 'installments-list-mock' {
  import { Installments } from '../components/InstallmentsContext'

  export const installmentsList: Installments[]
  export const visaInstallments: Installments[]
  export const installmentsListMastercardMax: Installments[]
  export const masterCardInstallments: Installments[]
}

```


[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-price/react/typings/css.d.ts from the product-price VTEX app.

[Assistant]
Here's the implementation:

```typescript
declare module '*.css' {
  type Styles = {
    [selector: string]: string
  }

  const styles: Styles

  export default styles
}

```


[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-price/react/components/InstallmentsContext.ts from the product-price VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { createContext, useContext } from 'react'
import { ProductTypes } from 'vtex.product-context'

const InstallmentsContext = createContext<
  { installment: ProductTypes.Installment } | undefined
>(undefined)

export const InstallmentsContextProvider = InstallmentsContext.Provider

export function useInstallments() {
  return useContext(InstallmentsContext)
}

```


[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-price/react/components/InstallmentsRenderer.tsx from the product-price VTEX app.

[Assistant]
Here's the implementation:

```tsx
import React from 'react'
import { FormattedNumber } from 'react-intl'
import { CssHandlesTypes } from 'vtex.css-handles'
import { FormattedCurrency } from 'vtex.format-currency'
import { IOMessageWithMarkers } from 'vtex.native-types'
import { ProductTypes } from 'vtex.product-context'

import { slug } from '../modules/slug'

export const CSS_HANDLES = [
  'installments',
  'installmentsNumber',
  'installmentValue',
  'installmentsTotalValue',
  'interestRate',
  'paymentSystemName',
] as const

interface Props {
  message: string
  markers: string[]
  installment: ProductTypes.Installment
  handles: CssHandlesTypes.CssHandlesBag<typeof CSS_HANDLES>['handles']
  handlesModifierFunction: CssHandlesTypes.CssHandlesBag<
    typeof CSS_HANDLES
  >['withModifiers']
}

function InstallmentsRenderer({
  message,
  markers,
  installment,
  handles,
  handlesModifierFunction: withModifiers,
}: Props) {
  const {
    Value,
    NumberOfInstallments,
    InterestRate,
    PaymentSystemName,
    TotalValuePlusInterestRate,
  } = installment

  const hasMoreThanOne = NumberOfInstallments > 1

  const hasInterest = InterestRate !== 0

  const interestRatePercent = InterestRate / 100

  return (
    <span className={handles.installments}>
      <IOMessageWithMarkers
        message={message}
        markers={markers}
        handleBase="installments"
        values={{
          installmentsNumber: (
            <span
              key="installmentsNumber"
              className={withModifiers(
                'installmentsNumber',
                `${NumberOfInstallments}`
              )}
            >
              {NumberOfInstallments && (
                <FormattedNumber value={NumberOfInstallments} />
              )}
            </span>
          ),
          installmentValue: (
            <span key="installmentValue" className={handles.installmentValue}>
              <FormattedCurrency value={Value} />
            </span>
          ),
          installmentsTotalValue: (
            <span
              key="installmentsTotalValue"
              className={handles.installmentsTotalValue}
            >
              <FormattedCurrency value={TotalValuePlusInterestRate} />
            </span>
          ),
          interestRate: (
            <span key="interestRate" className={handles.interestRate}>
              {interestRatePercent && (
                <FormattedNumber
                  value={interestRatePercent}
                  style="percent"
                  maximumFractionDigits={2}
                  minimumFractionDigits={0}
                />
              )}
            </span>
          ),
          paymentSystemName: (
            <span
              key="paymentSystemName"
              className={withModifiers(
                'paymentSystemName',
                `${slug(PaymentSystemName)}`
              )}
            >
              {PaymentSystemName}
            </span>
          ),
          hasInterest,
          hasMoreThanOne,
        }}
      />
    </span>
  )
}

export default InstallmentsRenderer

```


[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-price/react/__tests__/pickInstallments.test.ts from the product-price VTEX app.

[Assistant]
Here's the implementation:

```typescript
import {
  installmentsList,
  visaInstallments,
  installmentsListMastercardMax,
} from 'installments-list-mock'

import pickInstallmentsList, {
  pickMaxInstallmentsOption,
  pickMaxInstallmentsOptionWithoutInterest,
} from '../modules/pickInstallments'

describe('pickInstallmentsList', () => {
  it('should pick installments of the payment method with the biggest amount of options', () => {
    const pickedInstallments = pickInstallmentsList(
      installmentsList,
      'PaymentSystemName'
    )

    expect(pickedInstallments).toHaveLength(visaInstallments.length)
    expect(pickedInstallments).toEqual(visaInstallments)
  })

  it('should pick MasterCard because the NumberOfInstallments is bigger', () => {
    const pickedInstallments = pickInstallmentsList(
      installmentsListMastercardMax,
      'PaymentSystemName'
    )

    expect(pickedInstallments[0].PaymentSystemName).toBe('Mastercard')

    let maxNumberOfInstallments = pickedInstallments[0].NumberOfInstallments
    let index = 0

    for (let i = 1; i < pickedInstallments.length; i++) {
      if (
        maxNumberOfInstallments < pickedInstallments[i].NumberOfInstallments
      ) {
        maxNumberOfInstallments = pickedInstallments[i].NumberOfInstallments
        index = i
      }
    }

    expect(pickedInstallments[index].NumberOfInstallments).toBe(15)
  })
})

describe('pickMaxInstallmentsOption', () => {
  it('should pick the installments plan with the highest NumberOfInstallments', () => {
    const pickedInstallmentsPlan = pickMaxInstallmentsOption(
      installmentsListMastercardMax
    )

    expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(15)
    expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Mastercard')
  })

  it('should pick the installments plan with the highest NumberOfInstallments, from a certain payment system', () => {
    const pickedInstallmentsPlan = pickMaxInstallmentsOption(installmentsList, {
      paymentSystemName: 'Customer Credit',
    })

    expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(3)
    expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Customer Credit')
  })

  it('should pick an installments plan with a certain number of installments', () => {
    const pickedInstallmentsPlan = pickMaxInstallmentsOption(
      installmentsListMastercardMax,
      {
        installmentsQuantity: 14,
      }
    )

    expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(14)
  })

  it('should pick the installments plan with the highest NumberOfInstallments, using all filtering options', () => {
    const pickedInstallmentsPlan = pickMaxInstallmentsOption(installmentsList, {
      paymentSystemName: 'Customer Credit',
      installmentsQuantity: 2,
    })

    expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(2)
    expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Customer Credit')
  })
})

describe('pickMaxInstallmentsOptionWithoutInterest', () => {
  it('should pick the installments plan with the highest NumberOfInstallments and no interest', () => {
    const pickedInstallmentsPlan = pickMaxInstallmentsOptionWithoutInterest(
      installmentsListMastercardMax
    )

    expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(14)
    expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Visa')
    expect(pickedInstallmentsPlan.InterestRate).toBe(0)
  })

  it('should pick the installments plan with the second highest NumberOfInstallments and no interest, due to filtering rules', () => {
    const pickedInstallmentsPlan = pickMaxInstallmentsOptionWithoutInterest(
      installmentsList,
      {
        paymentSystemName: 'Customer Credit',
      }
    )

    expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(2)
    expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Customer Credit')
    expect(pickedInstallmentsPlan.InterestRate).toBe(0)
  })

  it("should pick the installments plan with the highest NumberOfInstallments overall, if there isn't an option with no interest", () => {
    const pickedInstallmentsPlan = pickMaxInstallmentsOptionWithoutInterest(
      visaInstallments
    )

    expect(pickedInstallmentsPlan.NumberOfInstallments).toBe(4)
    expect(pickedInstallmentsPlan.PaymentSystemName).toBe('Visa')
  })
})

```


[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-price/react/modules/pickInstallments.ts from the product-price VTEX app.

[Assistant]
Here's the implementation:

```typescript
import { ProductTypes } from 'vtex.product-context'

type ClusterBy = keyof ProductTypes.Installment

/**
 * Pick which installments should be used. First it clusters all installments
 * by the value of clusterBy, then picks the cluster with the biggest amount of
 * installments options and then return this list sorted by the amount of installments
 * @param installmentsList All installments
 * @param clusterBy
 */
export default function pickInstallmentsList(
  installmentsList: ProductTypes.Installment[],
  clusterBy: ClusterBy
) {
  const clusteredInstallments = clusterInstallments(installmentsList, clusterBy)

  const pickedInstallments = pickMaxOptionCount(
    clusteredInstallments,
    clusterBy
  )

  return pickedInstallments.sort(
    (a, b) => a.NumberOfInstallments - b.NumberOfInstallments
  )
}

/**
 * Cluster installments by the passed key of installments type
 * @param installmentsList List of installments to be clustered
 * @param clusterBy Key to be clustered by
 */
export function clusterInstallments(
  installmentsList: ProductTypes.Installment[],
  clusterBy: keyof ProductTypes.Installment
) {
  const clusteredInstallments: Record<string, ProductTypes.Installment[]> = {}

  for (const installment of installmentsList) {
    if (!clusteredInstallments[installment[clusterBy]]) {
      clusteredInstallments[installment[clusterBy]] = []
    }

    clusteredInstallments[installment[clusterBy]].push(installment)
  }

  return clusteredInstallments
}

/**
 * Pick the cluster with the biggest amount of options, if there are multiple
 * clusters with the biggest amount it will pick the one that has an installment
 * with the biggest NumberOfInstallments of all options
 * @param clusteredInstallments
 * @param clusterBy
 */
function pickMaxOptionCount(
  clusteredInstallments: Record<string, ProductTypes.Installment[]>,
  clusterBy: ClusterBy
) {
  const clusterKeys = Object.keys(clusteredInstallments)
  let maxOptionCount = clusteredInstallments[clusterKeys[0]].length

  for (let i = 1; i < clusterKeys.length; i++) {
    const curOption = clusteredInstallments[clusterKeys[i]]

    if (maxOptionCount < curOption.length) {
      maxOptionCount = curOption.length
    }
  }

  const maxOptions = clusterKeys.filter(
    key => clusteredInstallments[key].length === maxOptionCount
  )

  if (maxOptions.length === 1) {
    return clusteredInstallments[maxOptions[0]]
  }

  let biggestInstallmentsOption =
    clusteredInstallments[maxOptions[0]][0].NumberOfInstallments

  let [biggestInstallmentsOptionKey]: Array<string | number> = maxOptions

  for (const key of maxOptions) {
    for (const installmentsOptions of clusteredInstallments[key]) {
      if (
        installmentsOptions.NumberOfInstallments > biggestInstallmentsOption
      ) {
        biggestInstallmentsOption = installmentsOptions.NumberOfInstallments
        biggestInstallmentsOptionKey = installmentsOptions[clusterBy]
      }
    }
  }

  return clusteredInstallments[biggestInstallmentsOptionKey]
}

function applyFiltersToInstallmentsList(
  installmentsList: ProductTypes.Installment[],
  filteringRules: {
    paymentSystemName?: string
    installmentsQuantity?: number
  }
) {
  let filteredInstallmentsList = installmentsList

  if (filteringRules.paymentSystemName) {
    filteredInstallmentsList = filteredInstallmentsList.filter(
      installmentsOption =>
        installmentsOption.PaymentSystemName ===
        filteringRules.paymentSystemName
    )
  }

  if (filteringRules.installmentsQuantity) {
    filteredInstallmentsList = filteredInstallmentsList.filter(
      installmentsOption =>
        installmentsOption.NumberOfInstallments ===
        filteringRules.installmentsQuantity
    )
  }

  return filteredInstallmentsList
}

export function pickMaxInstallmentsOption(
  installmentsList: ProductTypes.Installment[],
  filteringRules?: {
    paymentSystemName?: string
    installmentsQuantity?: number
  }
) {
  const filteredInstallmentsList = filteringRules
    ? applyFiltersToInstallmentsList(installmentsList, filteringRules)
    : installmentsList

  let [maxInstallmentOption] = filteredInstallmentsList

  filteredInstallmentsList.forEach(installmentOption => {
    if (
      installmentOption.NumberOfInstallments >
      maxInstallmentOption.NumberOfInstallments
    ) {
      maxInstallmentOption = installmentOption
    }
  })

  return maxInstallmentOption
}

export function pickMaxInstallmentsOptionWithoutInterest(
  installmentsList: ProductTypes.Installment[],
  filteringRules?: {
    paymentSystemName?: string
    installmentsQuantity?: number
  }
) {
  const installmentsWithoutInterest = installmentsList.filter(
    installmentsOption => installmentsOption.InterestRate === 0
  )

  // There aren't any no-interest options
  if (installmentsWithoutInterest.length === 0) {
    return pickMaxInstallmentsOption(installmentsList, filteringRules)
  }

  return pickMaxInstallmentsOption(installmentsWithoutInterest, filteringRules)
}

```


[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-price/react/modules/slug.ts from the product-price VTEX app.

[Assistant]
Here's the implementation:

```typescript
import slugify from 'slugify'

export function slug(str: string) {
  const replaced =
    (typeof str === 'string' && str.replace(/[*+~.()'"!:@&[\]]/g, '')) || ''

  const slugified = slugify(replaced, { lower: true }) || ''

  return slugified
}

```