@graphcommerce/magento-product 8.1.0-canary.9 → 9.0.0-canary.55
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/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
|
@@ -1,19 +1,22 @@
|
|
1
1
|
import { useQuery } from '@graphcommerce/graphql'
|
2
2
|
import { StoreConfigDocument } from '@graphcommerce/magento-store'
|
3
|
-
import {
|
3
|
+
import { UnitFormat, UnitFormatProps } from '@graphcommerce/next-ui'
|
4
4
|
import { ProductWeightFragment } from './ProductWeight.gql'
|
5
5
|
|
6
|
-
export
|
7
|
-
|
6
|
+
export type ProductWeightProps = Omit<UnitFormatProps, 'unit'> & { product: ProductWeightFragment }
|
7
|
+
|
8
|
+
export function ProductWeight(props: ProductWeightProps) {
|
9
|
+
const { product, ...rest } = props
|
10
|
+
|
8
11
|
const { data: conf } = useQuery(StoreConfigDocument)
|
9
|
-
const unit = conf?.storeConfig?.weight_unit ?? ''
|
10
12
|
|
11
|
-
|
13
|
+
if (!product.weight) return null
|
14
|
+
|
15
|
+
const unit = conf?.storeConfig?.weight_unit === 'lbs' ? 'pound' : 'kilogram'
|
12
16
|
|
13
|
-
if (!numberFormatter || !weight) return null
|
14
17
|
return (
|
15
|
-
|
16
|
-
{
|
17
|
-
|
18
|
+
<UnitFormat unit={unit} {...rest}>
|
19
|
+
{product.weight}
|
20
|
+
</UnitFormat>
|
18
21
|
)
|
19
22
|
}
|
package/components/index.ts
CHANGED
@@ -41,3 +41,5 @@ export * from './ProductStaticPaths/getSitemapPaths'
|
|
41
41
|
export * from './ProductUpsells/UpsellProducts.gql'
|
42
42
|
export * from './ProductWeight/ProductWeight'
|
43
43
|
export * from './ProductListPrice'
|
44
|
+
export * from './ProductListSuggestions/ProductListSuggestions'
|
45
|
+
export * from './ProductListSuggestions/ProductListSuggestions.gql'
|