Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: add sponsored products to list #392

Merged
merged 13 commits into from
Aug 1, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.

## [Unreleased]

### Added

- Calls sponsored products on product list.

## [2.89.0] - 2023-12-21

### Added
Expand Down
2 changes: 1 addition & 1 deletion docs/ProductSummaryShelf.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ The Product Summary Shelf is the main block exported by the [Product Summary app
| `priceBehavior` | `enum` | Determines whether the component should fetch the most up-to-date price (`async`) or (`default`). Remember to configure the [Search Result](https://vtex.io/docs/components/content-blocks/[email protected]/#configuration)'s `simulationBehavior` prop to `skip` and use the Product Price [`product-price-suspense`](https://github.com/vtex-apps/product-price/blob/master/docs/README.md) block to render a loading spinner while the price information is being fetched. | `default` |
| `trackListName` | `boolean` | Determines whether the component should send the list name to the product page when the product summary is clicked. Disabling it will prevent the `productDetail` GTM event sent on the PDP to identify from which list the user navigated. | `true` |
| `sponsoredBadgeLabel` | `String` | The text of the "Sponsored" tag, if applicable. | `"store/sponsored-badge.label"`|
| `sponsoredBadgePosition` | `enum` | The position of the "Sponsored" tag, if applicable. Possible values are `titleTop`, `containerTopLeft` and `none`. | `"titleTop"`|
| `sponsoredBadgePosition` | `enum` | The position of the "Sponsored" tag, if applicable. Possible values are `titleTop`, `containerTopLeft` and `none`. | `"containerTopLeft"`|

## Customization

Expand Down
6 changes: 6 additions & 0 deletions messages/context.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
"admin/editor.productSummaryList.installmentCriteria.max-without-interest": "Maximum without interest enum name",
"admin/editor.productSummaryList.installmentCriteria.max-with-interest": "Maximum with interest enum name",
"admin/editor.productSummaryList.analyticsListName.title": "List name in Analytics",
"admin/editor.productSummaryList.showSponsoredProducts.title": "Show contextual sponsored products",
"admin/editor.productSummaryList.showSponsoredProducts.description": "Whether or not to show contextual sponsored products in this list.",
"admin/editor.productSummaryList.sponsoredCount.title": "Maximum amount of sponsored products",
"admin/editor.productSummaryList.sponsoredCount.description": "If there are sponsored products for this search, how many of them should be displayed before normal products",
"admin/editor.productSummaryList.repeatSponsoredProducts.title": "Repeat sponsored and regular products",
"admin/editor.productSummaryList.repeatSponsoredProducts.description": "Show the same product twice - once sponsored and another in its regular position. If false, only the sponsored product will be shown.",
"admin/editor.productSummary.trackListName.title": "Should track list name",
"admin/editor.productSummary.sponsoredBadge.title": "admin/editor.productSummary.sponsoredBadge.title",
"admin/editor.productSummary.sponsoredBadge.position": "admin/editor.productSummary.sponsoredBadge.position",
Expand Down
6 changes: 6 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@
"admin/editor.productSummaryList.installmentCriteria.max-without-interest": "Maximum without interest",
"admin/editor.productSummaryList.installmentCriteria.max-with-interest": "Maximum",
"admin/editor.productSummaryList.analyticsListName.title": "List name in Analytics",
"admin/editor.productSummaryList.showSponsoredProducts.title": "Show contextual sponsored products",
"admin/editor.productSummaryList.showSponsoredProducts.description": "Whether or not to show contextual sponsored products in this list.",
"admin/editor.productSummaryList.sponsoredCount.title": "Maximum amount of sponsored products",
"admin/editor.productSummaryList.sponsoredCount.description": "If there are sponsored products for this search, how many of them should be displayed before normal products",
"admin/editor.productSummaryList.repeatSponsoredProducts.title": "Repeat sponsored and regular products",
"admin/editor.productSummaryList.repeatSponsoredProducts.description": "Show the same product twice - once sponsored and another in its regular position. If false, only the sponsored product will be shown.",
"admin/editor.productSummary.trackListName.title": "Track list name",
"admin/editor.productSummary.sponsoredBadge.title": "Text of the sponsored product tag, if applicable",
"admin/editor.productSummary.sponsoredBadge.position": "Position of the sponsored product tag, if applicable",
Expand Down
6 changes: 6 additions & 0 deletions messages/pt-BR.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@
"admin/editor.productSummaryList.installmentCriteria.max-without-interest": "Máximo sem juros",
"admin/editor.productSummaryList.installmentCriteria.max-with-interest": "Máximo",
"admin/editor.productSummaryList.analyticsListName.title": "Nome da lista no Analytics",
"admin/editor.productSummaryList.showSponsoredProducts.title": "Mostrar produtos patrocinados contextuais",
"admin/editor.productSummaryList.showSponsoredProducts.description": "Se deve-se mostrar produtos patrocinados contextuais nesta lista.",
"admin/editor.productSummaryList.sponsoredCount.title": "Quantidade máxima de produtos patrocinados",
"admin/editor.productSummaryList.sponsoredCount.description": "Caso haja produtos patrocinados para esta busca, quantos deles devem ser exibidos antes dos produtos normais",
"admin/editor.productSummaryList.repeatSponsoredProducts.title": "Repetir produtos patrocinados e regulares",
"admin/editor.productSummaryList.repeatSponsoredProducts.description": "Mostrar o mesmo produto duas vezes - uma vez patrocinado e outro em sua posição regular. Se falso, apenas o produto patrocinado será exibido.",
"admin/editor.productSummary.trackListName.title": "Rastreia o nome da lista",
"admin/editor.productSummary.sponsoredBadge.title": "Texto da tag de produto patrocinado, se aplicável",
"admin/editor.productSummary.sponsoredBadge.position": "Posição da tag de produto patrocinado",
Expand Down
34 changes: 23 additions & 11 deletions react/ProductSummaryCustom.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import React, { useCallback, useMemo, useEffect, useRef } from 'react'
import type { PropsWithChildren } from 'react'
import classNames from 'classnames'
import { Link } from 'vtex.render-runtime'
import type { PropsWithChildren } from 'react'
import React, { useCallback, useEffect, useMemo, useRef } from 'react'
import type { CssHandlesTypes } from 'vtex.css-handles'
import { useCssHandles } from 'vtex.css-handles'
import { useOnView } from 'vtex.on-view'
import type { ProductTypes } from 'vtex.product-context'
import { ProductContextProvider } from 'vtex.product-context'
import { ProductListContext } from 'vtex.product-list-context'
import { ProductSummaryContext } from 'vtex.product-summary-context'
import type { ProductSummaryTypes } from 'vtex.product-summary-context'
import { ProductContextProvider } from 'vtex.product-context'
import type { ProductTypes } from 'vtex.product-context'
import { useCssHandles } from 'vtex.css-handles'
import type { CssHandlesTypes } from 'vtex.css-handles'
import { ProductSummaryContext } from 'vtex.product-summary-context'
import { SponsoredBadgePosition } from 'vtex.product-summary-context/react/ProductSummaryTypes'
import { Link } from 'vtex.render-runtime'

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

const {
ProductSummaryProvider,
Expand All @@ -39,6 +39,7 @@ function ProductSummaryCustom({
children,
href,
priceBehavior = 'default',
placement,
position,
classes,
}: PropsWithChildren<Props>) {
Expand Down Expand Up @@ -167,7 +168,12 @@ function ProductSummaryCustom({
onClickCapture: autocompleteSummary ? undefined : actionOnClick,
}

const adsDataProperties = getAdsDataProperties({ product, position })
const adsDataProperties = getAdsDataProperties({
product,
position,
placement,
})

const showSponsoredBadge = shouldShowSponsoredBadge(
product,
sponsoredBadge?.position as SponsoredBadgePosition,
Expand Down Expand Up @@ -241,6 +247,10 @@ interface Props {
* The label of the sponsored badge, if applicable.
*/
sponsoredBadgeLabel?: string
/**
* Where this ProductSummary is being shown. Used for analytics. E.g. "search" or "shelf".
*/
placement?: string
}

function ProductSummaryWrapper({
Expand All @@ -253,6 +263,7 @@ function ProductSummaryWrapper({
position,
sponsoredBadgePosition,
sponsoredBadgeLabel,
placement,
classes,
children,
}: PropsWithChildren<Props>) {
Expand All @@ -276,6 +287,7 @@ function ProductSummaryWrapper({
actionOnClick={actionOnClick}
priceBehavior={priceBehavior}
position={position}
placement={placement}
classes={classes}
>
{children}
Expand Down
49 changes: 45 additions & 4 deletions react/ProductSummaryList.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
import React, { useCallback, useEffect, useState } from 'react'
import type { ComponentType, PropsWithChildren } from 'react'
import React, { useCallback, useEffect, useState } from 'react'
import { useQuery } from 'react-apollo'
import { QueryProducts } from 'vtex.store-resources'
import { usePixel } from 'vtex.pixel-manager'
import { QueryProducts } from 'vtex.store-resources'
import { ProductList as ProductListStructuredData } from 'vtex.structured-data'
// eslint-disable-next-line no-restricted-imports
import { equals } from 'ramda'
import { canUseDOM } from 'vtex.render-runtime'

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

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

const ORDER_BY_OPTIONS = {
RELEVANCE: {
Expand Down Expand Up @@ -135,9 +141,15 @@
* */
preferredSKU?: PreferenceType
/** Slot of a product summary. */
ProductSummary: ComponentType<{ product: any; actionOnClick: any }>

Check warning on line 144 in react/ProductSummaryList.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type

Check warning on line 144 in react/ProductSummaryList.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
/** Callback on product click. */
actionOnProductClick?: (product: any) => void

Check warning on line 146 in react/ProductSummaryList.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
/** Whether or not to show sponsored products in this list. */
showSponsoredProducts: boolean
/** Maximum number of sponsored products to put on top of the regular products. */
sponsoredCount: number
/** If true, sponsored and regular products will be repeated on the list. */
repeatSponsoredProducts: boolean
}

function ProductSummaryList(props: PropsWithChildren<Props>) {
Expand All @@ -155,6 +167,9 @@
ProductSummary,
actionOnProductClick,
preferredSKU,
showSponsoredProducts = DEFAULT_SHOW_SPONSORED_PRODUCTS,
sponsoredCount = DEFAULT_SPONSORED_COUNT,
repeatSponsoredProducts = DEFAULT_REPEAT_SPONSORED_PRODUCTS,
} = props

const [shippingOptions, setShippingOptions] = useState([])
Expand Down Expand Up @@ -195,6 +210,12 @@
skusFilter,
installmentCriteria,
variant: getCookie('sp-variant'),
advertisementOptions: {
showSponsored: showSponsoredProducts,
sponsoredCount,
repeatSponsoredProducts,
advertisementPlacement: PRODUCT_LIST_PLACEMENT,
},
},
})

Expand All @@ -204,7 +225,7 @@
const listName = rawListName ? rawListName : 'List of products'

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

Check warning on line 228 in react/ProductSummaryList.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
actionOnProductClick?.(product)

const { position } = productClickParams ?? {}
Expand Down Expand Up @@ -322,6 +343,26 @@
title: 'admin/editor.productSummaryList.analyticsListName.title',
type: 'string',
},
showSponsoredProducts: {
title: 'admin/editor.productSummaryList.showSponsoredProducts.title',
description:
'admin/editor.productSummaryList.showSponsoredProducts.description',
type: 'boolean',
default: DEFAULT_SHOW_SPONSORED_PRODUCTS,
},
sponsoredCount: {
title: 'admin/editor.productSummaryList.sponsoredCount.title',
description: 'admin/editor.productSummaryList.sponsoredCount.description',
type: 'number',
default: DEFAULT_SPONSORED_COUNT,
},
repeatSponsoredProducts: {
title: 'admin/editor.productSummaryList.repeatSponsoredProducts.title',
description:
'admin/editor.productSummaryList.repeatSponsoredProducts.description',
type: 'boolean',
default: DEFAULT_REPEAT_SPONSORED_PRODUCTS,
},
},
}

Expand Down
15 changes: 10 additions & 5 deletions react/ProductSummaryListWithoutQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,39 @@
import React, { useMemo } from 'react'
import type { ComponentType, PropsWithChildren } from 'react'
import { ExtensionPoint, useTreePath } from 'vtex.render-runtime'
import { useListContext, ListContextProvider } from 'vtex.list-context'
import React, { useMemo } from 'react'
import { ListContextProvider, useListContext } from 'vtex.list-context'
import { ProductListContext } from 'vtex.product-list-context'
import { ExtensionPoint, useTreePath } from 'vtex.render-runtime'

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

const { ProductListProvider } = ProductListContext

export const PRODUCT_LIST_PLACEMENT = 'home_shelf'

type Props = PropsWithChildren<{
/** Array of products. */
products?: any[]

Check warning on line 20 in react/ProductSummaryListWithoutQuery.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
/** Slot of product summary. */
ProductSummary: ComponentType<{
product: any

Check warning on line 23 in react/ProductSummaryListWithoutQuery.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
actionOnClick: (
product: any,

Check warning on line 25 in react/ProductSummaryListWithoutQuery.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
productClickParams?: ProductClickParams
) => void
listName?: string
position?: number
placement?: string
}>
/** Name of the list property on Google Analytics events. */
listName?: string
/** Callback on product click. */
actionOnProductClick?: (
product: any,

Check warning on line 36 in react/ProductSummaryListWithoutQuery.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unexpected any. Specify a different type
productClickParams?: ProductClickParams
) => void
/** Logic to enable which SKU will be the selected item */
Expand Down Expand Up @@ -73,6 +76,7 @@
listName={listName}
actionOnClick={handleOnClick}
position={position}
placement={PRODUCT_LIST_PLACEMENT}
/>
)
}
Expand All @@ -86,6 +90,7 @@
listName={listName}
actionOnClick={handleOnClick}
position={position}
placement={PRODUCT_LIST_PLACEMENT}
/>
)
})
Expand Down
3 changes: 3 additions & 0 deletions react/utils/getAdsDataProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import { Product } from 'vtex.product-summary-context/react/ProductSummaryTypes'
type GetAdsDataPropertiesArgs = {
product: Product
position?: number
placement?: string
}

const getAdsDataProperties = ({
product,
position,
placement,
}: GetAdsDataPropertiesArgs) => {
if (!product.advertisement?.adId) return {}

Expand All @@ -26,6 +28,7 @@ const getAdsDataProperties = ({
'data-van-req-id': adRequestId,
'data-van-res-id': adResponseId,
'data-van-cpc': actionCost,
'data-van-placement': placement,
}
}

Expand Down
10 changes: 2 additions & 8 deletions store/blocks.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@
]
},
"product-summary-column#1": {
"children": [
"product-summary-price",
"product-summary-buy-button"
]
"children": ["product-summary-price", "product-summary-buy-button"]
},
"product-summary.unstable--flex": {
"children": [
Expand All @@ -24,9 +21,6 @@
]
},
"unstable--product-summary-column#1": {
"children": [
"product-summary-price",
"product-summary-buy-button"
]
"children": ["product-summary-price", "product-summary-buy-button"]
}
}
2 changes: 1 addition & 1 deletion store/contentSchemas.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"title": "admin/editor.productSummary.sponsoredBadge.position",
"type": "string",
"enum": ["titleTop", "containerTopLeft", "none"],
"default": "titleTop",
"default": "containerTopLeft",
"enumNames": [
"admin/editor.productSummary.sponsoredBadge.position.titleTop",
"admin/editor.productSummary.sponsoredBadge.position.containerTopLeft",
Expand Down
Loading