Skip to content
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

Allow Adding to Wishlist from the Product List Page #2

Merged
merged 37 commits into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e4852a8
All of my branch squashed
alexvuong Aug 9, 2021
5af18a7
refactor add/remove wishlist on PLP
alexvuong Aug 10, 2021
757af3b
lint
alexvuong Aug 11, 2021
0c748f0
use destructing form for confirming modal
alexvuong Aug 11, 2021
4eed67a
Merge branch 'develop' into feature-add-to-wishlist-plp-new
alexvuong Aug 11, 2021
91f7e79
remove rebundant code
alexvuong Aug 11, 2021
fda4202
Merge branch 'develop' into feature-add-to-wishlist-plp-new
alexvuong Aug 16, 2021
ef5e45b
test commit
alexvuong Aug 16, 2021
be3ec24
PR feedback and design feedback
alexvuong Aug 17, 2021
30bdf56
refactor wishlist to be aware of quantity
alexvuong Aug 18, 2021
68c276a
extract messages
alexvuong Aug 18, 2021
0ecae2c
Merge branch 'develop' into feature-add-to-wishlist-plp-new
alexvuong Aug 18, 2021
c6d1fbd
import noop properly
alexvuong Aug 18, 2021
04ae26b
refactor PLP wishlist
alexvuong Aug 18, 2021
a673154
remove unused method
alexvuong Aug 18, 2021
f37ae03
fix quantity for requests
alexvuong Aug 19, 2021
62cd1a7
PR feedback and add more tests
alexvuong Aug 20, 2021
6437fb3
add more unit test
alexvuong Aug 20, 2021
8b77e30
translations
alexvuong Aug 20, 2021
b5b79b7
PR feedback
adamraya Aug 21, 2021
f9a020c
lint
alexvuong Aug 21, 2021
920b6af
rollback some changes
alexvuong Aug 23, 2021
d046f95
add more tests
alexvuong Aug 23, 2021
cb2d449
Merge branch 'develop' into feature-add-to-wishlist-plp-new
alexvuong Aug 23, 2021
46aad23
fix test
alexvuong Aug 23, 2021
9f86f1f
rollback a test
alexvuong Aug 23, 2021
95d8454
compiled messages
alexvuong Aug 23, 2021
d301f8d
Merge branch 'develop' into feature-add-to-wishlist-plp-new
alexvuong Aug 23, 2021
b77330a
resolve some merged conflict
alexvuong Aug 23, 2021
576e3dc
linting
alexvuong Aug 24, 2021
bd66566
fix proptype
alexvuong Aug 24, 2021
2eb88ed
fix stale loading state when click heart icon so fast
alexvuong Aug 24, 2021
3dd7c08
Merge branch 'develop' into feature-add-to-wishlist-plp-new
alexvuong Aug 24, 2021
4ffa0b0
resolve merge conflict
alexvuong Aug 24, 2021
33fea95
fix tests
alexvuong Aug 24, 2021
bf9afe0
bump tests
alexvuong Aug 24, 2021
f9fb084
fix failing tests
alexvuong Aug 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 26 additions & 22 deletions packages/pwa/app/commerce-api/hooks/useCustomerProductLists.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@
* For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import {useContext, useMemo, useEffect, useState} from 'react'
import {isError, useCommerceAPI, CustomerProductListsContext, noop} from '../utils'
import {isError, useCommerceAPI, CustomerProductListsContext} from '../utils'
import {noop} from '../../utils/utils'

import useCustomer from './useCustomer'
import Queue from '../../utils/queue'
import {customerProductListTypes} from '../../constants'

// A event queue for the following use cases:
// 1. Allow user to add item to wishlist before wishlist is initialized
Expand Down Expand Up @@ -35,12 +38,28 @@ export default function useCustomerProductLists({eventHandler = noop, errorHandl
switch (action) {
case CustomerProductListEventQueue.eventTypes.ADD: {
try {
const productItem = {
productId: item.id,
quantity: item.quantity
const productList = self.getProductListPerType(listType)
const productListItem = productList.customerProductListItems.find(
({productId}) => productId === event.item.id
)
// if the item is already in the wishlist
// only update the quantity
if (productListItem) {
await self.updateCustomerProductListItem(productList, {
...productListItem,
quantity: event.item.quantity + productListItem.quantity
})
eventHandler(event)
} else {
await self.createCustomerProductListItem(productList, {
productId: event.item.id,
priority: 1,
quantity: parseInt(event.item.quantity),
public: false,
type: 'product'
})
eventHandler(event)
}
await addItemToCustomerProductList(productItem, list?.id, listType)
eventHandler(event)
} catch (error) {
errorHandler(error)
}
Expand All @@ -59,21 +78,6 @@ export default function useCustomerProductLists({eventHandler = noop, errorHandl
})
}, [customerProductLists])

const addItemToCustomerProductList = async (item, listId, listType) => {
// Either find the list by the id or by the type
const productList = listId
? customerProductLists.data.find((list) => list.id === listId)
: customerProductLists.data.find((list) => list.type === listType)

return await self.createCustomerProductListItem(productList, {
productId: item.productId,
priority: 1,
quantity: item.quantity,
public: false,
type: 'product'
})
}

const self = useMemo(() => {
return {
...customerProductLists,
Expand All @@ -96,6 +100,7 @@ export default function useCustomerProductLists({eventHandler = noop, errorHandl

/**
* Fetches product lists for registered users or creates a new list if none exist
* due to the api limitation, we can not get the list based on type but all lists
* @param {string} type type of list to fetch or create
* @returns product lists for registered users
*/
Expand Down Expand Up @@ -199,7 +204,6 @@ export default function useCustomerProductLists({eventHandler = noop, errorHandl
/**
* Adds an item to the customer's product list.
* @param {object} list
* @param {string} list.id id of the list to add the item to.
* @param {Object} item item to be added to the list.
*/
async createCustomerProductListItem(list, item) {
Expand Down
19 changes: 9 additions & 10 deletions packages/pwa/app/commerce-api/hooks/useShopper.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,17 +89,16 @@ const useShopper = () => {
useEffect(() => {
// Fetch product details for new items in product-lists
const hasCustomerProductLists = customerProductLists?.loaded
if (hasCustomerProductLists) {
customerProductLists.data.forEach((list) => {
let ids = list.customerProductListItems?.map((item) => item.productId)
if (list?._productItemsDetail) {
ids = ids.filter((id) => !list?._productItemsDetail[id])
}
if (!hasCustomerProductLists) return
customerProductLists.data.forEach((list) => {
let ids = list.customerProductListItems?.map((item) => item.productId)
if (list?._productItemsDetail) {
ids = ids.filter((id) => !list?._productItemsDetail[id])
}

customerProductLists.getProductsInList(ids?.toString(), list.id)
})
}
}, [customerProductLists])
customerProductLists.getProductsInList(ids?.toString(), list.id)
})
}, [customerProductLists.data])

return {customer, basket}
}
Expand Down
11 changes: 8 additions & 3 deletions packages/pwa/app/components/cart-item-variant/item-name.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* 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 {Text} from '@chakra-ui/react'
import {useCartItemVariant} from '.'
import Link from '../link'

/**
* In the context of a cart product item variant, this components simply renders
Expand All @@ -18,9 +18,14 @@ const ItemName = (props) => {
const variant = useCartItemVariant()

return (
<Text fontWeight="bold" {...props}>
<Link
fontWeight="bold"
{...props}
color="black.600"
to={`/product/${variant?.master?.masterId}`}
>
{variant.productName}
</Text>
</Link>
)
}

Expand Down
15 changes: 10 additions & 5 deletions packages/pwa/app/components/confirmation-modal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,23 @@ const ConfirmationModal = ({
}) => {
const {formatMessage} = useIntl()

const handleConfirmClicked = () => {
const handleConfirmClick = () => {
onPrimaryAction()
props.onClose()
}

const handleCancelClicked = () => {
const handleAlternateActionClick = () => {
onAlternateAction()
props.onClose()
}

return (
<AlertDialog isOpen={props.isOpen} isCentered onClose={handleCancelClicked} {...props}>
<AlertDialog
isOpen={props.isOpen}
isCentered
onClose={handleAlternateActionClick}
{...props}
>
<AlertDialogOverlay />
<AlertDialogContent>
<AlertDialogHeader>{formatMessage(dialogTitle)}</AlertDialogHeader>
Expand All @@ -52,10 +57,10 @@ const ConfirmationModal = ({
</AlertDialogBody>

<AlertDialogFooter>
<Button variant="ghost" mr={3} onClick={handleCancelClicked}>
<Button variant="ghost" mr={3} onClick={handleAlternateActionClick}>
{formatMessage(alternateActionLabel)}
</Button>
<Button variant="solid" onClick={handleConfirmClicked}>
<Button variant="solid" onClick={handleConfirmClick}>
{formatMessage(primaryActionLabel)}
</Button>
</AlertDialogFooter>
Expand Down
9 changes: 9 additions & 0 deletions packages/pwa/app/components/product-item/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ import CartItemVariantPrice from '../cart-item-variant/item-price'
import LoadingSpinner from '../loading-spinner'
import {noop} from '../../utils/utils'

/**
* Component representing a product item usually in a list with details about the product - name, variant, pricing, etc.
* @param {Object} product Product to be represented in the list item.
* @param {node} primaryAction Child component representing the most prominent action to be performed by the user.
* @param {node} secondaryActions Child component representing the other actions relevant to the product to be performed by the user.
* @param {func} onItemQuantityChange callback function to be invoked whenever item quantity changes.
* @param {boolean} showLoading Renders a loading spinner with overlay if set to true.
* @returns A JSX element representing product item in a list (eg: wishlist, cart, etc).
*/
const ProductItem = ({
product,
primaryAction,
Expand Down
57 changes: 54 additions & 3 deletions packages/pwa/app/components/product-tile/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import React from 'react'
import PropTypes from 'prop-types'
import {WishlistIcon, WishlistSolidIcon} from '../icons'

// Components
import {
Expand All @@ -16,15 +17,20 @@ import {
Skeleton as ChakraSkeleton,
Text,
Stack,
useMultiStyleConfig
useMultiStyleConfig,
IconButton
} from '@chakra-ui/react'

// Hooks
import {useIntl} from 'react-intl'

// Other
import {productUrlBuilder} from '../../utils/url'
import {noop} from '../../utils/utils'
import Link from '../link'
import withRegistration from '../../hoc/with-registration'

const IconButtonWithRegistration = withRegistration(IconButton)

// Component Skeleton
export const Skeleton = () => {
Expand All @@ -51,15 +57,19 @@ export const Skeleton = () => {
const ProductTile = (props) => {
const intl = useIntl()

const styles = useMultiStyleConfig('ProductTile')
// eslint-disable-next-line react/prop-types
const {
productSearchItem,
// eslint-disable-next-line react/prop-types
staticContext,
onAddToWishlistClick = noop,
onRemoveWishlistClick = noop,
isInWishlist,
isWishlistLoading,
...rest
} = props
const {currency, image, price, productName} = productSearchItem
const styles = useMultiStyleConfig('ProductTile', {isLoading: isWishlistLoading})

return (
<Link
Expand All @@ -72,6 +82,34 @@ const ProductTile = (props) => {
<AspectRatio {...styles.image} ratio={1}>
<Img alt={image.alt} src={image.disBaseLink} />
</AspectRatio>
{isInWishlist ? (
<IconButton
aria-label={intl.formatMessage({
defaultMessage: 'wishlist-solid'
})}
icon={<WishlistSolidIcon />}
variant="unstyled"
{...styles.iconButton}
onClick={(e) => {
e.preventDefault()
if (isWishlistLoading) return
onRemoveWishlistClick()
}}
/>
) : (
<IconButtonWithRegistration
aria-label={intl.formatMessage({
defaultMessage: 'wishlist'
})}
icon={<WishlistIcon />}
variant="unstyled"
{...styles.iconButton}
onClick={() => {
if (isWishlistLoading) return
onAddToWishlistClick()
}}
/>
)}
</Box>

{/* Title */}
Expand All @@ -94,7 +132,20 @@ ProductTile.propTypes = {
* The product search hit that will be represented in this
* component.
*/
productSearchItem: PropTypes.object.isRequired
productSearchItem: PropTypes.object.isRequired,
/**
* Types of lists the product/variant is added to. (eg: wishlist)
*/
isInWishlist: PropTypes.bool,
/**
* Callback function to be invoked when the user add item to wishlist
*/
onAddToWishlistClick: PropTypes.func,
/**
* Callback function to be invoked when the user removes item to wishlist
*/
onRemoveWishlistClick: PropTypes.func,
isWishlistLoading: PropTypes.func
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small warning: react_devtools_backend.js:2842 Warning: Failed prop type: Invalid prop isWishlistLoading of type boolean supplied to ProductTile, expected function.

}

export default ProductTile
3 changes: 2 additions & 1 deletion packages/pwa/app/hoc/with-registration/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const withRegistration = (Component) => {
const {formatMessage} = useIntl()
const showToast = useToast()

const handleClick = () => {
const handleClick = (e) => {
e.preventDefault()
if (customer?.authType !== 'registered') {
// Do not show auth modal if users is already on the login page
if (isLoginPage) {
Expand Down
16 changes: 6 additions & 10 deletions packages/pwa/app/hooks/use-toast.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,15 @@ import {
* to toasts when required. It supports all props supported by Chakra toast.
*
* @param {string} title Message text to be displayed in toast
* @param {string} id - id provided to the toast to avoid duplicate toast ids, use it if multiple toasts are needed
* @param {string} status Semantic state of the toast - success | error | info | warning
* @param {node} action Optional component to be displayed in the toast (eg. Button to allow user to perform action)
* @param {string} position The placement of the toast on screen
* @param {number} duration The delay before the toast hides (in milliseconds)
*/
export function useToast() {
const toast = useChakraToast()

return ({
title,
status,
Expand All @@ -36,9 +38,7 @@ export function useToast() {
variant = 'subtle',
isClosable = true
}) => {
const toastId = `${title}-${status}`.toLowerCase()
let toastConfig = {
id: toastId,
title,
status,
isClosable,
Expand All @@ -50,23 +50,19 @@ export function useToast() {
if (action) {
toastConfig = {
...toastConfig,
// eslint-disable-next-line react/display-name
render: () => (
/* eslint-disable-next-line react/display-name, react/prop-types */
render: ({onClose}) => (
<Alert status={status} variant="subtle" borderRadius="md" py={3} width="sm">
<AlertIcon />
<AlertTitle> {title} </AlertTitle>
<Spacer />
{action}
<Spacer />
<CloseButton onClick={() => toast.close(toastId)} />
<CloseButton onClick={onClose} />
</Alert>
)
}
}

// Prevent duplicate toasts
if (!toast.isActive(toastId)) {
toast(toastConfig)
}
toast(toastConfig)
}
}
Loading