@graphcommerce/magento-product 8.1.0-canary.3 → 8.1.0-canary.31
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/Api/ProductListItem.graphql +1 -1
- package/Api/ProductPageItem.graphql +1 -1
- package/CHANGELOG.md +157 -2
- package/components/AddProductsToCart/AddProductsToCartButton.tsx +1 -0
- package/components/AddProductsToCart/AddProductsToCartForm.tsx +7 -2
- package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +13 -14
- package/components/AddProductsToCart/UseAddProductsToCartAction.graphql +1 -1
- package/components/AddProductsToCart/findAddedItems.ts +81 -0
- package/components/AddProductsToCart/index.ts +3 -0
- package/components/AddProductsToCart/useAddProductsToCartAction.ts +6 -3
- package/components/AddProductsToCart/useFormAddProductsToCart.ts +1 -2
- package/components/JsonLdProduct/JsonLdProduct.graphql +1 -1
- package/components/JsonLdProduct/JsonLdProductOffer.graphql +2 -3
- package/components/JsonLdProduct/ProductPageJsonLd.tsx +10 -5
- package/components/JsonLdProduct/index.ts +1 -0
- package/components/ProductAddToCart/ProductAddToCart.tsx +6 -4
- package/components/ProductCustomizable/CustomizableAreaOption.tsx +41 -7
- package/components/ProductCustomizable/CustomizableDateOption.tsx +60 -7
- package/components/ProductCustomizable/CustomizableDropDownOption.tsx +63 -15
- package/components/ProductCustomizable/CustomizableFieldOption.tsx +40 -4
- package/components/ProductCustomizable/ProductCustomizable.graphql +1 -1
- package/components/ProductCustomizable/index.ts +1 -0
- package/components/ProductCustomizable/productCustomizableSelectors.ts +59 -0
- package/components/ProductFiltersPro/ProductFiltersPro.tsx +49 -12
- package/components/ProductFiltersPro/ProductFiltersProAggregations.tsx +14 -0
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +10 -10
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +23 -9
- package/components/ProductFiltersPro/ProductFiltersProCategorySection.tsx +70 -0
- package/components/ProductFiltersPro/ProductFiltersProChips.tsx +10 -8
- package/components/ProductFiltersPro/ProductFiltersProLayoutSidebar.tsx +15 -7
- package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +4 -1
- package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +9 -28
- package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +15 -0
- package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +12 -32
- package/components/ProductFiltersPro/index.ts +6 -0
- package/components/ProductFiltersPro/useProductFiltersProSort.tsx +76 -0
- package/components/ProductListFiltersContainer/ProductListFiltersContainer.tsx +2 -4
- package/components/ProductListItems/CategoryDefault.graphql +5 -0
- package/components/ProductListItems/ProductListItemsBase.tsx +1 -1
- package/components/ProductListItems/filterTypes.tsx +1 -1
- package/components/ProductListItems/filteredProductList.tsx +1 -1
- package/components/ProductListItems/productListApplyCategoryDefaults.ts +28 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.graphql +3 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +8 -3
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumbs.tsx +40 -0
- package/components/ProductPageBreadcrumb/index.ts +1 -0
- package/components/ProductPageDescription/ComplexTextValue.graphql +1 -1
- package/components/ProductPageGallery/ProductImage.graphql +1 -0
- package/components/ProductPageGallery/ProductPageGallery.tsx +14 -8
- package/components/ProductPagePrice/ProductPagePrice.graphql +3 -0
- package/components/ProductPagePrice/ProductPagePrice.tsx +11 -4
- package/components/ProductPagePrice/useCustomizableOptionPrice.ts +85 -0
- package/components/ProductShortDescription/ProductShortDescription.tsx +2 -0
- package/components/ProductStaticPaths/getProductStaticPaths.ts +2 -3
- package/components/ProductStaticPaths/getSitemapPaths.ts +3 -0
- package/components/index.ts +2 -0
- package/hooks/useProductListLink.ts +10 -5
- package/hooks/useProductListLinkReplace.ts +3 -0
- package/package.json +14 -13
- package/tsconfig.json +1 -1
@@ -12,6 +12,7 @@ export type ProductFiltersProLayoutSidebarProps = {
|
|
12
12
|
count?: React.ReactNode
|
13
13
|
pagination: React.ReactNode
|
14
14
|
header?: React.ReactNode
|
15
|
+
children?: React.ReactNode
|
15
16
|
} & Partial<OwnerProps>
|
16
17
|
|
17
18
|
type OwnerProps = {
|
@@ -32,6 +33,7 @@ export function ProductFiltersProLayoutSidebar(props: ProductFiltersProLayoutSid
|
|
32
33
|
sidebarFilters,
|
33
34
|
header,
|
34
35
|
headerPosition = 'before',
|
36
|
+
children,
|
35
37
|
} = props
|
36
38
|
|
37
39
|
const { form, submit } = useProductFiltersPro()
|
@@ -47,8 +49,6 @@ export function ProductFiltersProLayoutSidebar(props: ProductFiltersProLayoutSid
|
|
47
49
|
|
48
50
|
<FormAutoSubmit control={form.control} disabled={autoSubmitDisabled} submit={submit} />
|
49
51
|
|
50
|
-
<StickyBelowHeader sx={{ display: { md: 'none' } }}>{horizontalFilters}</StickyBelowHeader>
|
51
|
-
|
52
52
|
<Container
|
53
53
|
maxWidth={false}
|
54
54
|
className={classes.content}
|
@@ -56,12 +56,16 @@ export function ProductFiltersProLayoutSidebar(props: ProductFiltersProLayoutSid
|
|
56
56
|
display: 'grid',
|
57
57
|
gridTemplate: {
|
58
58
|
xs: `
|
59
|
-
"
|
60
|
-
"
|
61
|
-
"
|
59
|
+
"content" auto
|
60
|
+
"horizontalFilters" auto
|
61
|
+
"beforeContent" auto
|
62
|
+
"items" auto
|
63
|
+
"afterContent" auto
|
62
64
|
`,
|
63
|
-
md: `
|
64
|
-
"topleft
|
65
|
+
md: `
|
66
|
+
"topleft content" auto
|
67
|
+
"sidebar content" auto
|
68
|
+
"sidebar beforeContent" auto
|
65
69
|
"sidebar items" min-content
|
66
70
|
"sidebar afterContent" 1fr
|
67
71
|
/300px auto
|
@@ -87,6 +91,10 @@ export function ProductFiltersProLayoutSidebar(props: ProductFiltersProLayoutSid
|
|
87
91
|
{sidebarFilters}
|
88
92
|
</Box>
|
89
93
|
)}
|
94
|
+
{children && <Box gridArea='content'>{children}</Box>}
|
95
|
+
<StickyBelowHeader sx={{ display: { md: 'none', gridArea: 'horizontalFilters' } }}>
|
96
|
+
{horizontalFilters}
|
97
|
+
</StickyBelowHeader>
|
90
98
|
|
91
99
|
<Box gridArea='beforeContent' sx={{ mt: { md: 0 } }}>
|
92
100
|
{count}
|
@@ -11,11 +11,13 @@ import {
|
|
11
11
|
import { Trans } from '@lingui/react'
|
12
12
|
import { useMemo } from 'react'
|
13
13
|
import { useProductFiltersPro } from './ProductFiltersPro'
|
14
|
+
import { SxProps, Theme } from '@mui/material'
|
14
15
|
|
15
|
-
export type ProductFiltersProLimitSectionProps =
|
16
|
+
export type ProductFiltersProLimitSectionProps = { sx?: SxProps<Theme> }
|
16
17
|
|
17
18
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
18
19
|
export function ProductFiltersProLimitSection(props: ProductFiltersProLimitSectionProps) {
|
20
|
+
const { sx } = props
|
19
21
|
const { form } = useProductFiltersPro()
|
20
22
|
const { control } = form
|
21
23
|
const activePageSize = useWatch({ control, name: 'pageSize' })
|
@@ -38,6 +40,7 @@ export function ProductFiltersProLimitSection(props: ProductFiltersProLimitSecti
|
|
38
40
|
|
39
41
|
return (
|
40
42
|
<ActionCardAccordion
|
43
|
+
sx={sx}
|
41
44
|
defaultExpanded={!!activePageSize}
|
42
45
|
summary={<Trans id='Per page' />}
|
43
46
|
details={
|
@@ -1,53 +1,34 @@
|
|
1
|
-
import { useWatch } from '@graphcommerce/ecommerce-ui'
|
2
|
-
import { useQuery } from '@graphcommerce/graphql'
|
3
|
-
import { StoreConfigDocument } from '@graphcommerce/magento-store'
|
4
1
|
import {
|
5
2
|
ActionCard,
|
6
3
|
ActionCardListForm,
|
7
4
|
ChipOverlayOrPopper,
|
8
5
|
ChipOverlayOrPopperProps,
|
9
|
-
filterNonNullableKeys,
|
10
6
|
} from '@graphcommerce/next-ui'
|
11
7
|
import { Trans } from '@lingui/react'
|
12
|
-
import { useMemo } from 'react'
|
13
|
-
import { ProductListSortFragment } from '../ProductListSort/ProductListSort.gql'
|
14
8
|
import { useProductFiltersPro } from './ProductFiltersPro'
|
9
|
+
import { UseProductFiltersProSortProps, useProductFiltersProSort } from './useProductFiltersProSort'
|
15
10
|
|
16
|
-
export type ProductListActionSortProps =
|
11
|
+
export type ProductListActionSortProps = UseProductFiltersProSortProps &
|
17
12
|
Omit<
|
18
13
|
ChipOverlayOrPopperProps,
|
19
14
|
'label' | 'selected' | 'selectedLabel' | 'onApply' | 'onReset' | 'onClose' | 'children'
|
20
15
|
>
|
21
16
|
|
22
17
|
export function ProductFiltersProSortChip(props: ProductListActionSortProps) {
|
23
|
-
const { sort_fields, chipProps, ...rest } = props
|
24
|
-
const {
|
25
|
-
const {
|
26
|
-
const activeSort = useWatch({ control, name: 'sort' })
|
27
|
-
|
28
|
-
const { data: storeConfigQuery } = useQuery(StoreConfigDocument)
|
29
|
-
const defaultSort = storeConfigQuery?.storeConfig?.catalog_default_sort_by
|
30
|
-
|
31
|
-
const options = useMemo(
|
32
|
-
() =>
|
33
|
-
filterNonNullableKeys(sort_fields?.options, ['value', 'label']).map((option) => ({
|
34
|
-
...option,
|
35
|
-
value: option.value === defaultSort ? null : option.value,
|
36
|
-
title: option.label,
|
37
|
-
})),
|
38
|
-
[defaultSort, sort_fields?.options],
|
39
|
-
)
|
18
|
+
const { sort_fields, chipProps, category, ...rest } = props
|
19
|
+
const { submit, form } = useProductFiltersPro()
|
20
|
+
const { options, showReset, selected, selectedLabel } = useProductFiltersProSort(props)
|
40
21
|
|
41
22
|
return (
|
42
23
|
<ChipOverlayOrPopper
|
43
24
|
{...rest}
|
44
25
|
overlayProps={{ sizeSm: 'minimal', sizeMd: 'minimal', ...rest.overlayProps }}
|
45
26
|
label={<Trans id='Sort By' />}
|
46
|
-
selected={
|
47
|
-
selectedLabel={
|
27
|
+
selected={selected}
|
28
|
+
selectedLabel={selectedLabel}
|
48
29
|
onApply={submit}
|
49
30
|
onReset={
|
50
|
-
|
31
|
+
showReset
|
51
32
|
? () => {
|
52
33
|
form.setValue('sort', null)
|
53
34
|
form.setValue('dir', null)
|
@@ -60,7 +41,7 @@ export function ProductFiltersProSortChip(props: ProductListActionSortProps) {
|
|
60
41
|
>
|
61
42
|
{() => (
|
62
43
|
<ActionCardListForm
|
63
|
-
control={control}
|
44
|
+
control={form.control}
|
64
45
|
name='sort'
|
65
46
|
layout='list'
|
66
47
|
variant='default'
|
@@ -0,0 +1,15 @@
|
|
1
|
+
import { SortEnum } from '@graphcommerce/graphql-mesh'
|
2
|
+
import { IconSvg, iconArrowDown, iconArrowUp } from '@graphcommerce/next-ui'
|
3
|
+
|
4
|
+
type Props = {
|
5
|
+
sortDirection: SortEnum | null
|
6
|
+
}
|
7
|
+
|
8
|
+
export function ProductFiltersProSortDirectionArrow({ sortDirection }: Props) {
|
9
|
+
return (
|
10
|
+
<IconSvg
|
11
|
+
src={sortDirection === 'ASC' || sortDirection === null ? iconArrowUp : iconArrowDown}
|
12
|
+
sx={{ display: 'flex' }}
|
13
|
+
/>
|
14
|
+
)
|
15
|
+
}
|
@@ -1,46 +1,26 @@
|
|
1
|
-
import {
|
2
|
-
import { useQuery } from '@graphcommerce/graphql'
|
3
|
-
import { StoreConfigDocument } from '@graphcommerce/magento-store'
|
4
|
-
import {
|
5
|
-
ActionCard,
|
6
|
-
ActionCardAccordion,
|
7
|
-
ActionCardListForm,
|
8
|
-
Button,
|
9
|
-
filterNonNullableKeys,
|
10
|
-
} from '@graphcommerce/next-ui'
|
1
|
+
import { ActionCard, ActionCardAccordion, ActionCardListForm, Button } from '@graphcommerce/next-ui'
|
11
2
|
import { Trans } from '@lingui/react'
|
12
|
-
import {
|
13
|
-
import { ProductListSortFragment } from '../ProductListSort/ProductListSort.gql'
|
3
|
+
import { SxProps, Theme } from '@mui/material'
|
14
4
|
import { useProductFiltersPro } from './ProductFiltersPro'
|
5
|
+
import { UseProductFiltersProSortProps, useProductFiltersProSort } from './useProductFiltersProSort'
|
15
6
|
|
16
|
-
export type ProductFiltersProSortSectionProps =
|
7
|
+
export type ProductFiltersProSortSectionProps = UseProductFiltersProSortProps & {
|
8
|
+
sx?: SxProps<Theme>
|
9
|
+
}
|
17
10
|
|
18
11
|
export function ProductFiltersProSortSection(props: ProductFiltersProSortSectionProps) {
|
19
|
-
const {
|
12
|
+
const { sx } = props
|
20
13
|
const { form } = useProductFiltersPro()
|
21
|
-
const {
|
22
|
-
const activeSort = useWatch({ control, name: 'sort' })
|
23
|
-
|
24
|
-
const { data: storeConfigQuery } = useQuery(StoreConfigDocument)
|
25
|
-
const defaultSort = storeConfigQuery?.storeConfig?.catalog_default_sort_by
|
26
|
-
|
27
|
-
const options = useMemo(
|
28
|
-
() =>
|
29
|
-
filterNonNullableKeys(sort_fields?.options, ['value', 'label']).map((option) => ({
|
30
|
-
...option,
|
31
|
-
value: option.value === defaultSort ? null : option.value,
|
32
|
-
title: option.label,
|
33
|
-
})),
|
34
|
-
[defaultSort, sort_fields?.options],
|
35
|
-
)
|
14
|
+
const { options, showReset, selected } = useProductFiltersProSort(props)
|
36
15
|
|
37
16
|
return (
|
38
17
|
<ActionCardAccordion
|
39
|
-
|
18
|
+
sx={sx}
|
19
|
+
defaultExpanded={selected}
|
40
20
|
summary={<Trans id='Sort By' />}
|
41
21
|
details={
|
42
22
|
<ActionCardListForm
|
43
|
-
control={control}
|
23
|
+
control={form.control}
|
44
24
|
name='sort'
|
45
25
|
layout='list'
|
46
26
|
variant='default'
|
@@ -50,7 +30,7 @@ export function ProductFiltersProSortSection(props: ProductFiltersProSortSection
|
|
50
30
|
/>
|
51
31
|
}
|
52
32
|
right={
|
53
|
-
|
33
|
+
showReset ? (
|
54
34
|
<Button
|
55
35
|
color='primary'
|
56
36
|
onClick={(e) => {
|
@@ -1,11 +1,17 @@
|
|
1
1
|
export * from './ProductFilterEqualChip'
|
2
|
+
export * from './ProductFilterEqualSection'
|
2
3
|
export * from './ProductFilterRangeChip'
|
4
|
+
export * from './ProductFilterRangeSection'
|
3
5
|
export * from './ProductFiltersPro'
|
4
6
|
export * from './ProductFiltersProAllFiltersChip'
|
5
7
|
export * from './ProductFiltersProAllFiltersSidebar'
|
8
|
+
export * from './ProductFiltersProCategorySection'
|
6
9
|
export * from './ProductFiltersProChips'
|
7
10
|
export * from './ProductFiltersProClearAll'
|
8
11
|
export * from './ProductFiltersProLayoutSidebar'
|
9
12
|
export * from './ProductFiltersProLimitChip'
|
10
13
|
export * from './ProductFiltersProLimitSection'
|
11
14
|
export * from './ProductFiltersProSortChip'
|
15
|
+
export * from './ProductFiltersProSortDirectionArrow'
|
16
|
+
export * from './ProductFiltersProAggregations'
|
17
|
+
export * from './ProductFiltersProSortSection'
|
@@ -0,0 +1,76 @@
|
|
1
|
+
import { useWatch } from '@graphcommerce/ecommerce-ui'
|
2
|
+
import { useQuery } from '@graphcommerce/graphql'
|
3
|
+
import { StoreConfigDocument } from '@graphcommerce/magento-store'
|
4
|
+
import { filterNonNullableKeys } from '@graphcommerce/next-ui'
|
5
|
+
import { i18n } from '@lingui/core'
|
6
|
+
import { useMemo } from 'react'
|
7
|
+
import { CategoryDefaultFragment } from '../ProductListItems/CategoryDefault.gql'
|
8
|
+
import { ProductFilterParams } from '../ProductListItems/filterTypes'
|
9
|
+
import { ProductListSortFragment } from '../ProductListSort'
|
10
|
+
import { useProductFiltersPro } from './ProductFiltersPro'
|
11
|
+
import type { ProductListActionSortProps } from './ProductFiltersProSortChip'
|
12
|
+
import { ProductFiltersProSortDirectionArrow } from './ProductFiltersProSortDirectionArrow'
|
13
|
+
|
14
|
+
const exclude = ['relevance', 'position']
|
15
|
+
|
16
|
+
export type UseProductFiltersProSortProps = ProductListSortFragment & {
|
17
|
+
category?: CategoryDefaultFragment
|
18
|
+
}
|
19
|
+
|
20
|
+
export function useProductFiltersProSort(props: ProductListActionSortProps) {
|
21
|
+
const { sort_fields, category } = props
|
22
|
+
|
23
|
+
const { params, form } = useProductFiltersPro()
|
24
|
+
const { control, setValue } = form
|
25
|
+
|
26
|
+
const sortFields = useMemo(
|
27
|
+
() =>
|
28
|
+
filterNonNullableKeys(sort_fields?.options).map((o) =>
|
29
|
+
!category?.uid && o.value === 'position'
|
30
|
+
? { value: 'relevance', label: i18n._(/* i18n*/ 'Relevance') }
|
31
|
+
: o,
|
32
|
+
),
|
33
|
+
[category?.uid, sort_fields?.options],
|
34
|
+
)
|
35
|
+
const availableSortBy = category?.available_sort_by ?? sortFields.map((o) => o.value)
|
36
|
+
|
37
|
+
const conf = useQuery(StoreConfigDocument).data?.storeConfig
|
38
|
+
const defaultSortBy = (
|
39
|
+
category ? category.default_sort_by ?? conf?.catalog_default_sort_by ?? 'position' : 'relevance'
|
40
|
+
) as ProductFilterParams['sort']
|
41
|
+
|
42
|
+
const formSort = useWatch({ control, name: 'sort' })
|
43
|
+
const formDirection = useWatch({ control, name: 'dir' })
|
44
|
+
const showReset =
|
45
|
+
(formDirection !== null || formSort !== null) &&
|
46
|
+
Boolean(formSort !== defaultSortBy || formDirection === 'DESC')
|
47
|
+
const selected = Boolean(params.sort && (params.sort !== defaultSortBy || params.dir === 'DESC'))
|
48
|
+
|
49
|
+
const options = useMemo(
|
50
|
+
() =>
|
51
|
+
sortFields
|
52
|
+
.filter((o) => availableSortBy.includes(o.value))
|
53
|
+
.map((option) => {
|
54
|
+
const value = option.value === defaultSortBy ? null : option.value
|
55
|
+
const showSort = formSort === value && !exclude.includes(option.value)
|
56
|
+
|
57
|
+
return {
|
58
|
+
...option,
|
59
|
+
value,
|
60
|
+
title: option.label,
|
61
|
+
...(showSort && {
|
62
|
+
onClick: () => setValue('dir', formDirection === 'DESC' ? null : 'DESC'),
|
63
|
+
price: <ProductFiltersProSortDirectionArrow sortDirection={formDirection} />,
|
64
|
+
}),
|
65
|
+
}
|
66
|
+
}),
|
67
|
+
[sortFields, availableSortBy, defaultSortBy, formSort, formDirection, setValue],
|
68
|
+
)
|
69
|
+
|
70
|
+
return {
|
71
|
+
options,
|
72
|
+
selected,
|
73
|
+
showReset,
|
74
|
+
selectedLabel: options.find((option) => option.value === params.sort)?.label,
|
75
|
+
}
|
76
|
+
}
|
@@ -97,7 +97,7 @@ export function ProductListFiltersContainer(props: ProductListFiltersContainerPr
|
|
97
97
|
top: theme.page.vertical,
|
98
98
|
zIndex: 9,
|
99
99
|
margin: '0 auto',
|
100
|
-
|
100
|
+
|
101
101
|
[theme.breakpoints.down('md')]: {
|
102
102
|
textAlign: 'center',
|
103
103
|
maxWidth: 'unset',
|
@@ -136,8 +136,7 @@ export function ProductListFiltersContainer(props: ProductListFiltersContainerPr
|
|
136
136
|
className={classes.scroller}
|
137
137
|
hideScrollbar
|
138
138
|
sx={(theme) => ({
|
139
|
-
|
140
|
-
paddingRight: theme.page.horizontal,
|
139
|
+
px: theme.page.horizontal,
|
141
140
|
paddingBottom: '1px',
|
142
141
|
[theme.breakpoints.up('md')]: {
|
143
142
|
borderRadius: '99em',
|
@@ -145,7 +144,6 @@ export function ProductListFiltersContainer(props: ProductListFiltersContainerPr
|
|
145
144
|
paddingRight: '8px',
|
146
145
|
},
|
147
146
|
py: '5px',
|
148
|
-
|
149
147
|
columnGap: '6px',
|
150
148
|
gridAutoColumns: 'min-content',
|
151
149
|
})}
|
@@ -1,7 +1,7 @@
|
|
1
1
|
import { LazyHydrate, RenderType, extendableComponent, responsiveVal } from '@graphcommerce/next-ui'
|
2
2
|
import { Box, BoxProps } from '@mui/material'
|
3
3
|
import { ProductListItemFragment } from '../../Api/ProductListItem.gql'
|
4
|
-
import { AddProductsToCartForm } from '
|
4
|
+
import { AddProductsToCartForm } from '../AddProductsToCart'
|
5
5
|
import { ProductListItemProps } from '../ProductListItem/ProductListItem'
|
6
6
|
import { ProductListItemRenderer } from './renderer'
|
7
7
|
|
@@ -31,7 +31,7 @@ export type ProductFilterParams = {
|
|
31
31
|
|
32
32
|
export function toFilterParams(params: ProductListParams): ProductFilterParams {
|
33
33
|
const [sortKey] = Object.keys(params.sort) as [keyof ProductAttributeSortInput]
|
34
|
-
const dir = params.sort[sortKey] as SortEnum | undefined
|
34
|
+
const dir = params.sort[sortKey]?.toUpperCase() as SortEnum | undefined
|
35
35
|
|
36
36
|
return {
|
37
37
|
...params,
|
@@ -35,7 +35,7 @@ export function parseParams(
|
|
35
35
|
}
|
36
36
|
if (param === 'dir') {
|
37
37
|
const [sortBy] = Object.keys(categoryVariables.sort)
|
38
|
-
if (sortBy) categoryVariables.sort[sortBy] = value as SortEnum
|
38
|
+
if (sortBy) categoryVariables.sort[sortBy] = value?.toUpperCase() as SortEnum
|
39
39
|
return undefined
|
40
40
|
}
|
41
41
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { StoreConfigQuery } from '@graphcommerce/magento-store'
|
2
|
+
import { CategoryDefaultFragment } from './CategoryDefault.gql'
|
3
|
+
import { ProductListParams } from './filterTypes'
|
4
|
+
import { cloneDeep } from '@graphcommerce/graphql'
|
5
|
+
|
6
|
+
export async function productListApplyCategoryDefaults(
|
7
|
+
params: ProductListParams | undefined,
|
8
|
+
conf: StoreConfigQuery,
|
9
|
+
category: Promise<CategoryDefaultFragment | null | undefined>,
|
10
|
+
) {
|
11
|
+
if (!params) return params
|
12
|
+
|
13
|
+
const newParams = cloneDeep(params)
|
14
|
+
if (!newParams.pageSize) newParams.pageSize = conf.storeConfig?.grid_per_page ?? 12
|
15
|
+
|
16
|
+
if (Object.keys(params.sort).length === 0) {
|
17
|
+
const categorySort = (await category)?.default_sort_by as keyof ProductListParams['sort']
|
18
|
+
const defaultSort = conf.storeConfig?.catalog_default_sort_by as keyof ProductListParams['sort']
|
19
|
+
if (categorySort) newParams.sort = { [categorySort]: 'ASC' }
|
20
|
+
else if (defaultSort) newParams.sort = { [defaultSort]: 'ASC' }
|
21
|
+
}
|
22
|
+
|
23
|
+
if (!newParams.filters.category_uid?.in?.[0]) {
|
24
|
+
newParams.filters.category_uid = { eq: (await category)?.uid }
|
25
|
+
}
|
26
|
+
|
27
|
+
return newParams
|
28
|
+
}
|
@@ -8,6 +8,9 @@ import { ProductPageBreadcrumbFragment } from './ProductPageBreadcrumb.gql'
|
|
8
8
|
type ProductPageBreadcrumbsProps = ProductPageBreadcrumbFragment &
|
9
9
|
Omit<BreadcrumbsProps, 'children'>
|
10
10
|
|
11
|
+
/**
|
12
|
+
* @deprecated Please use ProductPageBreadcrumbs
|
13
|
+
*/
|
11
14
|
export function ProductPageBreadcrumb(props: ProductPageBreadcrumbsProps) {
|
12
15
|
const { categories, name, ...breadcrumbProps } = props
|
13
16
|
const prev = usePrevPageRouter()
|
@@ -32,9 +35,11 @@ export function ProductPageBreadcrumb(props: ProductPageBreadcrumbsProps) {
|
|
32
35
|
{breadcrumb.category_name}
|
33
36
|
</Link>
|
34
37
|
))}
|
35
|
-
|
36
|
-
{category?.
|
37
|
-
|
38
|
+
{category && (
|
39
|
+
<Link href={`/${category?.url_path}`} underline='hover' color='inherit'>
|
40
|
+
{category?.name}
|
41
|
+
</Link>
|
42
|
+
)}
|
38
43
|
<Typography color='text.primary'>{name}</Typography>
|
39
44
|
</Breadcrumbs>
|
40
45
|
)
|
@@ -0,0 +1,40 @@
|
|
1
|
+
import { usePrevPageRouter } from '@graphcommerce/framer-next-pages'
|
2
|
+
import { categoryToBreadcrumbs } from '@graphcommerce/magento-category'
|
3
|
+
import { Breadcrumbs } from '@graphcommerce/next-ui'
|
4
|
+
import { BreadcrumbsJsonLd } from '@graphcommerce/next-ui/Breadcrumbs/BreadcrumbsJsonLd'
|
5
|
+
import { jsonLdBreadcrumb } from '@graphcommerce/next-ui/Breadcrumbs/jsonLdBreadcrumb'
|
6
|
+
import { BreadcrumbsProps } from '@mui/material'
|
7
|
+
import { useRouter } from 'next/router'
|
8
|
+
import { BreadcrumbList } from 'schema-dts'
|
9
|
+
import { productPageCategory } from '../ProductPageCategory/productPageCategory'
|
10
|
+
import { ProductPageBreadcrumbFragment } from './ProductPageBreadcrumb.gql'
|
11
|
+
import { productLink } from '../../hooks/useProductLink'
|
12
|
+
|
13
|
+
export type ProductPageBreadcrumbsProps = Omit<BreadcrumbsProps, 'children'> & {
|
14
|
+
breadcrumbsAmount?: number
|
15
|
+
product: ProductPageBreadcrumbFragment
|
16
|
+
}
|
17
|
+
|
18
|
+
export function ProductPageBreadcrumbs(props: ProductPageBreadcrumbsProps) {
|
19
|
+
const { product, ...breadcrumbsProps } = props
|
20
|
+
const { categories } = product
|
21
|
+
const prev = usePrevPageRouter()
|
22
|
+
const router = useRouter()
|
23
|
+
|
24
|
+
const category =
|
25
|
+
categories?.find((c) => `/${c?.url_path}` === prev?.asPath) ?? productPageCategory(product)
|
26
|
+
|
27
|
+
if (!category || !product.name || !product.url_key) return null
|
28
|
+
|
29
|
+
const breadcrumbs = categoryToBreadcrumbs(category)
|
30
|
+
|
31
|
+
return (
|
32
|
+
<>
|
33
|
+
<BreadcrumbsJsonLd<BreadcrumbList>
|
34
|
+
breadcrumbs={[...breadcrumbs, { name: product.name, href: productLink(product) }]}
|
35
|
+
render={(bc) => ({ '@context': 'https://schema.org', ...jsonLdBreadcrumb(bc, router) })}
|
36
|
+
/>
|
37
|
+
<Breadcrumbs breadcrumbs={breadcrumbs} lastIsLink {...breadcrumbsProps} />
|
38
|
+
</>
|
39
|
+
)
|
40
|
+
}
|
@@ -22,15 +22,21 @@ export function ProductPageGallery(props: ProductPageGalleryProps) {
|
|
22
22
|
const images =
|
23
23
|
media_gallery
|
24
24
|
?.filter(nonNullable)
|
25
|
+
.filter((p) => p.disabled !== true)
|
25
26
|
.sort((a, b) => (a.position ?? 0) - (b.position ?? 0))
|
26
|
-
.map((item) =>
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
27
|
+
.map((item) =>
|
28
|
+
item.__typename === 'ProductImage'
|
29
|
+
? {
|
30
|
+
src: item.url ?? '',
|
31
|
+
alt: item.label || undefined,
|
32
|
+
width,
|
33
|
+
height,
|
34
|
+
}
|
35
|
+
: {
|
36
|
+
src: '',
|
37
|
+
alt: `{${item.__typename} not yet supported}`,
|
38
|
+
},
|
39
|
+
) ?? []
|
34
40
|
|
35
41
|
return (
|
36
42
|
<SidebarGallery
|
@@ -1,12 +1,17 @@
|
|
1
1
|
import { useWatch } from '@graphcommerce/ecommerce-ui'
|
2
2
|
import { Money } from '@graphcommerce/magento-store'
|
3
|
+
import { extendableComponent } from '@graphcommerce/next-ui'
|
4
|
+
import { Box } from '@mui/material'
|
3
5
|
import { AddToCartItemSelector, useFormAddProductsToCart } from '../AddProductsToCart'
|
4
6
|
import { ProductPagePriceFragment } from './ProductPagePrice.gql'
|
5
7
|
import { getProductTierPrice } from './getProductTierPrice'
|
6
|
-
import {
|
7
|
-
|
8
|
+
import {
|
9
|
+
UseCustomizableOptionPriceProps,
|
10
|
+
useCustomizableOptionPrice,
|
11
|
+
} from './useCustomizableOptionPrice'
|
8
12
|
|
9
|
-
export type ProductPagePriceProps = { product: ProductPagePriceFragment } & AddToCartItemSelector
|
13
|
+
export type ProductPagePriceProps = { product: ProductPagePriceFragment } & AddToCartItemSelector &
|
14
|
+
UseCustomizableOptionPriceProps
|
10
15
|
|
11
16
|
const { classes } = extendableComponent('ProductPagePrice', ['root', 'discountPrice'] as const)
|
12
17
|
|
@@ -18,6 +23,8 @@ export function ProductPagePrice(props: ProductPagePriceProps) {
|
|
18
23
|
const price =
|
19
24
|
getProductTierPrice(product, quantity) ?? product.price_range.minimum_price.final_price
|
20
25
|
|
26
|
+
const priceValue = useCustomizableOptionPrice(props)
|
27
|
+
|
21
28
|
return (
|
22
29
|
<>
|
23
30
|
{product.price_range.minimum_price.regular_price.value !== price.value && (
|
@@ -33,7 +40,7 @@ export function ProductPagePrice(props: ProductPagePriceProps) {
|
|
33
40
|
<Money {...product.price_range.minimum_price.regular_price} />
|
34
41
|
</Box>
|
35
42
|
)}
|
36
|
-
<Money {...price} />
|
43
|
+
<Money {...price} value={priceValue} />
|
37
44
|
</>
|
38
45
|
)
|
39
46
|
}
|