-
Notifications
You must be signed in to change notification settings - Fork 212
@W-18811979 BOPIS (buy online pick up in store) #2646
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
Changes from all commits
96a3994
9ff7e03
d2e515e
17ed9cb
983089a
51282f7
ca7d6ad
23d3bb1
f39b47f
c9b5e61
fe8e9a4
b8a71e9
5219741
f20e9e2
7625fd8
b66584f
cbcd369
af96e17
f76d717
de6d3f3
da1603a
9ca9ed7
5fe6ca1
dd6e286
392cff0
66e7c9c
b98aaf9
41161eb
e972465
80fe957
a9fe106
ecdee88
3a59152
b9d368f
5e354cb
4094418
d2896bf
1e727ea
a5e34fa
9ce5366
a107791
dfe0a87
92640dc
842ef98
755daed
45af5d9
187b12f
da836c5
a437bc1
1bd91f0
67649e4
22e1774
a14e224
cae0b24
8f82e54
8278190
39bef21
e4cd83c
42a1b47
61aa60d
41baa86
806b37a
4b89ef6
ab1c943
9188930
378a374
e14ae29
fdc9f73
bf28e78
db1a6d5
1c5e1ee
c559c4e
b017414
a65201a
3df3928
cab7bd6
90482c2
ad9ca30
b3f3d81
4400304
d2fc591
9e7b81b
b98a01a
58e1272
4a227e2
db02149
b2a7e0e
ed30e27
3ad2883
b54fe86
dd50db8
a2f23a1
9d7dd83
b9b15c9
5f91ea4
26444e6
b41462e
aa4c974
a57ed96
85cb778
f0353aa
3a929ee
fa20c82
a88558b
a8f5ce5
730686c
e316046
efee7bb
455ff9f
a462eb5
3005306
986228a
3a24fed
7992513
11ccaa8
4e4ed22
53959fa
890838a
d01600f
642094a
263df38
d1454ce
fe0f024
c29731c
0c77dd1
5eda890
d0c216b
47fa33d
6fb23b4
28e4de8
ccebc63
ffe1a18
b2fc757
c4750ed
e0f8721
fca0723
294b4fc
44d6ef1
9cd23ae
5c9a17f
8367fa6
5bff364
c59c6ea
3ea409c
840d33f
d76268d
f59ef18
0f46169
cfd0c5e
47917e0
cea2e28
6f29d79
e3649a9
0d7be96
541a00e
0c02e08
27f92bd
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 |
|---|---|---|
|
|
@@ -19,10 +19,20 @@ import { | |
| Text, | ||
| VStack, | ||
| Fade, | ||
| Stack, | ||
| Radio, | ||
| RadioGroup, | ||
| useTheme | ||
| } from '@salesforce/retail-react-app/app/components/shared/ui' | ||
|
|
||
| // Constants | ||
| const DELIVERY_OPTIONS = { | ||
| SHIP: 'ship', | ||
| PICKUP: 'pickup' | ||
| } | ||
| import {useCurrency, useDerivedProduct} from '@salesforce/retail-react-app/app/hooks' | ||
| import {useAddToCartModalContext} from '@salesforce/retail-react-app/app/hooks/use-add-to-cart-modal' | ||
| import {STORE_LOCATOR_IS_ENABLED} from '@salesforce/retail-react-app/app/constants' | ||
|
|
||
| // project components | ||
| import ImageGallery from '@salesforce/retail-react-app/app/components/image-gallery' | ||
|
|
@@ -84,7 +94,8 @@ ProductViewHeader.propTypes = { | |
| category: PropTypes.array, | ||
| priceData: PropTypes.object, | ||
| product: PropTypes.object, | ||
| isProductPartOfBundle: PropTypes.bool | ||
| isProductPartOfBundle: PropTypes.bool, | ||
| showDeliveryOptions: PropTypes.bool | ||
| } | ||
|
|
||
| const ButtonWithRegistration = withRegistration(Button) | ||
|
|
@@ -118,7 +129,11 @@ const ProductView = forwardRef( | |
| !isProductLoading && variant?.orderable && quantity > 0 && quantity <= stockLevel, | ||
| showImageGallery = true, | ||
| setSelectedBundleQuantity = () => {}, | ||
| selectedBundleParentQuantity = 1 | ||
| selectedBundleParentQuantity = 1, | ||
| pickupInStore = false, | ||
| setPickupInStore = () => {}, | ||
| onOpenStoreLocator = () => {}, | ||
| showDeliveryOptions = true | ||
| }, | ||
| ref | ||
| ) => { | ||
|
|
@@ -146,7 +161,9 @@ const ProductView = forwardRef( | |
| stockLevel, | ||
| stepQuantity, | ||
| isOutOfStock, | ||
| unfulfillable | ||
| unfulfillable, | ||
| isSelectedStoreOutOfStock, | ||
| selectedStore | ||
| } = useDerivedProduct(product, isProductPartOfSet, isProductPartOfBundle) | ||
| const priceData = useMemo(() => { | ||
| return getPriceData(product, {quantity}) | ||
|
|
@@ -155,6 +172,9 @@ const ProductView = forwardRef( | |
| const isProductASet = product?.type.set | ||
| const isProductABundle = product?.type.bundle | ||
| const errorContainerRef = useRef(null) | ||
| const [pickupEnabled, setPickupEnabled] = useState(false) | ||
| const storeName = selectedStore?.name | ||
| const inventoryId = selectedStore?.inventoryId | ||
|
|
||
| const {disableButton, customInventoryMessage} = useMemo(() => { | ||
| let shouldDisableButton = showInventoryMessage | ||
|
|
@@ -272,14 +292,17 @@ const ProductView = forwardRef( | |
| return | ||
| } | ||
| try { | ||
| const itemsAdded = await addToCart(variant, quantity) | ||
| const itemsAdded = await addToCart([{variant, quantity}]) | ||
| // Open modal only when `addToCart` returns some data | ||
| // It's possible that the item has been added to cart, but we don't want to open the modal. | ||
| // See wishlist_primary_action for example. | ||
| if (itemsAdded) { | ||
| onAddToCartModalOpen({ | ||
| product, | ||
| itemsAdded, | ||
| itemsAdded: itemsAdded.map((item) => ({ | ||
| ...item, | ||
| product // attach the full product object | ||
| })), | ||
| selectedQuantity: quantity | ||
| }) | ||
| } | ||
|
|
@@ -399,6 +422,18 @@ const ProductView = forwardRef( | |
| } | ||
| }, [showInventoryMessage, inventoryMessage]) | ||
|
|
||
| // Auto-switch off pickup in store when product becomes unavailable at selected store | ||
| useEffect(() => { | ||
| setPickupEnabled(!!selectedStore?.inventoryId) | ||
| if (pickupInStore && isSelectedStoreOutOfStock) { | ||
| setPickupInStore(false) | ||
| } | ||
| }, [selectedStore]) | ||
|
|
||
| const handleDeliveryOptionChange = (value) => { | ||
| setPickupInStore(value === DELIVERY_OPTIONS.PICKUP) | ||
| } | ||
|
|
||
| return ( | ||
| <Flex direction={'column'} data-testid="product-view" ref={ref}> | ||
| {/* Basic information etc. title, price, breadcrumb*/} | ||
|
|
@@ -641,12 +676,148 @@ const ProductView = forwardRef( | |
| </Text> | ||
| </Fade> | ||
| )} | ||
| <Box | ||
| display={ | ||
| isProductPartOfSet ? 'block' : ['none', 'none', 'none', 'block'] | ||
| } | ||
| > | ||
| {renderActionButtons()} | ||
| <Box> | ||
| {showDeliveryOptions && ( | ||
| <> | ||
| <Box mb={4}> | ||
| <Text fontWeight={600} mb={3}> | ||
| <FormattedMessage | ||
| defaultMessage="Delivery:" | ||
| id="product_view.label.delivery" | ||
| /> | ||
| </Text> | ||
| <RadioGroup | ||
| value={ | ||
| pickupInStore | ||
| ? DELIVERY_OPTIONS.PICKUP | ||
| : DELIVERY_OPTIONS.SHIP | ||
| } | ||
| onChange={handleDeliveryOptionChange} | ||
| mb={1} | ||
| > | ||
| <Stack direction="column" spacing={2}> | ||
| <Radio | ||
| value={DELIVERY_OPTIONS.SHIP} | ||
| isDisabled={disableButton} | ||
| > | ||
| <FormattedMessage | ||
| defaultMessage="Ship to Address" | ||
| id="product_view.label.ship_to_address" | ||
| /> | ||
| </Radio> | ||
| {STORE_LOCATOR_IS_ENABLED && ( | ||
| <Radio | ||
| value={DELIVERY_OPTIONS.PICKUP} | ||
| isDisabled={ | ||
| !pickupEnabled || | ||
| (storeName && | ||
| inventoryId && | ||
| isSelectedStoreOutOfStock) | ||
| } | ||
| > | ||
| <FormattedMessage | ||
| defaultMessage="Pickup in Store" | ||
| id="product_view.label.pickup_in_store" | ||
| /> | ||
| </Radio> | ||
| )} | ||
| </Stack> | ||
| </RadioGroup> | ||
| </Box> | ||
|
|
||
| {STORE_LOCATOR_IS_ENABLED && ( | ||
| <> | ||
| {storeName && inventoryId && ( | ||
| <Text | ||
| color="black" | ||
| fontWeight={600} | ||
| mb={2} | ||
| data-testid="store-stock-status-msg" | ||
| > | ||
| {!isSelectedStoreOutOfStock | ||
| ? intl.formatMessage( | ||
| { | ||
| id: 'product_view.status.in_stock_at_store', | ||
| defaultMessage: | ||
| 'In Stock at {storeName}' | ||
| }, | ||
| { | ||
| storeName: ( | ||
| <Link | ||
| as="button" | ||
| color="blue.600" | ||
| textDecoration="underline" | ||
| onClick={ | ||
| onOpenStoreLocator | ||
| } | ||
| > | ||
| {storeName} | ||
| </Link> | ||
| ) | ||
| } | ||
| ) | ||
| : intl.formatMessage( | ||
| { | ||
| id: 'product_view.status.out_of_stock_at_store', | ||
| defaultMessage: | ||
| 'Out of Stock at {storeName}' | ||
| }, | ||
| { | ||
| storeName: ( | ||
| <Link | ||
| as="button" | ||
| color="blue.600" | ||
| textDecoration="underline" | ||
| onClick={ | ||
| onOpenStoreLocator | ||
| } | ||
| > | ||
| {storeName} | ||
| </Link> | ||
| ) | ||
| } | ||
| )} | ||
| </Text> | ||
| )} | ||
|
|
||
| {/* Show label if pickup is disabled due to no store/inventoryId */} | ||
| {!pickupEnabled && !storeName && !inventoryId && ( | ||
| <Text | ||
| color="black" | ||
| fontWeight={600} | ||
| mb={3} | ||
| data-testid="pickup-select-store-msg" | ||
| > | ||
| <FormattedMessage | ||
| defaultMessage="Pickup in " | ||
| id="product_view.label.pickup_in_select_store_prefix" | ||
| />{' '} | ||
| <Link | ||
| as="button" | ||
| color="blue.600" | ||
| textDecoration="underline" | ||
| onClick={onOpenStoreLocator} | ||
| > | ||
| <FormattedMessage | ||
| defaultMessage="Select Store" | ||
| id="product_view.label.select_store_link" | ||
| /> | ||
| </Link> | ||
| </Text> | ||
| )} | ||
| </> | ||
| )} | ||
| </> | ||
| )} | ||
|
Contributor
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. Can we break this into its own component (delivery-option)? This will make it easier to strip the BOPIS feature during app generation, also make customization easier.
Contributor
Author
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. will address refactoring into smaller components after integrating muti-ship |
||
| <Box | ||
| display={ | ||
| isProductPartOfSet | ||
| ? 'block' | ||
| : ['none', 'none', 'none', 'block'] | ||
| } | ||
| > | ||
| {renderActionButtons()} | ||
| </Box> | ||
| </Box> | ||
| </Box> | ||
| </VStack> | ||
|
|
@@ -698,7 +869,11 @@ ProductView.propTypes = { | |
| validateOrderability: PropTypes.func, | ||
| showImageGallery: PropTypes.bool, | ||
| setSelectedBundleQuantity: PropTypes.func, | ||
| selectedBundleParentQuantity: PropTypes.number | ||
| selectedBundleParentQuantity: PropTypes.number, | ||
| pickupInStore: PropTypes.bool, | ||
| setPickupInStore: PropTypes.func, | ||
| onOpenStoreLocator: PropTypes.func, | ||
| showDeliveryOptions: PropTypes.bool | ||
| } | ||
|
|
||
| export default ProductView | ||
Uh oh!
There was an error while loading. Please reload this page.