@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.
- package/CHANGELOG.md +13 -0
- package/README.md +62 -0
- package/components/ProductWishlistChip/ProductWishlistChip.graphql +4 -0
- package/components/ProductWishlistChip/ProductWishlistChip.tsx +5 -0
- package/components/ProductWishlistChip/ProductWishlistChipBase.tsx +180 -0
- package/components/ProductWishlistChip/ProductWishlistChipDetail.tsx +12 -0
- package/components/ProductWishlistChip/ProductWishlistChipDetailConfigurable.tsx +23 -0
- package/components/WishlistFab/WishlistFab.tsx +91 -0
- package/components/WishlistItem/ProductAddToCart.tsx +135 -0
- package/components/WishlistItem/WishlistItem.graphql +3 -0
- package/components/WishlistItem/WishlistItem.tsx +30 -0
- package/components/WishlistItem/WishlistItemBase.tsx +303 -0
- package/components/WishlistItem/WishlistItemConfigurable.graphql +8 -0
- package/components/WishlistItem/WishlistItemConfigurable.tsx +11 -0
- package/components/WishlistItem/WishlistItemProduct.graphql +11 -0
- package/components/WishlistItems/WishlistItems.graphql +12 -0
- package/components/WishlistItems/WishlistItems.tsx +33 -0
- package/components/WishlistMenuFabItem/WishlistMenuFabItem.tsx +81 -0
- package/components/index.ts +10 -0
- package/hooks/index.ts +3 -0
- package/hooks/useMergeGuestWishlistWithCustomer.tsx +64 -0
- package/hooks/useWishlistEnabled.tsx +9 -0
- package/hooks/useWishlistItems.tsx +53 -0
- package/index.ts +12 -0
- package/package.json +39 -0
- package/queries/AddProductToWishlist.graphql +13 -0
- package/queries/GetGuestWishlistProducts.graphql +14 -0
- package/queries/GetIsInWishlists.graphql +7 -0
- package/queries/GetWishlistProducts.graphql +19 -0
- package/queries/GuestWishlist.graphql +11 -0
- package/queries/GuestWishlist.graphqls +13 -0
- package/queries/RemoveProductFromWishlist.graphql +7 -0
- package/queries/WishlistStoreConfigFragment.graphql +3 -0
- package/queries/WishlistSummaryFragment.graphql +13 -0
- package/tsconfig.json +5 -0
- 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,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,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,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
|
+
}
|