Skip to content
Merged
Show file tree
Hide file tree
Changes from 107 commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
818c63d
add isInitialized getter
kevinxh Aug 31, 2021
9ffb476
remove problematic wishlist init code
kevinxh Aug 31, 2021
f7618bb
rename getOrCreateProductLists
kevinxh Aug 31, 2021
ce2879f
extract contexts into its own file
kevinxh Aug 31, 2021
387a9f2
add empty reducer in wishlist context
kevinxh Aug 31, 2021
2d6e732
add init and reset methods
kevinxh Sep 1, 2021
6ea795f
fetch product details in wishlist
kevinxh Sep 1, 2021
14272bf
update loading and empty state
kevinxh Sep 1, 2021
14059c3
add list item methods
kevinxh Sep 1, 2021
5640563
add createListItem method
kevinxh Sep 1, 2021
e797be9
create useWishlist hook
kevinxh Sep 1, 2021
be0e6e3
rename file to useWishlist.js
kevinxh Sep 1, 2021
2dd157a
add createWishlistItem and updateWishlistItem
kevinxh Sep 1, 2021
33f0960
add createWishlistItem and updateWishlistItem 2
kevinxh Sep 1, 2021
202a746
remove unnecessary code
kevinxh Sep 1, 2021
560ef4b
rename useWishlist to useCustomerProductList
kevinxh Sep 1, 2021
64d289f
refactor useCustomerProductList
kevinxh Sep 2, 2021
6e157cc
fix PLP
kevinxh Sep 2, 2021
35ca633
clean up PLP
kevinxh Sep 2, 2021
2571722
clean up PDP
kevinxh Sep 2, 2021
47c9f5d
clean up Cart
kevinxh Sep 2, 2021
4acd16b
fix wishlist page
kevinxh Sep 2, 2021
eea0c42
make reducers pure
kevinxh Sep 2, 2021
d86836c
add isInitialized state
kevinxh Sep 2, 2021
7bd6d90
set isInitialized initial value
kevinxh Sep 2, 2021
eee8dbe
remove queue
kevinxh Sep 2, 2021
161cbb9
remove testing code
kevinxh Sep 2, 2021
4ff3562
remove unused constants
kevinxh Sep 2, 2021
4cc5310
move useWishlist from commerce api
kevinxh Sep 13, 2021
98e8bbd
move toast back to PLP due to i18n limitation
kevinxh Sep 13, 2021
35c65c2
merge develop
kevinxh Sep 13, 2021
9a89251
fallback to onClick navigation
kevinxh Sep 13, 2021
6dc5024
update plp remove wishlist
kevinxh Sep 13, 2021
9a45610
move toasts out from useWishlist hook
kevinxh Sep 14, 2021
2eea8c2
remove old code
kevinxh Sep 14, 2021
0e03015
catch update quantity error
kevinxh Sep 14, 2021
9be7858
fix product type
kevinxh Sep 14, 2021
9b5d118
fix add to cart
kevinxh Sep 14, 2021
ad6dfdf
merge conflict
kevinxh Sep 14, 2021
8019604
fix lint
kevinxh Sep 14, 2021
a455f2a
rewrite wishlist unit test
kevinxh Sep 14, 2021
f82e2f0
fix tests
kevinxh Sep 14, 2021
8654496
fix isCustomerProductListLoading
kevinxh Sep 14, 2021
a8b1d51
Merge branch 'develop' into wishlist_rework
kevinxh Sep 14, 2021
be9195b
add changelog
kevinxh Sep 14, 2021
c7d4a10
fix add item reducer
kevinxh Sep 14, 2021
54591bc
move comment
kevinxh Sep 14, 2021
1440d10
add missing toast
kevinxh Sep 14, 2021
15331d0
remove unnecessary optional chaining
kevinxh Sep 14, 2021
900b764
rename context
kevinxh Sep 14, 2021
e98dba2
remove console log
kevinxh Sep 14, 2021
f79f5d1
add comment
kevinxh Sep 21, 2021
5811f4d
rename _super to customerProductLists
kevinxh Sep 21, 2021
8cd8012
move methods to useCustomerProductLists
kevinxh Sep 21, 2021
b0d03bf
rename detail to product
kevinxh Sep 21, 2021
f211a10
add jsdoc
kevinxh Sep 21, 2021
e995a74
change isInitialized to a derived state
kevinxh Sep 21, 2021
09555e3
fix additem bug
kevinxh Sep 21, 2021
c99a2f4
fix remove item issue
kevinxh Sep 21, 2021
985b367
fix isInitialized
kevinxh Sep 21, 2021
ac926dd
Merge branch 'develop' into wishlist_rework
kevinxh Sep 21, 2021
b006de0
cleanup ProductTile api
kevinxh Sep 21, 2021
120aeb3
cleanup ProductTile api 2
kevinxh Sep 21, 2021
8e34fa2
clean up ProductTile styles
kevinxh Sep 21, 2021
bd47d50
rename wishlist icon to heart icon
kevinxh Sep 21, 2021
d39bf42
refactor PLP product tiles
kevinxh Sep 21, 2021
36f0408
decouple productscroller and producttile
kevinxh Sep 21, 2021
b51c3a8
remove testing code
kevinxh Sep 21, 2021
823dbf4
replace prev with acc
kevinxh Sep 22, 2021
ec28c0f
fix isInitialized
kevinxh Sep 22, 2021
4a2b544
Merge branch 'wishlist_rework' of https://github.com/SalesforceCommer…
kevinxh Sep 22, 2021
0945482
remove unnecessary toast id
kevinxh Sep 22, 2021
5415abb
merge develop
kevinxh Sep 22, 2021
08e0635
desctruct payload in reducer
kevinxh Sep 22, 2021
da61d82
fix test
kevinxh Sep 22, 2021
a4e4c79
rename file name to useCustomerProductLists
kevinxh Sep 22, 2021
ba0c503
add handleAsyncError util function
kevinxh Sep 22, 2021
506676c
move methods into useCustomerProductLists
kevinxh Sep 22, 2021
ead837b
finish init method in useWishlist
kevinxh Sep 22, 2021
8bbf0bc
simplify getOrCreateList
kevinxh Sep 22, 2021
1b36418
add getters to useWishlist
kevinxh Sep 22, 2021
7b7cb5f
add findItem method
kevinxh Sep 22, 2021
55dd6b7
add list item crud actions
kevinxh Sep 23, 2021
298b294
fix createListItem
kevinxh Sep 23, 2021
635f766
fix removeListItemByProductId
kevinxh Sep 23, 2021
4806ab5
fix pdp
kevinxh Sep 23, 2021
7b73696
fix wishlist item update
kevinxh Sep 23, 2021
6680c32
fix remove button and cart wishlist
kevinxh Sep 23, 2021
ec8fa21
add comments
kevinxh Sep 23, 2021
f3e9b5f
fix wishlist init
kevinxh Sep 23, 2021
f880203
return responses
kevinxh Sep 23, 2021
2fdaaea
bump bundle size limit
kevinxh Sep 23, 2021
cbab3d8
lint
kevinxh Sep 23, 2021
9b11aca
fix tests
kevinxh Sep 23, 2021
5361200
add tests
kevinxh Sep 23, 2021
313443a
remove actions from return
kevinxh Sep 23, 2021
6799779
Merge branch 'wishlist_rework' into W-9807216__wishlist-einstein
kevinxh Sep 23, 2021
3ef5a51
fix merge conflicts
kevinxh Sep 23, 2021
cf5c9a3
add more tests
kevinxh Sep 23, 2021
f1e2572
fix handleAsyncError comment
kevinxh Sep 23, 2021
5e1b0b0
add handleAsyncError tests
kevinxh Sep 23, 2021
11d4677
fix test case name
kevinxh Sep 23, 2021
55b5593
Merge branch 'wishlist_rework' into W-9807216__wishlist-einstein
kevinxh Sep 23, 2021
9f8fb4d
merge develop
kevinxh Sep 24, 2021
f8b011d
Merge branch 'develop' into W-9807216__wishlist-einstein
kevinxh Sep 24, 2021
88ec4a3
fix merge conflicts
kevinxh Sep 24, 2021
0189199
merge develop
kevinxh Sep 24, 2021
c2c6013
Merge branch 'develop' into W-9807216__wishlist-einstein
kevinxh Sep 28, 2021
c9e100e
update changelog
kevinxh Sep 28, 2021
151bab3
introduce productTilePropsFactory
kevinxh Sep 28, 2021
3d077dd
add loading state
kevinxh Sep 28, 2021
57cf2e6
remove testing code
kevinxh Sep 28, 2021
26941e2
rename productTileProps
kevinxh Sep 28, 2021
720719f
Chakra IconButton already supports disabled state
kevinxh Sep 28, 2021
3ee155d
avoid click event bubbling
kevinxh Sep 28, 2021
4322e35
fix tests
kevinxh Sep 29, 2021
88c2f62
fix import
kevinxh Sep 29, 2021
e379ca1
allow func or object
kevinxh Sep 29, 2021
9a531f2
add more tests
kevinxh Sep 29, 2021
da27b61
minor updates
kevinxh Sep 29, 2021
4ece56c
compile messages
kevinxh Sep 29, 2021
30b8f0d
Merge branch 'develop' into W-9807216__wishlist-einstein
kevinxh Sep 29, 2021
b124855
Merge branch 'develop' into W-9807216__wishlist-einstein
kevinxh Sep 29, 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
1 change: 1 addition & 0 deletions packages/pwa/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## To be released

- Fix wishlist bugs and provide better hooks for wishlist features. [#64](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/64)
- Integrate wishlist with einstein recommended products. [#131](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/131)
- Lazy load Popover component. [#134](https://github.com/SalesforceCommerceCloud/pwa-kit/pull/134)

## v1.0.0 (Sep 08, 2021)
Expand Down
4 changes: 2 additions & 2 deletions packages/pwa/app/components/header/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ import {
BasketIcon,
HamburgerIcon,
ChevronDownIcon,
WishlistIcon,
HeartIcon,
SignoutIcon
} from '../icons'

Expand Down Expand Up @@ -241,7 +241,7 @@ const Header = ({
aria-label={intl.formatMessage({
defaultMessage: 'Wishlist'
})}
icon={<WishlistIcon />}
icon={<HeartIcon />}
variant="unstyled"
{...styles.icons}
onClick={onWishlistClick}
Expand Down
8 changes: 4 additions & 4 deletions packages/pwa/app/components/icons/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ import '../../assets/svg/signout.svg'
import '../../assets/svg/user.svg'
import '../../assets/svg/visibility.svg'
import '../../assets/svg/visibility-off.svg'
import '../../assets/svg/wishlist.svg'
import '../../assets/svg/wishlist-solid.svg'
import '../../assets/svg/heart.svg'
import '../../assets/svg/heart-solid.svg'
import '../../assets/svg/close.svg'

// For non-square SVGs, we can use the symbol data from the import to set the
Expand Down Expand Up @@ -145,6 +145,6 @@ export const UserIcon = icon('user')
export const VisaIcon = icon('cc-visa', {viewBox: VisaSymbol.viewBox})
export const VisibilityIcon = icon('visibility')
export const VisibilityOffIcon = icon('visibility-off')
export const WishlistIcon = icon('wishlist')
export const WishlistSolidIcon = icon('wishlist-solid')
export const HeartIcon = icon('heart')
export const HeartSolidIcon = icon('heart-solid')
export const CloseIcon = icon('close')
13 changes: 4 additions & 9 deletions packages/pwa/app/components/product-scroller/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import React, {forwardRef, useRef} from 'react'
import PropTypes from 'prop-types'
import {AspectRatio, Box, Heading, IconButton, Skeleton, Stack} from '@chakra-ui/react'
import ProductTile from '../../components/product-tile'
import {ChevronLeftIcon, ChevronRightIcon} from '../icons'

/**
Expand All @@ -23,7 +22,7 @@ const ProductScroller = forwardRef(
isLoading,
scrollProps,
itemWidth = {base: '70%', md: '40%', lg: 'calc(33.33% - 10px)'},
onProductClick = () => null,
renderProduct,
...props
},
ref
Expand Down Expand Up @@ -92,11 +91,7 @@ const ProductScroller = forwardRef(
</Stack>
</Stack>
) : (
<ProductTile
data-testid="product-scroller-item"
productSearchItem={product}
onClick={() => onProductClick(product)}
/>
renderProduct(product)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's hard to customize the product tiles in the scroller so this is my attempt to decouple the two components...

feedback is welcomed

)}
</Box>
)
Expand Down Expand Up @@ -160,8 +155,8 @@ ProductScroller.propTypes = {
products: PropTypes.array,
isLoading: PropTypes.bool,
scrollProps: PropTypes.object,
itemWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]),
onProductClick: PropTypes.func
renderProduct: PropTypes.func,
itemWidth: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object])
}

export default ProductScroller
32 changes: 26 additions & 6 deletions packages/pwa/app/components/product-scroller/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,30 @@ const testProducts = [1, 2, 3, 4].map((i) => ({
currency: 'USD'
}))

const noop = () => {}

describe('Product Scroller', () => {
test('renders loading skeletons', () => {
renderWithProviders(<ProductScroller isLoading />)
renderWithProviders(<ProductScroller isLoading renderProduct={noop} />)
expect(screen.getAllByTestId('product-scroller-item-skeleton')).toHaveLength(4)
})
test('renders nothing when no products and not loading', () => {
renderWithProviders(<ProductScroller />)
renderWithProviders(<ProductScroller renderProduct={noop} />)
expect(screen.queryByTestId('product-scroller')).not.toBeInTheDocument()
})
test('Renders scrollable product tiles and title', () => {
renderWithProviders(<ProductScroller title="Scroller Title" products={testProducts} />)
test('renders products with renderProduct prop', () => {
const mock = jest.fn()
renderWithProviders(<ProductScroller products={testProducts} renderProduct={mock} />)
expect(mock).toHaveBeenCalledTimes(testProducts.length)
})
test('Renders product items', () => {
renderWithProviders(
<ProductScroller
title="Scroller Title"
products={testProducts}
renderProduct={() => <div data-testid="product-scroller-item"></div>}
/>
)
expect(screen.getByText('Scroller Title')).toBeInTheDocument()
expect(screen.getAllByTestId('product-scroller-item')).toHaveLength(4)
})
Expand All @@ -43,12 +56,15 @@ describe('Product Scroller', () => {
<ProductScroller
header={<h1 data-testid="custom-header">Scroller Header</h1>}
products={testProducts}
renderProduct={noop}
/>
)
expect(screen.getByTestId('custom-header')).toBeInTheDocument()
})
test('Renders left/right scroll buttons', () => {
renderWithProviders(<ProductScroller title="Scroller Title" products={testProducts} />)
renderWithProviders(
<ProductScroller title="Scroller Title" products={testProducts} renderProduct={noop} />
)
user.click(screen.getByTestId('product-scroller-nav-right'))
expect(window.HTMLElement.prototype.scrollBy).toHaveBeenCalledWith({
top: 0,
Expand All @@ -66,7 +82,11 @@ describe('Product Scroller', () => {
})
test('Does not render left/right scroll buttons when less than 4 products', () => {
renderWithProviders(
<ProductScroller title="Scroller Title" products={testProducts.slice(0, 2)} />
<ProductScroller
title="Scroller Title"
products={testProducts.slice(0, 2)}
renderProduct={noop}
/>
)
expect(screen.queryByTestId('product-scroller-nav-left')).not.toBeInTheDocument()
expect(screen.queryByTestId('product-scroller-nav-right')).not.toBeInTheDocument()
Expand Down
105 changes: 41 additions & 64 deletions packages/pwa/app/components/product-tile/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

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

// Components
import {
Expand Down Expand Up @@ -50,80 +50,47 @@ export const Skeleton = () => {
}

/**
* The ProductTile is a simple visual representation of a product search hit
* object. It will show it's default image, name and price.
* The ProductTile is a simple visual representation of a
* product object. It will show it's default image, name and price.
* It also supports favourite products, controlled by a heart icon.
*/
const ProductTile = (props) => {
const intl = useIntl()

// eslint-disable-next-line react/prop-types
const {
productSearchItem,
// eslint-disable-next-line react/prop-types
staticContext,
onAddToWishlistClick,
onRemoveWishlistClick,
isInWishlist,
isWishlistLoading,
...rest
} = props
const {currency, image, price, productName} = productSearchItem
const styles = useMultiStyleConfig('ProductTile', {isLoading: isWishlistLoading})
const {product, enableFavourite = false, isFavourite, onFavouriteToggle, ...rest} = props
Copy link
Contributor Author

@kevinxh kevinxh Sep 24, 2021

Choose a reason for hiding this comment

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

i'd like to de-couple the concept of wishlist from the components like ProductTile, so they are more re-usable.

i intentionally renamed wishlist related props to "favourite", which i think is more generic. feedback is welcomed

const {currency, image, price, productName, productId} = product
const styles = useMultiStyleConfig('ProductTile')

return (
<Link
data-testid="product-tile"
{...styles.container}
to={productUrlBuilder({id: productSearchItem?.productId}, intl.local)}
to={productUrlBuilder({id: productId}, intl.local)}
{...rest}
>
<Box {...styles.imageWrapper}>
<AspectRatio {...styles.image} ratio={1}>
<AspectRatio {...styles.image}>
<Img alt={image.alt} src={image.disBaseLink} />
</AspectRatio>
{onAddToWishlistClick && onRemoveWishlistClick && (
<>
{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()
}}
/>
)}
</>

{enableFavourite && (
<IconButtonWithRegistration
aria-label={intl.formatMessage({
defaultMessage: 'wishlist'
})}
icon={isFavourite ? <HeartSolidIcon /> : <HeartIcon />}
{...styles.favIcon}
onClick={() => {
onFavouriteToggle(!isFavourite)
}}
/>
)}
</Box>

{/* Title */}
<Text {...styles.title} aria-label="product name">
{productName}
</Text>
<Text {...styles.title}>{productName}</Text>

{/* Price */}
<Text {...styles.price} aria-label="price">
{intl.formatNumber(price, {style: 'currency', currency})}
</Text>
<Text {...styles.price}>{intl.formatNumber(price, {style: 'currency', currency})}</Text>
</Link>
)
}
Expand All @@ -135,20 +102,30 @@ ProductTile.propTypes = {
* The product search hit that will be represented in this
* component.
*/
productSearchItem: PropTypes.object.isRequired,
product: PropTypes.shape({
currency: PropTypes.string,
image: PropTypes.shape({
alt: PropTypes.string,
disBaseLink: PropTypes.string
}),
price: PropTypes.number,
productName: PropTypes.string,
productId: PropTypes.string
}),
/**
* Types of lists the product/variant is added to. (eg: wishlist)
* Enable adding/removing product as a favourite.
* Use case: wishlist.
*/
isInWishlist: PropTypes.bool,
enableFavourite: PropTypes.bool,
/**
* Callback function to be invoked when the user add item to wishlist
* Display the product as a faviourite.
*/
onAddToWishlistClick: PropTypes.func,
isFavourite: PropTypes.bool,
/**
* Callback function to be invoked when the user removes item to wishlist
* Callback function to be invoked when the user
* interacts with favourite icon/button.
*/
onRemoveWishlistClick: PropTypes.func,
isWishlistLoading: PropTypes.bool
onFavouriteToggle: PropTypes.func
}

export default ProductTile
4 changes: 1 addition & 3 deletions packages/pwa/app/components/product-tile/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,7 @@ const mockProductSearchItem = {
}

test('Renders Breadcrumb', () => {
const {getAllByRole} = renderWithProviders(
<ProductTile productSearchItem={mockProductSearchItem} />
)
const {getAllByRole} = renderWithProviders(<ProductTile product={mockProductSearchItem} />)

const link = getAllByRole('link')
const img = getAllByRole('img')
Expand Down
Loading