@graphcommerce/magento-wishlist 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +62 -0
  3. package/components/ProductWishlistChip/ProductWishlistChip.graphql +4 -0
  4. package/components/ProductWishlistChip/ProductWishlistChip.tsx +5 -0
  5. package/components/ProductWishlistChip/ProductWishlistChipBase.tsx +180 -0
  6. package/components/ProductWishlistChip/ProductWishlistChipDetail.tsx +12 -0
  7. package/components/ProductWishlistChip/ProductWishlistChipDetailConfigurable.tsx +23 -0
  8. package/components/WishlistFab/WishlistFab.tsx +91 -0
  9. package/components/WishlistItem/ProductAddToCart.tsx +135 -0
  10. package/components/WishlistItem/WishlistItem.graphql +3 -0
  11. package/components/WishlistItem/WishlistItem.tsx +30 -0
  12. package/components/WishlistItem/WishlistItemBase.tsx +303 -0
  13. package/components/WishlistItem/WishlistItemConfigurable.graphql +8 -0
  14. package/components/WishlistItem/WishlistItemConfigurable.tsx +11 -0
  15. package/components/WishlistItem/WishlistItemProduct.graphql +11 -0
  16. package/components/WishlistItems/WishlistItems.graphql +12 -0
  17. package/components/WishlistItems/WishlistItems.tsx +33 -0
  18. package/components/WishlistMenuFabItem/WishlistMenuFabItem.tsx +81 -0
  19. package/components/index.ts +10 -0
  20. package/hooks/index.ts +3 -0
  21. package/hooks/useMergeGuestWishlistWithCustomer.tsx +64 -0
  22. package/hooks/useWishlistEnabled.tsx +9 -0
  23. package/hooks/useWishlistItems.tsx +53 -0
  24. package/index.ts +12 -0
  25. package/package.json +39 -0
  26. package/queries/AddProductToWishlist.graphql +13 -0
  27. package/queries/GetGuestWishlistProducts.graphql +14 -0
  28. package/queries/GetIsInWishlists.graphql +7 -0
  29. package/queries/GetWishlistProducts.graphql +19 -0
  30. package/queries/GuestWishlist.graphql +11 -0
  31. package/queries/GuestWishlist.graphqls +13 -0
  32. package/queries/RemoveProductFromWishlist.graphql +7 -0
  33. package/queries/WishlistStoreConfigFragment.graphql +3 -0
  34. package/queries/WishlistSummaryFragment.graphql +13 -0
  35. package/tsconfig.json +5 -0
  36. package/typePolicies.ts +21 -0
@@ -0,0 +1,303 @@
1
+ /* eslint-disable @typescript-eslint/no-floating-promises */
2
+ /* eslint-disable react-hooks/rules-of-hooks */
3
+ import { useQuery, useMutation, useApolloClient } from '@graphcommerce/graphql'
4
+ import { Image } from '@graphcommerce/image'
5
+ import { useDisplayInclTax } from '@graphcommerce/magento-cart'
6
+ import { CustomerTokenDocument } from '@graphcommerce/magento-customer'
7
+ import { useProductLink } from '@graphcommerce/magento-product'
8
+ import { Money } from '@graphcommerce/magento-store'
9
+ import { responsiveVal, extendableComponent, iconEllypsis, IconSvg } from '@graphcommerce/next-ui'
10
+ import { Trans, t } from '@lingui/macro'
11
+ import { Badge, Box, Link, SxProps, Theme, Typography } from '@mui/material'
12
+ import IconButton from '@mui/material/IconButton'
13
+ import Menu from '@mui/material/Menu'
14
+ import MenuItem from '@mui/material/MenuItem'
15
+ import PageLink from 'next/link'
16
+ import { useState, PropsWithChildren } from 'react'
17
+ import { GetIsInWishlistsDocument } from '../../queries/GetIsInWishlists.gql'
18
+ import { RemoveProductFromWishlistDocument } from '../../queries/RemoveProductFromWishlist.gql'
19
+ import { WishlistItemProductFragment } from './WishlistItemProduct.gql'
20
+
21
+ const rowImageSize = responsiveVal(70, 125)
22
+
23
+ type OptionalProductWishlistParent = {
24
+ wishlistItemId?: string
25
+ }
26
+
27
+ export type WishlistItemBaseProps = PropsWithChildren<WishlistItemProductFragment> & {
28
+ sx?: SxProps<Theme>
29
+ } & OwnerState &
30
+ OptionalProductWishlistParent
31
+
32
+ type OwnerState = { withOptions?: boolean }
33
+ const compName = 'WishlistItemBase' as const
34
+ const parts = [
35
+ 'item',
36
+ 'picture',
37
+ 'badge',
38
+ 'productLink',
39
+ 'image',
40
+ 'itemName',
41
+ 'itemPrice',
42
+ 'discountPrice',
43
+ 'root',
44
+ ] as const
45
+ const { classes } = extendableComponent<OwnerState, typeof compName, typeof parts>(compName, parts)
46
+
47
+ export function WishlistItemBase(props: WishlistItemBaseProps) {
48
+ const {
49
+ sku,
50
+ name,
51
+ url_key,
52
+ price_range,
53
+ small_image,
54
+ __typename: productType,
55
+ children,
56
+ sx = [],
57
+ wishlistItemId,
58
+ } = props
59
+
60
+ const productLink = useProductLink({ url_key, __typename: productType })
61
+ const inclTaxes = useDisplayInclTax()
62
+ const { cache } = useApolloClient()
63
+
64
+ const { data: token } = useQuery(CustomerTokenDocument)
65
+ const isLoggedIn = token?.customerToken && token?.customerToken.valid
66
+
67
+ const { data: GetCustomerWishlistData, loading } = useQuery(GetIsInWishlistsDocument, {
68
+ skip: !isLoggedIn,
69
+ })
70
+
71
+ const [removeWishlistItem] = useMutation(RemoveProductFromWishlistDocument)
72
+
73
+ const [anchorEl, setAnchorEl] = useState(null)
74
+ const open = Boolean(anchorEl)
75
+ const handleClick = (event) => {
76
+ setAnchorEl(event.currentTarget)
77
+ }
78
+
79
+ const handleClose = (event) => {
80
+ if (event.target.id === 'remove') {
81
+ if (isLoggedIn) {
82
+ let itemIdToDelete = wishlistItemId
83
+
84
+ /** When no internal ID is provided, fetch it by sku */
85
+ if (!itemIdToDelete) {
86
+ const wishlistItemsInSession =
87
+ GetCustomerWishlistData?.customer?.wishlists[0]?.items_v2?.items || []
88
+
89
+ const item = wishlistItemsInSession.find((element) => element?.product?.sku === sku)
90
+ if (item?.id) {
91
+ itemIdToDelete = item.id
92
+ }
93
+ }
94
+
95
+ if (itemIdToDelete) {
96
+ removeWishlistItem({ variables: { wishlistItemId: itemIdToDelete } })
97
+ }
98
+ } else {
99
+ cache.modify({
100
+ id: cache.identify({ __typename: 'GuestWishlist' }),
101
+ fields: {
102
+ items(existingItems = []) {
103
+ const items = existingItems.filter((guestItem) => guestItem.sku !== sku)
104
+ return items
105
+ },
106
+ },
107
+ })
108
+ }
109
+ }
110
+ setAnchorEl(null)
111
+ }
112
+
113
+ return (
114
+ <Box
115
+ className={classes.item}
116
+ sx={[
117
+ (theme) => ({
118
+ position: 'relative',
119
+ display: 'grid',
120
+ gridTemplate: `
121
+ "picture itemName itemName iconMenu"
122
+ "picture itemOptions itemOptions"
123
+ "picture itemQuantity itemQuantity itemPrice"
124
+ "itemCartButton itemCartButton itemCartButton itemCartButton"`,
125
+ gridTemplateColumns: `${rowImageSize} 1fr minmax(120px, 1fr) 1fr`,
126
+ columnGap: theme.spacings.sm,
127
+ alignItems: 'baseline',
128
+ typography: 'body1',
129
+ paddingBottom: theme.spacings.xl,
130
+ paddingTop: theme.spacings.sm,
131
+ [theme.breakpoints.up('sm')]: {
132
+ paddingBottom: theme.spacings.md,
133
+ gridTemplate: `
134
+ "picture itemName itemName itemName iconMenu"
135
+ "picture itemQuantity itemOptions itemPrice itemPrice"
136
+ "itemCartButton itemCartButton itemCartButton itemCartButton itemCartButton"`,
137
+ gridTemplateColumns: `${rowImageSize} 4fr 1fr minmax(120px, 1fr) minmax(120px, 1fr)`,
138
+ },
139
+ borderBottom: `1px solid ${theme.palette.divider}`,
140
+
141
+ '&:not(.withOptions)': {
142
+ display: 'grid',
143
+ gridTemplate: `
144
+ "picture itemName itemName iconMenu"
145
+ "picture itemQuantity itemPrice itemPrice"
146
+ "itemCartButton itemCartButton itemCartButton itemCartButton"`,
147
+ alignItems: 'center',
148
+ paddingBottom: theme.spacings.xl,
149
+ gridTemplateColumns: `${rowImageSize} 1fr minmax(120px, 1fr) 1fr`,
150
+ [theme.breakpoints.up('sm')]: {
151
+ paddingBottom: theme.spacings.md,
152
+ gridTemplate: `
153
+ "picture itemName itemName itemName iconMenu"
154
+ "picture itemQuantity itemQuantity itemQuantity itemPrice"
155
+ "itemCartButton itemCartButton itemCartButton itemCartButton itemCartButton"
156
+ `,
157
+ gridTemplateColumns: `${rowImageSize} 4fr 1fr minmax(120px, 1fr) minmax(120px, 1fr)`,
158
+ },
159
+ },
160
+ }),
161
+ ...(Array.isArray(sx) ? sx : [sx]),
162
+ ]}
163
+ >
164
+ <Badge
165
+ color='default'
166
+ component='div'
167
+ className={classes.picture}
168
+ anchorOrigin={{
169
+ vertical: 'top',
170
+ horizontal: 'left',
171
+ }}
172
+ sx={(theme) => ({
173
+ gridArea: 'picture',
174
+ width: rowImageSize,
175
+ height: rowImageSize,
176
+ padding: responsiveVal(5, 10),
177
+ alignSelf: 'flex-start',
178
+ })}
179
+ >
180
+ <PageLink href={productLink} passHref>
181
+ <Box
182
+ component='a'
183
+ className={classes.productLink}
184
+ sx={{ display: 'block', width: '100%', overflow: 'hidden' }}
185
+ >
186
+ {small_image?.url && (
187
+ <Image
188
+ src={small_image.url ?? ''}
189
+ layout='fill'
190
+ alt={small_image.label ?? name ?? ''}
191
+ sizes={responsiveVal(70, 125)}
192
+ className={classes.image}
193
+ sx={(theme) => ({
194
+ gridColumn: 1,
195
+ backgroundColor: theme.palette.background.image,
196
+ objectFit: 'cover',
197
+ display: 'block',
198
+ width: '110% !important',
199
+ height: '110% !important',
200
+ marginLeft: '-5%',
201
+ marginTop: '-5%',
202
+ })}
203
+ />
204
+ )}
205
+ </Box>
206
+ </PageLink>
207
+ </Badge>
208
+
209
+ <PageLink href={productLink} passHref>
210
+ <Link
211
+ variant='body1'
212
+ className={classes.itemName}
213
+ underline='hover'
214
+ sx={(theme) => ({
215
+ typgrapht: 'subtitle1',
216
+ fontWeight: theme.typography.fontWeightBold,
217
+ gridArea: 'itemName',
218
+ color: theme.palette.text.primary,
219
+ textDecoration: 'none',
220
+ flexWrap: 'nowrap',
221
+ maxWidth: 'max-content',
222
+ '&:not(.withOptions)': {
223
+ alignSelf: 'flex-start',
224
+ },
225
+ })}
226
+ >
227
+ {name}
228
+ </Link>
229
+ </PageLink>
230
+
231
+ <Typography
232
+ component='div'
233
+ variant='body1'
234
+ className={classes.root}
235
+ sx={[
236
+ (theme) => ({ gridArea: 'itemPrice', marginLeft: 'auto' }),
237
+ ...(Array.isArray(sx) ? sx : [sx]),
238
+ ]}
239
+ >
240
+ {price_range.minimum_price.regular_price.value !==
241
+ price_range.minimum_price.final_price.value && (
242
+ <Box
243
+ component='span'
244
+ sx={{
245
+ textDecoration: 'line-through',
246
+ color: 'text.disabled',
247
+ marginRight: '8px',
248
+ }}
249
+ className={classes.discountPrice}
250
+ >
251
+ <Money {...price_range.minimum_price.regular_price} />
252
+ </Box>
253
+ )}
254
+ <Money {...price_range.minimum_price.final_price} />
255
+ </Typography>
256
+
257
+ <IconButton
258
+ aria-label='more'
259
+ id='long-button'
260
+ aria-controls={open ? 'long-menu' : undefined}
261
+ aria-expanded={open ? 'true' : undefined}
262
+ aria-haspopup='true'
263
+ onClick={handleClick}
264
+ sx={(theme) => ({
265
+ gridArea: 'iconMenu',
266
+ alignSelf: 'flex-start',
267
+ padding: '0',
268
+ marginLeft: 'auto',
269
+ borderRadius: '0',
270
+ })}
271
+ >
272
+ <IconSvg
273
+ src={iconEllypsis}
274
+ size='medium'
275
+ sx={(theme) => ({
276
+ fill: theme.palette.text.primary,
277
+ })}
278
+ />
279
+ </IconButton>
280
+ <Menu
281
+ id='long-menu'
282
+ MenuListProps={{
283
+ 'aria-labelledby': 'long-button',
284
+ }}
285
+ anchorEl={anchorEl}
286
+ open={open}
287
+ onClose={handleClose}
288
+ PaperProps={{
289
+ style: {
290
+ maxHeight: 65,
291
+ justifyContent: 'center',
292
+ },
293
+ }}
294
+ >
295
+ <MenuItem key='remove' id='remove' onClick={handleClose}>
296
+ <Trans>Remove Product</Trans>
297
+ </MenuItem>
298
+ </Menu>
299
+
300
+ {children}
301
+ </Box>
302
+ )
303
+ }
@@ -0,0 +1,8 @@
1
+ fragment WishlistItemConfigurable on ConfigurableWishlistItem {
2
+ configurable_options {
3
+ configurable_product_option_uid
4
+ configurable_product_option_value_uid
5
+ option_label
6
+ value_label
7
+ }
8
+ }
@@ -0,0 +1,11 @@
1
+ import { WishlistItem, WishlistItemProps } from './WishlistItem'
2
+ import { WishlistItemFragment } from './WishlistItem.gql'
3
+ import { WishlistItemConfigurableFragment } from './WishlistItemConfigurable.gql'
4
+
5
+ export function WishlistItemConfigurable(
6
+ props: WishlistItemConfigurableFragment & WishlistItemProps,
7
+ ) {
8
+ const { configurable_options, ...wishlistItemProps } = props
9
+
10
+ return <WishlistItem {...wishlistItemProps} />
11
+ }
@@ -0,0 +1,11 @@
1
+ fragment WishlistItemProduct on ProductInterface @injectable {
2
+ uid
3
+ ...ProductLink
4
+ name
5
+ url_key
6
+ thumbnail {
7
+ url
8
+ label
9
+ }
10
+ ...ProductListItem
11
+ }
@@ -0,0 +1,12 @@
1
+ fragment WishlistItems on Wishlist @injectable {
2
+ id
3
+ items_v2 {
4
+ items {
5
+ id
6
+ __typename
7
+ product {
8
+ ...WishlistItemProduct
9
+ }
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,33 @@
1
+ import { AnimatedRow, RenderType, TypeRenderer } from '@graphcommerce/next-ui'
2
+ import { AnimatePresence } from 'framer-motion'
3
+ import { useWishlistItems } from '../../hooks'
4
+ import { WishlistItemsFragment } from './WishlistItems.gql'
5
+
6
+ export type WishlistItemRenderer = TypeRenderer<
7
+ NonNullable<
8
+ NonNullable<NonNullable<NonNullable<WishlistItemsFragment['items_v2']>['items']>[0]>['product']
9
+ >
10
+ >
11
+
12
+ export type WishlistProps = { renderer: WishlistItemRenderer }
13
+
14
+ export function WishlistItems(props: WishlistProps) {
15
+ const { renderer } = props
16
+ const wishlistItemsData = useWishlistItems()
17
+
18
+ /** Structure between guest and customer wishlist differs */
19
+ return (
20
+ <AnimatePresence initial={false}>
21
+ {wishlistItemsData.items?.map((item) => {
22
+ if (!item?.uid && !item?.id) return null
23
+
24
+ const productData = item?.product ? item?.product : item
25
+ return (
26
+ <AnimatedRow key={item.id || item.uid}>
27
+ <RenderType renderer={renderer} wishlistItemId={item.id || null} {...productData} />
28
+ </AnimatedRow>
29
+ )
30
+ })}
31
+ </AnimatePresence>
32
+ )
33
+ }
@@ -0,0 +1,81 @@
1
+ import { useQuery } from '@graphcommerce/graphql'
2
+ import { CustomerTokenDocument } from '@graphcommerce/magento-customer'
3
+ import { MenuFabSecondaryItem, iconHeart, IconSvg } from '@graphcommerce/next-ui'
4
+ import { Badge, NoSsr, SxProps, Theme } from '@mui/material'
5
+ import React from 'react'
6
+ import { useWishlistEnabled } from '../../hooks'
7
+ import { GetIsInWishlistsDocument } from '../../queries/GetIsInWishlists.gql'
8
+ import { GuestWishlistDocument } from '../../queries/GuestWishlist.gql'
9
+
10
+ type WishlistMenuFabItemProps = {
11
+ icon?: React.ReactNode
12
+ children: React.ReactNode
13
+ sx?: SxProps<Theme>
14
+ }
15
+
16
+ const hideForGuest = process.env.NEXT_PUBLIC_WISHLIST_HIDE_FOR_GUEST === '1'
17
+
18
+ function WishlistMenuFabItemContent(props: WishlistMenuFabItemProps) {
19
+ const { icon, children, sx = [] } = props
20
+
21
+ const { data: token } = useQuery(CustomerTokenDocument)
22
+ const isLoggedIn = token?.customerToken && token?.customerToken.valid
23
+
24
+ const { data: GetCustomerWishlistData, loading } = useQuery(GetIsInWishlistsDocument, {
25
+ ssr: false,
26
+ skip: !isLoggedIn,
27
+ })
28
+
29
+ const { data: guestWishlistData, loading: loadingGuestWishlistData } = useQuery(
30
+ GuestWishlistDocument,
31
+ {
32
+ ssr: false,
33
+ skip: isLoggedIn === true,
34
+ },
35
+ )
36
+
37
+ let activeWishlist
38
+ if (isLoggedIn) {
39
+ const wishlistItemCount = GetCustomerWishlistData?.customer?.wishlists[0]?.items_count || 0
40
+ activeWishlist = wishlistItemCount > 0
41
+ } else {
42
+ const wishlist = guestWishlistData?.guestWishlist?.items || []
43
+ activeWishlist = wishlist.length > 0
44
+ }
45
+
46
+ return (
47
+ <MenuFabSecondaryItem
48
+ sx={sx}
49
+ icon={
50
+ <Badge
51
+ badgeContent={activeWishlist ? 1 : 0}
52
+ color='primary'
53
+ variant='dot'
54
+ overlap='circular'
55
+ >
56
+ {icon ?? <IconSvg src={iconHeart} size='medium' />}
57
+ </Badge>
58
+ }
59
+ href='/wishlist'
60
+ >
61
+ {children}
62
+ </MenuFabSecondaryItem>
63
+ )
64
+ }
65
+
66
+ export function WishlistMenuFabItem(props: WishlistMenuFabItemProps) {
67
+ const isWishlistEnabled = useWishlistEnabled()
68
+
69
+ const { data: token } = useQuery(CustomerTokenDocument)
70
+ const isLoggedIn = token?.customerToken && token?.customerToken.valid
71
+
72
+ return (
73
+ <>
74
+ {isWishlistEnabled && (!hideForGuest || isLoggedIn) && (
75
+ <NoSsr fallback={<WishlistMenuFabItemContent {...props} />}>
76
+ <WishlistMenuFabItemContent {...props} />
77
+ </NoSsr>
78
+ )}
79
+ </>
80
+ )
81
+ }
@@ -0,0 +1,10 @@
1
+ export * from './ProductWishlistChip/ProductWishlistChipDetail'
2
+ export * from './ProductWishlistChip/ProductWishlistChipDetailConfigurable'
3
+ export * from './ProductWishlistChip/ProductWishlistChip'
4
+ export * from './ProductWishlistChip/ProductWishlistChipBase'
5
+ export * from './WishlistItems/WishlistItems'
6
+ export * from './WishlistItem/WishlistItem'
7
+ export * from './WishlistItem/WishlistItemBase'
8
+ export * from './WishlistItem/WishlistItemConfigurable'
9
+ export * from './WishlistFab/WishlistFab'
10
+ export * from './WishlistMenuFabItem/WishlistMenuFabItem'
package/hooks/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './useMergeGuestWishlistWithCustomer'
2
+ export * from './useWishlistItems'
3
+ export * from './useWishlistEnabled'
@@ -0,0 +1,64 @@
1
+ /* eslint-disable react-hooks/exhaustive-deps */
2
+ /* eslint-disable @typescript-eslint/no-floating-promises */
3
+ /* eslint-disable react-hooks/rules-of-hooks */
4
+ import { useMutation, useQuery, useApolloClient } from '@graphcommerce/graphql'
5
+ import { CustomerTokenDocument } from '@graphcommerce/magento-customer'
6
+ import { useEffect } from 'react'
7
+ import { AddProductToWishlistDocument } from '../queries/AddProductToWishlist.gql'
8
+ import { GetGuestWishlistProductsDocument } from '../queries/GetGuestWishlistProducts.gql'
9
+ import { GuestWishlistDocument } from '../queries/GuestWishlist.gql'
10
+
11
+ /** Merge guest wishlist items to customer session upon login */
12
+ export function useMergeGuestWishlistWithCustomer() {
13
+ const customerToken = useQuery(CustomerTokenDocument)?.data?.customerToken
14
+ const isLoggedIn = customerToken?.token && customerToken?.valid
15
+ const { cache } = useApolloClient()
16
+
17
+ const guestWishlistData = useQuery(GuestWishlistDocument, {
18
+ ssr: false,
19
+ }).data?.guestWishlist
20
+
21
+ const guestDataSkus = guestWishlistData?.items.map((item) => item?.sku) || []
22
+ // eslint-disable-next-line react-hooks/exhaustive-deps
23
+ const validatedItems =
24
+ useQuery(GetGuestWishlistProductsDocument, {
25
+ ssr: false,
26
+ variables: {
27
+ filters: { sku: { in: guestDataSkus } },
28
+ },
29
+ skip: guestDataSkus.length === 0,
30
+ }).data?.products?.items?.map((item) => item?.sku) || []
31
+
32
+ const [addWishlistItem] = useMutation(AddProductToWishlistDocument)
33
+
34
+ useEffect(() => {
35
+ if (!isLoggedIn) return
36
+
37
+ if (!guestDataSkus.length) return
38
+
39
+ if (!validatedItems.length) {
40
+ /** Only outdated items were found, purge them */
41
+ cache.evict({
42
+ id: cache.identify({ __typename: 'GuestWishlist' }),
43
+ })
44
+ return
45
+ }
46
+
47
+ const wishlist =
48
+ guestWishlistData?.items.filter((item) => validatedItems.includes(item.sku)) || []
49
+
50
+ if (!wishlist.length) return
51
+
52
+ const payload = wishlist.map((item) => ({
53
+ sku: item.sku,
54
+ selected_options: item.selected_options,
55
+ quantity: item.quantity,
56
+ }))
57
+
58
+ addWishlistItem({ variables: { input: payload } }).then(() =>
59
+ cache.evict({
60
+ id: cache.identify({ __typename: 'GuestWishlist' }),
61
+ })
62
+ )
63
+ }, [isLoggedIn])
64
+ }
@@ -0,0 +1,9 @@
1
+ import { useQuery } from '@graphcommerce/graphql'
2
+ import { StoreConfigDocument } from '@graphcommerce/magento-store'
3
+
4
+ export function useWishlistEnabled() {
5
+ const isEnabled =
6
+ useQuery(StoreConfigDocument).data?.storeConfig?.magento_wishlist_general_is_enabled === '1'
7
+
8
+ return isEnabled
9
+ }
@@ -0,0 +1,53 @@
1
+ import { useQuery } from '@graphcommerce/graphql'
2
+ import { CustomerTokenDocument } from '@graphcommerce/magento-customer'
3
+ import { GetGuestWishlistProductsDocument } from '../queries/GetGuestWishlistProducts.gql'
4
+ import { GetWishlistProductsDocument } from '../queries/GetWishlistProducts.gql'
5
+ import { GuestWishlistDocument } from '../queries/GuestWishlist.gql'
6
+
7
+ export function useWishlistItems() {
8
+ const { data: token } = useQuery(CustomerTokenDocument)
9
+ const isLoggedIn = token?.customerToken && token?.customerToken.valid
10
+
11
+ let wishlistItems
12
+
13
+ /** Get customer wishlist from session */
14
+ const { data: GetCustomerWishlistData, loading: loadingCustomerItems } = useQuery(
15
+ GetWishlistProductsDocument,
16
+ {
17
+ skip: !isLoggedIn,
18
+ ssr: false,
19
+ },
20
+ )
21
+
22
+ /** Get guest wishlist items from cache and hydrate with catalog data */
23
+ const { data: guestWishlistData, loading: loadingGuestWishlistData } = useQuery(
24
+ GuestWishlistDocument,
25
+ {
26
+ ssr: false,
27
+ skip: isLoggedIn === true,
28
+ },
29
+ )
30
+ const guestData = guestWishlistData?.guestWishlist?.items.map((item) => item?.sku) || []
31
+
32
+ const { data: productGuestItems, loading: loadingGuestItems } = useQuery(
33
+ GetGuestWishlistProductsDocument,
34
+ {
35
+ ssr: false,
36
+ variables: {
37
+ filters: { sku: { in: guestData } },
38
+ },
39
+ skip: loadingGuestWishlistData || guestData.length === 0,
40
+ },
41
+ )
42
+
43
+ if (isLoggedIn) {
44
+ wishlistItems = GetCustomerWishlistData?.customer?.wishlists[0]?.items_v2?.items
45
+ } else {
46
+ wishlistItems = productGuestItems?.products?.items || []
47
+ }
48
+
49
+ return {
50
+ items: wishlistItems,
51
+ loading: loadingGuestWishlistData || loadingGuestItems || loadingCustomerItems,
52
+ }
53
+ }
package/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ export * from './components'
2
+ export * from './hooks'
3
+
4
+ export * from './queries/GetGuestWishlistProducts.gql'
5
+ export * from './queries/GetWishlistProducts.gql'
6
+ export * from './queries/AddProductToWishlist.gql'
7
+ export * from './queries/RemoveProductFromWishlist.gql'
8
+ export * from './queries/WishlistSummaryFragment.gql'
9
+ export * from './queries/GetIsInWishlists.gql'
10
+ export * from './queries/GuestWishlist.gql'
11
+
12
+ export * from './typePolicies'
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@graphcommerce/magento-wishlist",
3
+ "version": "1.0.0",
4
+ "sideEffects": false,
5
+ "prettier": "@graphcommerce/prettier-config-pwa",
6
+ "browserslist": [
7
+ "extends @graphcommerce/browserslist-config-pwa"
8
+ ],
9
+ "eslintConfig": {
10
+ "extends": "@graphcommerce/eslint-config-pwa",
11
+ "parserOptions": {
12
+ "project": "./tsconfig.json"
13
+ }
14
+ },
15
+ "devDependencies": {
16
+ "@graphcommerce/eslint-config-pwa": "^4.1.6",
17
+ "@graphcommerce/prettier-config-pwa": "^4.0.6",
18
+ "@graphcommerce/typescript-config-pwa": "^4.0.2",
19
+ "@playwright/test": "^1.21.1"
20
+ },
21
+ "dependencies": {
22
+ "@graphcommerce/graphql": "3.1.1",
23
+ "@graphcommerce/image": "3.1.5",
24
+ "@graphcommerce/magento-cart": "4.2.10",
25
+ "@graphcommerce/magento-customer": "4.2.8",
26
+ "@graphcommerce/magento-product": "4.3.0",
27
+ "@graphcommerce/magento-product-configurable": "4.1.0",
28
+ "@graphcommerce/magento-store": "4.2.0",
29
+ "@graphcommerce/next-ui": "4.7.0"
30
+ },
31
+ "peerDependencies": {
32
+ "@lingui/macro": "^3.13.2",
33
+ "@mui/material": "^5.5.3",
34
+ "framer-motion": "^6.2.4",
35
+ "next": "^12.1.2",
36
+ "react": "^17.0.2",
37
+ "react-dom": "^17.0.2"
38
+ }
39
+ }
@@ -0,0 +1,13 @@
1
+ type WishlistItemInput {
2
+ sku: String
3
+ quantity: Int!
4
+ selected_options: [ID]
5
+ }
6
+
7
+ mutation addProductToWishlist($input: [WishlistItemInput!]!) {
8
+ addProductsToWishlist(wishlistId: 0, wishlistItems: $input) {
9
+ wishlist {
10
+ ...WishlistSummaryFragment
11
+ }
12
+ }
13
+ }