@graphcommerce/magento-product 9.0.0-canary.115 → 9.0.0-canary.117
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 +8 -0
- package/components/ProductAddToCart/index.ts +0 -1
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +1 -3
- package/components/ProductFiltersPro/ProductFiltersProChips.tsx +3 -1
- package/components/ProductFiltersPro/ProductFiltersProLayoutSidebar.tsx +5 -1
- package/components/ProductFiltersPro/ProductFiltersProNoResults.tsx +2 -2
- package/components/ProductList/ProductList.graphql +4 -3
- package/components/ProductListFilters/ProductFilters.graphql +2 -2
- package/components/ProductListItem/ProductListItem.tsx +45 -13
- package/components/ProductListItem/ProductListItemImage.tsx +4 -4
- package/components/ProductListItem/ProductListItemLinkOrDiv.tsx +9 -5
- package/components/ProductListItems/renderer.tsx +1 -0
- package/components/ProductListPrice/ProductListPrice.tsx +9 -5
- package/components/ProductListSuggestions/ProductListSearchSuggestion.graphql +3 -0
- package/components/ProductListSuggestions/ProductListSuggestions.graphql +1 -1
- package/components/ProductPage/ProductPageAddToCartRow.tsx +1 -1
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +1 -0
- package/components/ProductPagePrice/ProductPagePrice.tsx +6 -6
- package/components/ProductPagePrice/ProductPagePriceTiers.tsx +3 -3
- package/components/ProductStaticPaths/getSitemapPaths.ts +1 -0
- package/components/ProductWeight/ProductWeight.tsx +1 -0
- package/components/index.ts +5 -0
- package/hooks/useProductLink.ts +1 -1
- package/hooks/useProductList.ts +4 -4
- package/package.json +13 -13
- package/components/ProductAddToCart/ProductAddToCart.tsx +0 -142
- package/components/ProductPageGallery/ProductImage.tsx +0 -10
- package/components/ProductPageGallery/ProductVideo.tsx +0 -10
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
# Change Log
|
2
2
|
|
3
|
+
## 9.0.0-canary.117
|
4
|
+
|
5
|
+
## 9.0.0-canary.116
|
6
|
+
|
7
|
+
### Patch Changes
|
8
|
+
|
9
|
+
- [#2452](https://github.com/graphcommerce-org/graphcommerce/pull/2452) [`5dfd3b2`](https://github.com/graphcommerce-org/graphcommerce/commit/5dfd3b201255ef35263485d04153d37bb7e4fe67) - Renamed useInContextQuery to usePrivateQuery ([@paales](https://github.com/paales))
|
10
|
+
|
3
11
|
## 9.0.0-canary.115
|
4
12
|
|
5
13
|
## 9.0.0-canary.114
|
@@ -17,9 +17,7 @@ export type ProductFiltersProAllFiltersSidebarProps = ProductFiltersProAggregati
|
|
17
17
|
|
18
18
|
/**
|
19
19
|
* @deprecated Not used anymore
|
20
|
-
*
|
21
|
-
* @param props
|
22
|
-
* @returns
|
20
|
+
* @public
|
23
21
|
*/
|
24
22
|
export function ProductFiltersProAllFiltersSidebar(props: ProductFiltersProAllFiltersSidebarProps) {
|
25
23
|
const { sort_fields, total_count, renderer, sx = [], category, params } = props
|
@@ -5,7 +5,9 @@ import {
|
|
5
5
|
} from './ProductFiltersProAggregations'
|
6
6
|
|
7
7
|
/**
|
8
|
-
* @deprecated Not used anymore, use `<ProductFiltersProAggregations
|
8
|
+
* @deprecated Not used anymore, use `<ProductFiltersProAggregations
|
9
|
+
* renderer={productFiltersProChipRenderer}/>`
|
10
|
+
* @public
|
9
11
|
*/
|
10
12
|
export function ProductFiltersProFilterChips(props: ProductFiltersProAggregationsProps) {
|
11
13
|
const { renderer } = props
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { FormAutoSubmit } from '@graphcommerce/ecommerce-ui'
|
2
|
-
import {
|
2
|
+
import { extendableComponent, StickyBelowHeader } from '@graphcommerce/next-ui'
|
3
3
|
import type { Theme } from '@mui/material'
|
4
4
|
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
5
5
|
import { Box, Container, useMediaQuery } from '@mui/material'
|
@@ -25,6 +25,10 @@ const name = 'ProductFiltersProLayoutSidebar'
|
|
25
25
|
const parts = ['root', 'content'] as const
|
26
26
|
const { withState } = extendableComponent<OwnerProps, typeof name, typeof parts>(name, parts)
|
27
27
|
|
28
|
+
/**
|
29
|
+
* @deprecated
|
30
|
+
* @public
|
31
|
+
*/
|
28
32
|
export function ProductFiltersProLayoutSidebar(props: ProductFiltersProLayoutSidebarProps) {
|
29
33
|
const {
|
30
34
|
items,
|
@@ -33,8 +33,8 @@ export function ProductFiltersProNoResults(props: ProductFitlersProNoResultProps
|
|
33
33
|
>
|
34
34
|
{term ? (
|
35
35
|
<>
|
36
|
-
<Typography variant='h5'
|
37
|
-
<Trans>We couldn
|
36
|
+
<Typography variant='h5'>
|
37
|
+
<Trans>We couldn’t find any results for ‘{term}’</Trans>
|
38
38
|
</Typography>
|
39
39
|
<p>
|
40
40
|
{hasFilters ? (
|
@@ -4,8 +4,9 @@ query ProductList(
|
|
4
4
|
$filters: ProductAttributeFilterInput = {}
|
5
5
|
$sort: ProductAttributeSortInput = {}
|
6
6
|
$search: String = ""
|
7
|
-
$context:
|
7
|
+
$context: PrivateContext
|
8
8
|
$onlyItems: Boolean = false
|
9
|
+
$quickSearch: Boolean = false
|
9
10
|
) {
|
10
11
|
products(
|
11
12
|
pageSize: $pageSize
|
@@ -13,10 +14,10 @@ query ProductList(
|
|
13
14
|
filter: $filters
|
14
15
|
sort: $sort
|
15
16
|
search: $search
|
16
|
-
) @
|
17
|
+
) @privateContext(context: $context) {
|
17
18
|
...ProductListSuggestions @skip(if: $onlyItems)
|
18
19
|
...ProductListFilters @skip(if: $onlyItems)
|
19
|
-
...ProductListCount @skip(if: $onlyItems)
|
20
|
+
...ProductListCount @skip(if: $onlyItems) @include(if: $quickSearch)
|
20
21
|
...ProductListPagination @skip(if: $onlyItems)
|
21
22
|
...ProductListSort @skip(if: $onlyItems)
|
22
23
|
...ProductListItems
|
@@ -1,11 +1,11 @@
|
|
1
1
|
query ProductFilters(
|
2
2
|
$filters: ProductAttributeFilterInput = {}
|
3
3
|
$search: String
|
4
|
-
$context:
|
4
|
+
$context: PrivateContext
|
5
5
|
$pageSize: Int = 1
|
6
6
|
) {
|
7
7
|
filters: products(filter: $filters, currentPage: 1, pageSize: $pageSize, search: $search)
|
8
|
-
@
|
8
|
+
@privateContext(context: $context) {
|
9
9
|
page_info {
|
10
10
|
total_pages
|
11
11
|
}
|
@@ -14,7 +14,7 @@ import type {
|
|
14
14
|
ProductListsItemImageAreaProps,
|
15
15
|
} from './ProductListItemImageContainer'
|
16
16
|
import { ProductImageContainer, ProductListItemImageAreas } from './ProductListItemImageContainer'
|
17
|
-
import { ProductListItemLinkOrDiv } from './ProductListItemLinkOrDiv'
|
17
|
+
import { ProductListItemLinkOrDiv, ProductListItemLinkOrDivProps } from './ProductListItemLinkOrDiv'
|
18
18
|
import type { ProductListItemTitleAndPriceProps } from './ProductListItemTitleAndPrice'
|
19
19
|
import { ProductListItemTitleAndPrice } from './ProductListItemTitleAndPrice'
|
20
20
|
|
@@ -44,21 +44,29 @@ export type BaseProps = {
|
|
44
44
|
imageOnly?: boolean
|
45
45
|
children?: React.ReactNode
|
46
46
|
sx?: SxProps<Theme>
|
47
|
-
|
48
|
-
|
47
|
+
onClick?: (
|
48
|
+
event: React.MouseEvent<HTMLAnchorElement | HTMLDivElement>,
|
49
|
+
item: ProductListItemFragment,
|
50
|
+
) => void
|
51
|
+
slotProps?: {
|
52
|
+
root?: Partial<ProductListItemLinkOrDivProps>
|
53
|
+
image?: Partial<ProductListItemImageProps>
|
54
|
+
imageAreas?: Partial<ProductListsItemImageAreaProps>
|
55
|
+
titleAndPrice?: Partial<ProductListItemTitleAndPriceProps>
|
56
|
+
}
|
49
57
|
} & StyleProps &
|
50
58
|
Omit<ProductListItemTitleAndPriceProps, 'title' | 'classes' | 'children'> &
|
51
59
|
Omit<ProductListItemImageProps, 'classes'> &
|
52
60
|
Omit<ProductListsItemImageAreaProps, 'classes'> &
|
53
61
|
Pick<ImageProps, 'loading' | 'sizes' | 'dontReportWronglySizedImages'>
|
54
62
|
|
55
|
-
// eslint-disable-next-line react/no-unused-prop-types
|
56
63
|
export type SkeletonProps = BaseProps & { __typename: 'Skeleton' }
|
57
64
|
|
58
65
|
export type ProductProps = BaseProps & ProductListItemFragment
|
59
66
|
|
60
67
|
export type ProductListItemProps = ProductProps | SkeletonProps
|
61
68
|
|
69
|
+
/** @public */
|
62
70
|
export function ProductListItemReal(props: ProductProps) {
|
63
71
|
const {
|
64
72
|
subTitle,
|
@@ -78,18 +86,20 @@ export function ProductListItemReal(props: ProductProps) {
|
|
78
86
|
titleComponent = 'h2',
|
79
87
|
sx = [],
|
80
88
|
onClick,
|
89
|
+
slotProps = {},
|
81
90
|
} = props
|
82
91
|
|
83
|
-
const handleClick = useEventCallback((e: React.MouseEvent<HTMLAnchorElement>) =>
|
84
|
-
onClick?.(e, props),
|
85
|
-
)
|
86
|
-
|
87
92
|
return (
|
88
93
|
<ProductListItemLinkOrDiv
|
89
94
|
href={productLink(props)}
|
90
95
|
className={classes.root}
|
91
|
-
|
92
|
-
|
96
|
+
onClick={(e: React.MouseEvent<HTMLAnchorElement | HTMLDivElement>) => onClick?.(e, props)}
|
97
|
+
{...slotProps.root}
|
98
|
+
sx={[
|
99
|
+
...(Array.isArray(sx) ? sx : [sx]),
|
100
|
+
...(Array.isArray(slotProps.root?.sx) ? slotProps.root.sx : [slotProps.root?.sx]),
|
101
|
+
]}
|
102
|
+
ref={slotProps.root?.ref as React.Ref<HTMLAnchorElement | HTMLDivElement>}
|
93
103
|
>
|
94
104
|
<ProductImageContainer className={classes.imageContainer}>
|
95
105
|
<ProductListItemImage
|
@@ -100,6 +110,7 @@ export function ProductListItemReal(props: ProductProps) {
|
|
100
110
|
loading={loading}
|
101
111
|
sizes={sizes}
|
102
112
|
dontReportWronglySizedImages={dontReportWronglySizedImages}
|
113
|
+
{...slotProps.image}
|
103
114
|
/>
|
104
115
|
|
105
116
|
{!imageOnly && (
|
@@ -114,6 +125,7 @@ export function ProductListItemReal(props: ProductProps) {
|
|
114
125
|
{topLeft}
|
115
126
|
</>
|
116
127
|
}
|
128
|
+
{...slotProps.imageAreas}
|
117
129
|
/>
|
118
130
|
)}
|
119
131
|
</ProductImageContainer>
|
@@ -125,6 +137,7 @@ export function ProductListItemReal(props: ProductProps) {
|
|
125
137
|
titleComponent={titleComponent}
|
126
138
|
title={name}
|
127
139
|
subTitle={subTitle}
|
140
|
+
{...slotProps.titleAndPrice}
|
128
141
|
>
|
129
142
|
<ProductListPrice {...price_range.minimum_price} />
|
130
143
|
</ProductListItemTitleAndPrice>
|
@@ -135,13 +148,30 @@ export function ProductListItemReal(props: ProductProps) {
|
|
135
148
|
)
|
136
149
|
}
|
137
150
|
|
151
|
+
/** @public */
|
138
152
|
export function ProductListItemSkeleton(props: BaseProps) {
|
139
|
-
const {
|
153
|
+
const {
|
154
|
+
children,
|
155
|
+
imageOnly = false,
|
156
|
+
aspectRatio,
|
157
|
+
titleComponent = 'h2',
|
158
|
+
sx = [],
|
159
|
+
slotProps = {},
|
160
|
+
} = props
|
140
161
|
|
141
162
|
return (
|
142
|
-
<ProductListItemLinkOrDiv
|
163
|
+
<ProductListItemLinkOrDiv
|
164
|
+
sx={sx}
|
165
|
+
className={classes.root}
|
166
|
+
{...slotProps.root}
|
167
|
+
ref={slotProps.root?.ref as React.Ref<HTMLAnchorElement | HTMLDivElement>}
|
168
|
+
>
|
143
169
|
<ProductImageContainer className={classes.imageContainer}>
|
144
|
-
<ProductListItemImageSkeleton
|
170
|
+
<ProductListItemImageSkeleton
|
171
|
+
classes={classes}
|
172
|
+
aspectRatio={aspectRatio}
|
173
|
+
{...slotProps.image}
|
174
|
+
/>
|
145
175
|
</ProductImageContainer>
|
146
176
|
|
147
177
|
{!imageOnly && (
|
@@ -151,6 +181,7 @@ export function ProductListItemSkeleton(props: BaseProps) {
|
|
151
181
|
titleComponent={titleComponent}
|
152
182
|
title={<Skeleton variant='text' sx={{ width: '100px' }} />}
|
153
183
|
subTitle={<Skeleton variant='text' sx={{ width: '20px' }} />}
|
184
|
+
{...slotProps.titleAndPrice}
|
154
185
|
>
|
155
186
|
<Skeleton variant='text' sx={{ width: '20px' }} />
|
156
187
|
</ProductListItemTitleAndPrice>
|
@@ -164,6 +195,7 @@ export function ProductListItemSkeleton(props: BaseProps) {
|
|
164
195
|
function isSkeleton(props: ProductListItemProps): props is SkeletonProps {
|
165
196
|
return props.__typename === 'Skeleton'
|
166
197
|
}
|
198
|
+
|
167
199
|
export function ProductListItem(props: ProductListItemProps) {
|
168
200
|
return isSkeleton(props) ? (
|
169
201
|
<ProductListItemSkeleton {...props} />
|
@@ -26,7 +26,7 @@ function PlaceHolderContainer(props: BoxProps) {
|
|
26
26
|
|
27
27
|
export type ProductListItemImageProps = {
|
28
28
|
aspectRatio?: [number, number]
|
29
|
-
classes
|
29
|
+
classes?: {
|
30
30
|
image?: string
|
31
31
|
placeholder?: string
|
32
32
|
}
|
@@ -35,7 +35,7 @@ export type ProductListItemImageProps = {
|
|
35
35
|
export function ProductListItemImageSkeleton(props: ProductListItemImageProps) {
|
36
36
|
const { aspectRatio = [4, 3], classes } = props
|
37
37
|
return (
|
38
|
-
<PlaceHolderContainer className={`${classes
|
38
|
+
<PlaceHolderContainer className={`${classes?.placeholder} ${classes?.image}`}>
|
39
39
|
<Skeleton
|
40
40
|
animation='wave'
|
41
41
|
sx={{
|
@@ -66,7 +66,7 @@ export function ProductListItemImage(props: ImageOrPlaceholderProps) {
|
|
66
66
|
src={src}
|
67
67
|
alt={alt ?? ''}
|
68
68
|
{...image}
|
69
|
-
className={classes
|
69
|
+
className={classes?.image}
|
70
70
|
sx={[
|
71
71
|
{
|
72
72
|
objectFit: 'contain',
|
@@ -80,7 +80,7 @@ export function ProductListItemImage(props: ImageOrPlaceholderProps) {
|
|
80
80
|
}
|
81
81
|
|
82
82
|
return (
|
83
|
-
<PlaceHolderContainer className={`${classes
|
83
|
+
<PlaceHolderContainer className={`${classes?.placeholder} ${classes?.image}`}>
|
84
84
|
<Box
|
85
85
|
sx={[
|
86
86
|
{
|
@@ -1,6 +1,7 @@
|
|
1
|
-
import {
|
1
|
+
import { breakpointVal, NextLink } from '@graphcommerce/next-ui'
|
2
2
|
import type { BoxProps, ButtonBaseProps, SxProps, Theme } from '@mui/material'
|
3
3
|
import { Box, ButtonBase } from '@mui/material'
|
4
|
+
import React from 'react'
|
4
5
|
|
5
6
|
export type ProductListItemLinkProps = ButtonBaseProps<typeof NextLink>
|
6
7
|
export type ProductListItemLinkOrDivProps = ProductListItemLinkProps | BoxProps
|
@@ -9,7 +10,10 @@ function isLink(props: ProductListItemLinkOrDivProps): props is ProductListItemL
|
|
9
10
|
return 'href' in props
|
10
11
|
}
|
11
12
|
|
12
|
-
export
|
13
|
+
export const ProductListItemLinkOrDiv = React.forwardRef<
|
14
|
+
HTMLDivElement | HTMLAnchorElement,
|
15
|
+
ProductListItemLinkOrDivProps
|
16
|
+
>((props, ref) => {
|
13
17
|
const { sx = [] } = props
|
14
18
|
|
15
19
|
const sxProps: SxProps<Theme> = [
|
@@ -28,8 +32,8 @@ export function ProductListItemLinkOrDiv(props: ProductListItemLinkOrDivProps) {
|
|
28
32
|
]
|
29
33
|
|
30
34
|
return isLink(props) ? (
|
31
|
-
<ButtonBase component={NextLink} {...props} sx={sxProps} />
|
35
|
+
<ButtonBase ref={ref} component={NextLink} {...props} sx={sxProps} focusRipple />
|
32
36
|
) : (
|
33
|
-
<Box {...props} sx={sxProps} />
|
37
|
+
<Box ref={ref} component='div' {...props} sx={sxProps} />
|
34
38
|
)
|
35
|
-
}
|
39
|
+
})
|
@@ -8,6 +8,7 @@ export type ProductListItemRenderer = TypeRenderer<ProductListItemFragment | Ske
|
|
8
8
|
|
9
9
|
/**
|
10
10
|
* @deprecated Please use productListRenderer from the example directory instead.
|
11
|
+
* @public
|
11
12
|
*/
|
12
13
|
export const renderer: ProductListItemRenderer = {
|
13
14
|
Skeleton: ProductListItem,
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { PrivateQueryMask } from '@graphcommerce/graphql'
|
2
2
|
import { Money } from '@graphcommerce/magento-store'
|
3
3
|
import { extendableComponent } from '@graphcommerce/next-ui'
|
4
4
|
import type { TypographyProps } from '@mui/material'
|
@@ -21,7 +21,7 @@ export function ProductListPrice(props: ProductListPriceProps) {
|
|
21
21
|
return (
|
22
22
|
<Typography component='div' variant='body1' className={classes.root} sx={sx}>
|
23
23
|
{regular_price.value !== final_price.value && (
|
24
|
-
<
|
24
|
+
<PrivateQueryMask
|
25
25
|
component='span'
|
26
26
|
sx={{
|
27
27
|
textDecoration: 'line-through',
|
@@ -32,11 +32,15 @@ export function ProductListPrice(props: ProductListPriceProps) {
|
|
32
32
|
className={classes.discountPrice}
|
33
33
|
>
|
34
34
|
<Money {...regular_price} />
|
35
|
-
</
|
35
|
+
</PrivateQueryMask>
|
36
36
|
)}
|
37
|
-
<
|
37
|
+
<PrivateQueryMask
|
38
|
+
className={classes.finalPrice}
|
39
|
+
component='span'
|
40
|
+
skeleton={{ width: '3.5em' }}
|
41
|
+
>
|
38
42
|
<Money {...final_price} />
|
39
|
-
</
|
43
|
+
</PrivateQueryMask>
|
40
44
|
</Typography>
|
41
45
|
)
|
42
46
|
}
|
@@ -10,7 +10,7 @@ export type ProductPageAddToCartRowProps = {
|
|
10
10
|
product: UseAddProductsToCartActionFragment
|
11
11
|
}
|
12
12
|
|
13
|
-
|
13
|
+
function ProductPageAddToCartRow(props: ProductPageAddToCartRowProps) {
|
14
14
|
const { sx, children, after } = props
|
15
15
|
return (
|
16
16
|
<>
|
@@ -11,6 +11,7 @@ export type ProductPageBreadcrumbProps = ProductPageBreadcrumbFragment &
|
|
11
11
|
|
12
12
|
/**
|
13
13
|
* @deprecated Please use ProductPageBreadcrumbs
|
14
|
+
* @public
|
14
15
|
*/
|
15
16
|
export function ProductPageBreadcrumb(props: ProductPageBreadcrumbProps) {
|
16
17
|
const { categories, name, ...breadcrumbProps } = props
|
@@ -1,11 +1,11 @@
|
|
1
1
|
import { useWatch } from '@graphcommerce/ecommerce-ui'
|
2
|
-
import {
|
2
|
+
import { PrivateQueryMask } from '@graphcommerce/graphql'
|
3
3
|
import { Money } from '@graphcommerce/magento-store'
|
4
4
|
import { extendableComponent } from '@graphcommerce/next-ui'
|
5
5
|
import type { AddToCartItemSelector } from '../AddProductsToCart'
|
6
6
|
import { useFormAddProductsToCart } from '../AddProductsToCart'
|
7
|
-
import type { ProductPagePriceFragment } from './ProductPagePrice.gql'
|
8
7
|
import { getProductTierPrice } from './getProductTierPrice'
|
8
|
+
import type { ProductPagePriceFragment } from './ProductPagePrice.gql'
|
9
9
|
import type { UseCustomizableOptionPriceProps } from './useCustomizableOptionPrice'
|
10
10
|
import { useCustomizableOptionPrice } from './useCustomizableOptionPrice'
|
11
11
|
|
@@ -31,22 +31,22 @@ export function ProductPagePrice(props: ProductPagePriceProps) {
|
|
31
31
|
return (
|
32
32
|
<>
|
33
33
|
{regularPrice.value !== price.value && (
|
34
|
-
<
|
34
|
+
<PrivateQueryMask
|
35
35
|
component='span'
|
36
36
|
className={classes.discountPrice}
|
37
37
|
skeleton={{ variant: 'text', sx: { width: '3em', transform: 'none' } }}
|
38
38
|
sx={[{ textDecoration: 'line-through', color: 'text.disabled', marginRight: '8px' }]}
|
39
39
|
>
|
40
40
|
<Money {...regularPrice} />
|
41
|
-
</
|
41
|
+
</PrivateQueryMask>
|
42
42
|
)}
|
43
|
-
<
|
43
|
+
<PrivateQueryMask
|
44
44
|
component='span'
|
45
45
|
skeleton={{ variant: 'text', sx: { width: '3em', transform: 'none' } }}
|
46
46
|
className={classes.finalPrice}
|
47
47
|
>
|
48
48
|
<Money {...price} value={priceValue} />
|
49
|
-
</
|
49
|
+
</PrivateQueryMask>
|
50
50
|
</>
|
51
51
|
)
|
52
52
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import {
|
1
|
+
import { PrivateQueryMask } from '@graphcommerce/graphql'
|
2
2
|
import { Money } from '@graphcommerce/magento-store'
|
3
3
|
import { filterNonNullableKeys } from '@graphcommerce/next-ui'
|
4
4
|
import { Trans } from '@lingui/react'
|
@@ -22,7 +22,7 @@ export function ProductPagePriceTiers(props: ProductPagePriceTiersProps) {
|
|
22
22
|
if (!priceTiers.length) return null
|
23
23
|
|
24
24
|
return (
|
25
|
-
<
|
25
|
+
<PrivateQueryMask sx={sx} variant='rectangular'>
|
26
26
|
{priceTiers.map(({ quantity, final_price, discount }) => (
|
27
27
|
<div key={quantity}>
|
28
28
|
<Trans
|
@@ -32,6 +32,6 @@ export function ProductPagePriceTiers(props: ProductPagePriceTiersProps) {
|
|
32
32
|
/>
|
33
33
|
</div>
|
34
34
|
))}
|
35
|
-
</
|
35
|
+
</PrivateQueryMask>
|
36
36
|
)
|
37
37
|
}
|
@@ -6,6 +6,7 @@ import type { ProductWeightFragment } from './ProductWeight.gql'
|
|
6
6
|
|
7
7
|
export type ProductWeightProps = Omit<UnitFormatProps, 'unit'> & { product: ProductWeightFragment }
|
8
8
|
|
9
|
+
/** @public */
|
9
10
|
export function ProductWeight(props: ProductWeightProps) {
|
10
11
|
const { product, ...rest } = props
|
11
12
|
|
package/components/index.ts
CHANGED
@@ -8,6 +8,10 @@ export * from './ProductListCount/ProductListCount'
|
|
8
8
|
export * from './ProductListFilters'
|
9
9
|
export * from './ProductListFiltersContainer/ProductListFiltersContainer'
|
10
10
|
export * from './ProductListItem/ProductListItem'
|
11
|
+
export * from './ProductListItem/ProductListItemImage'
|
12
|
+
export * from './ProductListItem/ProductListItemTitleAndPrice'
|
13
|
+
export * from './ProductListItem/ProductListItemImageContainer'
|
14
|
+
export * from './ProductListItem/ProductListItemLinkOrDiv'
|
11
15
|
export * from './ProductListItems/filteredProductList'
|
12
16
|
export * from './ProductListItems/filterTypes'
|
13
17
|
export * from './ProductListItems/getFilterTypes'
|
@@ -43,3 +47,4 @@ export * from './ProductWeight/ProductWeight'
|
|
43
47
|
export * from './ProductListPrice'
|
44
48
|
export * from './ProductListSuggestions/ProductListSuggestions'
|
45
49
|
export * from './ProductListSuggestions/ProductListSuggestions.gql'
|
50
|
+
export * from './ProductListSuggestions/ProductListSearchSuggestion.gql'
|
package/hooks/useProductLink.ts
CHANGED
@@ -2,7 +2,7 @@ import type { ProductLinkFragment } from './ProductLink.gql'
|
|
2
2
|
|
3
3
|
export type ProductLinkProps = Omit<ProductLinkFragment, 'uid'>
|
4
4
|
|
5
|
-
|
5
|
+
const productRoute = import.meta.graphCommerce.productRoute ?? '/p/'
|
6
6
|
|
7
7
|
export function productPath(urlKey: string) {
|
8
8
|
return `${productRoute}${urlKey}`
|
package/hooks/useProductList.ts
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import { debounce } from '@graphcommerce/ecommerce-ui'
|
2
2
|
import type { ApolloClient } from '@graphcommerce/graphql'
|
3
|
-
import {
|
3
|
+
import { getPrivateQueryContext, usePrivateQuery, useQuery } from '@graphcommerce/graphql'
|
4
4
|
import { StoreConfigDocument } from '@graphcommerce/magento-store'
|
5
5
|
import { showPageLoadIndicator } from '@graphcommerce/next-ui'
|
6
6
|
import { useEventCallback } from '@mui/material'
|
@@ -41,7 +41,7 @@ export const prefetchProductList = debounce(
|
|
41
41
|
|
42
42
|
showPageLoadIndicator.set(true)
|
43
43
|
|
44
|
-
const context =
|
44
|
+
const context = getPrivateQueryContext(client)
|
45
45
|
const productList = client.query({
|
46
46
|
query: ProductListDocument,
|
47
47
|
variables: { ...variables, context },
|
@@ -103,8 +103,8 @@ export function useProductList<
|
|
103
103
|
const { params, shallow } = useRouterFilterParams(props)
|
104
104
|
const variables = useProductListApplyCategoryDefaults(params, category)
|
105
105
|
|
106
|
-
const result =
|
107
|
-
const filters =
|
106
|
+
const result = usePrivateQuery(ProductListDocument, { variables, skip: !shallow }, props)
|
107
|
+
const filters = usePrivateQuery(
|
108
108
|
ProductFiltersDocument,
|
109
109
|
{ variables: categoryDefaultsToProductListFilters(variables), skip: !shallow },
|
110
110
|
props,
|
package/package.json
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
"name": "@graphcommerce/magento-product",
|
3
3
|
"homepage": "https://www.graphcommerce.org/",
|
4
4
|
"repository": "github:graphcommerce-org/graphcommerce",
|
5
|
-
"version": "9.0.0-canary.
|
5
|
+
"version": "9.0.0-canary.117",
|
6
6
|
"sideEffects": false,
|
7
7
|
"prettier": "@graphcommerce/prettier-config-pwa",
|
8
8
|
"eslintConfig": {
|
@@ -18,18 +18,18 @@
|
|
18
18
|
"typescript": "5.7.2"
|
19
19
|
},
|
20
20
|
"peerDependencies": {
|
21
|
-
"@graphcommerce/ecommerce-ui": "^9.0.0-canary.
|
22
|
-
"@graphcommerce/eslint-config-pwa": "^9.0.0-canary.
|
23
|
-
"@graphcommerce/framer-next-pages": "^9.0.0-canary.
|
24
|
-
"@graphcommerce/framer-scroller": "^9.0.0-canary.
|
25
|
-
"@graphcommerce/graphql": "^9.0.0-canary.
|
26
|
-
"@graphcommerce/graphql-mesh": "^9.0.0-canary.
|
27
|
-
"@graphcommerce/image": "^9.0.0-canary.
|
28
|
-
"@graphcommerce/magento-cart": "^9.0.0-canary.
|
29
|
-
"@graphcommerce/magento-store": "^9.0.0-canary.
|
30
|
-
"@graphcommerce/next-ui": "^9.0.0-canary.
|
31
|
-
"@graphcommerce/prettier-config-pwa": "^9.0.0-canary.
|
32
|
-
"@graphcommerce/typescript-config-pwa": "^9.0.0-canary.
|
21
|
+
"@graphcommerce/ecommerce-ui": "^9.0.0-canary.117",
|
22
|
+
"@graphcommerce/eslint-config-pwa": "^9.0.0-canary.117",
|
23
|
+
"@graphcommerce/framer-next-pages": "^9.0.0-canary.117",
|
24
|
+
"@graphcommerce/framer-scroller": "^9.0.0-canary.117",
|
25
|
+
"@graphcommerce/graphql": "^9.0.0-canary.117",
|
26
|
+
"@graphcommerce/graphql-mesh": "^9.0.0-canary.117",
|
27
|
+
"@graphcommerce/image": "^9.0.0-canary.117",
|
28
|
+
"@graphcommerce/magento-cart": "^9.0.0-canary.117",
|
29
|
+
"@graphcommerce/magento-store": "^9.0.0-canary.117",
|
30
|
+
"@graphcommerce/next-ui": "^9.0.0-canary.117",
|
31
|
+
"@graphcommerce/prettier-config-pwa": "^9.0.0-canary.117",
|
32
|
+
"@graphcommerce/typescript-config-pwa": "^9.0.0-canary.117",
|
33
33
|
"@lingui/core": "^4.2.1",
|
34
34
|
"@lingui/macro": "^4.2.1",
|
35
35
|
"@lingui/react": "^4.2.1",
|
@@ -1,142 +0,0 @@
|
|
1
|
-
import { NumberFieldElement } from '@graphcommerce/ecommerce-ui'
|
2
|
-
import type { ProductInterface } from '@graphcommerce/graphql-mesh'
|
3
|
-
import { ApolloCartErrorAlert, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
|
4
|
-
import type { MoneyProps } from '@graphcommerce/magento-store'
|
5
|
-
import { Money } from '@graphcommerce/magento-store'
|
6
|
-
import {
|
7
|
-
Button,
|
8
|
-
IconSvg,
|
9
|
-
MessageSnackbar,
|
10
|
-
extendableComponent,
|
11
|
-
iconChevronRight,
|
12
|
-
} from '@graphcommerce/next-ui'
|
13
|
-
import { Trans } from '@lingui/macro'
|
14
|
-
import type { ButtonProps } from '@mui/material'
|
15
|
-
import { Alert, Box, Divider, Typography } from '@mui/material'
|
16
|
-
import React from 'react'
|
17
|
-
import type { ProductAddToCartMutationVariables } from './ProductAddToCart.gql'
|
18
|
-
import { ProductAddToCartDocument } from './ProductAddToCart.gql'
|
19
|
-
|
20
|
-
const { classes, selectors } = extendableComponent('ProductAddToCart', [
|
21
|
-
'root',
|
22
|
-
'button',
|
23
|
-
'price',
|
24
|
-
'divider',
|
25
|
-
'buttonWrapper',
|
26
|
-
] as const)
|
27
|
-
|
28
|
-
export type AddToCartProps = React.ComponentProps<typeof ProductAddToCart>
|
29
|
-
|
30
|
-
/** @deprecated Please us AddProductsToCartForm and it's components */
|
31
|
-
export function ProductAddToCart(
|
32
|
-
props: Pick<ProductInterface, 'name'> & {
|
33
|
-
variables: Omit<ProductAddToCartMutationVariables, 'cartId'>
|
34
|
-
name: string
|
35
|
-
price: MoneyProps
|
36
|
-
additionalButtons?: React.ReactNode
|
37
|
-
children?: React.ReactNode
|
38
|
-
} & Omit<ButtonProps, 'type' | 'name'>,
|
39
|
-
) {
|
40
|
-
const { name, children, variables, price, sx, additionalButtons, ...buttonProps } = props
|
41
|
-
|
42
|
-
const form = useFormGqlMutationCart(ProductAddToCartDocument, {
|
43
|
-
defaultValues: { ...variables },
|
44
|
-
})
|
45
|
-
|
46
|
-
const { handleSubmit, formState, error, control, required, data } = form
|
47
|
-
const submitHandler = handleSubmit(() => {})
|
48
|
-
|
49
|
-
return (
|
50
|
-
<Box component='form' onSubmit={submitHandler} noValidate className={classes.root} sx={sx}>
|
51
|
-
<Divider className={classes.divider} sx={(theme) => ({ margin: `${theme.spacings.xs} 0` })} />
|
52
|
-
|
53
|
-
<Typography
|
54
|
-
variant='h4'
|
55
|
-
className={classes.price}
|
56
|
-
sx={(theme) => ({
|
57
|
-
fontWeight: theme.typography.fontWeightBold,
|
58
|
-
margin: `${theme.spacings.sm} 0`,
|
59
|
-
})}
|
60
|
-
>
|
61
|
-
<Money {...price} />
|
62
|
-
</Typography>
|
63
|
-
|
64
|
-
<NumberFieldElement
|
65
|
-
variant='outlined'
|
66
|
-
error={formState.isSubmitted && !!formState.errors.quantity}
|
67
|
-
required={required.quantity}
|
68
|
-
inputProps={{ min: 1 }}
|
69
|
-
name='quantity'
|
70
|
-
rules={{ required: required.quantity }}
|
71
|
-
helperText={formState.isSubmitted && formState.errors.quantity?.message}
|
72
|
-
disabled={formState.isSubmitting}
|
73
|
-
size='small'
|
74
|
-
control={control}
|
75
|
-
/>
|
76
|
-
{children}
|
77
|
-
<Box
|
78
|
-
sx={(theme) => ({
|
79
|
-
display: 'flex',
|
80
|
-
alignItems: 'center',
|
81
|
-
columnGap: theme.spacings.xs,
|
82
|
-
})}
|
83
|
-
className={classes.buttonWrapper}
|
84
|
-
>
|
85
|
-
<Button
|
86
|
-
type='submit'
|
87
|
-
className={classes.button}
|
88
|
-
loading={formState.isSubmitting}
|
89
|
-
color='primary'
|
90
|
-
variant='pill'
|
91
|
-
size='large'
|
92
|
-
sx={(theme) => ({
|
93
|
-
marginTop: theme.spacings.sm,
|
94
|
-
marginBottom: theme.spacings.sm,
|
95
|
-
width: '100%',
|
96
|
-
})}
|
97
|
-
{...buttonProps}
|
98
|
-
>
|
99
|
-
<Trans>Add to Cart</Trans>
|
100
|
-
</Button>
|
101
|
-
{additionalButtons}
|
102
|
-
</Box>
|
103
|
-
|
104
|
-
<ApolloCartErrorAlert error={error} />
|
105
|
-
|
106
|
-
{data?.addProductsToCart?.user_errors.map((e) => (
|
107
|
-
<Box key={e?.code}>
|
108
|
-
<Alert severity='error'>{e?.message}</Alert>
|
109
|
-
</Box>
|
110
|
-
))}
|
111
|
-
|
112
|
-
<MessageSnackbar
|
113
|
-
open={
|
114
|
-
!formState.isSubmitting &&
|
115
|
-
formState.isSubmitSuccessful &&
|
116
|
-
!error?.message &&
|
117
|
-
!data?.addProductsToCart?.user_errors?.length
|
118
|
-
}
|
119
|
-
variant='pill'
|
120
|
-
severity='success'
|
121
|
-
autoHide
|
122
|
-
action={
|
123
|
-
<Button
|
124
|
-
href='/cart'
|
125
|
-
id='view-shopping-cart-button'
|
126
|
-
size='medium'
|
127
|
-
variant='pill'
|
128
|
-
color='secondary'
|
129
|
-
endIcon={<IconSvg src={iconChevronRight} />}
|
130
|
-
>
|
131
|
-
<Trans>View shopping cart</Trans>
|
132
|
-
</Button>
|
133
|
-
}
|
134
|
-
>
|
135
|
-
<Trans>
|
136
|
-
<strong>{name}</strong> has been added to your shopping cart!
|
137
|
-
</Trans>
|
138
|
-
</MessageSnackbar>
|
139
|
-
</Box>
|
140
|
-
)
|
141
|
-
}
|
142
|
-
ProductAddToCart.selectors = selectors
|
@@ -1,10 +0,0 @@
|
|
1
|
-
import { Image } from '@graphcommerce/image'
|
2
|
-
import type { ProductImageFragment } from './ProductImage.gql'
|
3
|
-
|
4
|
-
export function ProductImage(props: ProductImageFragment) {
|
5
|
-
const { url, label } = props
|
6
|
-
|
7
|
-
if (!url) return null
|
8
|
-
|
9
|
-
return <Image src={url} width={328} height={328} alt={label ?? ''} dontReportWronglySizedImages />
|
10
|
-
}
|
@@ -1,10 +0,0 @@
|
|
1
|
-
import type { ProductVideoFragment } from './ProductVideo.gql'
|
2
|
-
|
3
|
-
export function ProductVideo(props: ProductVideoFragment) {
|
4
|
-
const { video_content } = props
|
5
|
-
|
6
|
-
if (!video_content?.video_url) return null
|
7
|
-
|
8
|
-
// eslint-disable-next-line jsx-a11y/media-has-caption
|
9
|
-
return <video src={video_content.video_url} />
|
10
|
-
}
|