@graphcommerce/magento-product 8.1.0-canary.8 → 9.0.0-canary.54
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 -2
- package/Api/ProductPageItem.graphql +1 -1
- package/CHANGELOG.md +113 -0
- package/Config.graphqls +13 -0
- package/components/AddProductsToCart/AddProductsToCartButton.tsx +3 -2
- package/components/AddProductsToCart/AddProductsToCartFab.tsx +2 -2
- package/components/AddProductsToCart/AddProductsToCartForm.tsx +31 -28
- package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +25 -16
- package/components/AddProductsToCart/UseAddProductsToCartAction.graphql +1 -1
- package/components/AddProductsToCart/findAddedItems.ts +1 -4
- package/components/AddProductsToCart/useAddProductsToCartAction.ts +2 -1
- package/components/AddProductsToCart/useFormAddProductsToCart.ts +1 -2
- package/components/JsonLdProduct/JsonLdProduct.graphql +1 -1
- package/components/JsonLdProduct/ProductPageJsonLd.tsx +1 -1
- package/components/ProductAddToCart/ProductAddToCart.tsx +6 -8
- package/components/ProductCustomizable/ProductCustomizable.graphql +1 -1
- package/components/ProductCustomizable/index.ts +1 -0
- package/components/ProductCustomizable/productCustomizableSelectors.ts +59 -0
- package/components/ProductFiltersPro/PriceSlider.tsx +1 -2
- package/components/ProductFiltersPro/ProductFilterEqualChip.tsx +1 -1
- package/components/ProductFiltersPro/ProductFilterEqualSection.tsx +2 -2
- package/components/ProductFiltersPro/ProductFilterRangeChip.tsx +1 -1
- package/components/ProductFiltersPro/ProductFilterRangeSection.tsx +1 -1
- package/components/ProductFiltersPro/ProductFiltersPro.tsx +103 -19
- package/components/ProductFiltersPro/ProductFiltersProAggregations.tsx +31 -18
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +6 -10
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +18 -8
- package/components/ProductFiltersPro/ProductFiltersProCategorySection.tsx +130 -0
- package/components/ProductFiltersPro/ProductFiltersProChips.tsx +10 -8
- package/components/ProductFiltersPro/ProductFiltersProClearAll.tsx +4 -16
- package/components/ProductFiltersPro/ProductFiltersProLayoutSidebar.tsx +15 -7
- package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +5 -2
- package/components/ProductFiltersPro/ProductFiltersProNoResults.tsx +79 -0
- package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +1 -1
- package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +2 -4
- package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +7 -2
- package/components/ProductFiltersPro/activeAggregations.ts +5 -9
- package/components/ProductFiltersPro/applyAggregationCount.ts +14 -8
- package/components/ProductFiltersPro/index.ts +9 -0
- package/components/ProductFiltersPro/{useClearAllFiltersHandler.ts → useProductFiltersProClearAllAction.ts} +1 -1
- package/components/ProductFiltersPro/useProductFiltersProHasFiltersApplied.ts +21 -0
- package/components/ProductFiltersPro/useProductFiltersProSort.tsx +4 -2
- package/components/ProductList/ProductList.graphql +8 -5
- package/components/ProductListCount/ProductListCount.tsx +3 -1
- package/components/ProductListFilters/ProductFilters.graphql +7 -2
- package/components/ProductListFilters/ProductListFilters.graphql +1 -1
- package/components/ProductListFiltersContainer/ProductListFiltersContainer.tsx +2 -4
- package/components/ProductListItem/ProductDiscountLabel.tsx +2 -3
- package/components/ProductListItem/ProductListItem.tsx +3 -3
- package/components/ProductListItem/ProductListItemTitleAndPrice.tsx +18 -15
- package/components/ProductListItems/ProductListItemsBase.tsx +65 -23
- package/components/ProductListItems/filterTypes.tsx +14 -5
- package/components/ProductListItems/filteredProductList.tsx +23 -0
- package/components/ProductListItems/productListApplyCategoryDefaults.ts +44 -4
- package/components/ProductListItems/renderer.tsx +8 -2
- package/components/ProductListPagination/ProductListPagination.tsx +39 -20
- package/components/ProductListPrice/ProductListPrice.tsx +9 -4
- package/components/ProductListSuggestions/ProductListSuggestions.graphql +5 -0
- package/components/ProductListSuggestions/ProductListSuggestions.tsx +42 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.graphql +3 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +3 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumbs.tsx +40 -0
- package/components/ProductPageBreadcrumb/index.ts +1 -0
- package/components/ProductPageDescription/ComplexTextValue.graphql +1 -1
- package/components/ProductPageDescription/ProductPageDescription.tsx +1 -1
- package/components/ProductPageGallery/ProductImage.graphql +1 -0
- package/components/ProductPageGallery/ProductPageGallery.tsx +14 -8
- package/components/ProductPagePrice/ProductPagePrice.graphql +0 -6
- package/components/ProductPagePrice/ProductPagePrice.tsx +19 -12
- package/components/ProductPagePrice/ProductPagePriceTiers.tsx +4 -3
- package/components/ProductPagePrice/useCustomizableOptionPrice.ts +11 -53
- package/components/ProductShortDescription/ProductShortDescription.tsx +2 -0
- package/components/ProductStaticPaths/getProductStaticPaths.ts +2 -3
- package/components/ProductStaticPaths/getSitemapPaths.ts +3 -0
- package/components/ProductWeight/ProductWeight.tsx +12 -9
- package/components/index.ts +2 -0
- package/hooks/useProductList.ts +123 -0
- package/hooks/useProductListLink.ts +6 -3
- package/index.ts +1 -0
- package/package.json +14 -13
@@ -4,7 +4,10 @@ import type {
|
|
4
4
|
FilterRangeTypeInput,
|
5
5
|
SortEnum,
|
6
6
|
} from '@graphcommerce/graphql-mesh'
|
7
|
+
import { useRouter } from 'next/router'
|
7
8
|
import { FilterTypes, ProductListParams } from './filterTypes'
|
9
|
+
// eslint-disable-next-line import/no-extraneous-dependencies
|
10
|
+
import { equal } from '@wry/equality'
|
8
11
|
|
9
12
|
export function parseParams(
|
10
13
|
url: string,
|
@@ -73,3 +76,23 @@ export function extractUrlQuery(params?: { url: string[] }) {
|
|
73
76
|
if (queryIndex > 0 && !query.length) return [undefined, undefined] as const
|
74
77
|
return [url, query] as const
|
75
78
|
}
|
79
|
+
|
80
|
+
export function useRouterFilterParams(props: {
|
81
|
+
filterTypes?: FilterTypes | undefined
|
82
|
+
params?: ProductListParams
|
83
|
+
}) {
|
84
|
+
const { filterTypes, params } = props
|
85
|
+
const router = useRouter()
|
86
|
+
|
87
|
+
const path = router.asPath.startsWith('/c/') ? router.asPath.slice(3) : router.asPath.slice(1)
|
88
|
+
const [url, query] = extractUrlQuery({ url: path.split('#')[0].split('/') })
|
89
|
+
if (!url || !query || !filterTypes) return { params, shallow: false }
|
90
|
+
|
91
|
+
const searchParam = url.startsWith('search') ? decodeURI(url.split('/')[1] ?? '') : null
|
92
|
+
const clientParams = parseParams(url, query, filterTypes, searchParam)
|
93
|
+
|
94
|
+
if (clientParams && !clientParams?.filters.category_uid && params?.filters.category_uid)
|
95
|
+
clientParams.filters.category_uid = params?.filters.category_uid
|
96
|
+
|
97
|
+
return { params: clientParams, shallow: !equal(params, clientParams) }
|
98
|
+
}
|
@@ -1,13 +1,53 @@
|
|
1
|
-
import {
|
1
|
+
import { cloneDeep, useQuery } from '@graphcommerce/graphql'
|
2
|
+
import { StoreConfigDocument, StoreConfigQuery } from '@graphcommerce/magento-store'
|
2
3
|
import { CategoryDefaultFragment } from './CategoryDefault.gql'
|
3
4
|
import { ProductListParams } from './filterTypes'
|
4
|
-
import {
|
5
|
+
import { ProductListQueryVariables } from '../ProductList/ProductList.gql'
|
5
6
|
|
7
|
+
export function useProductListApplyCategoryDefaults(
|
8
|
+
params: ProductListParams | undefined,
|
9
|
+
category: CategoryDefaultFragment | null | undefined,
|
10
|
+
): ProductListQueryVariables | undefined {
|
11
|
+
const storeConfig = useQuery(StoreConfigDocument)
|
12
|
+
|
13
|
+
if (!params) return params
|
14
|
+
|
15
|
+
const variables = cloneDeep(params)
|
16
|
+
if (!variables.pageSize) variables.pageSize = storeConfig.data?.storeConfig?.grid_per_page ?? 12
|
17
|
+
|
18
|
+
if (Object.keys(params.sort).length === 0) {
|
19
|
+
const categorySort = category?.default_sort_by as keyof ProductListParams['sort']
|
20
|
+
const defaultSort = storeConfig.data?.storeConfig
|
21
|
+
?.catalog_default_sort_by as keyof ProductListParams['sort']
|
22
|
+
if (categorySort) variables.sort = { [categorySort]: 'ASC' }
|
23
|
+
else if (defaultSort) variables.sort = { [defaultSort]: 'ASC' }
|
24
|
+
}
|
25
|
+
|
26
|
+
if (!variables.filters.category_uid?.in?.[0]) {
|
27
|
+
variables.filters.category_uid = { eq: category?.uid }
|
28
|
+
}
|
29
|
+
|
30
|
+
return variables
|
31
|
+
}
|
32
|
+
|
33
|
+
export async function productListApplyCategoryDefaults(
|
34
|
+
params: ProductListParams,
|
35
|
+
conf: StoreConfigQuery,
|
36
|
+
category:
|
37
|
+
| Promise<CategoryDefaultFragment | null | undefined>
|
38
|
+
| CategoryDefaultFragment
|
39
|
+
| null
|
40
|
+
| undefined,
|
41
|
+
): Promise<ProductListQueryVariables>
|
6
42
|
export async function productListApplyCategoryDefaults(
|
7
43
|
params: ProductListParams | undefined,
|
8
44
|
conf: StoreConfigQuery,
|
9
|
-
category:
|
10
|
-
|
45
|
+
category:
|
46
|
+
| Promise<CategoryDefaultFragment | null | undefined>
|
47
|
+
| CategoryDefaultFragment
|
48
|
+
| null
|
49
|
+
| undefined,
|
50
|
+
): Promise<ProductListQueryVariables | undefined> {
|
11
51
|
if (!params) return params
|
12
52
|
|
13
53
|
const newParams = cloneDeep(params)
|
@@ -1,17 +1,23 @@
|
|
1
1
|
import { TypeRenderer } from '@graphcommerce/next-ui'
|
2
2
|
import { ProductListItemFragment } from '../../Api/ProductListItem.gql'
|
3
|
-
import { ProductListItem
|
3
|
+
import { ProductListItem } from '../ProductListItem/ProductListItem'
|
4
4
|
|
5
5
|
type SkeletonType = { __typename: 'Skeleton'; uid: string }
|
6
6
|
export type ProductListItemType = ProductListItemFragment | SkeletonType
|
7
7
|
export type ProductListItemRenderer = TypeRenderer<ProductListItemFragment | SkeletonType>
|
8
8
|
|
9
|
+
/**
|
10
|
+
* @deprecated Please use productListRenderer from the example directory instead.
|
11
|
+
*/
|
9
12
|
export const renderer: ProductListItemRenderer = {
|
10
|
-
Skeleton:
|
13
|
+
Skeleton: ProductListItem,
|
11
14
|
SimpleProduct: ProductListItem,
|
12
15
|
ConfigurableProduct: ProductListItem,
|
13
16
|
BundleProduct: ProductListItem,
|
14
17
|
VirtualProduct: ProductListItem,
|
15
18
|
DownloadableProduct: ProductListItem,
|
16
19
|
GroupedProduct: ProductListItem,
|
20
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
21
|
+
// @ts-ignore GiftCardProduct is only available in Commerce
|
22
|
+
GiftCardProduct: ProductListItem,
|
17
23
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Pagination } from '@graphcommerce/next-ui'
|
1
|
+
import { NextLink, PaginationExtended, Pagination } from '@graphcommerce/next-ui'
|
2
2
|
import { Link, PaginationProps } from '@mui/material'
|
3
3
|
import { productListLink } from '../../hooks/useProductListLink'
|
4
4
|
import { ProductListParams } from '../ProductListItems/filterTypes'
|
@@ -16,23 +16,42 @@ export function ProductListPagination({
|
|
16
16
|
}: ProductPaginationProps) {
|
17
17
|
if (!page_info || !page_info.total_pages || !page_info.current_page) return null
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
19
|
+
if (import.meta.graphCommerce.productListPaginationVariant !== 'EXTENDED') {
|
20
|
+
return (
|
21
|
+
<Pagination
|
22
|
+
count={page_info?.total_pages}
|
23
|
+
page={page_info?.current_page ?? 1}
|
24
|
+
renderLink={(_, icon, btnProps) => {
|
25
|
+
const suffix = btnProps.page === 1 ? '' : `#products`
|
26
|
+
return (
|
27
|
+
<Link
|
28
|
+
{...btnProps}
|
29
|
+
href={`${productListLink({ ...params, currentPage: btnProps.page })}${suffix}`}
|
30
|
+
component={NextLink}
|
31
|
+
shallow
|
32
|
+
color='inherit'
|
33
|
+
>
|
34
|
+
{icon}
|
35
|
+
</Link>
|
36
|
+
)
|
37
|
+
}}
|
38
|
+
{...paginationProps}
|
39
|
+
/>
|
40
|
+
)
|
41
|
+
}
|
42
|
+
|
43
|
+
if (import.meta.graphCommerce.productListPaginationVariant === 'EXTENDED') {
|
44
|
+
return (
|
45
|
+
<PaginationExtended
|
46
|
+
count={page_info?.total_pages}
|
47
|
+
page={page_info?.current_page ?? 1}
|
48
|
+
paginationHref={({ page }) =>
|
49
|
+
`${productListLink({ ...params, currentPage: page })}${page === 1 ? '' : '#products'}`
|
50
|
+
}
|
51
|
+
{...paginationProps}
|
52
|
+
/>
|
53
|
+
)
|
54
|
+
}
|
55
|
+
|
56
|
+
return null
|
38
57
|
}
|
@@ -1,10 +1,12 @@
|
|
1
|
+
import { InContextMask } from '@graphcommerce/graphql'
|
1
2
|
import { Money } from '@graphcommerce/magento-store'
|
2
3
|
import { extendableComponent } from '@graphcommerce/next-ui'
|
3
|
-
import { Typography, TypographyProps
|
4
|
+
import { Typography, TypographyProps } from '@mui/material'
|
4
5
|
import { ProductListPriceFragment } from './ProductListPrice.gql'
|
5
6
|
|
6
7
|
export const productListPrice = extendableComponent('ProductListPrice', [
|
7
8
|
'root',
|
9
|
+
'finalPrice',
|
8
10
|
'discountPrice',
|
9
11
|
] as const)
|
10
12
|
|
@@ -18,19 +20,22 @@ export function ProductListPrice(props: ProductListPriceProps) {
|
|
18
20
|
return (
|
19
21
|
<Typography component='div' variant='body1' className={classes.root} sx={sx}>
|
20
22
|
{regular_price.value !== final_price.value && (
|
21
|
-
<
|
23
|
+
<InContextMask
|
22
24
|
component='span'
|
23
25
|
sx={{
|
24
26
|
textDecoration: 'line-through',
|
25
27
|
color: 'text.disabled',
|
26
28
|
marginRight: '8px',
|
27
29
|
}}
|
30
|
+
skeleton={{ width: '3.5em' }}
|
28
31
|
className={classes.discountPrice}
|
29
32
|
>
|
30
33
|
<Money {...regular_price} />
|
31
|
-
</
|
34
|
+
</InContextMask>
|
32
35
|
)}
|
33
|
-
<
|
36
|
+
<InContextMask className={classes.finalPrice} component='span' skeleton={{ width: '3.5em' }}>
|
37
|
+
<Money {...final_price} />
|
38
|
+
</InContextMask>
|
34
39
|
</Typography>
|
35
40
|
)
|
36
41
|
}
|
@@ -0,0 +1,42 @@
|
|
1
|
+
import { ListFormat, filterNonNullableKeys } from '@graphcommerce/next-ui'
|
2
|
+
import { Trans } from '@lingui/macro'
|
3
|
+
import { Box, Link } from '@mui/material'
|
4
|
+
import { productListLinkFromFilter } from '../../hooks/useProductListLink'
|
5
|
+
import { useProductFiltersPro } from '../ProductFiltersPro'
|
6
|
+
import { ProductListSuggestionsFragment } from './ProductListSuggestions.gql'
|
7
|
+
|
8
|
+
type ProductListSuggestionsProps = {
|
9
|
+
products: ProductListSuggestionsFragment
|
10
|
+
}
|
11
|
+
|
12
|
+
export function ProductListSuggestions(props: ProductListSuggestionsProps) {
|
13
|
+
const { products } = props
|
14
|
+
|
15
|
+
const { form, submit, params } = useProductFiltersPro()
|
16
|
+
|
17
|
+
if (!products.suggestions || !products.suggestions.length) return null
|
18
|
+
|
19
|
+
const list = (
|
20
|
+
<ListFormat listStyle='short' type='disjunction'>
|
21
|
+
{filterNonNullableKeys(products.suggestions).map((s) => (
|
22
|
+
<Link
|
23
|
+
key={s.search}
|
24
|
+
href={productListLinkFromFilter({ ...params, search: s.search })}
|
25
|
+
onClick={() => {
|
26
|
+
form.setValue('currentPage', 1)
|
27
|
+
form.setValue('search', s.search)
|
28
|
+
return submit()
|
29
|
+
}}
|
30
|
+
>
|
31
|
+
{s.search}
|
32
|
+
</Link>
|
33
|
+
))}
|
34
|
+
</ListFormat>
|
35
|
+
)
|
36
|
+
|
37
|
+
return (
|
38
|
+
<Box>
|
39
|
+
<Trans>Did you mean: {list}</Trans>
|
40
|
+
</Box>
|
41
|
+
)
|
42
|
+
}
|
@@ -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()
|
@@ -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
|
+
}
|
@@ -26,7 +26,7 @@ export function ProductPageDescription(props: ProductPageDescriptionProps) {
|
|
26
26
|
const { product, right, fontSize = 'subtitle1', maxWidth = 'lg', sx = [] } = props
|
27
27
|
|
28
28
|
return (
|
29
|
-
<LazyHydrate>
|
29
|
+
<LazyHydrate height={500}>
|
30
30
|
<ColumnTwoWithTop
|
31
31
|
maxWidth={maxWidth}
|
32
32
|
className={classes.root}
|
@@ -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,7 +1,7 @@
|
|
1
1
|
import { useWatch } from '@graphcommerce/ecommerce-ui'
|
2
|
+
import { InContextMask } from '@graphcommerce/graphql'
|
2
3
|
import { Money } from '@graphcommerce/magento-store'
|
3
4
|
import { extendableComponent } from '@graphcommerce/next-ui'
|
4
|
-
import { Box } from '@mui/material'
|
5
5
|
import { AddToCartItemSelector, useFormAddProductsToCart } from '../AddProductsToCart'
|
6
6
|
import { ProductPagePriceFragment } from './ProductPagePrice.gql'
|
7
7
|
import { getProductTierPrice } from './getProductTierPrice'
|
@@ -13,7 +13,10 @@ import {
|
|
13
13
|
export type ProductPagePriceProps = { product: ProductPagePriceFragment } & AddToCartItemSelector &
|
14
14
|
UseCustomizableOptionPriceProps
|
15
15
|
|
16
|
-
const { classes } = extendableComponent('ProductPagePrice', [
|
16
|
+
const { classes } = extendableComponent('ProductPagePrice', [
|
17
|
+
'finalPrice',
|
18
|
+
'discountPrice',
|
19
|
+
] as const)
|
17
20
|
|
18
21
|
export function ProductPagePrice(props: ProductPagePriceProps) {
|
19
22
|
const { product, index = 0 } = props
|
@@ -24,23 +27,27 @@ export function ProductPagePrice(props: ProductPagePriceProps) {
|
|
24
27
|
getProductTierPrice(product, quantity) ?? product.price_range.minimum_price.final_price
|
25
28
|
|
26
29
|
const priceValue = useCustomizableOptionPrice(props)
|
30
|
+
const regularPrice = product.price_range.minimum_price.regular_price
|
27
31
|
|
28
32
|
return (
|
29
33
|
<>
|
30
|
-
{
|
31
|
-
<
|
34
|
+
{regularPrice.value !== price.value && (
|
35
|
+
<InContextMask
|
32
36
|
component='span'
|
33
|
-
sx={{
|
34
|
-
textDecoration: 'line-through',
|
35
|
-
color: 'text.disabled',
|
36
|
-
marginRight: '8px',
|
37
|
-
}}
|
38
37
|
className={classes.discountPrice}
|
38
|
+
skeleton={{ variant: 'text', sx: { width: '3em', transform: 'none' } }}
|
39
|
+
sx={[{ textDecoration: 'line-through', color: 'text.disabled', marginRight: '8px' }]}
|
39
40
|
>
|
40
|
-
<Money {...
|
41
|
-
</
|
41
|
+
<Money {...regularPrice} />
|
42
|
+
</InContextMask>
|
42
43
|
)}
|
43
|
-
<
|
44
|
+
<InContextMask
|
45
|
+
component='span'
|
46
|
+
skeleton={{ variant: 'text', sx: { width: '3em', transform: 'none' } }}
|
47
|
+
className={classes.finalPrice}
|
48
|
+
>
|
49
|
+
<Money {...price} value={priceValue} />
|
50
|
+
</InContextMask>
|
44
51
|
</>
|
45
52
|
)
|
46
53
|
}
|
@@ -1,7 +1,8 @@
|
|
1
|
+
import { InContextMask } from '@graphcommerce/graphql'
|
1
2
|
import { Money } from '@graphcommerce/magento-store'
|
2
3
|
import { filterNonNullableKeys } from '@graphcommerce/next-ui'
|
3
4
|
import { Trans } from '@lingui/react'
|
4
|
-
import {
|
5
|
+
import { SxProps, Theme } from '@mui/material'
|
5
6
|
import { ProductPagePriceFragment } from './ProductPagePrice.gql'
|
6
7
|
|
7
8
|
export type ProductPagePriceTiersProps = {
|
@@ -21,7 +22,7 @@ export function ProductPagePriceTiers(props: ProductPagePriceTiersProps) {
|
|
21
22
|
if (!priceTiers.length) return null
|
22
23
|
|
23
24
|
return (
|
24
|
-
<
|
25
|
+
<InContextMask sx={sx} variant='rectangular'>
|
25
26
|
{priceTiers.map(({ quantity, final_price, discount }) => (
|
26
27
|
<div key={quantity}>
|
27
28
|
<Trans
|
@@ -31,6 +32,6 @@ export function ProductPagePriceTiers(props: ProductPagePriceTiersProps) {
|
|
31
32
|
/>
|
32
33
|
</div>
|
33
34
|
))}
|
34
|
-
</
|
35
|
+
</InContextMask>
|
35
36
|
)
|
36
37
|
}
|
@@ -1,65 +1,23 @@
|
|
1
1
|
import { useWatch } from '@graphcommerce/ecommerce-ui'
|
2
|
-
import { PriceTypeEnum } from '@graphcommerce/graphql-mesh'
|
3
2
|
import { MoneyFragment } from '@graphcommerce/magento-store'
|
4
3
|
import { filterNonNullableKeys, isTypename, nonNullable } from '@graphcommerce/next-ui'
|
5
|
-
import type { Simplify } from 'type-fest'
|
6
4
|
import { AddToCartItemSelector, useFormAddProductsToCart } from '../AddProductsToCart'
|
7
|
-
import
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
import { CustomizableRadioOptionFragment } from '../ProductCustomizable/CustomizableRadioOption.gql'
|
15
|
-
import { ProductCustomizable_SimpleProduct_Fragment } from '../ProductCustomizable/ProductCustomizable.gql'
|
5
|
+
import {
|
6
|
+
productCustomizableSelectors,
|
7
|
+
CustomizableProductOptionBase,
|
8
|
+
OptionValueSelector,
|
9
|
+
AnyOption,
|
10
|
+
SelectorsProp,
|
11
|
+
} from '../ProductCustomizable/productCustomizableSelectors'
|
16
12
|
import { ProductPagePriceFragment } from './ProductPagePrice.gql'
|
17
13
|
import { getProductTierPrice } from './getProductTierPrice'
|
18
14
|
|
19
|
-
type AnyOption = NonNullable<
|
20
|
-
NonNullable<ProductCustomizable_SimpleProduct_Fragment['options']>[number]
|
21
|
-
>
|
22
|
-
type OptionValueSelector = {
|
23
|
-
[T in AnyOption as T['__typename']]: (option: T) => Option | Option[]
|
24
|
-
}
|
25
|
-
|
26
|
-
const defaultSelectors = {
|
27
|
-
CustomizableAreaOption: (o: CustomizableAreaOptionFragment) => o.areaValue,
|
28
|
-
CustomizableCheckboxOption: (o: CustomizableCheckboxOptionFragment) => o.checkboxValue,
|
29
|
-
CustomizableFileOption: (o: CustomizableFileOptionFragment) => o.fileValue,
|
30
|
-
CustomizableDateOption: (o: CustomizableDateOptionFragment) => o.dateValue,
|
31
|
-
CustomizableDropDownOption: (o: CustomizableDropDownOptionFragment) => o.dropdownValue,
|
32
|
-
CustomizableFieldOption: (o: CustomizableFieldOptionFragment) => o.fieldValue,
|
33
|
-
CustomizableMultipleOption: (o: CustomizableMultipleOptionFragment) => o.multipleValue,
|
34
|
-
CustomizableRadioOption: (o: CustomizableRadioOptionFragment) => o.radioValue,
|
35
|
-
}
|
36
|
-
|
37
|
-
type MissingOptionValueSelectors = Omit<OptionValueSelector, keyof typeof defaultSelectors>
|
38
|
-
type DefinedOptionValueSelectors = Partial<Pick<OptionValueSelector, keyof typeof defaultSelectors>>
|
39
|
-
|
40
|
-
type Selectors = Simplify<
|
41
|
-
keyof MissingOptionValueSelectors extends never
|
42
|
-
? (MissingOptionValueSelectors & DefinedOptionValueSelectors) | undefined
|
43
|
-
: MissingOptionValueSelectors & DefinedOptionValueSelectors
|
44
|
-
>
|
45
|
-
|
46
15
|
export type UseCustomizableOptionPriceProps = {
|
47
16
|
product: ProductPagePriceFragment
|
48
17
|
} & AddToCartItemSelector &
|
49
|
-
|
50
|
-
? { selectors?: Selectors }
|
51
|
-
: { selectors: Selectors })
|
52
|
-
|
53
|
-
type Option =
|
54
|
-
| {
|
55
|
-
price?: number | null | undefined
|
56
|
-
price_type?: PriceTypeEnum | null | undefined
|
57
|
-
uid?: string | null | undefined
|
58
|
-
}
|
59
|
-
| undefined
|
60
|
-
| null
|
18
|
+
SelectorsProp
|
61
19
|
|
62
|
-
function calcOptionPrice(option:
|
20
|
+
function calcOptionPrice(option: CustomizableProductOptionBase, product: MoneyFragment) {
|
63
21
|
if (!option?.price) return 0
|
64
22
|
switch (option.price_type) {
|
65
23
|
case 'DYNAMIC':
|
@@ -81,7 +39,7 @@ export function useCustomizableOptionPrice(props: UseCustomizableOptionPriceProp
|
|
81
39
|
getProductTierPrice(product, cartItem?.quantity) ??
|
82
40
|
product.price_range.minimum_price.final_price
|
83
41
|
|
84
|
-
const allSelectors: OptionValueSelector = { ...
|
42
|
+
const allSelectors: OptionValueSelector = { ...productCustomizableSelectors, ...selectors }
|
85
43
|
|
86
44
|
if (isTypename(product, ['GroupedProduct'])) return price.value
|
87
45
|
if (!product.options || product.options.length === 0) return price.value
|
@@ -95,7 +53,7 @@ export function useCustomizableOptionPrice(props: UseCustomizableOptionPriceProp
|
|
95
53
|
|
96
54
|
const selector = allSelectors[productOption.__typename] as
|
97
55
|
| undefined
|
98
|
-
| ((option: AnyOption) =>
|
56
|
+
| ((option: AnyOption) => CustomizableProductOptionBase | CustomizableProductOptionBase[])
|
99
57
|
const value = selector ? selector(productOption) : null
|
100
58
|
|
101
59
|
if (!value) return 0
|
@@ -11,7 +11,7 @@ export type ProductTypenames = NonNullable<
|
|
11
11
|
export async function getProductStaticPaths(
|
12
12
|
client: ApolloClient<NormalizedCacheObject>,
|
13
13
|
locale: string,
|
14
|
-
|
14
|
+
options: { limit?: boolean } = { limit: import.meta.graphCommerce.limitSsg || false },
|
15
15
|
) {
|
16
16
|
const query = client.query({
|
17
17
|
query: ProductStaticPathsDocument,
|
@@ -36,8 +36,7 @@ export async function getProductStaticPaths(
|
|
36
36
|
const paths: Return['paths'] = (await Promise.all(pages))
|
37
37
|
.map((q) => q.data.products?.items)
|
38
38
|
.flat(1)
|
39
|
-
.filter((item) => (typename ? item?.__typename === typename : true))
|
40
39
|
.map((p) => ({ params: { url: `${p?.url_key}` }, locale }))
|
41
40
|
|
42
|
-
return
|
41
|
+
return options.limit ? paths.slice(0, 1) : paths
|
43
42
|
}
|
@@ -3,6 +3,9 @@ import { canonicalize, nonNullable } from '@graphcommerce/next-ui'
|
|
3
3
|
import { productLink } from '../../hooks/useProductLink'
|
4
4
|
import { ProductStaticPathsDocument } from './ProductStaticPaths.gql'
|
5
5
|
|
6
|
+
/**
|
7
|
+
* @deprecated Not used anymore, use `getProductStaticPaths` instead.
|
8
|
+
*/
|
6
9
|
export async function getSitemapPaths(
|
7
10
|
client: ApolloClient<NormalizedCacheObject>,
|
8
11
|
ctx: { locale?: string; defaultLocale?: string },
|