-
Notifications
You must be signed in to change notification settings - Fork 146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Product sets: basic pdp (@W-12301851@) #897
Changes from 17 commits
14ffcfb
c5f71bb
05cf172
77a2ba6
3842f9a
aa5fdb9
8400a46
868a5a3
e108963
31bcd93
7d30f54
9a56c27
a81034f
5f5edc6
b3bd3b2
3acf93d
a670c64
1b2a480
e3d27f3
5ad9cf4
9aed81f
41cde1c
5a8f6ce
4ddb22e
31aa735
1607644
88fb12f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 usePDPSearchParams = (productId) => { | ||
const {search} = useLocation() | ||
|
||
const allParams = new URLSearchParams(search) | ||
const productParams = new URLSearchParams(allParams.get(productId) || '') | ||
|
||
return [allParams, productParams] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,17 +15,18 @@ const OUT_OF_STOCK = 'OUT_OF_STOCK' | |
const UNFULFILLABLE = 'UNFULFILLABLE' | ||
|
||
// TODO: This needs to be refactored. | ||
export const useProduct = (product) => { | ||
export const useProduct = (product, isSetProduct = false) => { | ||
const showLoading = !product | ||
const stockLevel = product?.inventory?.stockLevel || 0 | ||
const stepQuantity = product?.stepQuantity || 1 | ||
const minOrderQuantity = stockLevel > 0 ? product?.minOrderQuantity || 1 : 0 | ||
const initialQuantity = product?.quantity || product?.minOrderQuantity || 1 | ||
|
||
const intl = useIntl() | ||
const variant = useVariant(product) | ||
const variationParams = useVariationParams(product) | ||
const variationAttributes = useVariationAttributes(product) | ||
const variant = useVariant(product, isSetProduct) | ||
const variationParams = useVariationParams(product, isSetProduct) | ||
const variationAttributes = useVariationAttributes(product, isSetProduct) | ||
// console.log('--- variationAttributes', variationAttributes) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't forget to remove this 👍 |
||
const [quantity, setQuantity] = useState(initialQuantity) | ||
|
||
// A product is considered out of stock if the stock level is 0 or if we have all our | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,25 +5,27 @@ | |
* 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 {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 = {}) => { | ||
export const useVariationParams = (product = {}, isSetProduct = false) => { | ||
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 | ||
const [allParams, productParams] = usePDPSearchParams(product.id) | ||
const params = isSetProduct ? productParams : allParams | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried to reuse the existing React hooks. The main difference is for set products, the hooks focus in on a specific subset of the page url params, rather than all of the params. |
||
|
||
// 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 | ||
return variationParams | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,22 +5,13 @@ | |
* 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' | ||
|
||
// 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' | ||
|
@@ -32,6 +23,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' | ||
|
@@ -155,105 +147,36 @@ const ProductDetail = ({category, product, isLoading}) => { | |
</Helmet> | ||
|
||
<Stack spacing={16}> | ||
<ProductView | ||
product={product} | ||
category={primaryCategory?.parentCategoryTree || []} | ||
addToCart={(variant, quantity) => handleAddToCart(variant, quantity)} | ||
addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} | ||
isProductLoading={isLoading} | ||
isCustomerProductListLoading={!wishlist.isInitialized} | ||
/> | ||
|
||
{/* Information Accordion */} | ||
<Stack direction="row" spacing={[0, 0, 0, 16]}> | ||
<Accordion allowMultiple allowToggle maxWidth={'896px'} flex={[1, 1, 1, 5]}> | ||
{/* Details */} | ||
<AccordionItem> | ||
<h2> | ||
<AccordionButton height="64px"> | ||
<Box flex="1" textAlign="left" fontWeight="bold" fontSize="lg"> | ||
{formatMessage({ | ||
defaultMessage: 'Product Detail', | ||
id: 'product_detail.accordion.button.product_detail' | ||
})} | ||
</Box> | ||
<AccordionIcon /> | ||
</AccordionButton> | ||
</h2> | ||
<AccordionPanel mb={6} mt={4}> | ||
<div | ||
dangerouslySetInnerHTML={{ | ||
__html: product?.longDescription | ||
}} | ||
/> | ||
</AccordionPanel> | ||
</AccordionItem> | ||
|
||
{/* Size & Fit */} | ||
<AccordionItem> | ||
<h2> | ||
<AccordionButton height="64px"> | ||
<Box flex="1" textAlign="left" fontWeight="bold" fontSize="lg"> | ||
{formatMessage({ | ||
defaultMessage: 'Size & Fit', | ||
id: 'product_detail.accordion.button.size_fit' | ||
})} | ||
</Box> | ||
<AccordionIcon /> | ||
</AccordionButton> | ||
</h2> | ||
<AccordionPanel mb={6} mt={4}> | ||
{formatMessage({ | ||
defaultMessage: 'Coming Soon', | ||
id: 'product_detail.accordion.message.coming_soon' | ||
})} | ||
</AccordionPanel> | ||
</AccordionItem> | ||
|
||
{/* Reviews */} | ||
<AccordionItem> | ||
<h2> | ||
<AccordionButton height="64px"> | ||
<Box flex="1" textAlign="left" fontWeight="bold" fontSize="lg"> | ||
{formatMessage({ | ||
defaultMessage: 'Reviews', | ||
id: 'product_detail.accordion.button.reviews' | ||
})} | ||
</Box> | ||
<AccordionIcon /> | ||
</AccordionButton> | ||
</h2> | ||
<AccordionPanel mb={6} mt={4}> | ||
{formatMessage({ | ||
defaultMessage: 'Coming Soon', | ||
id: 'product_detail.accordion.message.coming_soon' | ||
})} | ||
</AccordionPanel> | ||
</AccordionItem> | ||
|
||
{/* Questions */} | ||
<AccordionItem> | ||
<h2> | ||
<AccordionButton height="64px"> | ||
<Box flex="1" textAlign="left" fontWeight="bold" fontSize="lg"> | ||
{formatMessage({ | ||
defaultMessage: 'Questions', | ||
id: 'product_detail.accordion.button.questions' | ||
})} | ||
</Box> | ||
<AccordionIcon /> | ||
</AccordionButton> | ||
</h2> | ||
<AccordionPanel mb={6} mt={4}> | ||
{formatMessage({ | ||
defaultMessage: 'Coming Soon', | ||
id: 'product_detail.accordion.message.coming_soon' | ||
})} | ||
</AccordionPanel> | ||
</AccordionItem> | ||
</Accordion> | ||
<Box display={['none', 'none', 'none', 'block']} flex={4}></Box> | ||
</Stack> | ||
{product?.type.set ? ( | ||
// Product Set: render the child products | ||
product.setProducts.map((childProduct) => ( | ||
<Fragment key={childProduct.id}> | ||
<ProductView | ||
product={childProduct} | ||
isSetProduct={true} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you thought about other product types that we currently don't support that we might in the future. And how using Side note: Is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @bendvc Good questions.. I don't know yet at this time, but there's a separate refactoring ticket for supporting other product types.🤔 However, I'll think about it a bit more at this stage anyways. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, |
||
addToCart={(variant, quantity) => | ||
handleAddToCart(variant, quantity) | ||
} | ||
addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} | ||
isProductLoading={isLoading} | ||
isCustomerProductListLoading={!wishlist.isInitialized} | ||
/> | ||
<InformationAccordion product={childProduct} /> | ||
</Fragment> | ||
)) | ||
) : ( | ||
<Fragment> | ||
<ProductView | ||
product={product} | ||
category={primaryCategory?.parentCategoryTree || []} | ||
addToCart={(variant, quantity) => handleAddToCart(variant, quantity)} | ||
addToWishlist={(_, quantity) => handleAddToWishlist(quantity)} | ||
isProductLoading={isLoading} | ||
isCustomerProductListLoading={!wishlist.isInitialized} | ||
/> | ||
<InformationAccordion product={product} /> | ||
</Fragment> | ||
)} | ||
|
||
{/* Product Recommendations */} | ||
<Stack spacing={16}> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Aside: there was a subtle bug with the original code. Now the parent (SwatchGroup) no longer overriding the children's
value
. They should be distinct.