-
Notifications
You must be signed in to change notification settings - Fork 214
@W-18407137 Trigger a modal if new bonus products exist in AddToCart response #2541
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 18 commits
968f1bb
4259a1b
58ad8fe
8c44ea7
802b9d3
790a72b
c2021fa
0aadd8b
83b1dbb
0a697ef
4a0950c
afb7dc9
bd8f645
392c52f
9414838
f8fd65e
e441ee9
4848f14
9f6cfe5
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 |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ import { | |
| } from '@salesforce/retail-react-app/app/components/shared/ui' | ||
| 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 {useBonusProductModalContext} from '@salesforce/retail-react-app/app/hooks/use-bonus-product-modal' | ||
|
|
||
| // project components | ||
| import ImageGallery from '@salesforce/retail-react-app/app/components/image-gallery' | ||
|
|
@@ -131,6 +132,13 @@ const ProductView = forwardRef( | |
| onOpen: onAddToCartModalOpen, | ||
| onClose: onAddToCartModalClose | ||
| } = useAddToCartModalContext() | ||
| const { | ||
| isOpen: isBonusProductModalOpen, | ||
| onOpen: onBonusProductModalOpen, | ||
| onClose: onBonusProductModalClose, | ||
| bonusProducts, | ||
| addBonusProducts | ||
| } = useBonusProductModalContext() | ||
| const theme = useTheme() | ||
| const [showOptionsMessage, toggleShowOptionsMessage] = useState(false) | ||
| const { | ||
|
|
@@ -272,16 +280,49 @@ const ProductView = forwardRef( | |
| return | ||
| } | ||
| try { | ||
| const itemsAdded = await addToCart(variant, quantity) | ||
| const addToCartResponse = await addToCart(variant, quantity) | ||
|
|
||
| // For regular products: addToCartResponse has productSelectionValues and possibly bonusDiscountLineItems | ||
| // For product bundles: addToCartResponse is just the childProductSelections array | ||
| const itemsAdded = | ||
| addToCartResponse?.productSelectionValues || addToCartResponse | ||
| const isValidResponse = | ||
| itemsAdded && (Array.isArray(itemsAdded) || itemsAdded.length > 0) | ||
|
|
||
| // Compare existing bonus products with new bonus discount line items | ||
| // Only regular products (not bundles) can have bonusDiscountLineItems | ||
| const newBonusItems = | ||
| addToCartResponse?.bonusDiscountLineItems?.filter( | ||
| (newItem) => | ||
|
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. change name to bonusItem?
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. You can move this to within the "isValidResponse" check, right? |
||
| !bonusProducts.some( | ||
| (existingItem) => existingItem.id === newItem.id | ||
| ) | ||
| ) || [] | ||
|
|
||
| // 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, | ||
| selectedQuantity: quantity | ||
| }) | ||
| if (isValidResponse) { | ||
| // Show bonus product modal first if there are bonus items | ||
| if (newBonusItems?.length > 0) { | ||
| // Update bonusProducts list with the new bonus items | ||
| addBonusProducts(newBonusItems) | ||
| onBonusProductModalOpen({ | ||
| newBonusItems, | ||
| allBonusItems: addToCartResponse.bonusDiscountLineItems, | ||
| openAddToCartModalIfNeeded: true, | ||
| product, | ||
| itemsAdded, | ||
| selectedQuantity: quantity | ||
| }) | ||
| } else { | ||
| // If no bonus items, just show add to cart modal | ||
| onAddToCartModalOpen({ | ||
| product, | ||
| itemsAdded, | ||
| selectedQuantity: quantity | ||
| }) | ||
| } | ||
| } | ||
| } catch (e) { | ||
| showError() | ||
|
|
@@ -364,6 +405,9 @@ const ProductView = forwardRef( | |
| if (isAddToCartModalOpen) { | ||
| onAddToCartModalClose() | ||
| } | ||
| if (isBonusProductModalOpen) { | ||
| onBonusProductModalClose() | ||
| } | ||
sf-madhuri-uppu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }, [location.pathname]) | ||
|
|
||
| useEffect(() => { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,133 @@ | ||
| /* | ||
| * Copyright (c) 2025, 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, {useContext, useState, useEffect} from 'react' | ||
| import {useLocation} from 'react-router-dom' | ||
| import {useAddToCartModalContext} from '@salesforce/retail-react-app/app/hooks/use-add-to-cart-modal' | ||
| import PropTypes from 'prop-types' | ||
| import {useCurrentBasket} from '@salesforce/retail-react-app/app/hooks/use-current-basket' | ||
| import { | ||
| Modal, | ||
| ModalCloseButton, | ||
| ModalContent, | ||
| ModalOverlay, | ||
| useBreakpointValue, | ||
alexvuong marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| ModalHeader, | ||
| ModalBody, | ||
| Heading | ||
| } from '@salesforce/retail-react-app/app/components/shared/ui' | ||
|
|
||
| export const BonusProductModalContext = React.createContext() | ||
|
|
||
| export const useBonusProductModalContext = () => useContext(BonusProductModalContext) | ||
|
|
||
| export const BonusProductModalProvider = ({children}) => { | ||
| const {data: basket} = useCurrentBasket() | ||
| const bonusProductState = useBonusState(basket) | ||
|
|
||
| return ( | ||
| <BonusProductModalContext.Provider value={bonusProductState}> | ||
| {children} | ||
| <BonusProductModal /> | ||
| </BonusProductModalContext.Provider> | ||
| ) | ||
| } | ||
| BonusProductModalProvider.propTypes = { | ||
| children: PropTypes.node.isRequired | ||
| } | ||
|
|
||
| export const BonusProductModal = () => { | ||
|
Collaborator
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 would move components to the component directory
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. Let's break this down into two parts - the hook for business logic (keep it under hooks) and component for presentation (under components)
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. @kzheng-sfdc Sure. This PR was already merged by the time this comment was made. @sf-vrushal-kulkarni will be making this change in his PR |
||
| const {isOpen, data, onClose, onOpen} = useBonusProductModalContext() | ||
| const size = useBreakpointValue({base: 'full', lg: '2xl', xl: '4xl'}) | ||
|
|
||
| if (!isOpen) { | ||
| return null | ||
| } | ||
| return ( | ||
| <Modal size={size} isOpen={isOpen} onClose={onClose} scrollBehavior="inside" isCentered> | ||
| <ModalOverlay /> | ||
| <ModalContent | ||
| margin="0" | ||
| borderRadius={{base: 'none', md: 'base'}} | ||
| bgColor="gray.50" | ||
| containerProps={{'data-testid': 'bonus-product-modal'}} | ||
| > | ||
| <ModalHeader paddingY="8" bgColor="white"> | ||
| <Heading as="h1" fontSize="2xl"></Heading> | ||
| </ModalHeader> | ||
| <ModalCloseButton /> | ||
| {/* Add your modal content here */} | ||
| <ModalBody bgColor="white" padding="0" marginBottom={{base: 40, lg: 0}}></ModalBody> | ||
sf-madhuri-uppu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| </ModalContent> | ||
| </Modal> | ||
| ) | ||
| } | ||
|
|
||
| export const useBonusState = (basket) => { | ||
| const [state, setState] = useState({ | ||
| isOpen: false, | ||
| data: {}, | ||
| bonusProducts: basket?.bonusDiscountLineItems || [] | ||
| }) | ||
| const {pathname} = useLocation() | ||
| const {onOpen: onAddToCartModalOpen} = useAddToCartModalContext() | ||
|
|
||
| useEffect(() => { | ||
| if (state.isOpen) { | ||
| setState((prev) => ({ | ||
| ...prev, | ||
| isOpen: false | ||
| })) | ||
| } | ||
| }, [pathname]) | ||
|
|
||
| // Update bonusProducts when basket changes | ||
| useEffect(() => { | ||
| setState((prev) => ({ | ||
| ...prev, | ||
| bonusProducts: basket?.bonusDiscountLineItems || [] | ||
| })) | ||
| }, [basket]) | ||
|
|
||
| const addBonusProducts = (newBonusItems) => { | ||
| setState((prev) => { | ||
| const updatedBonusProducts = [...prev.bonusProducts, ...newBonusItems] | ||
| return { | ||
| ...prev, | ||
| bonusProducts: updatedBonusProducts | ||
| } | ||
| }) | ||
| } | ||
|
|
||
| return { | ||
| isOpen: state.isOpen, | ||
| data: state.data, | ||
| bonusProducts: state.bonusProducts, | ||
| addBonusProducts, | ||
| onClose: () => { | ||
| setState((prev) => ({ | ||
| ...prev, | ||
| isOpen: false, | ||
| data: {} | ||
| })) | ||
|
|
||
| if (state.data.openAddToCartModalIfNeeded && state.data.product) { | ||
| onAddToCartModalOpen({ | ||
| product: state.data.product, | ||
| itemsAdded: state.data.itemsAdded, | ||
| selectedQuantity: state.data.selectedQuantity | ||
| }) | ||
| } | ||
| }, | ||
| onOpen: (data) => { | ||
| setState((prev) => ({ | ||
| ...prev, | ||
| isOpen: true, | ||
| data | ||
| })) | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.