From 1a3791451c68209b80816344ca3572089f3c5ce9 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Thu, 17 Oct 2019 14:46:15 -0300 Subject: [PATCH 01/28] Add products query --- react/graphql/products.graphql | 79 ++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 react/graphql/products.graphql diff --git a/react/graphql/products.graphql b/react/graphql/products.graphql new file mode 100644 index 00000000..39c412b3 --- /dev/null +++ b/react/graphql/products.graphql @@ -0,0 +1,79 @@ +query Products( + $category: String + $collection: String + $specificationFilters: [String] + $orderBy: String + $from: Int + $to: Int + $hideUnavailableItems: Boolean = false +) { + products( + category: $category + collection: $collection + specificationFilters: $specificationFilters + orderBy: $orderBy + from: $from + to: $to + hideUnavailableItems: $hideUnavailableItems + ) @context(provider: "vtex.search-graphql") { + cacheId + productId + productName + productReference + description + link + linkText + brand + brandId + specificationGroups { + name + specifications { + name + values + } + } + items { + name + itemId + measurementUnit + unitMultiplier + referenceId { + Value + } + images { + imageUrl + imageTag + imageLabel + } + sellers { + sellerId + commertialOffer { + Installments(criteria: MAX) { + Value + InterestRate + TotalValuePlusInterestRate + NumberOfInstallments + Name + } + AvailableQuantity + Price + ListPrice + teasers { + name + } + discountHighlights { + name + } + } + } + } + productClusters { + id + name + } + properties { + name + values + } + } +} From 5b6f3c2f404f09db2688119e6abfac1078e88ec6 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Thu, 17 Oct 2019 14:46:28 -0300 Subject: [PATCH 02/28] Delete unnecessary tsconfig :( --- react/tsconfig.json | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 react/tsconfig.json diff --git a/react/tsconfig.json b/react/tsconfig.json deleted file mode 100644 index f845a455..00000000 --- a/react/tsconfig.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "compilerOptions": { - "alwaysStrict": true, - "esModuleInterop": true, - "jsx": "react", - "lib": ["es2017", "dom", "es2018.promise"], - "module": "es6", - "moduleResolution": "node", - "noImplicitAny": true, - "noImplicitReturns": true, - "noImplicitThis": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "skipLibCheck": true, - "sourceMap": true, - "strictFunctionTypes": true, - "strictNullChecks": true, - "strictPropertyInitialization": true, - "target": "es2017", - "typeRoots": ["node_modules/@types"], - "types": ["node", "jest", "graphql"] - }, - "exclude": ["node_modules"], - "include": ["./typings/*.d.ts", "./**/*.tsx", "./**/*.ts"], - "typeAcquisition": { - "enable": false - } -} From e75ee6d704618b2372041c2a36d66eb4ad8c0b77 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Thu, 17 Oct 2019 14:51:45 -0300 Subject: [PATCH 03/28] First implementation for ProductSummaryList component --- react/ProductSummaryList.js | 112 ++++++++++++++++++++++++++++ react/useProductSummaryListState.js | 3 + 2 files changed, 115 insertions(+) create mode 100644 react/ProductSummaryList.js create mode 100644 react/useProductSummaryListState.js diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js new file mode 100644 index 00000000..b5eda876 --- /dev/null +++ b/react/ProductSummaryList.js @@ -0,0 +1,112 @@ +import React, { createContext, useContext } from 'react' +import { compose, graphql } from 'react-apollo' + +import ProductSummary from './components/ProductSummary' +import ProductSummaryName from './components/ProductSummaryName/ProductSummaryName' +import ProductSummaryPrice from './components/ProductSummaryPrice/ProductSummaryPrice' +import ProductSummaryBuyButton from './components/ProductSummaryBuyButton/ProductSummaryBuyButton' +import ProductSummaryImage from './components/ProductSummaryImage/ProductImage' +import Spacer from './Spacer' +import { mapCatalogProductToProductSummary } from './utils/normalize' + +import productsQuery from './graphql/products.graphql' + +const ProductSummaryListStateContext = createContext(undefined) + +const ProductSummaryList = ({ children, data }) => { + const componentList = data.products.map(product => { + const normalizedProduct = mapCatalogProductToProductSummary(product) + + return ( + + + + + + + + ) + }) + + return ( + + {children} + + ) +} + +function useProductSummaryListState() { + const context = useContext(ProductSummaryListStateContext) + return context +} + +const parseFilters = ({ id, value }) => `specificationFilter_${id}:${value}` + +const ORDER_BY_OPTIONS = { + ORDER_BY_RELEVANCE: { + name: 'admin/editor.shelf.orderType.relevance', + value: '', + }, + ORDER_BY_TOP_SALE_DESC: { + name: 'admin/editor.shelf.orderType.sales', + value: 'OrderByTopSaleDESC', + }, + ORDER_BY_PRICE_DESC: { + name: 'admin/editor.shelf.orderType.priceDesc', + value: 'OrderByPriceDESC', + }, + ORDER_BY_PRICE_ASC: { + name: 'admin/editor.shelf.orderType.priceAsc', + value: 'OrderByPriceASC', + }, + ORDER_BY_NAME_ASC: { + name: 'admin/editor.shelf.orderType.nameAsc', + value: 'OrderByNameASC', + }, + ORDER_BY_NAME_DESC: { + name: 'admin/editor.shelf.orderType.nameDesc', + value: 'OrderByNameDESC', + }, + ORDER_BY_RELEASE_DATE_DESC: { + name: 'admin/editor.shelf.orderType.releaseDate', + value: 'OrderByReleaseDateDESC', + }, + ORDER_BY_BEST_DISCOUNT_DESC: { + name: 'admin/editor.shelf.orderType.discount', + value: 'OrderByBestDiscountDESC', + }, +} + +const productQueryOptions = { + options: ({ + category = '', + collection, + hideUnavailableItems = false, + orderBy = ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, + specificationFilters = [], + }) => ({ + ssr: true, + name: 'productList', + variables: { + category, + ...(collection != null + ? { + collection, + } + : {}), + specificationFilters: specificationFilters.map(parseFilters), + orderBy, + from: 0, + to: 9, + hideUnavailableItems, + }, + }), +} + +const EnhancedProductList = compose( + graphql(productsQuery, productQueryOptions) +)(ProductSummaryList) + +export default EnhancedProductList + +export { useProductSummaryListState } diff --git a/react/useProductSummaryListState.js b/react/useProductSummaryListState.js new file mode 100644 index 00000000..37f1bcb6 --- /dev/null +++ b/react/useProductSummaryListState.js @@ -0,0 +1,3 @@ +import { useProductSummaryListState } from './ProductSummaryList' + +export default useProductSummaryListState From e6bd97c27b85dced14e80b2c0952ecf6ef3ca08a Mon Sep 17 00:00:00 2001 From: victorhmp Date: Thu, 17 Oct 2019 17:28:11 -0300 Subject: [PATCH 04/28] Add schema to enable edition via Site Editor --- react/ProductSummaryList.js | 95 +++++++++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 9 deletions(-) diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index b5eda876..6e775646 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -44,35 +44,35 @@ const parseFilters = ({ id, value }) => `specificationFilter_${id}:${value}` const ORDER_BY_OPTIONS = { ORDER_BY_RELEVANCE: { - name: 'admin/editor.shelf.orderType.relevance', + name: 'admin/editor.productSummaryList.orderType.relevance', value: '', }, ORDER_BY_TOP_SALE_DESC: { - name: 'admin/editor.shelf.orderType.sales', + name: 'admin/editor.productSummaryList.orderType.sales', value: 'OrderByTopSaleDESC', }, ORDER_BY_PRICE_DESC: { - name: 'admin/editor.shelf.orderType.priceDesc', + name: 'admin/editor.productSummaryList.orderType.priceDesc', value: 'OrderByPriceDESC', }, ORDER_BY_PRICE_ASC: { - name: 'admin/editor.shelf.orderType.priceAsc', + name: 'admin/editor.productSummaryList.orderType.priceAsc', value: 'OrderByPriceASC', }, ORDER_BY_NAME_ASC: { - name: 'admin/editor.shelf.orderType.nameAsc', + name: 'admin/editor.productSummaryList.orderType.nameAsc', value: 'OrderByNameASC', }, ORDER_BY_NAME_DESC: { - name: 'admin/editor.shelf.orderType.nameDesc', + name: 'admin/editor.productSummaryList.orderType.nameDesc', value: 'OrderByNameDESC', }, ORDER_BY_RELEASE_DATE_DESC: { - name: 'admin/editor.shelf.orderType.releaseDate', + name: 'admin/editor.productSummaryList.orderType.releaseDate', value: 'OrderByReleaseDateDESC', }, ORDER_BY_BEST_DISCOUNT_DESC: { - name: 'admin/editor.shelf.orderType.discount', + name: 'admin/editor.productSummaryList.orderType.discount', value: 'OrderByBestDiscountDESC', }, } @@ -84,6 +84,7 @@ const productQueryOptions = { hideUnavailableItems = false, orderBy = ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, specificationFilters = [], + maxItems = 10, }) => ({ ssr: true, name: 'productList', @@ -97,16 +98,92 @@ const productQueryOptions = { specificationFilters: specificationFilters.map(parseFilters), orderBy, from: 0, - to: 9, + to: maxItems - 1, hideUnavailableItems, }, }), } +function getOrdenationNames() { + const names = [] + for (const key in ORDER_BY_OPTIONS) { + names.push(ORDER_BY_OPTIONS[key].name) + } + return names +} + +function getOrdenationValues() { + const values = [] + for (const key in ORDER_BY_OPTIONS) { + values.push(ORDER_BY_OPTIONS[key].value) + } + return values +} + const EnhancedProductList = compose( graphql(productsQuery, productQueryOptions) )(ProductSummaryList) +EnhancedProductList.getSchema = () => ({ + title: 'admin/editor.productSummaryList.title', + description: 'admin/editor.productSummaryList.description', + type: 'object', + properties: { + category: { + title: 'admin/editor.productSummaryList.category.title', + description: 'admin/editor.productSummaryList.category.description', + type: 'string', + isLayout: false, + }, + specificationFilters: { + title: 'admin/editor.productSummaryList.specificationFilters.title', + type: 'array', + items: { + title: + 'admin/editor.productSummaryList.specificationFilters.item.title', + type: 'object', + properties: { + id: { + type: 'string', + title: + 'admin/editor.productSummaryList.specificationFilters.item.id.title', + }, + value: { + type: 'string', + title: + 'admin/editor.productSummaryList.specificationFilters.item.value.title', + }, + }, + }, + }, + collection: { + title: 'admin/editor.productSummaryList.collection.title', + type: 'number', + isLayout: false, + }, + orderBy: { + title: 'admin/editor.productSummaryList.orderBy.title', + type: 'string', + enum: getOrdenationValues(), + enumNames: getOrdenationNames(), + default: ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, + isLayout: false, + }, + hideUnavailableItems: { + title: 'admin/editor.productSummaryList.hideUnavailableItems', + type: 'boolean', + default: false, + isLayout: false, + }, + maxItems: { + title: 'admin/editor.productSummaryList.maxItems.title', + type: 'number', + isLayout: false, + default: 10, + }, + }, +}) + export default EnhancedProductList export { useProductSummaryListState } From f68456354ae42ea05cf5941b337ac01c8cd939df Mon Sep 17 00:00:00 2001 From: victorhmp Date: Thu, 17 Oct 2019 17:28:21 -0300 Subject: [PATCH 05/28] Add messages for ProductList --- messages/context.json | 22 +++++++++++++++++++++- messages/en.json | 22 +++++++++++++++++++++- messages/es.json | 22 +++++++++++++++++++++- messages/pt.json | 22 +++++++++++++++++++++- 4 files changed, 84 insertions(+), 4 deletions(-) diff --git a/messages/context.json b/messages/context.json index 29cb8143..06bae374 100644 --- a/messages/context.json +++ b/messages/context.json @@ -34,5 +34,25 @@ "admin/editor.productSummaryImage.hoverImageLabel.title": "admin/editor.productSummaryImage.hoverImageLabel.title", "admin/editor.productSummaryBuyButton.title": "admin/editor.productSummaryBuyButton.title", "admin/editor.productSummaryName.title": "admin/editor.productSummaryName.title", - "admin/editor.product-summary-specification-badges.title": "admin/editor.product-summary-specification-badges.title" + "admin/editor.product-summary-specification-badges.title": "admin/editor.product-summary-specification-badges.title", + "admin/editor.productSummaryList.title": "Product List", + "admin/editor.productSummaryList.description": "A product list featuring a collection", + "admin/editor.productSummaryList.category.title": "Category Id", + "admin/editor.productSummaryList.category.description": "For sub-categories, use \"/\" (e.g. 1/2/3)", + "admin/editor.productSummaryList.collection.title": "Collection", + "admin/editor.productSummaryList.orderBy.title": "List Ordenation", + "admin/editor.productSummaryList.hideUnavailableItems": "Hide unavailable items", + "admin/editor.productSummaryList.maxItems.title": "Max Items", + "admin/editor.productSummaryList.specificationFilters.title": "Specification Filters", + "admin/editor.productSummaryList.specificationFilters.item.title": "Specification Filter Item", + "admin/editor.productSummaryList.specificationFilters.item.id.title": "Specification Filter ID", + "admin/editor.productSummaryList.specificationFilters.item.value.title": "Specification Filter Value", + "admin/editor.productSummaryList.orderType.sales": "Sales", + "admin/editor.productSummaryList.orderType.priceDesc": "Price, descending", + "admin/editor.productSummaryList.orderType.priceAsc": "Price, ascending", + "admin/editor.productSummaryList.orderType.nameAsc": "Name, ascending", + "admin/editor.productSummaryList.orderType.nameDesc": "Name, descending", + "admin/editor.productSummaryList.orderType.releaseDate": "Release Date", + "admin/editor.productSummaryList.orderType.discount": "Discount", + "admin/editor.productSummaryList.orderType.relevance": "Relevance" } diff --git a/messages/en.json b/messages/en.json index ed144e3d..09a96f65 100644 --- a/messages/en.json +++ b/messages/en.json @@ -34,5 +34,25 @@ "admin/editor.productSummaryImage.hoverImageLabel.title": "Hover Image Label", "admin/editor.productSummaryBuyButton.title": "Product Summary Buy Button", "admin/editor.productSummaryName.title": "Product Summary Name", - "admin/editor.product-summary-specification-badges.title": "Product Summary Specification Badges" + "admin/editor.product-summary-specification-badges.title": "Product Summary Specification Badges", + "admin/editor.productSummaryList.title": "Product List", + "admin/editor.productSummaryList.description": "A product list featuring a collection", + "admin/editor.productSummaryList.category.title": "Category Id", + "admin/editor.productSummaryList.category.description": "For sub-categories, use \"/\" (e.g. 1/2/3)", + "admin/editor.productSummaryList.collection.title": "Collection", + "admin/editor.productSummaryList.orderBy.title": "List Ordenation", + "admin/editor.productSummaryList.hideUnavailableItems": "Hide unavailable items", + "admin/editor.productSummaryList.maxItems.title": "Max Items", + "admin/editor.productSummaryList.specificationFilters.title": "Specification Filters", + "admin/editor.productSummaryList.specificationFilters.item.title": "Specification Filter Item", + "admin/editor.productSummaryList.specificationFilters.item.id.title": "Specification Filter ID", + "admin/editor.productSummaryList.specificationFilters.item.value.title": "Specification Filter Value", + "admin/editor.productSummaryList.orderType.sales": "Sales", + "admin/editor.productSummaryList.orderType.priceDesc": "Price, descending", + "admin/editor.productSummaryList.orderType.priceAsc": "Price, ascending", + "admin/editor.productSummaryList.orderType.nameAsc": "Name, ascending", + "admin/editor.productSummaryList.orderType.nameDesc": "Name, descending", + "admin/editor.productSummaryList.orderType.releaseDate": "Release Date", + "admin/editor.productSummaryList.orderType.discount": "Discount", + "admin/editor.productSummaryList.orderType.relevance": "Relevance" } diff --git a/messages/es.json b/messages/es.json index 7ad86593..dd952c84 100644 --- a/messages/es.json +++ b/messages/es.json @@ -34,5 +34,25 @@ "admin/editor.productSummaryImage.hoverImageLabel.title": "Etiqueta de imagen secundaria", "admin/editor.productSummaryBuyButton.title": "Botón de compra", "admin/editor.productSummaryName.title": "Nombre de Resumen del producto", - "admin/editor.product-summary-specification-badges.title": "Resumen del producto Especificaciones Medallas" + "admin/editor.product-summary-specification-badges.title": "Resumen del producto Especificaciones Medallas", + "admin/editor.productSummaryList.title": "Lista de productos", + "admin/editor.productSummaryList.description": "Una lista de productos con una colección de productos", + "admin/editor.productSummaryList.category.title": "Id de la Categoría", + "admin/editor.productSummaryList.category.description": "Para subcategorías, utilice \"/\" (por ejemplo: 1/2/3)", + "admin/editor.productSummaryList.collection.title": "Colección", + "admin/editor.productSummaryList.orderBy.title": "Ordenación de la Lista", + "admin/editor.productSummaryList.hideUnavailableItems": "Ocultar artículos no disponibles", + "admin/editor.productSummaryList.maxItems.title": "Cantidad máxima de Elementos", + "admin/editor.productSummaryList.specificationFilters.title": "Filtros de especificación", + "admin/editor.productSummaryList.specificationFilters.item.title": "Elemento de filtro de especificación", + "admin/editor.productSummaryList.specificationFilters.item.id.title": "ID del filtro de especificación", + "admin/editor.productSummaryList.specificationFilters.item.value.title": "Valor del filtro de especificación", + "admin/editor.productSummaryList.orderType.sales": "Ventas", + "admin/editor.productSummaryList.orderType.priceDesc": "Precio, descendiendo", + "admin/editor.productSummaryList.orderType.priceAsc": "Precio, ascendente", + "admin/editor.productSummaryList.orderType.nameAsc": "Nombre, ascendente", + "admin/editor.productSummaryList.orderType.nameDesc": "Nombre, descendiendo", + "admin/editor.productSummaryList.orderType.releaseDate": "Fecha de lanzamiento", + "admin/editor.productSummaryList.orderType.discount": "Descuento", + "admin/editor.productSummaryList.orderType.relevance": "Relevancia" } diff --git a/messages/pt.json b/messages/pt.json index 258a8a87..48fa8475 100644 --- a/messages/pt.json +++ b/messages/pt.json @@ -34,5 +34,25 @@ "admin/editor.productSummaryImage.hoverImageLabel.title": "Rótulo da imagem secundária", "admin/editor.productSummaryBuyButton.title": "Botão de compra", "admin/editor.productSummaryName.title": "Nome do resumo do produto", - "admin/editor.product-summary-specification-badges.title": "Medalhas de Especificação do resumo do produto" + "admin/editor.product-summary-specification-badges.title": "Medalhas de Especificação do resumo do produto", + "admin/editor.productSummaryList.title": "Lista de produtos", + "admin/editor.productSummaryList.description": "Uma lista de produtos de uma coleção", + "admin/editor.productSummaryList.category.title": "Id da Categoria", + "admin/editor.productSummaryList.category.description": "Para sub-categorias, use \"/\" (por exemplo: 1/2/3)", + "admin/editor.productSummaryList.collection.title": "Coleção", + "admin/editor.productSummaryList.orderBy.title": "Ordenação da Lista", + "admin/editor.productSummaryList.hideUnavailableItems": "Esconder itens não disponíveis", + "admin/editor.productSummaryList.maxItems.title": "Quantidade máxima de itens", + "admin/editor.productSummaryList.specificationFilters.title": "Filtros de Especificação", + "admin/editor.productSummaryList.specificationFilters.item.title": "Item de Filtro de Especificação", + "admin/editor.productSummaryList.specificationFilters.item.id.title": "ID do Filtro de Especificação", + "admin/editor.productSummaryList.specificationFilters.item.value.title": "Valor do Filtro de Especificação", + "admin/editor.productSummaryList.orderType.sales": "Vendas", + "admin/editor.productSummaryList.orderType.priceDesc": "Preço, descrescente", + "admin/editor.productSummaryList.orderType.priceAsc": "Preço, crescente", + "admin/editor.productSummaryList.orderType.nameAsc": "Nome, crescente", + "admin/editor.productSummaryList.orderType.nameDesc": "Nome, decrescente", + "admin/editor.productSummaryList.orderType.releaseDate": "Data de lançamento", + "admin/editor.productSummaryList.orderType.discount": "Desconto", + "admin/editor.productSummaryList.orderType.relevance": "Relevância" } From 45f74f55c0ebb2952046074918c3958cd3f4bc35 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Thu, 17 Oct 2019 17:55:59 -0300 Subject: [PATCH 06/28] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13773cbe..79fd7f14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `ProductSummaryList` component. ## [2.50.1] - 2020-01-23 From 30485d497caa4cd2cff3000578ee07a8ec366205 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Thu, 17 Oct 2019 18:51:27 -0300 Subject: [PATCH 07/28] Fix typos --- react/ProductSummaryList.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index 6e775646..43b086d8 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -104,7 +104,7 @@ const productQueryOptions = { }), } -function getOrdenationNames() { +function getOrdinationNames() { const names = [] for (const key in ORDER_BY_OPTIONS) { names.push(ORDER_BY_OPTIONS[key].name) @@ -112,7 +112,7 @@ function getOrdenationNames() { return names } -function getOrdenationValues() { +function getOrdinationValues() { const values = [] for (const key in ORDER_BY_OPTIONS) { values.push(ORDER_BY_OPTIONS[key].value) @@ -164,8 +164,8 @@ EnhancedProductList.getSchema = () => ({ orderBy: { title: 'admin/editor.productSummaryList.orderBy.title', type: 'string', - enum: getOrdenationValues(), - enumNames: getOrdenationNames(), + enum: getOrdinationValues(), + enumNames: getOrdinationNames(), default: ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, isLayout: false, }, From 8c3bc9fc7fda9bba31bcecb067d70e311cbde7ae Mon Sep 17 00:00:00 2001 From: victorhmp Date: Fri, 18 Oct 2019 12:36:18 -0300 Subject: [PATCH 08/28] Add `BuyButtonText` prop to `ProductSummaryList` --- react/ProductSummaryList.js | 10 +++++++--- store/contentSchemas.json | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index 43b086d8..b58b9289 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -1,5 +1,7 @@ import React, { createContext, useContext } from 'react' import { compose, graphql } from 'react-apollo' +import { injectIntl } from 'react-intl' +import { formatIOMessage } from 'vtex.native-types' import ProductSummary from './components/ProductSummary' import ProductSummaryName from './components/ProductSummaryName/ProductSummaryName' @@ -13,7 +15,8 @@ import productsQuery from './graphql/products.graphql' const ProductSummaryListStateContext = createContext(undefined) -const ProductSummaryList = ({ children, data }) => { +const ProductSummaryList = ({ children, data, intl, buyButtonText }) => { + const formattedBuyButtonText = formatIOMessage({ id: buyButtonText, intl }) const componentList = data.products.map(product => { const normalizedProduct = mapCatalogProductToProductSummary(product) @@ -23,7 +26,7 @@ const ProductSummaryList = ({ children, data }) => { - + ) }) @@ -121,7 +124,8 @@ function getOrdinationValues() { } const EnhancedProductList = compose( - graphql(productsQuery, productQueryOptions) + graphql(productsQuery, productQueryOptions), + injectIntl )(ProductSummaryList) EnhancedProductList.getSchema = () => ({ diff --git a/store/contentSchemas.json b/store/contentSchemas.json index 74af593f..158f1076 100644 --- a/store/contentSchemas.json +++ b/store/contentSchemas.json @@ -22,6 +22,11 @@ "default": "store/pricing.from" } } + }, + "ProductSummaryList": { + "title": "admin/editor.productSummary.buyButtonText.title", + "$ref": "app:vtex.native-types#/definitions/text", + "default": "store/button-label" } } } From 1ee7e1c254c08aec0279d95b768dc38db98574a9 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Mon, 21 Oct 2019 19:46:07 -0300 Subject: [PATCH 09/28] Create new "list-context.product-list" based on the new implementation of list-context --- manifest.json | 4 +++- react/ProductSummaryList.js | 47 +++++++++++++++++-------------------- store/interfaces.json | 12 ++++++++++ 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/manifest.json b/manifest.json index 6076a154..df484bfe 100644 --- a/manifest.json +++ b/manifest.json @@ -35,7 +35,9 @@ "vtex.flex-layout": "0.x", "vtex.rich-text": "0.x", "vtex.add-to-cart-button": "0.x", - "vtex.product-bookmark-interfaces": "1.x" + "vtex.product-bookmark-interfaces": "1.x", + "vtex.search-graphql": "0.x", + "vtex.list-context": "0.x" }, "$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema" } diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index b58b9289..fd9f0fff 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -1,7 +1,8 @@ -import React, { createContext, useContext } from 'react' +import React from 'react' import { compose, graphql } from 'react-apollo' import { injectIntl } from 'react-intl' import { formatIOMessage } from 'vtex.native-types' +import { useListContext, ListContextProvider } from 'vtex.list-context' import ProductSummary from './components/ProductSummary' import ProductSummaryName from './components/ProductSummaryName/ProductSummaryName' @@ -13,36 +14,34 @@ import { mapCatalogProductToProductSummary } from './utils/normalize' import productsQuery from './graphql/products.graphql' -const ProductSummaryListStateContext = createContext(undefined) - const ProductSummaryList = ({ children, data, intl, buyButtonText }) => { const formattedBuyButtonText = formatIOMessage({ id: buyButtonText, intl }) - const componentList = data.products.map(product => { - const normalizedProduct = mapCatalogProductToProductSummary(product) - - return ( - - - - - - - - ) - }) + const { list } = useListContext() + const componentList = + data.products && + data.products.map(product => { + const normalizedProduct = mapCatalogProductToProductSummary(product) + + return ( + + + + + + + + ) + }) + + const newListContextValue = list.concat(componentList) return ( - + {children} - + ) } -function useProductSummaryListState() { - const context = useContext(ProductSummaryListStateContext) - return context -} - const parseFilters = ({ id, value }) => `specificationFilter_${id}:${value}` const ORDER_BY_OPTIONS = { @@ -189,5 +188,3 @@ EnhancedProductList.getSchema = () => ({ }) export default EnhancedProductList - -export { useProductSummaryListState } diff --git a/store/interfaces.json b/store/interfaces.json index 90a417b0..6ecaab74 100644 --- a/store/interfaces.json +++ b/store/interfaces.json @@ -139,5 +139,17 @@ }, "addon-summary-btn": { "component": "*" + }, + "list-context.product-list": { + "component": "ProductSummaryList", + "composition": "children", + "allowed": "*", + "content": { + "properties": { + "buyButtonText": { + "$ref": "app:vtex.product-summary#/definitions/ProductSummaryList" + } + } + } } } From 8e0bd0efec4f1dc79709dca0bf4d81d4b15104c3 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Tue, 22 Oct 2019 11:27:02 -0300 Subject: [PATCH 10/28] Fix typo --- messages/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/messages/en.json b/messages/en.json index 09a96f65..8b53fd3c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -40,7 +40,7 @@ "admin/editor.productSummaryList.category.title": "Category Id", "admin/editor.productSummaryList.category.description": "For sub-categories, use \"/\" (e.g. 1/2/3)", "admin/editor.productSummaryList.collection.title": "Collection", - "admin/editor.productSummaryList.orderBy.title": "List Ordenation", + "admin/editor.productSummaryList.orderBy.title": "List Ordination", "admin/editor.productSummaryList.hideUnavailableItems": "Hide unavailable items", "admin/editor.productSummaryList.maxItems.title": "Max Items", "admin/editor.productSummaryList.specificationFilters.title": "Specification Filters", From 90d22a0c6554d63fd7774b1f148d81ab2d8edf56 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Tue, 22 Oct 2019 11:27:36 -0300 Subject: [PATCH 11/28] Cleanup ProductSummaryList --- react/ProductSummaryList.js | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index fd9f0fff..f619cde1 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -106,20 +106,10 @@ const productQueryOptions = { }), } -function getOrdinationNames() { - const names = [] - for (const key in ORDER_BY_OPTIONS) { - names.push(ORDER_BY_OPTIONS[key].name) - } - return names -} - -function getOrdinationValues() { - const values = [] - for (const key in ORDER_BY_OPTIONS) { - values.push(ORDER_BY_OPTIONS[key].value) - } - return values +function getOrdinationProp(attribute) { + return Object.keys(ORDER_BY_OPTIONS).map( + key => ORDER_BY_OPTIONS[key][attribute] + ) } const EnhancedProductList = compose( @@ -167,8 +157,8 @@ EnhancedProductList.getSchema = () => ({ orderBy: { title: 'admin/editor.productSummaryList.orderBy.title', type: 'string', - enum: getOrdinationValues(), - enumNames: getOrdinationNames(), + enum: getOrdinationProp('value'), + enumNames: getOrdinationProp('name'), default: ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, isLayout: false, }, From d0b646c3bccd5ccdfa79777d2f9c253fdd0a1883 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Tue, 22 Oct 2019 12:01:18 -0300 Subject: [PATCH 12/28] Use productSearchV2 query from vtex.store-resources --- react/ProductSummaryList.js | 10 +++-- react/graphql/products.graphql | 79 ---------------------------------- 2 files changed, 6 insertions(+), 83 deletions(-) delete mode 100644 react/graphql/products.graphql diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index f619cde1..d5ddbb15 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -12,14 +12,14 @@ import ProductSummaryImage from './components/ProductSummaryImage/ProductImage' import Spacer from './Spacer' import { mapCatalogProductToProductSummary } from './utils/normalize' -import productsQuery from './graphql/products.graphql' +import { productSearchV2 } from 'vtex.store-resources/Queries' const ProductSummaryList = ({ children, data, intl, buyButtonText }) => { const formattedBuyButtonText = formatIOMessage({ id: buyButtonText, intl }) const { list } = useListContext() const componentList = - data.products && - data.products.map(product => { + data.productSearch && + data.productSearch.products.map(product => { const normalizedProduct = mapCatalogProductToProductSummary(product) return ( @@ -87,6 +87,7 @@ const productQueryOptions = { orderBy = ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, specificationFilters = [], maxItems = 10, + withFacets = false, }) => ({ ssr: true, name: 'productList', @@ -102,6 +103,7 @@ const productQueryOptions = { from: 0, to: maxItems - 1, hideUnavailableItems, + withFacets, }, }), } @@ -113,7 +115,7 @@ function getOrdinationProp(attribute) { } const EnhancedProductList = compose( - graphql(productsQuery, productQueryOptions), + graphql(productSearchV2, productQueryOptions), injectIntl )(ProductSummaryList) diff --git a/react/graphql/products.graphql b/react/graphql/products.graphql deleted file mode 100644 index 39c412b3..00000000 --- a/react/graphql/products.graphql +++ /dev/null @@ -1,79 +0,0 @@ -query Products( - $category: String - $collection: String - $specificationFilters: [String] - $orderBy: String - $from: Int - $to: Int - $hideUnavailableItems: Boolean = false -) { - products( - category: $category - collection: $collection - specificationFilters: $specificationFilters - orderBy: $orderBy - from: $from - to: $to - hideUnavailableItems: $hideUnavailableItems - ) @context(provider: "vtex.search-graphql") { - cacheId - productId - productName - productReference - description - link - linkText - brand - brandId - specificationGroups { - name - specifications { - name - values - } - } - items { - name - itemId - measurementUnit - unitMultiplier - referenceId { - Value - } - images { - imageUrl - imageTag - imageLabel - } - sellers { - sellerId - commertialOffer { - Installments(criteria: MAX) { - Value - InterestRate - TotalValuePlusInterestRate - NumberOfInstallments - Name - } - AvailableQuantity - Price - ListPrice - teasers { - name - } - discountHighlights { - name - } - } - } - } - productClusters { - id - name - } - properties { - name - values - } - } -} From 1969c92dd874e7a3e0e81b4e1467ba5bceef6af0 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Tue, 22 Oct 2019 18:13:11 -0300 Subject: [PATCH 13/28] Add new `product-list-block` interface --- react/ProductSummaryList.js | 45 ++++++++++++----------------- react/ProductSummaryListChildren.js | 8 +++++ store/interfaces.json | 6 +++- 3 files changed, 32 insertions(+), 27 deletions(-) create mode 100644 react/ProductSummaryListChildren.js diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index d5ddbb15..d523ad98 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -1,44 +1,38 @@ import React from 'react' -import { compose, graphql } from 'react-apollo' -import { injectIntl } from 'react-intl' -import { formatIOMessage } from 'vtex.native-types' -import { useListContext, ListContextProvider } from 'vtex.list-context' +import { graphql } from 'react-apollo' +import { useListContext } from 'vtex.list-context' +import { ExtensionPoint, useTreePath } from 'vtex.render-runtime' -import ProductSummary from './components/ProductSummary' -import ProductSummaryName from './components/ProductSummaryName/ProductSummaryName' -import ProductSummaryPrice from './components/ProductSummaryPrice/ProductSummaryPrice' -import ProductSummaryBuyButton from './components/ProductSummaryBuyButton/ProductSummaryBuyButton' -import ProductSummaryImage from './components/ProductSummaryImage/ProductImage' -import Spacer from './Spacer' import { mapCatalogProductToProductSummary } from './utils/normalize' import { productSearchV2 } from 'vtex.store-resources/Queries' -const ProductSummaryList = ({ children, data, intl, buyButtonText }) => { - const formattedBuyButtonText = formatIOMessage({ id: buyButtonText, intl }) +const ProductSummaryList = ({ data }) => { const { list } = useListContext() + const { treePath } = useTreePath() + const componentList = data.productSearch && data.productSearch.products.map(product => { const normalizedProduct = mapCatalogProductToProductSummary(product) return ( - - - - - - - + ) }) const newListContextValue = list.concat(componentList) return ( - - {children} - + ) } @@ -114,10 +108,9 @@ function getOrdinationProp(attribute) { ) } -const EnhancedProductList = compose( - graphql(productSearchV2, productQueryOptions), - injectIntl -)(ProductSummaryList) +const EnhancedProductList = graphql(productSearchV2, productQueryOptions)( + ProductSummaryList +) EnhancedProductList.getSchema = () => ({ title: 'admin/editor.productSummaryList.title', diff --git a/react/ProductSummaryListChildren.js b/react/ProductSummaryListChildren.js new file mode 100644 index 00000000..721ea295 --- /dev/null +++ b/react/ProductSummaryListChildren.js @@ -0,0 +1,8 @@ +import React from 'react' +import { ListContextProvider } from 'vtex.list-context' + +const ProductSummaryListChildren = ({ children, newList }) => ( + {children} +) + +export default ProductSummaryListChildren diff --git a/store/interfaces.json b/store/interfaces.json index 6ecaab74..f9def90b 100644 --- a/store/interfaces.json +++ b/store/interfaces.json @@ -140,8 +140,12 @@ "addon-summary-btn": { "component": "*" }, - "list-context.product-list": { + "product-list-block": { "component": "ProductSummaryList", + "required": ["product-summary", "list-context.product-list"] + }, + "list-context.product-list": { + "component": "ProductSummaryListChildren", "composition": "children", "allowed": "*", "content": { From 68c5eb2eec2e42d7dcd9a6ff550ce5bee2c32ba8 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Tue, 22 Oct 2019 18:15:08 -0300 Subject: [PATCH 14/28] Remove search-graphql dependency --- manifest.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/manifest.json b/manifest.json index df484bfe..ebc2dec4 100644 --- a/manifest.json +++ b/manifest.json @@ -35,8 +35,6 @@ "vtex.flex-layout": "0.x", "vtex.rich-text": "0.x", "vtex.add-to-cart-button": "0.x", - "vtex.product-bookmark-interfaces": "1.x", - "vtex.search-graphql": "0.x", "vtex.list-context": "0.x" }, "$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema" From b22b549ff8366430cc75aeeedee102b8d2659705 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Wed, 23 Oct 2019 14:04:47 -0300 Subject: [PATCH 15/28] Add ProductSummaryList documentation --- docs/ProductSummaryList.md | 43 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 docs/ProductSummaryList.md diff --git a/docs/ProductSummaryList.md b/docs/ProductSummaryList.md new file mode 100644 index 00000000..5574e66d --- /dev/null +++ b/docs/ProductSummaryList.md @@ -0,0 +1,43 @@ +# ProductSummaryList + +The `list-context.product-list` interface is a stance of the `list-context` interfaces, which means its part of a set of special interfaces that enables you to create lists of content that can be edited via Site Editor. + +In order to create a list of products, you need to use `product-list-block` and `list-context.product-list` blocks. + +## product-list-block + +This block is used to specify what variation of `product-summary` to be used to create the list of products, and the `list-context.product-list` you want as follows: + +```json + "product-summary.shelf#demo1": { + "children": [ + "stack-layout#prodsum", + "product-summary-name", + "product-rating-inline", + "product-summary-space", + "product-summary-price", + "product-summary-buy-button" + ] + }, + "product-list-block#demo1": { + "blocks": ["product-summary.shelf#demo1", "list-context.product-list#demo1"] + }, + "list-context.product-list#demo1": { + "children": ["slider-layout#demo-products"] + }, +``` + +`product-summary-block` is also the one that actually performs the GraphQL query that fetches the list of products, so it can receive the following props: + +| Prop name | Type | Description | Default value | +| ---------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | +| `category` | `String` | Category ID of the listed items. For sub-categories, use "/" (e.g. "1/2/3") | - | +| `specificationFilters` | `Array({ id: String, value: String })` | Specification Filters of the listed items. | [] | +| `collection` | `String` | Filter by collection. | - | +| `orderBy` | `Enum` | Ordination type of the items. Possible values: `OrderByTopSaleDESC`, `OrderByReleaseDateDESC`, `OrderByBestDiscountDESC`, `OrderByPriceDESC`, `OrderByPriceASC`, `OrderByNameASC`, `OrderByNameDESC` | `OrderByTopSaleDESC` | +| `hideUnavailableItems` | `Boolean` | Hides items that are unavailable. | `false` | +| `maxItems` | `Number` | Maximum items to be fetched. | `10` | + +## list-context.product-list + +This is the interface that extends the `list-context` from `vtex.list-context`, and has `"composition": "children"` to enable this list to be composable with other contexts. It should not expect any props to be passed to it and just wraps its children with a `ListContextProvider` so that they have access to the list of `product-summary`s created by `product-list-block`. \ No newline at end of file From d347ff1a46550ded09a5121696f5662e0a0702d3 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Fri, 31 Jan 2020 17:09:30 -0300 Subject: [PATCH 16/28] Remove unnecessary dependencies --- manifest.json | 8 -------- 1 file changed, 8 deletions(-) diff --git a/manifest.json b/manifest.json index ebc2dec4..4d335db1 100644 --- a/manifest.json +++ b/manifest.json @@ -19,22 +19,14 @@ "vtex.native-types": "0.x", "vtex.store-components": "3.x", "vtex.store-resources": "0.x", - "vtex.product-review-interfaces": "1.x", "vtex.product-summary-context": "0.x", - "vtex.product-identifier": "0.x", "vtex.styleguide": "9.x", "vtex.pixel-manager": "1.x", - "vtex.product-teaser-interfaces": "1.x", "vtex.device-detector": "0.x", - "vtex.product-quantity": "1.x", "vtex.product-specification-badges": "0.x", - "vtex.stack-layout": "0.x", "vtex.responsive-values": "0.x", "vtex.css-handles": "0.x", "vtex.product-context": "0.x", - "vtex.flex-layout": "0.x", - "vtex.rich-text": "0.x", - "vtex.add-to-cart-button": "0.x", "vtex.list-context": "0.x" }, "$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema" From f0798837c0ef575418118a42b939efd12b74a7d3 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Fri, 31 Jan 2020 17:13:57 -0300 Subject: [PATCH 17/28] Update implementation of ProductSummaryList --- react/ProductSummaryList.js | 11 ++--- react/ProductSummaryListChildren.js | 8 ---- store/interfaces.json | 72 +---------------------------- 3 files changed, 7 insertions(+), 84 deletions(-) delete mode 100644 react/ProductSummaryListChildren.js diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index d523ad98..53b4f814 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -1,13 +1,13 @@ import React from 'react' import { graphql } from 'react-apollo' -import { useListContext } from 'vtex.list-context' import { ExtensionPoint, useTreePath } from 'vtex.render-runtime' +import { useListContext, ListContextProvider } from 'vtex.list-context' import { mapCatalogProductToProductSummary } from './utils/normalize' import { productSearchV2 } from 'vtex.store-resources/Queries' -const ProductSummaryList = ({ data }) => { +const ProductSummaryList = ({ data, children }) => { const { list } = useListContext() const { treePath } = useTreePath() @@ -29,10 +29,9 @@ const ProductSummaryList = ({ data }) => { const newListContextValue = list.concat(componentList) return ( - + + {children} + ) } diff --git a/react/ProductSummaryListChildren.js b/react/ProductSummaryListChildren.js deleted file mode 100644 index 721ea295..00000000 --- a/react/ProductSummaryListChildren.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import { ListContextProvider } from 'vtex.list-context' - -const ProductSummaryListChildren = ({ children, newList }) => ( - {children} -) - -export default ProductSummaryListChildren diff --git a/store/interfaces.json b/store/interfaces.json index f9def90b..e4da0c8c 100644 --- a/store/interfaces.json +++ b/store/interfaces.json @@ -7,75 +7,18 @@ "allowed": ["addon-summary-btn"] }, "product-summary.shelf": { - "allowed": [ - "product-summary-column", - "product-summary-attachment-list", - "product-summary-buy-button", - "product-summary-description", - "product-summary-image", - "product-summary-name", - "product-summary-price", - "product-identifier", - "product-summary-sku-selector", - "product-summary-space", - "product-summary-add-to-list-button", - "product-rating-inline", - "product-teaser.summary", - "product-summary-brand", - "product-summary-quantity", - "product-summary-specification-badges", - "product-bookmark", - "rich-text", - "flex-layout", - "stack-layout", - "add-to-cart-button" - ], "component": "ProductSummaryCustom", "composition": "children" }, "product-summary-column": { - "allowed": [ - "product-summary-attachment-list", - "product-summary-buy-button", - "product-summary-description", - "product-summary-image", - "product-summary-name", - "product-summary-price", - "product-identifier", - "product-summary-space", - "product-summary-add-to-list-button", - "product-rating-inline", - "product-summary-specification-badges", - "product-teaser.summary" - ], "component": "Column", "composition": "children" }, "product-summary.unstable--flex": { - "allowed": [ - "unstable--product-summary-column", - "product-summary-attachment-list", - "product-summary-buy-button", - "product-summary-description", - "product-summary-image", - "product-summary-name", - "product-summary-price", - "product-summary-space", - "product-rating-inline" - ], "component": "ProductSummaryCustom", "composition": "children" }, "unstable--product-summary-column": { - "allowed": [ - "product-summary-attachment-list", - "product-summary-buy-button", - "product-summary-description", - "product-summary-image", - "product-summary-name", - "product-summary-price", - "product-summary-space" - ], "component": "Column", "composition": "children" }, @@ -140,20 +83,9 @@ "addon-summary-btn": { "component": "*" }, - "product-list-block": { - "component": "ProductSummaryList", - "required": ["product-summary", "list-context.product-list"] - }, "list-context.product-list": { - "component": "ProductSummaryListChildren", + "allowed": ["product-summary"], "composition": "children", - "allowed": "*", - "content": { - "properties": { - "buyButtonText": { - "$ref": "app:vtex.product-summary#/definitions/ProductSummaryList" - } - } - } + "component": "ProductSummaryList" } } From 01a389c4b0e57aca5ea49cf6bfdda98c61f78b7a Mon Sep 17 00:00:00 2001 From: victorhmp Date: Mon, 3 Feb 2020 10:53:43 -0300 Subject: [PATCH 18/28] Add react-intersection-observer dependency --- react/package.json | 1 + react/yarn.lock | 12 ++++++++++++ 2 files changed, 13 insertions(+) diff --git a/react/package.json b/react/package.json index 0d7c4a98..14f2bc2d 100644 --- a/react/package.json +++ b/react/package.json @@ -15,6 +15,7 @@ "react": "^16.8.6", "react-apollo": "^2.4.1", "react-content-loader": "^3.1.2", + "react-intersection-observer": "^8.25.2", "react-intl": "^2.7.2" }, "devDependencies": { diff --git a/react/yarn.lock b/react/yarn.lock index 2d3263b7..80342331 100644 --- a/react/yarn.lock +++ b/react/yarn.lock @@ -4299,6 +4299,13 @@ react-dom@^16.8.4: prop-types "^15.6.2" scheduler "^0.16.2" +react-intersection-observer@^8.25.2: + version "8.25.2" + resolved "https://registry.yarnpkg.com/react-intersection-observer/-/react-intersection-observer-8.25.2.tgz#be165f4827dc8f1927ab68ecb837efe913d94909" + integrity sha512-KymKTOQK0TxQFW/Km80oS8VVOZAX8nt74fsYEGw6pqBLSJlqBXEhdFpyPcBa08GiGnup8lrFRkSY4JioP+pqww== + dependencies: + tiny-invariant "^1.0.6" + react-intl@^2.7.2, react-intl@^2.8.0: version "2.9.0" resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-2.9.0.tgz#c97c5d17d4718f1575fdbd5a769f96018a3b1843" @@ -5006,6 +5013,11 @@ through@^2.3.6: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= +tiny-invariant@^1.0.6: + version "1.1.0" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.1.0.tgz#634c5f8efdc27714b7f386c35e6760991d230875" + integrity sha512-ytxQvrb1cPc9WBEI/HSeYYoGD0kWnGEOR8RY6KomWLBVhqz0RgTwVO9dLrGz7dC+nN9llyI7OKAgRq8Vq4ZBSw== + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" From be66886b36074e1599a4a1f145be9714058cfcc3 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Mon, 3 Feb 2020 10:53:59 -0300 Subject: [PATCH 19/28] Add vtex.product-list-context dependency --- manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 4d335db1..df00b575 100644 --- a/manifest.json +++ b/manifest.json @@ -27,7 +27,8 @@ "vtex.responsive-values": "0.x", "vtex.css-handles": "0.x", "vtex.product-context": "0.x", - "vtex.list-context": "0.x" + "vtex.list-context": "0.x", + "vtex.product-list-context": "0.x" }, "$schema": "https://raw.githubusercontent.com/vtex/node-vtex-api/master/gen/manifest.schema" } From 81cf29355ea692172dec4e5cfa91b34345275765 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Mon, 3 Feb 2020 15:41:18 -0300 Subject: [PATCH 20/28] Add support for productImpression events --- react/ProductSummaryList.js | 108 +++++++++++---------- react/components/ProductListEventCaller.js | 8 ++ react/components/ProductSummary.js | 41 ++++++-- 3 files changed, 102 insertions(+), 55 deletions(-) create mode 100644 react/components/ProductListEventCaller.js diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index 53b4f814..35914f56 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -1,42 +1,14 @@ import React from 'react' -import { graphql } from 'react-apollo' +import { useQuery } from 'react-apollo' +import { ProductListContext } from 'vtex.product-list-context' import { ExtensionPoint, useTreePath } from 'vtex.render-runtime' import { useListContext, ListContextProvider } from 'vtex.list-context' import { mapCatalogProductToProductSummary } from './utils/normalize' +import ProductListEventCaller from './components/ProductListEventCaller' import { productSearchV2 } from 'vtex.store-resources/Queries' -const ProductSummaryList = ({ data, children }) => { - const { list } = useListContext() - const { treePath } = useTreePath() - - const componentList = - data.productSearch && - data.productSearch.products.map(product => { - const normalizedProduct = mapCatalogProductToProductSummary(product) - - return ( - - ) - }) - - const newListContextValue = list.concat(componentList) - - return ( - - {children} - - ) -} - -const parseFilters = ({ id, value }) => `specificationFilter_${id}:${value}` - const ORDER_BY_OPTIONS = { ORDER_BY_RELEVANCE: { name: 'admin/editor.productSummaryList.orderType.relevance', @@ -71,17 +43,25 @@ const ORDER_BY_OPTIONS = { value: 'OrderByBestDiscountDESC', }, } +const parseFilters = ({ id, value }) => `specificationFilter_${id}:${value}` -const productQueryOptions = { - options: ({ - category = '', - collection, - hideUnavailableItems = false, - orderBy = ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, - specificationFilters = [], - maxItems = 10, - withFacets = false, - }) => ({ +function getOrdinationProp(attribute) { + return Object.keys(ORDER_BY_OPTIONS).map( + key => ORDER_BY_OPTIONS[key][attribute] + ) +} + +const ProductSummaryList = ({ + children, + category = '', + collection, + hideUnavailableItems = false, + orderBy = ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, + specificationFilters = [], + maxItems = 10, + withFacets = false, +}) => { + const { data, loading, error } = useQuery(productSearchV2, { ssr: true, name: 'productList', variables: { @@ -98,18 +78,48 @@ const productQueryOptions = { hideUnavailableItems, withFacets, }, - }), -} + }) -function getOrdinationProp(attribute) { - return Object.keys(ORDER_BY_OPTIONS).map( - key => ORDER_BY_OPTIONS[key][attribute] + const { list } = useListContext() + const { treePath } = useTreePath() + // useProductImpression() + + if (loading) return null + + const componentList = + data.productSearch && + data.productSearch.products.map(product => { + const normalizedProduct = mapCatalogProductToProductSummary(product) + + return ( + + ) + }) + + const newListContextValue = list.concat(componentList) + + return ( + + {children} + ) } -const EnhancedProductList = graphql(productSearchV2, productQueryOptions)( - ProductSummaryList -) +const EnhancedProductList = ({ children }) => { + const { ProductListProvider } = ProductListContext + + return ( + + {children} + + + ) +} EnhancedProductList.getSchema = () => ({ title: 'admin/editor.productSummaryList.title', diff --git a/react/components/ProductListEventCaller.js b/react/components/ProductListEventCaller.js new file mode 100644 index 00000000..9a18f63b --- /dev/null +++ b/react/components/ProductListEventCaller.js @@ -0,0 +1,8 @@ +import { useProductImpression } from 'vtex.product-list-context' + +const ProductListEventCaller = () => { + useProductImpression() + return null +} + +export default ProductListEventCaller diff --git a/react/components/ProductSummary.js b/react/components/ProductSummary.js index 251212e0..31942e9a 100644 --- a/react/components/ProductSummary.js +++ b/react/components/ProductSummary.js @@ -2,7 +2,9 @@ import React, { useCallback, useMemo, useEffect } from 'react' import PropTypes from 'prop-types' import classNames from 'classnames' import { pathOr, path } from 'ramda' +import { useInView } from 'react-intersection-observer' import { Link } from 'vtex.render-runtime' +import { ProductListContext } from 'vtex.product-list-context' import ProductSummaryContext from './ProductSummaryContext' import { ProductSummaryProvider, @@ -17,11 +19,34 @@ import { useCssHandles } from 'vtex.css-handles' const PRODUCT_SUMMARY_MAX_WIDTH = 300 const CSS_HANDLES = ['container', 'containerNormal', 'element', 'clearLink'] -const ProductSummaryCustom = ({ product, actionOnClick, children, containerRef }) => { +const ProductSummaryCustom = ({ + product, + actionOnClick, + children, + containerRef, +}) => { const { isLoading, isHovering, selectedItem, query } = useProductSummary() const dispatch = useProductSummaryDispatch() const handles = useCssHandles(CSS_HANDLES) + // Use ProductListContext to send pixel events + const { useProductListDispatch } = ProductListContext + const productListDispatch = useProductListDispatch() + const [inViewRef, inView] = useInView({ + // Triggers the event when the element is 75% visible + threshold: 0.75, + triggerOnce: true, + }) + useEffect(() => { + if (inView) { + productListDispatch && + productListDispatch({ + type: 'SEND_IMPRESSION', + args: { product: product }, + }) + } + }, [productListDispatch, inView, product]) + useEffect(() => { if (product) { dispatch({ @@ -76,10 +101,7 @@ const ProductSummaryCustom = ({ product, actionOnClick, children, containerRef } 'pointer pt3 pb4 flex flex-column h-100' ) - const linkClasses = classNames( - handles.clearLink, - 'h-100 flex flex-column' - ) + const linkClasses = classNames(handles.clearLink, 'h-100 flex flex-column') const skuId = pathOr( path(['sku', 'itemId'], product), @@ -95,7 +117,8 @@ const ProductSummaryCustom = ({ product, actionOnClick, children, containerRef } onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} style={{ maxWidth: PRODUCT_SUMMARY_MAX_WIDTH }} - ref={containerRef} + // If containerRef is passed, it should be used + ref={containerRef || inViewRef} > Date: Mon, 3 Feb 2020 15:44:13 -0300 Subject: [PATCH 21/28] Fix proptypes --- react/components/ProductSummary.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/components/ProductSummary.js b/react/components/ProductSummary.js index 31942e9a..b5ebc0c9 100644 --- a/react/components/ProductSummary.js +++ b/react/components/ProductSummary.js @@ -148,7 +148,7 @@ ProductSummaryCustom.propTypes = { // Either a function PropTypes.func, // Or the instance of a DOM native element - PropTypes.shape({ current: PropTypes.instanceOf(Element) }), + PropTypes.shape({ current: PropTypes.instanceOf(PropTypes.Element) }), ]), } From f1af937d09dbb41bc90400328c728ff95cfe7dab Mon Sep 17 00:00:00 2001 From: victorhmp Date: Mon, 3 Feb 2020 16:16:02 -0300 Subject: [PATCH 22/28] Add mock definition for vtex.product-list-context --- react/__mocks__/vtex.product-list-context.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 react/__mocks__/vtex.product-list-context.js diff --git a/react/__mocks__/vtex.product-list-context.js b/react/__mocks__/vtex.product-list-context.js new file mode 100644 index 00000000..c03fb313 --- /dev/null +++ b/react/__mocks__/vtex.product-list-context.js @@ -0,0 +1,8 @@ +import React, { Fragment } from 'react' + +export const useProductImpression = () => null +export const ProductListContext = { + useProductListDispatch: () => null, + // eslint-disable-next-line react/display-name + ProductListProvider: ({ children }) => {children}, +} From bb05ec49397dcae8eab9df4c0694577650fb9299 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Mon, 3 Feb 2020 16:21:46 -0300 Subject: [PATCH 23/28] Update documentation --- docs/ProductSummaryList.md | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/docs/ProductSummaryList.md b/docs/ProductSummaryList.md index 5574e66d..93ce04ce 100644 --- a/docs/ProductSummaryList.md +++ b/docs/ProductSummaryList.md @@ -2,7 +2,7 @@ The `list-context.product-list` interface is a stance of the `list-context` interfaces, which means its part of a set of special interfaces that enables you to create lists of content that can be edited via Site Editor. -In order to create a list of products, you need to use `product-list-block` and `list-context.product-list` blocks. +In order to create a list of products, you need to use the `list-context.product-list` block and a `product-summary.shelf`. ## product-list-block @@ -19,15 +19,13 @@ This block is used to specify what variation of `product-summary` to be used to "product-summary-buy-button" ] }, - "product-list-block#demo1": { - "blocks": ["product-summary.shelf#demo1", "list-context.product-list#demo1"] - }, "list-context.product-list#demo1": { + "blocks": ["product-summary.shelf#demo1"], "children": ["slider-layout#demo-products"] }, ``` -`product-summary-block` is also the one that actually performs the GraphQL query that fetches the list of products, so it can receive the following props: +`list-context.product-list` is also responsible for performing the GraphQL query that fetches the list of products, so it can receive the following props: | Prop name | Type | Description | Default value | | ---------------------- | -------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- | @@ -36,8 +34,4 @@ This block is used to specify what variation of `product-summary` to be used to | `collection` | `String` | Filter by collection. | - | | `orderBy` | `Enum` | Ordination type of the items. Possible values: `OrderByTopSaleDESC`, `OrderByReleaseDateDESC`, `OrderByBestDiscountDESC`, `OrderByPriceDESC`, `OrderByPriceASC`, `OrderByNameASC`, `OrderByNameDESC` | `OrderByTopSaleDESC` | | `hideUnavailableItems` | `Boolean` | Hides items that are unavailable. | `false` | -| `maxItems` | `Number` | Maximum items to be fetched. | `10` | - -## list-context.product-list - -This is the interface that extends the `list-context` from `vtex.list-context`, and has `"composition": "children"` to enable this list to be composable with other contexts. It should not expect any props to be passed to it and just wraps its children with a `ListContextProvider` so that they have access to the list of `product-summary`s created by `product-list-block`. \ No newline at end of file +| `maxItems` | `Number` | Maximum items to be fetched. | `10` | \ No newline at end of file From b4cd95f6cf3b9b0646c82eb5486e1a8b2d6cdef0 Mon Sep 17 00:00:00 2001 From: Victor Hugo Miranda Pinto Date: Tue, 4 Feb 2020 15:47:30 -0300 Subject: [PATCH 24/28] Update react/ProductSummaryList.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Iaron da Costa Araújo --- react/ProductSummaryList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index 35914f56..07ad866b 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -12,7 +12,7 @@ import { productSearchV2 } from 'vtex.store-resources/Queries' const ORDER_BY_OPTIONS = { ORDER_BY_RELEVANCE: { name: 'admin/editor.productSummaryList.orderType.relevance', - value: '', + value: 'OrderByScoreDESC', }, ORDER_BY_TOP_SALE_DESC: { name: 'admin/editor.productSummaryList.orderType.sales', From bebd793bb03a719ffc6b3d0d7cf71b88348e9d8f Mon Sep 17 00:00:00 2001 From: victorhmp Date: Tue, 4 Feb 2020 15:55:03 -0300 Subject: [PATCH 25/28] Update variable naming --- react/ProductSummaryList.js | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/react/ProductSummaryList.js b/react/ProductSummaryList.js index 07ad866b..a9d7f678 100644 --- a/react/ProductSummaryList.js +++ b/react/ProductSummaryList.js @@ -10,35 +10,35 @@ import ProductListEventCaller from './components/ProductListEventCaller' import { productSearchV2 } from 'vtex.store-resources/Queries' const ORDER_BY_OPTIONS = { - ORDER_BY_RELEVANCE: { + RELEVANCE: { name: 'admin/editor.productSummaryList.orderType.relevance', value: 'OrderByScoreDESC', }, - ORDER_BY_TOP_SALE_DESC: { + TOP_SALE_DESC: { name: 'admin/editor.productSummaryList.orderType.sales', value: 'OrderByTopSaleDESC', }, - ORDER_BY_PRICE_DESC: { + PRICE_DESC: { name: 'admin/editor.productSummaryList.orderType.priceDesc', value: 'OrderByPriceDESC', }, - ORDER_BY_PRICE_ASC: { + PRICE_ASC: { name: 'admin/editor.productSummaryList.orderType.priceAsc', value: 'OrderByPriceASC', }, - ORDER_BY_NAME_ASC: { + NAME_ASC: { name: 'admin/editor.productSummaryList.orderType.nameAsc', value: 'OrderByNameASC', }, - ORDER_BY_NAME_DESC: { + NAME_DESC: { name: 'admin/editor.productSummaryList.orderType.nameDesc', value: 'OrderByNameDESC', }, - ORDER_BY_RELEASE_DATE_DESC: { + RELEASE_DATE_DESC: { name: 'admin/editor.productSummaryList.orderType.releaseDate', value: 'OrderByReleaseDateDESC', }, - ORDER_BY_BEST_DISCOUNT_DESC: { + BEST_DISCOUNT_DESC: { name: 'admin/editor.productSummaryList.orderType.discount', value: 'OrderByBestDiscountDESC', }, @@ -56,12 +56,12 @@ const ProductSummaryList = ({ category = '', collection, hideUnavailableItems = false, - orderBy = ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, + orderBy = ORDER_BY_OPTIONS.TOP_SALE_DESC.value, specificationFilters = [], maxItems = 10, withFacets = false, }) => { - const { data, loading, error } = useQuery(productSearchV2, { + const { data } = useQuery(productSearchV2, { ssr: true, name: 'productList', variables: { @@ -82,9 +82,6 @@ const ProductSummaryList = ({ const { list } = useListContext() const { treePath } = useTreePath() - // useProductImpression() - - if (loading) return null const componentList = data.productSearch && @@ -163,7 +160,7 @@ EnhancedProductList.getSchema = () => ({ type: 'string', enum: getOrdinationProp('value'), enumNames: getOrdinationProp('name'), - default: ORDER_BY_OPTIONS.ORDER_BY_TOP_SALE_DESC.value, + default: ORDER_BY_OPTIONS.TOP_SALE_DESC.value, isLayout: false, }, hideUnavailableItems: { From eb21903b8d87f88db0441e71a522ba57eef8cd00 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Tue, 4 Feb 2020 16:03:38 -0300 Subject: [PATCH 26/28] Add informative comment to ProductSummary --- react/components/ProductSummary.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/react/components/ProductSummary.js b/react/components/ProductSummary.js index b5ebc0c9..3616b092 100644 --- a/react/components/ProductSummary.js +++ b/react/components/ProductSummary.js @@ -29,7 +29,12 @@ const ProductSummaryCustom = ({ const dispatch = useProductSummaryDispatch() const handles = useCssHandles(CSS_HANDLES) - // Use ProductListContext to send pixel events + /* + Use ProductListContext to send pixel events. + Beware that productListDispatch could be undefined if + this component is not wrapped by a . + In that case we don't need to send events. + */ const { useProductListDispatch } = ProductListContext const productListDispatch = useProductListDispatch() const [inViewRef, inView] = useInView({ From 6cd7f6e1e5c7565bd2b8f8e6e90b9cf04e128899 Mon Sep 17 00:00:00 2001 From: Victor Hugo Miranda Pinto Date: Wed, 5 Feb 2020 19:00:31 -0300 Subject: [PATCH 27/28] Update docs/ProductSummaryList.md Co-Authored-By: Rafael Klynger --- docs/ProductSummaryList.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ProductSummaryList.md b/docs/ProductSummaryList.md index 93ce04ce..5fad7c97 100644 --- a/docs/ProductSummaryList.md +++ b/docs/ProductSummaryList.md @@ -1,6 +1,6 @@ # ProductSummaryList -The `list-context.product-list` interface is a stance of the `list-context` interfaces, which means its part of a set of special interfaces that enables you to create lists of content that can be edited via Site Editor. +The `list-context.product-list` interface is a instance of the `list-context` interfaces, which means its part of a set of special interfaces that enables you to create lists of content that can be edited via Site Editor. In order to create a list of products, you need to use the `list-context.product-list` block and a `product-summary.shelf`. @@ -34,4 +34,4 @@ This block is used to specify what variation of `product-summary` to be used to | `collection` | `String` | Filter by collection. | - | | `orderBy` | `Enum` | Ordination type of the items. Possible values: `OrderByTopSaleDESC`, `OrderByReleaseDateDESC`, `OrderByBestDiscountDESC`, `OrderByPriceDESC`, `OrderByPriceASC`, `OrderByNameASC`, `OrderByNameDESC` | `OrderByTopSaleDESC` | | `hideUnavailableItems` | `Boolean` | Hides items that are unavailable. | `false` | -| `maxItems` | `Number` | Maximum items to be fetched. | `10` | \ No newline at end of file +| `maxItems` | `Number` | Maximum items to be fetched. | `10` | From 64303f6a88920c6e8c5262dc6950f76be8a6de98 Mon Sep 17 00:00:00 2001 From: victorhmp Date: Thu, 6 Feb 2020 15:48:58 -0300 Subject: [PATCH 28/28] trigger ci run