From 14ffcfbc3d53155f5bb05c71f79c625a46d3cfa5 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Thu, 5 Jan 2023 13:34:54 -0800 Subject: [PATCH 01/25] Product set: render the children products with minimal UI --- .../app/pages/product-detail/index.jsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 2e36f8bd57..191185804e 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -295,6 +295,22 @@ const ProductDetail = ({category, product, isLoading}) => { /> + + {/* Product Set */} + + {product.setProducts && + product.setProducts.map((childProduct) => ( + handleAddToCart(variant, quantity)} + addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} + isProductLoading={isLoading} + isCustomerProductListLoading={!wishlist.isInitialized} + /> + ))} + ) } From c5f71bbebbed872a0b64df433086e71b87f1950d Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Thu, 5 Jan 2023 17:20:11 -0800 Subject: [PATCH 02/25] No longer showing breadcrumbs in the child items --- .../app/pages/product-detail/index.jsx | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 191185804e..89b64c89bf 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -255,6 +255,20 @@ const ProductDetail = ({category, product, isLoading}) => { + {/* Product Set */} + {product?.setProducts && + // TODO: page url params + product.setProducts.map((childProduct) => ( + handleAddToCart(variant, quantity)} + addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} + isProductLoading={isLoading} + isCustomerProductListLoading={!wishlist.isInitialized} + /> + ))} + {/* Product Recommendations */} { /> - - {/* Product Set */} - - {product.setProducts && - product.setProducts.map((childProduct) => ( - handleAddToCart(variant, quantity)} - addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} - isProductLoading={isLoading} - isCustomerProductListLoading={!wishlist.isInitialized} - /> - ))} - ) } From 77a2ba6b3b061d918b3f886c2027bf6327f11006 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Fri, 6 Jan 2023 15:39:44 -0800 Subject: [PATCH 03/25] Individual swatches now have correct `value` There was a subtle bug where the `value` of the SwatchGroup was passed down to override all the values of the children Swatch components. --- .../app/components/swatch-group/index.jsx | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/packages/template-retail-react-app/app/components/swatch-group/index.jsx b/packages/template-retail-react-app/app/components/swatch-group/index.jsx index d005367321..e05e3dbc42 100644 --- a/packages/template-retail-react-app/app/components/swatch-group/index.jsx +++ b/packages/template-retail-react-app/app/components/swatch-group/index.jsx @@ -15,7 +15,14 @@ import {noop} from '../../utils/utils' * Each Swatch is a link with will direct to a href passed to them */ const SwatchGroup = (props) => { - const {displayName, children, value, label = '', variant = 'square', onChange = noop} = props + const { + displayName, + children, + value: selectedValue, + label = '', + variant = 'square', + onChange = noop + } = props const styles = useStyleConfig('SwatchGroup') return ( @@ -28,9 +35,7 @@ const SwatchGroup = (props) => { const childValue = child.props.value return React.cloneElement(child, { - selected: childValue === value, - key: childValue, - value, + selected: childValue === selectedValue, variant, onChange }) From aa5fdb97447ac7f79be92f818d298f543ed309ef Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Tue, 10 Jan 2023 12:59:18 -0800 Subject: [PATCH 04/25] 1st attempt at saving swatches state in the page url --- .../app/hooks/use-product.js | 7 ++-- .../app/hooks/use-variation-attributes.js | 32 ++++++++++++++++--- .../app/hooks/use-variation-params.js | 26 +++++++++------ .../app/pages/product-detail/index.jsx | 2 +- .../app/partials/product-view/index.jsx | 5 +-- 5 files changed, 52 insertions(+), 20 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-product.js b/packages/template-retail-react-app/app/hooks/use-product.js index 5c1e238779..a75b4c4aa3 100644 --- a/packages/template-retail-react-app/app/hooks/use-product.js +++ b/packages/template-retail-react-app/app/hooks/use-product.js @@ -15,7 +15,7 @@ const OUT_OF_STOCK = 'OUT_OF_STOCK' const UNFULFILLABLE = 'UNFULFILLABLE' // TODO: This needs to be refactored. -export const useProduct = (product) => { +export const useProduct = (product, productType) => { const showLoading = !product const stockLevel = product?.inventory?.stockLevel || 0 const stepQuantity = product?.stepQuantity || 1 @@ -24,8 +24,9 @@ export const useProduct = (product) => { const intl = useIntl() const variant = useVariant(product) - const variationParams = useVariationParams(product) - const variationAttributes = useVariationAttributes(product) + const variationParams = useVariationParams(product, productType) + const variationAttributes = useVariationAttributes(product, productType) + // console.log('--- variationAttributes', variationAttributes) const [quantity, setQuantity] = useState(initialQuantity) // A product is considered out of stock if the stock level is 0 or if we have all our diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index 7a62d32e5d..e8d8cee4d9 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -47,7 +47,28 @@ const getVariantValueSwatch = (product, variationValue) => { * @param {Object} location * @returns {String} a product url for the current variation value. */ -const buildVariantValueHref = (product, params, location) => { +const buildVariantValueHref = (params, location, {productType, productId} = {}) => { + // console.log('--- buildVariantValueHref', params, location) + const searchParams = new URLSearchParams(location.search) + + if (productType === 'set') { + const childProductParams = new URLSearchParams(searchParams.get(productId)) + + Object.entries(params).forEach(([key, value]) => { + // 0 is a valid value as for a param + if (!value && value !== 0) { + childProductParams.delete(key) + } else { + childProductParams.set(key, value) + } + }) + + searchParams.set(productId, childProductParams.toString()) + + return `${location.pathname}?${searchParams.toString()}` + } + + // TODO: maybe don't call rebuildPathWithParams here return rebuildPathWithParams(`${location.pathname}${location.search}`, params) } @@ -80,10 +101,10 @@ const isVariantValueOrderable = (product, variationParams) => { * @returns {Array} a decorated variation attributes list. * */ -export const useVariationAttributes = (product = {}) => { +export const useVariationAttributes = (product = {}, productType) => { const {variationAttributes = []} = product const location = useLocation() - const variationParams = useVariationParams(product) + const variationParams = useVariationParams(product, productType) return useMemo( () => @@ -104,7 +125,10 @@ export const useVariationAttributes = (product = {}) => { return { ...value, image: getVariantValueSwatch(product, value), - href: buildVariantValueHref(product, params, location), + href: buildVariantValueHref(params, location, { + productType, + productId: product.id + }), orderable: isVariantValueOrderable(product, params) } }) diff --git a/packages/template-retail-react-app/app/hooks/use-variation-params.js b/packages/template-retail-react-app/app/hooks/use-variation-params.js index d210b39a45..ddc52ae5a0 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-params.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-params.js @@ -11,19 +11,25 @@ import {useLocation} from 'react-router-dom' * This hook will return only the params that are also product attributes for the * passed in product object. */ -export const useVariationParams = (product = {}) => { +export const useVariationParams = (product = {}, productType) => { const {variationAttributes = [], variationValues = {}} = product - const variationParams = variationAttributes.map(({id}) => id) - const {search} = useLocation() - const params = new URLSearchParams(search) - // Using all the variation attribute id from the array generated above, get + let params = new URLSearchParams(search) + + if (productType === 'set') { + params = new URLSearchParams(params.get(product.id)) + } + + // Using all the variation attribute id from the array generated below, get // the value if there is one from the location search params and add it to the // accumulator. - const filteredVariationParams = variationParams.reduce((acc, key) => { - let value = params.get(`${key}`) || variationValues?.[key] - return value ? {...acc, [key]: value} : acc - }, {}) + const variationParams = variationAttributes + .map(({id}) => id) + .reduce((acc, key) => { + let value = params.get(`${key}`) || variationValues?.[key] + return value ? {...acc, [key]: value} : acc + }, {}) - return filteredVariationParams + // console.log('--- variationParams', variationParams, productType) + return variationParams } diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 89b64c89bf..2d45104b15 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -257,11 +257,11 @@ const ProductDetail = ({category, product, isLoading}) => { {/* Product Set */} {product?.setProducts && - // TODO: page url params product.setProducts.map((childProduct) => ( handleAddToCart(variant, quantity)} addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} isProductLoading={isLoading} diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index 067493c27a..d375b28930 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -78,7 +78,8 @@ const ProductView = ({ updateCart, addToWishlist, updateWishlist, - isProductLoading + isProductLoading, + productType }) => { const intl = useIntl() const history = useHistory() @@ -102,7 +103,7 @@ const ProductView = ({ variationAttributes, stockLevel, stepQuantity - } = useProduct(product) + } = useProduct(product, productType) const canAddToWishlist = !isProductLoading const canOrder = !isProductLoading && From 8400a46a2ada5a2373912ece15964a73583cfc8a Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Tue, 10 Jan 2023 15:18:40 -0800 Subject: [PATCH 05/25] Avoid null in the url search params --- .../app/hooks/use-variation-attributes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index e8d8cee4d9..b81719506c 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -52,7 +52,7 @@ const buildVariantValueHref = (params, location, {productType, productId} = {}) const searchParams = new URLSearchParams(location.search) if (productType === 'set') { - const childProductParams = new URLSearchParams(searchParams.get(productId)) + const childProductParams = new URLSearchParams(searchParams.get(productId) || '') Object.entries(params).forEach(([key, value]) => { // 0 is a valid value as for a param From 868a5a37fcfe083f17889ec7055a40ec153caa7a Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Tue, 10 Jan 2023 15:41:46 -0800 Subject: [PATCH 06/25] Some refactoring --- .../app/hooks/use-variation-attributes.js | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index b81719506c..35bb6376f7 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -48,28 +48,28 @@ const getVariantValueSwatch = (product, variationValue) => { * @returns {String} a product url for the current variation value. */ const buildVariantValueHref = (params, location, {productType, productId} = {}) => { - // console.log('--- buildVariantValueHref', params, location) const searchParams = new URLSearchParams(location.search) + const childProductParams = new URLSearchParams(searchParams.get(productId) || '') if (productType === 'set') { - const childProductParams = new URLSearchParams(searchParams.get(productId) || '') - - Object.entries(params).forEach(([key, value]) => { - // 0 is a valid value as for a param - if (!value && value !== 0) { - childProductParams.delete(key) - } else { - childProductParams.set(key, value) - } - }) - + updateSearchParams(childProductParams, params) searchParams.set(productId, childProductParams.toString()) - - return `${location.pathname}?${searchParams.toString()}` + } else { + updateSearchParams(searchParams, params) } - // TODO: maybe don't call rebuildPathWithParams here - return rebuildPathWithParams(`${location.pathname}${location.search}`, params) + return `${location.pathname}?${searchParams.toString()}` +} + +const updateSearchParams = (searchParams, params) => { + Object.entries(params).forEach(([key, value]) => { + // 0 is a valid value as for a param + if (!value && value !== 0) { + searchParams.delete(key) + } else { + searchParams.set(key, value) + } + }) } /** From e108963d82929797df5c9e295096d86a76edb7a6 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Tue, 10 Jan 2023 15:57:04 -0800 Subject: [PATCH 07/25] Add-to-cart now works for child items --- packages/template-retail-react-app/app/hooks/use-product.js | 2 +- packages/template-retail-react-app/app/hooks/use-variant.js | 4 ++-- .../app/hooks/use-variation-attributes.js | 4 ++-- .../app/hooks/use-variation-params.js | 2 +- .../app/partials/product-view/index.jsx | 1 + 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-product.js b/packages/template-retail-react-app/app/hooks/use-product.js index a75b4c4aa3..129d7ced10 100644 --- a/packages/template-retail-react-app/app/hooks/use-product.js +++ b/packages/template-retail-react-app/app/hooks/use-product.js @@ -23,7 +23,7 @@ export const useProduct = (product, productType) => { const initialQuantity = product?.quantity || product?.minOrderQuantity || 1 const intl = useIntl() - const variant = useVariant(product) + const variant = useVariant(product, productType) const variationParams = useVariationParams(product, productType) const variationAttributes = useVariationAttributes(product, productType) // console.log('--- variationAttributes', variationAttributes) diff --git a/packages/template-retail-react-app/app/hooks/use-variant.js b/packages/template-retail-react-app/app/hooks/use-variant.js index f99afb7f80..9365e10fb1 100644 --- a/packages/template-retail-react-app/app/hooks/use-variant.js +++ b/packages/template-retail-react-app/app/hooks/use-variant.js @@ -16,9 +16,9 @@ import {useVariationParams} from './use-variation-params' * @param {Object} product * @returns {Object} the currently selected `Variant` object. */ -export const useVariant = (product = {}) => { +export const useVariant = (product = {}, productType) => { const {variants = []} = product - const variationParams = useVariationParams(product) + const variationParams = useVariationParams(product, productType) // Get a filtered array of variants. The resulting array will only have variants // which have all the current variation params values set. diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index 35bb6376f7..b7011d53f3 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -61,8 +61,8 @@ const buildVariantValueHref = (params, location, {productType, productId} = {}) return `${location.pathname}?${searchParams.toString()}` } -const updateSearchParams = (searchParams, params) => { - Object.entries(params).forEach(([key, value]) => { +const updateSearchParams = (searchParams, newParams) => { + Object.entries(newParams).forEach(([key, value]) => { // 0 is a valid value as for a param if (!value && value !== 0) { searchParams.delete(key) diff --git a/packages/template-retail-react-app/app/hooks/use-variation-params.js b/packages/template-retail-react-app/app/hooks/use-variation-params.js index ddc52ae5a0..d67a7b66c1 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-params.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-params.js @@ -17,7 +17,7 @@ export const useVariationParams = (product = {}, productType) => { let params = new URLSearchParams(search) if (productType === 'set') { - params = new URLSearchParams(params.get(product.id)) + params = new URLSearchParams(params.get(product.id) || '') } // Using all the variation attribute id from the array generated below, get diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index d375b28930..b3158d77cb 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -124,6 +124,7 @@ const ProductView = ({ await updateCart(variant, quantity) return } + // TODO: for product set, make sure modal shows the product attributes data (e.g. size, colour) await addToCart(variant, quantity) onAddToCartModalOpen({product, quantity}) } From 31bcd9369f9e37d80bbf187e9bd2c1597c69873a Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Tue, 10 Jan 2023 16:35:03 -0800 Subject: [PATCH 08/25] Show accordion for each child item --- .../app/pages/product-detail/index.jsx | 137 ++++-------------- .../partials/information-accordion.jsx | 121 ++++++++++++++++ .../app/partials/product-view/index.jsx | 1 + 3 files changed, 152 insertions(+), 107 deletions(-) create mode 100644 packages/template-retail-react-app/app/pages/product-detail/partials/information-accordion.jsx diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 2d45104b15..4eec2a078f 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import React, {useEffect, useState} from 'react' +import React, {Fragment, useEffect, useState} from 'react' import PropTypes from 'prop-types' import {Helmet} from 'react-helmet' import {FormattedMessage, useIntl} from 'react-intl' @@ -32,6 +32,7 @@ import useEinstein from '../../commerce-api/hooks/useEinstein' // Project Components import RecommendedProducts from '../../components/recommended-products' import ProductView from '../../partials/product-view' +import InformationAccordion from './partials/information-accordion' // Others/Utils import {HTTPNotFound} from 'pwa-kit-react-sdk/ssr/universal/errors' @@ -57,6 +58,10 @@ const ProductDetail = ({category, product, isLoading}) => { const navigate = useNavigation() const [primaryCategory, setPrimaryCategory] = useState(category) + console.log('--- variant', variant) + + const productType = product && Object.keys(product.type)[0] + // This page uses the `primaryCategoryId` to retrieve the category data. This attribute // is only available on `master` products. Since a variation will be loaded once all the // attributes are selected (to get the correct inventory values), the category information @@ -155,118 +160,36 @@ const ProductDetail = ({category, product, isLoading}) => { - handleAddToCart(variant, quantity)} - addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} - isProductLoading={isLoading} - isCustomerProductListLoading={!wishlist.isInitialized} - /> - - {/* Information Accordion */} - - - {/* Details */} - -

- - - {formatMessage({ - defaultMessage: 'Product Detail', - id: 'product_detail.accordion.button.product_detail' - })} - - - -

- -
- - - - {/* Size & Fit */} - -

- - - {formatMessage({ - defaultMessage: 'Size & Fit', - id: 'product_detail.accordion.button.size_fit' - })} - - - -

- - {formatMessage({ - defaultMessage: 'Coming Soon', - id: 'product_detail.accordion.message.coming_soon' - })} - -
- - {/* Reviews */} - -

- - - {formatMessage({ - defaultMessage: 'Reviews', - id: 'product_detail.accordion.button.reviews' - })} - - - -

- - {formatMessage({ - defaultMessage: 'Coming Soon', - id: 'product_detail.accordion.message.coming_soon' - })} - -
- - {/* Questions */} - -

- - - {formatMessage({ - defaultMessage: 'Questions', - id: 'product_detail.accordion.button.questions' - })} - - - -

- - {formatMessage({ - defaultMessage: 'Coming Soon', - id: 'product_detail.accordion.message.coming_soon' - })} - -
- - - - - {/* Product Set */} - {product?.setProducts && - product.setProducts.map((childProduct) => ( + {productType !== 'set' && ( + handleAddToCart(variant, quantity)} addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} isProductLoading={isLoading} isCustomerProductListLoading={!wishlist.isInitialized} /> + + + )} + + {/* Product Set */} + {productType === 'set' && + product.setProducts.map((childProduct) => ( + + + handleAddToCart(variant, quantity) + } + addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} + isProductLoading={isLoading} + isCustomerProductListLoading={!wishlist.isInitialized} + /> + + ))} {/* Product Recommendations */} diff --git a/packages/template-retail-react-app/app/pages/product-detail/partials/information-accordion.jsx b/packages/template-retail-react-app/app/pages/product-detail/partials/information-accordion.jsx new file mode 100644 index 0000000000..7d1316362b --- /dev/null +++ b/packages/template-retail-react-app/app/pages/product-detail/partials/information-accordion.jsx @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ + +import React from 'react' +import PropTypes from 'prop-types' +import { + Accordion, + AccordionItem, + AccordionButton, + AccordionPanel, + AccordionIcon, + Box, + Stack +} from '@chakra-ui/react' +import {useIntl} from 'react-intl' + +const InformationAccordion = ({product}) => { + const {formatMessage} = useIntl() + + return ( + + + {/* Details */} + +

+ + + {formatMessage({ + defaultMessage: 'Product Detail', + id: 'product_detail.accordion.button.product_detail' + })} + + + +

+ +
+ + + + {/* Size & Fit */} + +

+ + + {formatMessage({ + defaultMessage: 'Size & Fit', + id: 'product_detail.accordion.button.size_fit' + })} + + + +

+ + {formatMessage({ + defaultMessage: 'Coming Soon', + id: 'product_detail.accordion.message.coming_soon' + })} + +
+ + {/* Reviews */} + +

+ + + {formatMessage({ + defaultMessage: 'Reviews', + id: 'product_detail.accordion.button.reviews' + })} + + + +

+ + {formatMessage({ + defaultMessage: 'Coming Soon', + id: 'product_detail.accordion.message.coming_soon' + })} + +
+ + {/* Questions */} + +

+ + + {formatMessage({ + defaultMessage: 'Questions', + id: 'product_detail.accordion.button.questions' + })} + + + +

+ + {formatMessage({ + defaultMessage: 'Coming Soon', + id: 'product_detail.accordion.message.coming_soon' + })} + +
+ + + + ) +} + +InformationAccordion.propTypes = { + product: PropTypes.object +} + +export default InformationAccordion diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index b3158d77cb..bd80b1ec29 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -421,6 +421,7 @@ const ProductView = ({ ProductView.propTypes = { product: PropTypes.object, + productType: PropTypes.string, category: PropTypes.array, isProductLoading: PropTypes.bool, isWishlistLoading: PropTypes.bool, From 7d30f54ad525f1a9e71a4c383f784db673784434 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Tue, 10 Jan 2023 16:44:28 -0800 Subject: [PATCH 09/25] Add todo --- .../app/partials/product-view/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index bd80b1ec29..4ed508385c 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -401,6 +401,7 @@ const ProductView = ({ + {/* TODO: what to do for product set? */} {/*Add to Cart Button for mobile versions*/} Date: Wed, 11 Jan 2023 00:21:17 -0800 Subject: [PATCH 10/25] Rename variable for accuracy The child product itself is actually not a set, but is considered a set product. --- .../template-retail-react-app/app/hooks/use-product.js | 8 ++++---- .../template-retail-react-app/app/hooks/use-variant.js | 4 ++-- .../app/hooks/use-variation-attributes.js | 10 +++++----- .../app/hooks/use-variation-params.js | 7 ++++--- .../app/pages/product-detail/index.jsx | 9 +++++---- .../app/partials/product-view/index.jsx | 6 +++--- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-product.js b/packages/template-retail-react-app/app/hooks/use-product.js index 129d7ced10..71c1343a81 100644 --- a/packages/template-retail-react-app/app/hooks/use-product.js +++ b/packages/template-retail-react-app/app/hooks/use-product.js @@ -15,7 +15,7 @@ const OUT_OF_STOCK = 'OUT_OF_STOCK' const UNFULFILLABLE = 'UNFULFILLABLE' // TODO: This needs to be refactored. -export const useProduct = (product, productType) => { +export const useProduct = (product, isSetProduct) => { const showLoading = !product const stockLevel = product?.inventory?.stockLevel || 0 const stepQuantity = product?.stepQuantity || 1 @@ -23,9 +23,9 @@ export const useProduct = (product, productType) => { const initialQuantity = product?.quantity || product?.minOrderQuantity || 1 const intl = useIntl() - const variant = useVariant(product, productType) - const variationParams = useVariationParams(product, productType) - const variationAttributes = useVariationAttributes(product, productType) + const variant = useVariant(product, isSetProduct) + const variationParams = useVariationParams(product, isSetProduct) + const variationAttributes = useVariationAttributes(product, isSetProduct) // console.log('--- variationAttributes', variationAttributes) const [quantity, setQuantity] = useState(initialQuantity) diff --git a/packages/template-retail-react-app/app/hooks/use-variant.js b/packages/template-retail-react-app/app/hooks/use-variant.js index 9365e10fb1..6365329a83 100644 --- a/packages/template-retail-react-app/app/hooks/use-variant.js +++ b/packages/template-retail-react-app/app/hooks/use-variant.js @@ -16,9 +16,9 @@ import {useVariationParams} from './use-variation-params' * @param {Object} product * @returns {Object} the currently selected `Variant` object. */ -export const useVariant = (product = {}, productType) => { +export const useVariant = (product = {}, isSetProduct) => { const {variants = []} = product - const variationParams = useVariationParams(product, productType) + const variationParams = useVariationParams(product, isSetProduct) // Get a filtered array of variants. The resulting array will only have variants // which have all the current variation params values set. diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index b7011d53f3..624b5277cc 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -47,11 +47,11 @@ const getVariantValueSwatch = (product, variationValue) => { * @param {Object} location * @returns {String} a product url for the current variation value. */ -const buildVariantValueHref = (params, location, {productType, productId} = {}) => { +const buildVariantValueHref = (params, location, {isSetProduct, productId} = {}) => { const searchParams = new URLSearchParams(location.search) const childProductParams = new URLSearchParams(searchParams.get(productId) || '') - if (productType === 'set') { + if (isSetProduct) { updateSearchParams(childProductParams, params) searchParams.set(productId, childProductParams.toString()) } else { @@ -101,10 +101,10 @@ const isVariantValueOrderable = (product, variationParams) => { * @returns {Array} a decorated variation attributes list. * */ -export const useVariationAttributes = (product = {}, productType) => { +export const useVariationAttributes = (product = {}, isSetProduct) => { const {variationAttributes = []} = product const location = useLocation() - const variationParams = useVariationParams(product, productType) + const variationParams = useVariationParams(product, isSetProduct) return useMemo( () => @@ -126,7 +126,7 @@ export const useVariationAttributes = (product = {}, productType) => { ...value, image: getVariantValueSwatch(product, value), href: buildVariantValueHref(params, location, { - productType, + isSetProduct, productId: product.id }), orderable: isVariantValueOrderable(product, params) diff --git a/packages/template-retail-react-app/app/hooks/use-variation-params.js b/packages/template-retail-react-app/app/hooks/use-variation-params.js index d67a7b66c1..fb09b3f3ab 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-params.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-params.js @@ -11,12 +11,13 @@ import {useLocation} from 'react-router-dom' * This hook will return only the params that are also product attributes for the * passed in product object. */ -export const useVariationParams = (product = {}, productType) => { +export const useVariationParams = (product = {}, isSetProduct) => { const {variationAttributes = [], variationValues = {}} = product const {search} = useLocation() let params = new URLSearchParams(search) + // TODO: usePDPSearchParams(productId?) -> entire params AND the one specific to productId - {searchParams, productParams} - if (productType === 'set') { + if (isSetProduct) { params = new URLSearchParams(params.get(product.id) || '') } @@ -30,6 +31,6 @@ export const useVariationParams = (product = {}, productType) => { return value ? {...acc, [key]: value} : acc }, {}) - // console.log('--- variationParams', variationParams, productType) + // console.log('--- variationParams', variationParams, isSetProduct) return variationParams } diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 4eec2a078f..6fef29777e 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -60,7 +60,8 @@ const ProductDetail = ({category, product, isLoading}) => { console.log('--- variant', variant) - const productType = product && Object.keys(product.type)[0] + // TODO: see https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-products?meta=getProduct + // const productType = product && Object.keys(product.type)[0] // This page uses the `primaryCategoryId` to retrieve the category data. This attribute // is only available on `master` products. Since a variation will be loaded once all the @@ -160,7 +161,7 @@ const ProductDetail = ({category, product, isLoading}) => { - {productType !== 'set' && ( + {!product?.type.set && ( { )} {/* Product Set */} - {productType === 'set' && + {product?.type.set && product.setProducts.map((childProduct) => ( handleAddToCart(variant, quantity) } diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index 4ed508385c..9ba7e52be2 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -79,7 +79,7 @@ const ProductView = ({ addToWishlist, updateWishlist, isProductLoading, - productType + isSetProduct }) => { const intl = useIntl() const history = useHistory() @@ -103,7 +103,7 @@ const ProductView = ({ variationAttributes, stockLevel, stepQuantity - } = useProduct(product, productType) + } = useProduct(product, isSetProduct) const canAddToWishlist = !isProductLoading const canOrder = !isProductLoading && @@ -422,7 +422,7 @@ const ProductView = ({ ProductView.propTypes = { product: PropTypes.object, - productType: PropTypes.string, + isSetProduct: PropTypes.bool, category: PropTypes.array, isProductLoading: PropTypes.bool, isWishlistLoading: PropTypes.bool, From a81034fea2b23baeafc953a271e9db70913eac54 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Wed, 11 Jan 2023 00:38:16 -0800 Subject: [PATCH 11/25] Optional argument --- packages/template-retail-react-app/app/hooks/use-product.js | 2 +- packages/template-retail-react-app/app/hooks/use-variant.js | 2 +- .../app/hooks/use-variation-attributes.js | 6 +++--- .../app/hooks/use-variation-params.js | 2 +- .../app/partials/product-view/index.jsx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-product.js b/packages/template-retail-react-app/app/hooks/use-product.js index 71c1343a81..1673c08f64 100644 --- a/packages/template-retail-react-app/app/hooks/use-product.js +++ b/packages/template-retail-react-app/app/hooks/use-product.js @@ -15,7 +15,7 @@ const OUT_OF_STOCK = 'OUT_OF_STOCK' const UNFULFILLABLE = 'UNFULFILLABLE' // TODO: This needs to be refactored. -export const useProduct = (product, isSetProduct) => { +export const useProduct = (product, isSetProduct = false) => { const showLoading = !product const stockLevel = product?.inventory?.stockLevel || 0 const stepQuantity = product?.stepQuantity || 1 diff --git a/packages/template-retail-react-app/app/hooks/use-variant.js b/packages/template-retail-react-app/app/hooks/use-variant.js index 6365329a83..7dce62e339 100644 --- a/packages/template-retail-react-app/app/hooks/use-variant.js +++ b/packages/template-retail-react-app/app/hooks/use-variant.js @@ -16,7 +16,7 @@ import {useVariationParams} from './use-variation-params' * @param {Object} product * @returns {Object} the currently selected `Variant` object. */ -export const useVariant = (product = {}, isSetProduct) => { +export const useVariant = (product = {}, isSetProduct = false) => { const {variants = []} = product const variationParams = useVariationParams(product, isSetProduct) diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index 624b5277cc..2b0c3b7fce 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -101,7 +101,7 @@ const isVariantValueOrderable = (product, variationParams) => { * @returns {Array} a decorated variation attributes list. * */ -export const useVariationAttributes = (product = {}, isSetProduct) => { +export const useVariationAttributes = (product = {}, isSetProduct = false) => { const {variationAttributes = []} = product const location = useLocation() const variationParams = useVariationParams(product, isSetProduct) @@ -126,8 +126,8 @@ export const useVariationAttributes = (product = {}, isSetProduct) => { ...value, image: getVariantValueSwatch(product, value), href: buildVariantValueHref(params, location, { - isSetProduct, - productId: product.id + productId: product.id, + isSetProduct }), orderable: isVariantValueOrderable(product, params) } diff --git a/packages/template-retail-react-app/app/hooks/use-variation-params.js b/packages/template-retail-react-app/app/hooks/use-variation-params.js index fb09b3f3ab..30f9016326 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-params.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-params.js @@ -11,7 +11,7 @@ import {useLocation} from 'react-router-dom' * This hook will return only the params that are also product attributes for the * passed in product object. */ -export const useVariationParams = (product = {}, isSetProduct) => { +export const useVariationParams = (product = {}, isSetProduct = false) => { const {variationAttributes = [], variationValues = {}} = product const {search} = useLocation() let params = new URLSearchParams(search) diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index 9ba7e52be2..ab65efc28c 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -79,7 +79,7 @@ const ProductView = ({ addToWishlist, updateWishlist, isProductLoading, - isSetProduct + isSetProduct = false }) => { const intl = useIntl() const history = useHistory() From 5f5edc6275d6590d83a758a136f8545d799fd068 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Wed, 11 Jan 2023 10:53:53 -0800 Subject: [PATCH 12/25] Some refactoring and fix linting --- .../app/hooks/use-variation-attributes.js | 13 +---------- .../app/pages/product-detail/index.jsx | 11 +-------- .../app/utils/url.js | 23 ++++++++++--------- 3 files changed, 14 insertions(+), 33 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index 2b0c3b7fce..4ef4886f8f 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -12,7 +12,7 @@ import {useLocation} from 'react-router-dom' import {useVariationParams} from './use-variation-params' // Utils -import {rebuildPathWithParams} from '../utils/url' +import {updateSearchParams} from '../utils/url' /** * Return the first image in the `swatch` type image group for a given @@ -61,17 +61,6 @@ const buildVariantValueHref = (params, location, {isSetProduct, productId} = {}) return `${location.pathname}?${searchParams.toString()}` } -const updateSearchParams = (searchParams, newParams) => { - Object.entries(newParams).forEach(([key, value]) => { - // 0 is a valid value as for a param - if (!value && value !== 0) { - searchParams.delete(key) - } else { - searchParams.set(key, value) - } - }) -} - /** * Determine if a products variant attribute value is orderable without having to * load the variant in question, but filtering the list of variants with the diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 6fef29777e..d3a8efd4ed 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -11,16 +11,7 @@ import {Helmet} from 'react-helmet' import {FormattedMessage, useIntl} from 'react-intl' // Components -import { - Accordion, - AccordionItem, - AccordionButton, - AccordionPanel, - AccordionIcon, - Box, - Button, - Stack -} from '@chakra-ui/react' +import {Box, Button, Stack} from '@chakra-ui/react' // Hooks import useBasket from '../../commerce-api/hooks/useBasket' diff --git a/packages/template-retail-react-app/app/utils/url.js b/packages/template-retail-react-app/app/utils/url.js index d7747aced8..3a9a272cf9 100644 --- a/packages/template-retail-react-app/app/utils/url.js +++ b/packages/template-retail-react-app/app/utils/url.js @@ -46,17 +46,7 @@ export const rebuildPathWithParams = (url, extraParams) => { const [pathname, search] = url.split('?') const params = new URLSearchParams(search) - // Apply any extra params. - Object.keys(extraParams).forEach((key) => { - const value = extraParams[key] - - // 0 is a valid value as for a param - if (!value && value !== 0) { - params.delete(key) - } else { - params.set(key, value) - } - }) + updateSearchParams(params, extraParams) // Clean up any trailing `=` for params without values. const paramStr = params @@ -68,6 +58,17 @@ export const rebuildPathWithParams = (url, extraParams) => { return `${pathname}${Array.from(paramStr).length > 0 ? `?${paramStr}` : ''}` } +export const updateSearchParams = (searchParams, newParams) => { + Object.entries(newParams).forEach(([key, value]) => { + // 0 is a valid value as for a param + if (!value && value !== 0) { + searchParams.delete(key) + } else { + searchParams.set(key, value) + } + }) +} + /** * Builds a list of modified Urls with the provided params key and values, * preserving any search params provided in the original url.Optionally From b3bd3b2810e2615d3f59408d35eb3cd5e6b6b99f Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Wed, 11 Jan 2023 12:08:57 -0800 Subject: [PATCH 13/25] Helper hook for url search params --- .../app/hooks/use-url-search-params.js | 16 ++++++++++++++ .../app/hooks/use-variation-attributes.js | 21 ++++++++++++------- .../app/hooks/use-variation-params.js | 11 +++------- 3 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 packages/template-retail-react-app/app/hooks/use-url-search-params.js diff --git a/packages/template-retail-react-app/app/hooks/use-url-search-params.js b/packages/template-retail-react-app/app/hooks/use-url-search-params.js new file mode 100644 index 0000000000..d24e6accf2 --- /dev/null +++ b/packages/template-retail-react-app/app/hooks/use-url-search-params.js @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023, Salesforce, Inc. + * All rights reserved. + * SPDX-License-Identifier: BSD-3-Clause + * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause + */ +import {useLocation} from 'react-router-dom' + +export const useURLSearchParams = (key) => { + const {search} = useLocation() + + const allParams = new URLSearchParams(search) + const keyParam = new URLSearchParams(allParams.get(key) || '') + + return [allParams, keyParam] +} diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index 4ef4886f8f..139902bc5d 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -13,6 +13,7 @@ import {useVariationParams} from './use-variation-params' // Utils import {updateSearchParams} from '../utils/url' +import {useURLSearchParams} from './use-url-search-params' /** * Return the first image in the `swatch` type image group for a given @@ -47,18 +48,17 @@ const getVariantValueSwatch = (product, variationValue) => { * @param {Object} location * @returns {String} a product url for the current variation value. */ -const buildVariantValueHref = (params, location, {isSetProduct, productId} = {}) => { - const searchParams = new URLSearchParams(location.search) - const childProductParams = new URLSearchParams(searchParams.get(productId) || '') +const buildVariantValueHref = ({pathname, existingParams, newParams, productId, isSetProduct}) => { + const [allParams, productParam] = existingParams if (isSetProduct) { - updateSearchParams(childProductParams, params) - searchParams.set(productId, childProductParams.toString()) + updateSearchParams(productParam, newParams) + allParams.set(productId, productParam.toString()) } else { - updateSearchParams(searchParams, params) + updateSearchParams(allParams, newParams) } - return `${location.pathname}?${searchParams.toString()}` + return `${pathname}?${allParams.toString()}` } /** @@ -95,6 +95,8 @@ export const useVariationAttributes = (product = {}, isSetProduct = false) => { const location = useLocation() const variationParams = useVariationParams(product, isSetProduct) + const existingParams = useURLSearchParams(product.id) + return useMemo( () => variationAttributes.map((variationAttribute) => ({ @@ -114,7 +116,10 @@ export const useVariationAttributes = (product = {}, isSetProduct = false) => { return { ...value, image: getVariantValueSwatch(product, value), - href: buildVariantValueHref(params, location, { + href: buildVariantValueHref({ + pathname: location.pathname, + existingParams, + newParams: params, productId: product.id, isSetProduct }), diff --git a/packages/template-retail-react-app/app/hooks/use-variation-params.js b/packages/template-retail-react-app/app/hooks/use-variation-params.js index 30f9016326..985dda5572 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-params.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-params.js @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import {useLocation} from 'react-router-dom' +import {useURLSearchParams} from './use-url-search-params' /* * This hook will return only the params that are also product attributes for the @@ -13,13 +13,9 @@ import {useLocation} from 'react-router-dom' */ export const useVariationParams = (product = {}, isSetProduct = false) => { const {variationAttributes = [], variationValues = {}} = product - const {search} = useLocation() - let params = new URLSearchParams(search) - // TODO: usePDPSearchParams(productId?) -> entire params AND the one specific to productId - {searchParams, productParams} - if (isSetProduct) { - params = new URLSearchParams(params.get(product.id) || '') - } + const [allParams, productParam] = useURLSearchParams(product.id) + const params = isSetProduct ? productParam : allParams // Using all the variation attribute id from the array generated below, get // the value if there is one from the location search params and add it to the @@ -31,6 +27,5 @@ export const useVariationParams = (product = {}, isSetProduct = false) => { return value ? {...acc, [key]: value} : acc }, {}) - // console.log('--- variationParams', variationParams, isSetProduct) return variationParams } From 3acf93d3611fe8b0ea3b758159d792d0570b66ab Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Wed, 11 Jan 2023 14:37:17 -0800 Subject: [PATCH 14/25] Clean up --- .../app/pages/product-detail/index.jsx | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index d3a8efd4ed..e9e06e7d69 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -49,11 +49,6 @@ const ProductDetail = ({category, product, isLoading}) => { const navigate = useNavigation() const [primaryCategory, setPrimaryCategory] = useState(category) - console.log('--- variant', variant) - - // TODO: see https://developer.salesforce.com/docs/commerce/commerce-api/references/shopper-products?meta=getProduct - // const productType = product && Object.keys(product.type)[0] - // This page uses the `primaryCategoryId` to retrieve the category data. This attribute // is only available on `master` products. Since a variation will be loaded once all the // attributes are selected (to get the correct inventory values), the category information @@ -152,22 +147,8 @@ const ProductDetail = ({category, product, isLoading}) => { - {!product?.type.set && ( - - handleAddToCart(variant, quantity)} - addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} - isProductLoading={isLoading} - isCustomerProductListLoading={!wishlist.isInitialized} - /> - - - )} - - {/* Product Set */} - {product?.type.set && + {product?.type.set ? ( + // Product Set: render the child products product.setProducts.map((childProduct) => ( { /> - ))} + )) + ) : ( + + handleAddToCart(variant, quantity)} + addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} + isProductLoading={isLoading} + isCustomerProductListLoading={!wishlist.isInitialized} + /> + + + )} {/* Product Recommendations */} From a670c64404caccce46f88a497ec8b7114a64606a Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Wed, 11 Jan 2023 14:56:52 -0800 Subject: [PATCH 15/25] Rename module for a specific use case (PDP) --- ...e-url-search-params.js => use-pdp-search-params.js} | 6 +++--- .../app/hooks/use-variation-attributes.js | 10 +++++----- .../app/hooks/use-variation-params.js | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) rename packages/template-retail-react-app/app/hooks/{use-url-search-params.js => use-pdp-search-params.js} (68%) diff --git a/packages/template-retail-react-app/app/hooks/use-url-search-params.js b/packages/template-retail-react-app/app/hooks/use-pdp-search-params.js similarity index 68% rename from packages/template-retail-react-app/app/hooks/use-url-search-params.js rename to packages/template-retail-react-app/app/hooks/use-pdp-search-params.js index d24e6accf2..5c71c907a4 100644 --- a/packages/template-retail-react-app/app/hooks/use-url-search-params.js +++ b/packages/template-retail-react-app/app/hooks/use-pdp-search-params.js @@ -6,11 +6,11 @@ */ import {useLocation} from 'react-router-dom' -export const useURLSearchParams = (key) => { +export const usePDPSearchParams = (productId) => { const {search} = useLocation() const allParams = new URLSearchParams(search) - const keyParam = new URLSearchParams(allParams.get(key) || '') + const productParams = new URLSearchParams(allParams.get(productId) || '') - return [allParams, keyParam] + return [allParams, productParams] } diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index 139902bc5d..17b4f6ce54 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -13,7 +13,7 @@ import {useVariationParams} from './use-variation-params' // Utils import {updateSearchParams} from '../utils/url' -import {useURLSearchParams} from './use-url-search-params' +import {usePDPSearchParams} from './use-pdp-search-params' /** * Return the first image in the `swatch` type image group for a given @@ -49,11 +49,11 @@ const getVariantValueSwatch = (product, variationValue) => { * @returns {String} a product url for the current variation value. */ const buildVariantValueHref = ({pathname, existingParams, newParams, productId, isSetProduct}) => { - const [allParams, productParam] = existingParams + const [allParams, productParams] = existingParams if (isSetProduct) { - updateSearchParams(productParam, newParams) - allParams.set(productId, productParam.toString()) + updateSearchParams(productParams, newParams) + allParams.set(productId, productParams.toString()) } else { updateSearchParams(allParams, newParams) } @@ -95,7 +95,7 @@ export const useVariationAttributes = (product = {}, isSetProduct = false) => { const location = useLocation() const variationParams = useVariationParams(product, isSetProduct) - const existingParams = useURLSearchParams(product.id) + const existingParams = usePDPSearchParams(product.id) return useMemo( () => diff --git a/packages/template-retail-react-app/app/hooks/use-variation-params.js b/packages/template-retail-react-app/app/hooks/use-variation-params.js index 985dda5572..af36d5d404 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-params.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-params.js @@ -5,7 +5,7 @@ * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause */ -import {useURLSearchParams} from './use-url-search-params' +import {usePDPSearchParams} from './use-pdp-search-params' /* * This hook will return only the params that are also product attributes for the @@ -14,8 +14,8 @@ import {useURLSearchParams} from './use-url-search-params' export const useVariationParams = (product = {}, isSetProduct = false) => { const {variationAttributes = [], variationValues = {}} = product - const [allParams, productParam] = useURLSearchParams(product.id) - const params = isSetProduct ? productParam : allParams + const [allParams, productParams] = usePDPSearchParams(product.id) + const params = isSetProduct ? productParams : allParams // Using all the variation attribute id from the array generated below, get // the value if there is one from the location search params and add it to the From 1b2a480eb4b63fdb7a50eb98e25ccc30577ff7e7 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Wed, 11 Jan 2023 17:41:20 -0800 Subject: [PATCH 16/25] Show/hide add-to-cart buttons accordingly --- .../app/partials/product-view/index.jsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index ab65efc28c..da4be4224d 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -395,19 +395,18 @@ const ProductView = ({ )} - + {renderActionButtons()} - {/* TODO: what to do for product set? */} {/*Add to Cart Button for mobile versions*/} Date: Wed, 11 Jan 2023 17:42:57 -0800 Subject: [PATCH 17/25] Modal shows product set data --- .../app/hooks/use-add-to-cart-modal.js | 4 ++-- .../app/partials/product-view/index.jsx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js b/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js index 13889d059e..243bf7d856 100644 --- a/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js +++ b/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js @@ -54,11 +54,11 @@ AddToCartModalProvider.propTypes = { */ export const AddToCartModal = () => { const {isOpen, onClose, data} = useAddToCartModalContext() - const {product, quantity} = data || {} + const {product, isSetProduct, quantity} = data || {} const intl = useIntl() const basket = useBasket() const size = useBreakpointValue({base: 'full', lg: '2xl', xl: '4xl'}) - const variationAttributes = useVariationAttributes(product) + const variationAttributes = useVariationAttributes(product, isSetProduct) if (!isOpen) { return null } diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index da4be4224d..f6c450bad7 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -124,9 +124,8 @@ const ProductView = ({ await updateCart(variant, quantity) return } - // TODO: for product set, make sure modal shows the product attributes data (e.g. size, colour) await addToCart(variant, quantity) - onAddToCartModalOpen({product, quantity}) + onAddToCartModalOpen({product, isSetProduct, quantity}) } const handleWishlistItem = async () => { From 5ad9cf4125fb3ec0de783ec35d09a3710605a878 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Fri, 13 Jan 2023 17:26:15 -0800 Subject: [PATCH 18/25] Parent product --- .../app/pages/product-detail/index.jsx | 44 ++++--- .../app/partials/product-view/index.jsx | 108 ++++++++++-------- 2 files changed, 89 insertions(+), 63 deletions(-) diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index e9e06e7d69..8849464541 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -148,22 +148,34 @@ const ProductDetail = ({category, product, isLoading}) => { {product?.type.set ? ( - // Product Set: render the child products - product.setProducts.map((childProduct) => ( - - - handleAddToCart(variant, quantity) - } - addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} - isProductLoading={isLoading} - isCustomerProductListLoading={!wishlist.isInitialized} - /> - - - )) + + {/* Product Set: parent product */} + handleAddToCart(variant, quantity)} + addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} + isProductLoading={isLoading} + isCustomerProductListLoading={!wishlist.isInitialized} + /> + + {// Product Set: render the child products + product.setProducts.map((childProduct) => ( + + + handleAddToCart(variant, quantity) + } + addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} + isProductLoading={isLoading} + isCustomerProductListLoading={!wishlist.isInitialized} + /> + + + ))} + ) : ( { +const ProductViewHeader = ({name, price, currency, category, productType}) => { const intl = useIntl() const {currency: activeCurrency} = useCurrency() return ( {category && ( - + )} @@ -43,8 +43,9 @@ const ProductViewHeader = ({name, price, currency, category}) => { {/* Price */} - + + {productType?.set && 'Starting at '} {intl.formatNumber(price, { style: 'currency', currency: currency || activeCurrency @@ -199,6 +200,8 @@ const ProductView = ({ } }, [variant?.productId]) + console.log('--- product type', product?.type, product?.name) + return ( {/* Basic information etc. title, price, breadcrumb*/} @@ -206,6 +209,7 @@ const ProductView = ({ @@ -243,6 +247,7 @@ const ProductView = ({ @@ -320,47 +325,49 @@ const ProductView = ({ )} {/* Quantity Selector */} - - - - + {!product?.type.set && ( + + + + - { - // Set the Quantity of product to value of input if value number - if (numberValue >= 0) { - setQuantity(numberValue) - } else if (stringValue === '') { - // We want to allow the use to clear the input to start a new input so here we set the quantity to '' so NAN is not displayed - // User will not be able to add '' qauntity to the cart due to the add to cart button enablement rules - setQuantity(stringValue) - } - }} - onBlur={(e) => { - // Default to 1the `minOrderQuantity` if a user leaves the box with an invalid value - const value = e.target.value - if (parseInt(value) < 0 || value === '') { - setQuantity(minOrderQuantity) - } - }} - onFocus={(e) => { - // This is useful for mobile devices, this allows the user to pop open the keyboard and set the - // new quantity with one click. NOTE: This is something that can be refactored into the parent - // component, potentially as a prop called `selectInputOnFocus`. - e.target.select() - }} - /> - + { + // Set the Quantity of product to value of input if value number + if (numberValue >= 0) { + setQuantity(numberValue) + } else if (stringValue === '') { + // We want to allow the use to clear the input to start a new input so here we set the quantity to '' so NAN is not displayed + // User will not be able to add '' qauntity to the cart due to the add to cart button enablement rules + setQuantity(stringValue) + } + }} + onBlur={(e) => { + // Default to 1the `minOrderQuantity` if a user leaves the box with an invalid value + const value = e.target.value + if (parseInt(value) < 0 || value === '') { + setQuantity(minOrderQuantity) + } + }} + onFocus={(e) => { + // This is useful for mobile devices, this allows the user to pop open the keyboard and set the + // new quantity with one click. NOTE: This is something that can be refactored into the parent + // component, potentially as a prop called `selectInputOnFocus`. + e.target.select() + }} + /> + + )} {!showLoading && showOptionsMessage && ( @@ -384,6 +391,7 @@ const ProductView = ({ )} + {product?.type.set &&

{product?.shortDescription}

}
@@ -394,9 +402,13 @@ const ProductView = ({ )} - - {renderActionButtons()} - + {!product?.type.set && ( + + {renderActionButtons()} + + )} @@ -405,7 +417,9 @@ const ProductView = ({ position="fixed" bg="white" width="100%" - display={isSetProduct ? 'none' : ['block', 'block', 'block', 'none']} + display={ + isSetProduct || product?.type.set ? 'none' : ['block', 'block', 'block', 'none'] + } p={[4, 4, 6]} left={0} bottom={0} From 9aed81f9743c282f4cfa13a4dcb13e2d0b7f30c5 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Mon, 16 Jan 2023 12:31:08 -0800 Subject: [PATCH 19/25] Variant's product image shows up in add-to-cart modal --- .../app/hooks/use-add-to-cart-modal.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js b/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js index 243bf7d856..78dab4a299 100644 --- a/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js +++ b/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js @@ -29,6 +29,7 @@ import RecommendedProducts from '../components/recommended-products' import {LockIcon} from '../components/icons' import {useVariationAttributes} from './' import {findImageGroupBy} from '../utils/image-groups-utils' +import {useVariant} from './use-variant' /** * This is the context for managing the AddToCartModal. @@ -54,20 +55,21 @@ AddToCartModalProvider.propTypes = { */ export const AddToCartModal = () => { const {isOpen, onClose, data} = useAddToCartModalContext() + if (!isOpen) { + return null + } const {product, isSetProduct, quantity} = data || {} const intl = useIntl() const basket = useBasket() const size = useBreakpointValue({base: 'full', lg: '2xl', xl: '4xl'}) const variationAttributes = useVariationAttributes(product, isSetProduct) - if (!isOpen) { - return null - } + const variant = useVariant(product, isSetProduct) const {currency, productItems, productSubTotal, itemAccumulatedCount} = basket const {id, variationValues} = product const lineItemPrice = productItems?.find((item) => item.productId === id)?.basePrice * quantity const image = findImageGroupBy(product.imageGroups, { viewType: 'small', - selectedVariationAttributes: variationValues + selectedVariationAttributes: variationValues || variant?.variationValues })?.images?.[0] return ( From 41cde1c3225d7a54561cc1952054fb48389a9785 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Mon, 16 Jan 2023 12:55:49 -0800 Subject: [PATCH 20/25] Rename variables for clarity --- .../app/hooks/use-add-to-cart-modal.js | 6 ++--- .../app/hooks/use-product.js | 8 +++--- .../app/hooks/use-variant.js | 4 +-- .../app/hooks/use-variation-attributes.js | 16 ++++++++---- .../app/hooks/use-variation-params.js | 4 +-- .../app/pages/product-detail/index.jsx | 6 +++-- .../app/partials/product-view/index.jsx | 26 +++++++++++-------- 7 files changed, 41 insertions(+), 29 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js b/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js index 78dab4a299..756b3de2f0 100644 --- a/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js +++ b/packages/template-retail-react-app/app/hooks/use-add-to-cart-modal.js @@ -58,12 +58,12 @@ export const AddToCartModal = () => { if (!isOpen) { return null } - const {product, isSetProduct, quantity} = data || {} + const {product, isProductPartOfSet, quantity} = data || {} const intl = useIntl() const basket = useBasket() const size = useBreakpointValue({base: 'full', lg: '2xl', xl: '4xl'}) - const variationAttributes = useVariationAttributes(product, isSetProduct) - const variant = useVariant(product, isSetProduct) + const variationAttributes = useVariationAttributes(product, isProductPartOfSet) + const variant = useVariant(product, isProductPartOfSet) const {currency, productItems, productSubTotal, itemAccumulatedCount} = basket const {id, variationValues} = product const lineItemPrice = productItems?.find((item) => item.productId === id)?.basePrice * quantity diff --git a/packages/template-retail-react-app/app/hooks/use-product.js b/packages/template-retail-react-app/app/hooks/use-product.js index 1673c08f64..af3baecac0 100644 --- a/packages/template-retail-react-app/app/hooks/use-product.js +++ b/packages/template-retail-react-app/app/hooks/use-product.js @@ -15,7 +15,7 @@ const OUT_OF_STOCK = 'OUT_OF_STOCK' const UNFULFILLABLE = 'UNFULFILLABLE' // TODO: This needs to be refactored. -export const useProduct = (product, isSetProduct = false) => { +export const useProduct = (product, isProductPartOfSet = false) => { const showLoading = !product const stockLevel = product?.inventory?.stockLevel || 0 const stepQuantity = product?.stepQuantity || 1 @@ -23,9 +23,9 @@ export const useProduct = (product, isSetProduct = false) => { const initialQuantity = product?.quantity || product?.minOrderQuantity || 1 const intl = useIntl() - const variant = useVariant(product, isSetProduct) - const variationParams = useVariationParams(product, isSetProduct) - const variationAttributes = useVariationAttributes(product, isSetProduct) + const variant = useVariant(product, isProductPartOfSet) + const variationParams = useVariationParams(product, isProductPartOfSet) + const variationAttributes = useVariationAttributes(product, isProductPartOfSet) // console.log('--- variationAttributes', variationAttributes) const [quantity, setQuantity] = useState(initialQuantity) diff --git a/packages/template-retail-react-app/app/hooks/use-variant.js b/packages/template-retail-react-app/app/hooks/use-variant.js index 7dce62e339..ab4024eab5 100644 --- a/packages/template-retail-react-app/app/hooks/use-variant.js +++ b/packages/template-retail-react-app/app/hooks/use-variant.js @@ -16,9 +16,9 @@ import {useVariationParams} from './use-variation-params' * @param {Object} product * @returns {Object} the currently selected `Variant` object. */ -export const useVariant = (product = {}, isSetProduct = false) => { +export const useVariant = (product = {}, isProductPartOfSet = false) => { const {variants = []} = product - const variationParams = useVariationParams(product, isSetProduct) + const variationParams = useVariationParams(product, isProductPartOfSet) // Get a filtered array of variants. The resulting array will only have variants // which have all the current variation params values set. diff --git a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js index 17b4f6ce54..6370ab04cb 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-attributes.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-attributes.js @@ -48,10 +48,16 @@ const getVariantValueSwatch = (product, variationValue) => { * @param {Object} location * @returns {String} a product url for the current variation value. */ -const buildVariantValueHref = ({pathname, existingParams, newParams, productId, isSetProduct}) => { +const buildVariantValueHref = ({ + pathname, + existingParams, + newParams, + productId, + isProductPartOfSet +}) => { const [allParams, productParams] = existingParams - if (isSetProduct) { + if (isProductPartOfSet) { updateSearchParams(productParams, newParams) allParams.set(productId, productParams.toString()) } else { @@ -90,10 +96,10 @@ const isVariantValueOrderable = (product, variationParams) => { * @returns {Array} a decorated variation attributes list. * */ -export const useVariationAttributes = (product = {}, isSetProduct = false) => { +export const useVariationAttributes = (product = {}, isProductPartOfSet = false) => { const {variationAttributes = []} = product const location = useLocation() - const variationParams = useVariationParams(product, isSetProduct) + const variationParams = useVariationParams(product, isProductPartOfSet) const existingParams = usePDPSearchParams(product.id) @@ -121,7 +127,7 @@ export const useVariationAttributes = (product = {}, isSetProduct = false) => { existingParams, newParams: params, productId: product.id, - isSetProduct + isProductPartOfSet }), orderable: isVariantValueOrderable(product, params) } diff --git a/packages/template-retail-react-app/app/hooks/use-variation-params.js b/packages/template-retail-react-app/app/hooks/use-variation-params.js index af36d5d404..630c6dd06b 100644 --- a/packages/template-retail-react-app/app/hooks/use-variation-params.js +++ b/packages/template-retail-react-app/app/hooks/use-variation-params.js @@ -11,11 +11,11 @@ import {usePDPSearchParams} from './use-pdp-search-params' * This hook will return only the params that are also product attributes for the * passed in product object. */ -export const useVariationParams = (product = {}, isSetProduct = false) => { +export const useVariationParams = (product = {}, isProductPartOfSet = false) => { const {variationAttributes = [], variationValues = {}} = product const [allParams, productParams] = usePDPSearchParams(product.id) - const params = isSetProduct ? productParams : allParams + const params = isProductPartOfSet ? productParams : allParams // Using all the variation attribute id from the array generated below, get // the value if there is one from the location search params and add it to the diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 8849464541..0b42b568e3 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -49,6 +49,8 @@ const ProductDetail = ({category, product, isLoading}) => { const navigate = useNavigation() const [primaryCategory, setPrimaryCategory] = useState(category) + const isProductASet = product?.type.set + // This page uses the `primaryCategoryId` to retrieve the category data. This attribute // is only available on `master` products. Since a variation will be loaded once all the // attributes are selected (to get the correct inventory values), the category information @@ -147,7 +149,7 @@ const ProductDetail = ({category, product, isLoading}) => { - {product?.type.set ? ( + {isProductASet ? ( {/* Product Set: parent product */} { handleAddToCart(variant, quantity) } diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index c23061b950..a6da5c1c2e 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -80,7 +80,7 @@ const ProductView = ({ addToWishlist, updateWishlist, isProductLoading, - isSetProduct = false + isProductPartOfSet = false }) => { const intl = useIntl() const history = useHistory() @@ -104,7 +104,7 @@ const ProductView = ({ variationAttributes, stockLevel, stepQuantity - } = useProduct(product, isSetProduct) + } = useProduct(product, isProductPartOfSet) const canAddToWishlist = !isProductLoading const canOrder = !isProductLoading && @@ -112,6 +112,8 @@ const ProductView = ({ parseInt(quantity) > 0 && parseInt(quantity) <= stockLevel + const isProductASet = product?.type.set + const renderActionButtons = () => { const buttons = [] @@ -126,7 +128,7 @@ const ProductView = ({ return } await addToCart(variant, quantity) - onAddToCartModalOpen({product, isSetProduct, quantity}) + onAddToCartModalOpen({product, isProductPartOfSet, quantity}) } const handleWishlistItem = async () => { @@ -200,8 +202,6 @@ const ProductView = ({ } }, [variant?.productId]) - console.log('--- product type', product?.type, product?.name) - return ( {/* Basic information etc. title, price, breadcrumb*/} @@ -325,7 +325,7 @@ const ProductView = ({ )} {/* Quantity Selector */} - {!product?.type.set && ( + {!isProductASet && ( @@ -402,9 +402,11 @@ const ProductView = ({ )} - {!product?.type.set && ( + {!isProductASet && ( {renderActionButtons()} @@ -418,7 +420,9 @@ const ProductView = ({ bg="white" width="100%" display={ - isSetProduct || product?.type.set ? 'none' : ['block', 'block', 'block', 'none'] + isProductPartOfSet || isProductASet + ? 'none' + : ['block', 'block', 'block', 'none'] } p={[4, 4, 6]} left={0} @@ -434,7 +438,7 @@ const ProductView = ({ ProductView.propTypes = { product: PropTypes.object, - isSetProduct: PropTypes.bool, + isProductPartOfSet: PropTypes.bool, category: PropTypes.array, isProductLoading: PropTypes.bool, isWishlistLoading: PropTypes.bool, From 5a8f6ce9bcc354a4a419be0d7fb51fc394c315fd Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Mon, 16 Jan 2023 12:59:15 -0800 Subject: [PATCH 21/25] Fix linting error --- .../app/partials/product-view/index.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index a6da5c1c2e..2769f18381 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -29,6 +29,8 @@ import QuantityPicker from '../../components/quantity-picker' const ProductViewHeader = ({name, price, currency, category, productType}) => { const intl = useIntl() const {currency: activeCurrency} = useCurrency() + const isProductASet = productType?.set + return ( {category && ( @@ -45,7 +47,7 @@ const ProductViewHeader = ({name, price, currency, category, productType}) => { {/* Price */} - {productType?.set && 'Starting at '} + {isProductASet && 'Starting at '} {intl.formatNumber(price, { style: 'currency', currency: currency || activeCurrency @@ -60,7 +62,8 @@ ProductViewHeader.propTypes = { name: PropTypes.string, price: PropTypes.number, currency: PropTypes.string, - category: PropTypes.array + category: PropTypes.array, + productType: PropTypes.object } const ButtonWithRegistration = withRegistration(Button) From 4ddb22ecb104bf0d05978b9ffc88b5ba7ed5e735 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Mon, 16 Jan 2023 13:11:25 -0800 Subject: [PATCH 22/25] Add horizontal rules to separate the parent and child items --- .../app/pages/product-detail/index.jsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 0b42b568e3..8c76a117c9 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -161,6 +161,8 @@ const ProductDetail = ({category, product, isLoading}) => { isCustomerProductListLoading={!wishlist.isInitialized} /> +
+ {// Product Set: render the child products product.setProducts.map((childProduct) => ( @@ -175,6 +177,10 @@ const ProductDetail = ({category, product, isLoading}) => { isCustomerProductListLoading={!wishlist.isInitialized} /> + + +
+
))}
From 31aa735598ac09d48a700ecf96721a9be63cf2b1 Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Mon, 16 Jan 2023 13:43:54 -0800 Subject: [PATCH 23/25] Tweak whitespace --- .../app/partials/product-view/index.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index 2769f18381..5a13317c8c 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -244,8 +244,8 @@ const ProductView = ({ )}
- {/* Variations & Quantity Selector */} - + {/* Variations & Quantity Selector & CTA buttons */} + Date: Mon, 16 Jan 2023 16:47:26 -0800 Subject: [PATCH 24/25] Localize the Starting At label --- packages/template-retail-react-app/app/hooks/use-product.js | 1 - .../app/partials/product-view/index.jsx | 6 +++++- .../app/translations/compiled/en-US.json | 6 ++++++ .../template-retail-react-app/app/translations/en-US.json | 3 +++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/template-retail-react-app/app/hooks/use-product.js b/packages/template-retail-react-app/app/hooks/use-product.js index af3baecac0..bf350b6c21 100644 --- a/packages/template-retail-react-app/app/hooks/use-product.js +++ b/packages/template-retail-react-app/app/hooks/use-product.js @@ -26,7 +26,6 @@ export const useProduct = (product, isProductPartOfSet = false) => { const variant = useVariant(product, isProductPartOfSet) const variationParams = useVariationParams(product, isProductPartOfSet) const variationAttributes = useVariationAttributes(product, isProductPartOfSet) - // console.log('--- variationAttributes', variationAttributes) const [quantity, setQuantity] = useState(initialQuantity) // A product is considered out of stock if the stock level is 0 or if we have all our diff --git a/packages/template-retail-react-app/app/partials/product-view/index.jsx b/packages/template-retail-react-app/app/partials/product-view/index.jsx index 5a13317c8c..701a74ae4d 100644 --- a/packages/template-retail-react-app/app/partials/product-view/index.jsx +++ b/packages/template-retail-react-app/app/partials/product-view/index.jsx @@ -47,7 +47,11 @@ const ProductViewHeader = ({name, price, currency, category, productType}) => { {/* Price */} - {isProductASet && 'Starting at '} + {isProductASet && + `${intl.formatMessage({ + id: 'product_view.label.starting_at_price', + defaultMessage: 'Starting at' + })} `} {intl.formatNumber(price, { style: 'currency', currency: currency || activeCurrency diff --git a/packages/template-retail-react-app/app/translations/compiled/en-US.json b/packages/template-retail-react-app/app/translations/compiled/en-US.json index a07f957b83..f526fbad7d 100644 --- a/packages/template-retail-react-app/app/translations/compiled/en-US.json +++ b/packages/template-retail-react-app/app/translations/compiled/en-US.json @@ -2335,6 +2335,12 @@ "value": "Quantity" } ], + "product_view.label.starting_at_price": [ + { + "type": 0, + "value": "Starting at" + } + ], "product_view.link.full_details": [ { "type": 0, diff --git a/packages/template-retail-react-app/app/translations/en-US.json b/packages/template-retail-react-app/app/translations/en-US.json index 4ebd6e071b..2e0b1d9d99 100644 --- a/packages/template-retail-react-app/app/translations/en-US.json +++ b/packages/template-retail-react-app/app/translations/en-US.json @@ -1023,6 +1023,9 @@ "product_view.label.quantity": { "defaultMessage": "Quantity" }, + "product_view.label.starting_at_price": { + "defaultMessage": "Starting at" + }, "product_view.link.full_details": { "defaultMessage": "See full details" }, From 88fb12f014559236b3ac7305f9b62a6140c6e02b Mon Sep 17 00:00:00 2001 From: Vincent Marta Date: Tue, 17 Jan 2023 09:02:47 -0800 Subject: [PATCH 25/25] Add todo --- .../template-retail-react-app/app/pages/product-detail/index.jsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/template-retail-react-app/app/pages/product-detail/index.jsx b/packages/template-retail-react-app/app/pages/product-detail/index.jsx index 8c76a117c9..8ef6f7f4a4 100644 --- a/packages/template-retail-react-app/app/pages/product-detail/index.jsx +++ b/packages/template-retail-react-app/app/pages/product-detail/index.jsx @@ -163,6 +163,7 @@ const ProductDetail = ({category, product, isLoading}) => {
+ {/* TODO: consider `childProduct.belongsToSet` */} {// Product Set: render the child products product.setProducts.map((childProduct) => (