@graphcommerce/magento-compare 6.2.0-canary.9
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 +3 -0
- package/Config.graphqls +17 -0
- package/components/CompareFab.tsx +100 -0
- package/components/CompareListAttributes.tsx +45 -0
- package/components/CompareListForm.tsx +83 -0
- package/components/CompareListIntroText.tsx +21 -0
- package/components/CompareListItems.tsx +28 -0
- package/components/CompareListRow.tsx +62 -0
- package/components/CompareListRowMoreInformation.tsx +58 -0
- package/components/CompareListSelect.tsx +77 -0
- package/components/CompareMessageSnackbar.tsx +59 -0
- package/components/CompareProductButton.tsx +59 -0
- package/components/CompareProductToggle.tsx +155 -0
- package/components/EmptyCompareList.tsx +19 -0
- package/components/EmptyCompareListButton.tsx +58 -0
- package/components/index.ts +13 -0
- package/graphql/AddProductsToCompareList.graphql +5 -0
- package/graphql/AssignCustomerToCompareList.graphql +5 -0
- package/graphql/ComparableItem.graphql +11 -0
- package/graphql/CompareList.graphql +5 -0
- package/graphql/CompareListFragment.graphql +12 -0
- package/graphql/CompareProductIdInternal.graphql +6 -0
- package/graphql/CompareSummary.graphql +5 -0
- package/graphql/CompareSummaryFragment.graphql +7 -0
- package/graphql/CreateCompareList.graphql +5 -0
- package/graphql/CurrentCompareUid.graphql +6 -0
- package/graphql/CurrentCompareUid.graphqls +7 -0
- package/graphql/CustomerCompareList.graphql +7 -0
- package/graphql/DeleteCompareList.graphql +5 -0
- package/graphql/RemoveProductsFromCompareList.graphql +5 -0
- package/graphql/index.ts +12 -0
- package/hooks/index.ts +6 -0
- package/hooks/useAssignCurrentCompareListUid.ts +16 -0
- package/hooks/useClearCurrentCompareListUid.ts +15 -0
- package/hooks/useCompareList.ts +10 -0
- package/hooks/useCompareListStyles.ts +21 -0
- package/hooks/useCompareListUidCreate.ts +27 -0
- package/hooks/useCompareSummary.ts +13 -0
- package/index.ts +3 -0
- package/next-env.d.ts +4 -0
- package/package.json +38 -0
- package/plugins/AddCompareFabNextToCart.tsx +19 -0
- package/plugins/AddCompareToProductPage.tsx +36 -0
- package/plugins/AddCompareTypePolicies.tsx +25 -0
- package/plugins/CompareAbleProductListItem.tsx +41 -0
- package/tsconfig.json +5 -0
- package/typePolicies.ts +12 -0
package/CHANGELOG.md
ADDED
package/Config.graphqls
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
enum CompareVariant {
|
|
2
|
+
ICON
|
|
3
|
+
CHECKBOX
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
extend input GraphCommerceConfig {
|
|
7
|
+
"""
|
|
8
|
+
Use compare functionality
|
|
9
|
+
"""
|
|
10
|
+
compare: Boolean
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
By default the compare feature is denoted with a 'compare ICON' (2 arrows facing one another).
|
|
14
|
+
This may be fine for experienced users, but for more clarity it's also possible to present the compare feature as a CHECKBOX accompanied by the 'Compare' label
|
|
15
|
+
"""
|
|
16
|
+
compareVariant: CompareVariant
|
|
17
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import {
|
|
2
|
+
extendableComponent,
|
|
3
|
+
IconSvg,
|
|
4
|
+
useFabSize,
|
|
5
|
+
iconCompare,
|
|
6
|
+
useScrollY,
|
|
7
|
+
} from '@graphcommerce/next-ui'
|
|
8
|
+
import { i18n } from '@lingui/core'
|
|
9
|
+
import { Trans } from '@lingui/react'
|
|
10
|
+
import { styled, Box, SxProps, Theme, NoSsr, Badge, Button, ButtonProps } from '@mui/material'
|
|
11
|
+
import { m, useTransform } from 'framer-motion'
|
|
12
|
+
import React from 'react'
|
|
13
|
+
import { useCompareSummary } from '../hooks'
|
|
14
|
+
|
|
15
|
+
export type CompareFabProps = {
|
|
16
|
+
icon?: React.ReactNode
|
|
17
|
+
sx?: SxProps<Theme>
|
|
18
|
+
} & Pick<ButtonProps, 'color' | 'size' | 'variant'>
|
|
19
|
+
|
|
20
|
+
type CompareFabContentProps = CompareFabProps & { total_quantity: number }
|
|
21
|
+
|
|
22
|
+
const MotionDiv = styled(m.div)({})
|
|
23
|
+
|
|
24
|
+
const MotionFab = m(
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
26
|
+
React.forwardRef<any, Omit<ButtonProps, 'style' | 'onDrag'>>((props, ref) => (
|
|
27
|
+
<Button variant='pill' {...props} ref={ref} />
|
|
28
|
+
)),
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
const { classes } = extendableComponent('CompareFab', ['root', 'compare', 'shadow'] as const)
|
|
32
|
+
|
|
33
|
+
function CompareFabContent(props: CompareFabContentProps) {
|
|
34
|
+
const { total_quantity, icon, sx = [], ...rest } = props
|
|
35
|
+
const scrollY = useScrollY()
|
|
36
|
+
const opacity = useTransform(scrollY, [50, 60], [0, 1])
|
|
37
|
+
|
|
38
|
+
const compareIcon = icon ?? <IconSvg src={iconCompare} size='large' />
|
|
39
|
+
const fabIconSize = useFabSize('responsive')
|
|
40
|
+
|
|
41
|
+
if (total_quantity === 0) return null
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Box
|
|
45
|
+
className={classes.root}
|
|
46
|
+
sx={[{ position: 'relative', height: fabIconSize }, ...(Array.isArray(sx) ? sx : [sx])]}
|
|
47
|
+
>
|
|
48
|
+
<Badge
|
|
49
|
+
color='primary'
|
|
50
|
+
variant='standard'
|
|
51
|
+
overlap='circular'
|
|
52
|
+
badgeContent={total_quantity}
|
|
53
|
+
sx={{ height: '100%', '& .MuiBadge-badge': { zIndex: 2000 } }}
|
|
54
|
+
>
|
|
55
|
+
<MotionFab
|
|
56
|
+
href='/compare'
|
|
57
|
+
className={classes.compare}
|
|
58
|
+
aria-label={i18n._(/* i18n */ 'Compare')}
|
|
59
|
+
color='inherit'
|
|
60
|
+
sx={(theme) => ({
|
|
61
|
+
width: 'unset',
|
|
62
|
+
backgroundColor: `${theme.palette.background.paper} !important`,
|
|
63
|
+
[theme.breakpoints.down('md')]: {},
|
|
64
|
+
})}
|
|
65
|
+
{...rest}
|
|
66
|
+
>
|
|
67
|
+
{compareIcon} <Trans id='Compare' />
|
|
68
|
+
</MotionFab>
|
|
69
|
+
</Badge>
|
|
70
|
+
|
|
71
|
+
<MotionDiv
|
|
72
|
+
className={classes.shadow}
|
|
73
|
+
sx={(theme) => ({
|
|
74
|
+
pointerEvents: 'none',
|
|
75
|
+
borderRadius: '99em',
|
|
76
|
+
position: 'absolute',
|
|
77
|
+
height: '100%',
|
|
78
|
+
width: '100%',
|
|
79
|
+
boxShadow: 5,
|
|
80
|
+
top: 0,
|
|
81
|
+
[theme.breakpoints.down('md')]: {
|
|
82
|
+
opacity: '1 !important',
|
|
83
|
+
},
|
|
84
|
+
})}
|
|
85
|
+
style={{ opacity }}
|
|
86
|
+
/>
|
|
87
|
+
</Box>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function CompareFab(props: CompareFabProps) {
|
|
92
|
+
const compareList = useCompareSummary()
|
|
93
|
+
const totalQuantity = compareList.data?.compareList?.item_count ?? 0
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<NoSsr fallback={<CompareFabContent total_quantity={0} {...props} />}>
|
|
97
|
+
{totalQuantity > 0 && <CompareFabContent total_quantity={totalQuantity} {...props} />}
|
|
98
|
+
</NoSsr>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { filterNonNullableKeys } from '@graphcommerce/next-ui'
|
|
2
|
+
import { Box, SxProps, Theme } from '@mui/material'
|
|
3
|
+
import { useCompareList } from '../hooks/useCompareList'
|
|
4
|
+
import { useCompareVisibleItems } from './CompareListForm'
|
|
5
|
+
import { CompareListRow } from './CompareListRow'
|
|
6
|
+
import { CompareListRowMoreInformation } from './CompareListRowMoreInformation'
|
|
7
|
+
|
|
8
|
+
export type CompareListAttributesProps = {
|
|
9
|
+
sx?: SxProps<Theme>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function CompareListAttributes(props: CompareListAttributesProps) {
|
|
13
|
+
const { sx } = props
|
|
14
|
+
const compareList = useCompareList()
|
|
15
|
+
const compareListAttributes = filterNonNullableKeys(compareList.data?.compareList?.attributes)
|
|
16
|
+
const items = useCompareVisibleItems()
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<Box
|
|
20
|
+
sx={[
|
|
21
|
+
(theme) => ({
|
|
22
|
+
bgcolor: theme.palette.background.default,
|
|
23
|
+
'& :first-of-type > div': {
|
|
24
|
+
mt: 0,
|
|
25
|
+
},
|
|
26
|
+
mx: `calc(${theme.page.horizontal} * -1)`,
|
|
27
|
+
py: theme.spacings.md,
|
|
28
|
+
px: theme.page.horizontal,
|
|
29
|
+
[theme.breakpoints.up('lg')]: {
|
|
30
|
+
borderRadius: theme.shape.borderRadius * 1.5,
|
|
31
|
+
mx: `calc(${theme.spacings.lg} * -1)`,
|
|
32
|
+
p: theme.spacings.lg,
|
|
33
|
+
},
|
|
34
|
+
}),
|
|
35
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
36
|
+
]}
|
|
37
|
+
>
|
|
38
|
+
{compareListAttributes.map((attribute) => (
|
|
39
|
+
<CompareListRow compareAbleItems={items} attribute={attribute} key={attribute?.code} />
|
|
40
|
+
))}
|
|
41
|
+
|
|
42
|
+
<CompareListRowMoreInformation compareAbleItems={items} />
|
|
43
|
+
</Box>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useForm, useFormPersist, UseFormReturn, useWatch } from '@graphcommerce/ecommerce-ui'
|
|
2
|
+
import { filterNonNullableKeys, nonNullable } from '@graphcommerce/next-ui'
|
|
3
|
+
import React, { createContext, useContext, useEffect, useMemo, useRef } from 'react'
|
|
4
|
+
import { useCompareList } from '../hooks'
|
|
5
|
+
|
|
6
|
+
type CompareListFormProps = { children?: React.ReactNode }
|
|
7
|
+
|
|
8
|
+
type FormFields = { selected: number[] }
|
|
9
|
+
|
|
10
|
+
type CompareFormContextType = Omit<UseFormReturn<FormFields>, 'formState' | 'watch'> & {
|
|
11
|
+
selectedPrevious: React.MutableRefObject<number[]>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const CompareFormContext = createContext<CompareFormContextType | undefined>(undefined)
|
|
15
|
+
|
|
16
|
+
export function CompareListForm(props: CompareListFormProps) {
|
|
17
|
+
const { children } = props
|
|
18
|
+
|
|
19
|
+
const compareList = useCompareList()
|
|
20
|
+
const compareListData = compareList.data
|
|
21
|
+
const compareListCount = compareListData?.compareList?.item_count ?? 0
|
|
22
|
+
const gridColumns = compareListCount <= 3 ? compareListCount : 3
|
|
23
|
+
|
|
24
|
+
const form = useForm<FormFields>({
|
|
25
|
+
defaultValues: { selected: [...Array(gridColumns).keys()] },
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
useFormPersist({ form, name: 'CompareList', storage: 'localStorage' })
|
|
29
|
+
const selectedState = form.watch('selected')
|
|
30
|
+
const selectedPrevious = useRef<number[]>(selectedState)
|
|
31
|
+
const compareAbleItems = compareListData?.compareList?.items
|
|
32
|
+
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (compareAbleItems?.length) {
|
|
35
|
+
selectedPrevious.current = selectedState
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* It's possible that the user has 5 items in his comparelist, so [0,1,2,3,4] are all selectable indexes
|
|
39
|
+
* If the user has a selected state of indexes [0,3,4] but then removes the 4th item, the currentCompareProducts[4] would be undefined and the UI would be corrupt
|
|
40
|
+
* So we need to get the first index that isnt already in the selectedState array (as we cant have duplicates)
|
|
41
|
+
*/
|
|
42
|
+
selectedState.forEach((selectedIndex, index) => {
|
|
43
|
+
if (selectedIndex >= compareAbleItems.length) {
|
|
44
|
+
const allIndexes = [...Array(compareAbleItems.length).keys()]
|
|
45
|
+
const allowedIndexes = allIndexes.filter((el) => !selectedState.includes(el))
|
|
46
|
+
form.setValue(`selected.${index}`, allowedIndexes[0])
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
// if there are less items in the compare list than in our selectedState
|
|
51
|
+
if (compareListCount < selectedState.length) {
|
|
52
|
+
form.setValue(`selected`, [...Array(compareListCount).keys()])
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// if there are less items in our selectedState than we have columns
|
|
56
|
+
if (selectedState.length < gridColumns) {
|
|
57
|
+
form.setValue(`selected`, [...Array(gridColumns).keys()])
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}, [compareAbleItems?.length, compareListCount, form, gridColumns, selectedState])
|
|
61
|
+
|
|
62
|
+
const value = useMemo(
|
|
63
|
+
() => ({ ...form, selectedPrevious } satisfies CompareFormContextType),
|
|
64
|
+
[form],
|
|
65
|
+
)
|
|
66
|
+
return <CompareFormContext.Provider value={value}>{children}</CompareFormContext.Provider>
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function useCompareForm() {
|
|
70
|
+
const context = useContext(CompareFormContext)
|
|
71
|
+
if (!context) throw Error('useCompareForm must be used inside a CompareForm')
|
|
72
|
+
return context
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function useCompareVisibleItems() {
|
|
76
|
+
const { control } = useCompareForm()
|
|
77
|
+
const selected = useWatch<FormFields>({ control, name: 'selected' })
|
|
78
|
+
|
|
79
|
+
const selectedState = Array.isArray(selected) ? selected : [selected]
|
|
80
|
+
|
|
81
|
+
const compareList = filterNonNullableKeys(useCompareList().data?.compareList?.items)
|
|
82
|
+
return selectedState.map((i) => compareList[i]).filter(nonNullable)
|
|
83
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Box, Container } from '@mui/material'
|
|
2
|
+
|
|
3
|
+
type CompareListIntroTextProps = {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function CompareListIntroText({ children }: CompareListIntroTextProps) {
|
|
8
|
+
return (
|
|
9
|
+
<Box
|
|
10
|
+
sx={(theme) => ({
|
|
11
|
+
position: 'relative',
|
|
12
|
+
textAlign: 'center',
|
|
13
|
+
paddingBottom: 2,
|
|
14
|
+
background: theme.palette.background.paper,
|
|
15
|
+
zIndex: 11,
|
|
16
|
+
})}
|
|
17
|
+
>
|
|
18
|
+
<Container>{children}</Container>
|
|
19
|
+
</Box>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AddProductsToCartForm,
|
|
3
|
+
ProductItemsGridProps,
|
|
4
|
+
ProductListItemsBase,
|
|
5
|
+
} from '@graphcommerce/magento-product'
|
|
6
|
+
import { useCompareListStyles } from '../hooks/useCompareListStyles'
|
|
7
|
+
import { useCompareVisibleItems } from './CompareListForm'
|
|
8
|
+
|
|
9
|
+
export type CompareListItemsProps = Pick<ProductItemsGridProps, 'renderers' | 'sx'>
|
|
10
|
+
|
|
11
|
+
export function CompareListItems(props: CompareListItemsProps) {
|
|
12
|
+
const { renderers, sx } = props
|
|
13
|
+
|
|
14
|
+
const compareListStyles = useCompareListStyles()
|
|
15
|
+
|
|
16
|
+
const items = useCompareVisibleItems().map((i) => i.product)
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<AddProductsToCartForm>
|
|
20
|
+
<ProductListItemsBase
|
|
21
|
+
title='Compare items'
|
|
22
|
+
items={items}
|
|
23
|
+
renderers={renderers}
|
|
24
|
+
sx={[compareListStyles, ...(Array.isArray(sx) ? sx : [sx])]}
|
|
25
|
+
/>
|
|
26
|
+
</AddProductsToCartForm>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { SectionContainer } from '@graphcommerce/next-ui'
|
|
2
|
+
import { Box } from '@mui/material'
|
|
3
|
+
import { ComparableItemFragment } from '../graphql'
|
|
4
|
+
import { useCompareListStyles } from '../hooks/useCompareListStyles'
|
|
5
|
+
|
|
6
|
+
export type CompareRowProps = {
|
|
7
|
+
compareAbleItems: ComparableItemFragment[]
|
|
8
|
+
attribute: {
|
|
9
|
+
code: string
|
|
10
|
+
label: string
|
|
11
|
+
} | null
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function CompareListRow(props: CompareRowProps) {
|
|
15
|
+
const { attribute, compareAbleItems } = props
|
|
16
|
+
const columnCount = compareAbleItems.length <= 3 ? compareAbleItems.length : 3
|
|
17
|
+
|
|
18
|
+
const compareListStyles = useCompareListStyles()
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<Box>
|
|
22
|
+
<SectionContainer
|
|
23
|
+
labelLeft={attribute?.label}
|
|
24
|
+
sx={(theme) => ({
|
|
25
|
+
'& .SectionHeader-root': {
|
|
26
|
+
justifyContent: 'center',
|
|
27
|
+
borderBottom: 'none',
|
|
28
|
+
pb: 0,
|
|
29
|
+
'& > .MuiTypography-root': {
|
|
30
|
+
pb: theme.spacings.xxs,
|
|
31
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
32
|
+
width: `calc(calc(calc(100% / 3) * ${columnCount}) + ${
|
|
33
|
+
columnCount > 1 ? theme.spacings.md : '0px'
|
|
34
|
+
})`,
|
|
35
|
+
[theme.breakpoints.down('md')]: {
|
|
36
|
+
width: '100%',
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
})}
|
|
41
|
+
>
|
|
42
|
+
<Box sx={[compareListStyles, (theme) => ({ mb: theme.spacings.lg })]}>
|
|
43
|
+
{compareAbleItems?.map((item, idx) => (
|
|
44
|
+
<Box
|
|
45
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
46
|
+
key={idx}
|
|
47
|
+
sx={{
|
|
48
|
+
'& > p:first-of-type': { marginTop: 0 },
|
|
49
|
+
'& > p:last-of-type': { marginBottom: 0 },
|
|
50
|
+
}}
|
|
51
|
+
dangerouslySetInnerHTML={{
|
|
52
|
+
__html:
|
|
53
|
+
item?.attributes.find((itemAttribute) => itemAttribute?.code === attribute?.code)
|
|
54
|
+
?.value ?? '',
|
|
55
|
+
}}
|
|
56
|
+
/>
|
|
57
|
+
))}
|
|
58
|
+
</Box>
|
|
59
|
+
</SectionContainer>
|
|
60
|
+
</Box>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { productLink } from '@graphcommerce/magento-product'
|
|
2
|
+
import { Button, iconChevronRight, IconSvg, SectionContainer } from '@graphcommerce/next-ui'
|
|
3
|
+
import { Trans } from '@lingui/react'
|
|
4
|
+
import { Box } from '@mui/material'
|
|
5
|
+
import { useCompareListStyles } from '../hooks/useCompareListStyles'
|
|
6
|
+
import { CompareRowProps } from './CompareListRow'
|
|
7
|
+
|
|
8
|
+
export type CompareListRowMoreInformationProps = Pick<CompareRowProps, 'compareAbleItems'>
|
|
9
|
+
|
|
10
|
+
export function CompareListRowMoreInformation(props: CompareListRowMoreInformationProps) {
|
|
11
|
+
const { compareAbleItems } = props
|
|
12
|
+
const columnCount = compareAbleItems.length <= 3 ? compareAbleItems.length : 3
|
|
13
|
+
|
|
14
|
+
const compareListStyles = useCompareListStyles()
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Box>
|
|
18
|
+
<SectionContainer
|
|
19
|
+
labelLeft={<Trans id='More information' />}
|
|
20
|
+
sx={(theme) => ({
|
|
21
|
+
'& .SectionHeader-root': {
|
|
22
|
+
justifyContent: 'center',
|
|
23
|
+
borderBottom: 'none',
|
|
24
|
+
pb: 0,
|
|
25
|
+
'& > .MuiTypography-root': {
|
|
26
|
+
pb: theme.spacings.xxs,
|
|
27
|
+
borderBottom: `1px solid ${theme.palette.divider}`,
|
|
28
|
+
width: `calc(calc(calc(100% / 3) * ${columnCount}) + ${
|
|
29
|
+
columnCount > 1 ? theme.spacings.md : '0px'
|
|
30
|
+
})`,
|
|
31
|
+
[theme.breakpoints.down('md')]: {
|
|
32
|
+
width: '100%',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
})}
|
|
37
|
+
>
|
|
38
|
+
<Box sx={compareListStyles}>
|
|
39
|
+
{compareAbleItems?.map((item) => {
|
|
40
|
+
if (!item?.product) return null
|
|
41
|
+
return (
|
|
42
|
+
<Box key={item.uid}>
|
|
43
|
+
<Button
|
|
44
|
+
variant='inline'
|
|
45
|
+
href={productLink(item?.product)}
|
|
46
|
+
endIcon={<IconSvg key='icon' src={iconChevronRight} size='inherit' />}
|
|
47
|
+
sx={{ justifyContent: 'flex-start' }}
|
|
48
|
+
>
|
|
49
|
+
<Trans id='View Product' />
|
|
50
|
+
</Button>
|
|
51
|
+
</Box>
|
|
52
|
+
)
|
|
53
|
+
})}
|
|
54
|
+
</Box>
|
|
55
|
+
</SectionContainer>
|
|
56
|
+
</Box>
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { SelectElement } from '@graphcommerce/ecommerce-ui'
|
|
2
|
+
import { useMotionValueValue } from '@graphcommerce/framer-utils'
|
|
3
|
+
import { useScrollY } from '@graphcommerce/next-ui'
|
|
4
|
+
import { Box, Container, FormControl, SxProps, Theme, useTheme } from '@mui/material'
|
|
5
|
+
import { useCompareList, useCompareListStyles } from '../hooks'
|
|
6
|
+
import { useCompareForm } from './CompareListForm'
|
|
7
|
+
|
|
8
|
+
export type CompareListSelectProps = {
|
|
9
|
+
bgColor?: 'default' | 'paper'
|
|
10
|
+
sx?: SxProps<Theme>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function CompareListSelect(props: CompareListSelectProps) {
|
|
14
|
+
const { bgColor = 'paper', sx = [] } = props
|
|
15
|
+
|
|
16
|
+
const compareList = useCompareList().data?.compareList
|
|
17
|
+
const compareListCount = compareList?.item_count ?? 0
|
|
18
|
+
const gridColumns = compareListCount <= 3 ? compareListCount : 3
|
|
19
|
+
const compareAbleItems = compareList?.items ?? []
|
|
20
|
+
|
|
21
|
+
const { control, selectedPrevious, setValue } = useCompareForm()
|
|
22
|
+
|
|
23
|
+
const compareListStyles = useCompareListStyles()
|
|
24
|
+
const theme2 = useTheme()
|
|
25
|
+
const scrollY = useScrollY()
|
|
26
|
+
const scrolled = useMotionValueValue(scrollY, (y) => ({
|
|
27
|
+
xs:
|
|
28
|
+
y >
|
|
29
|
+
parseInt(theme2.appShell.headerHeightSm, 10) -
|
|
30
|
+
parseInt(theme2.appShell.headerHeightSm, 10) * 0.5,
|
|
31
|
+
md: y > parseInt(theme2.appShell.headerHeightSm, 10),
|
|
32
|
+
}))
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<Box
|
|
36
|
+
sx={[
|
|
37
|
+
(theme) => ({
|
|
38
|
+
pt: 1,
|
|
39
|
+
pb: theme.spacings.xxs,
|
|
40
|
+
background: theme.palette.background[bgColor],
|
|
41
|
+
position: 'sticky',
|
|
42
|
+
zIndex: 10,
|
|
43
|
+
top: {
|
|
44
|
+
xs: `calc(${theme.appShell.headerHeightSm} / 2)`,
|
|
45
|
+
lg: theme.page.vertical,
|
|
46
|
+
},
|
|
47
|
+
boxShadow: {
|
|
48
|
+
xs: scrolled.xs ? theme.shadows[1] : 'none',
|
|
49
|
+
md: scrolled.md ? theme.shadows[1] : 'none',
|
|
50
|
+
},
|
|
51
|
+
}),
|
|
52
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
|
53
|
+
]}
|
|
54
|
+
>
|
|
55
|
+
<Container sx={{ ...compareListStyles }}>
|
|
56
|
+
{[...Array(gridColumns).keys()].map((compareSelectIndex) => (
|
|
57
|
+
<FormControl key={compareSelectIndex}>
|
|
58
|
+
<SelectElement
|
|
59
|
+
control={control}
|
|
60
|
+
name={`selected.${compareSelectIndex}`}
|
|
61
|
+
options={compareAbleItems.map((i, id) => ({
|
|
62
|
+
id,
|
|
63
|
+
label: i?.product?.name ?? '',
|
|
64
|
+
}))}
|
|
65
|
+
size='small'
|
|
66
|
+
onChange={(to) => {
|
|
67
|
+
const from = selectedPrevious.current?.[compareSelectIndex]
|
|
68
|
+
const found = selectedPrevious.current?.indexOf(Number(to))
|
|
69
|
+
if (found > -1) setValue(`selected.${found}`, from)
|
|
70
|
+
}}
|
|
71
|
+
/>
|
|
72
|
+
</FormControl>
|
|
73
|
+
))}
|
|
74
|
+
</Container>
|
|
75
|
+
</Box>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Button, iconChevronRight, IconSvg, MessageSnackbar } from '@graphcommerce/next-ui'
|
|
2
|
+
import { Trans } from '@lingui/react'
|
|
3
|
+
import { SetStateAction } from 'react'
|
|
4
|
+
|
|
5
|
+
type CompareMessageSnackbarProps = {
|
|
6
|
+
count: number | undefined
|
|
7
|
+
name: string | null | undefined
|
|
8
|
+
displayMessageBar: boolean
|
|
9
|
+
setDisplayMessageBar: (value: SetStateAction<boolean>) => void
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function CompareMessageSnackbar(props: CompareMessageSnackbarProps) {
|
|
13
|
+
const { count, name, displayMessageBar, setDisplayMessageBar } = props
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<MessageSnackbar
|
|
17
|
+
open={displayMessageBar}
|
|
18
|
+
onMouseDown={(e) => {
|
|
19
|
+
e.stopPropagation()
|
|
20
|
+
}}
|
|
21
|
+
onClick={(e) => {
|
|
22
|
+
e.stopPropagation()
|
|
23
|
+
}}
|
|
24
|
+
onClose={() => {
|
|
25
|
+
setDisplayMessageBar(false)
|
|
26
|
+
}}
|
|
27
|
+
variant='pill'
|
|
28
|
+
action={
|
|
29
|
+
count && count > 1 ? (
|
|
30
|
+
<Button
|
|
31
|
+
href='/compare'
|
|
32
|
+
id='view-wishlist-button'
|
|
33
|
+
size='medium'
|
|
34
|
+
variant='pill'
|
|
35
|
+
color='secondary'
|
|
36
|
+
fullWidth
|
|
37
|
+
endIcon={<IconSvg src={iconChevronRight} />}
|
|
38
|
+
>
|
|
39
|
+
<Trans id='View comparison' />
|
|
40
|
+
</Button>
|
|
41
|
+
) : null
|
|
42
|
+
}
|
|
43
|
+
>
|
|
44
|
+
<>
|
|
45
|
+
<Trans
|
|
46
|
+
id='<0>{name}</0> has been added to your comparison!'
|
|
47
|
+
components={{ 0: <strong /> }}
|
|
48
|
+
values={{ name }}
|
|
49
|
+
/>
|
|
50
|
+
{count === 1 ? (
|
|
51
|
+
<>
|
|
52
|
+
{' '}
|
|
53
|
+
<Trans id='Add another product to start comparing.' />
|
|
54
|
+
</>
|
|
55
|
+
) : null}
|
|
56
|
+
</>
|
|
57
|
+
</MessageSnackbar>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { useMutation } from '@graphcommerce/graphql'
|
|
2
|
+
import { Trans } from '@lingui/react'
|
|
3
|
+
import { Badge, Box, Button, Checkbox, SxProps, Theme } from '@mui/material'
|
|
4
|
+
import { useState } from 'react'
|
|
5
|
+
import {
|
|
6
|
+
AddProductsToCompareListDocument,
|
|
7
|
+
CompareProductIdInternalFragment,
|
|
8
|
+
RemoveProductsFromCompareListDocument,
|
|
9
|
+
} from '../graphql'
|
|
10
|
+
import { useCompareSummary } from '../hooks'
|
|
11
|
+
import { useCompareListUidCreate } from '../hooks/useCompareListUidCreate'
|
|
12
|
+
import { CompareMessageSnackbar } from './CompareMessageSnackbar'
|
|
13
|
+
|
|
14
|
+
type CompareProductButtonProps = CompareProductIdInternalFragment & { sx?: SxProps<Theme> }
|
|
15
|
+
|
|
16
|
+
export function CompareProductButton(props: CompareProductButtonProps) {
|
|
17
|
+
const { compare_product_id, name, sx } = props
|
|
18
|
+
const idString = String(compare_product_id)
|
|
19
|
+
const create = useCompareListUidCreate()
|
|
20
|
+
const compareList = useCompareSummary()
|
|
21
|
+
const inCompareList =
|
|
22
|
+
compareList.data?.compareList?.items?.some((i) => i?.uid === idString) ?? false
|
|
23
|
+
const [add] = useMutation(AddProductsToCompareListDocument)
|
|
24
|
+
const [remove] = useMutation(RemoveProductsFromCompareListDocument)
|
|
25
|
+
const [displayMessageBar, setDisplayMessageBar] = useState(false)
|
|
26
|
+
|
|
27
|
+
const handleClick: React.MouseEventHandler<HTMLButtonElement> = async (e) => {
|
|
28
|
+
if (inCompareList) {
|
|
29
|
+
await remove({ variables: { products: [idString], uid: await create() } })
|
|
30
|
+
} else {
|
|
31
|
+
await add({ variables: { products: [idString], uid: await create() } })
|
|
32
|
+
setDisplayMessageBar(true)
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Box sx={[...(Array.isArray(sx) ? sx : [sx])]}>
|
|
38
|
+
<Badge badgeContent={compareList.data?.compareList?.item_count} color='primary'>
|
|
39
|
+
<Button
|
|
40
|
+
variant='contained'
|
|
41
|
+
onClick={handleClick}
|
|
42
|
+
onMouseDown={(e) => e.stopPropagation()}
|
|
43
|
+
startIcon={<Checkbox checked={inCompareList} />}
|
|
44
|
+
>
|
|
45
|
+
{inCompareList ? <Trans id='In comparelist' /> : <Trans id='Compare' />}
|
|
46
|
+
</Button>
|
|
47
|
+
</Badge>
|
|
48
|
+
|
|
49
|
+
{displayMessageBar && (
|
|
50
|
+
<CompareMessageSnackbar
|
|
51
|
+
displayMessageBar={displayMessageBar}
|
|
52
|
+
setDisplayMessageBar={setDisplayMessageBar}
|
|
53
|
+
count={compareList.data?.compareList?.item_count}
|
|
54
|
+
name={name}
|
|
55
|
+
/>
|
|
56
|
+
)}
|
|
57
|
+
</Box>
|
|
58
|
+
)
|
|
59
|
+
}
|