@graphcommerce/magento-product 8.1.0-canary.9 → 9.0.0-canary.101
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 +278 -84
- package/Config.graphqls +13 -0
- package/components/AddProductsToCart/AddProductsToCartButton.tsx +17 -4
- package/components/AddProductsToCart/AddProductsToCartFab.tsx +7 -2
- package/components/AddProductsToCart/AddProductsToCartForm.tsx +31 -29
- package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +14 -63
- package/components/AddProductsToCart/AddProductsToCartSnackbarMessage.tsx +84 -0
- package/components/AddProductsToCart/UseAddProductsToCartAction.graphql +1 -1
- package/components/AddProductsToCart/findAddedItems.ts +1 -4
- package/components/AddProductsToCart/index.ts +1 -0
- 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/CustomizableCheckboxOption.tsx +3 -4
- package/components/ProductCustomizable/CustomizableMultipleOption.tsx +2 -2
- package/components/ProductCustomizable/CustomizableRadioOption.tsx +2 -2
- 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 +4 -5
- package/components/ProductFiltersPro/ProductFilterEqualSection.tsx +6 -7
- 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 +41 -20
- 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/ProductFiltersProLimitChip.tsx +2 -8
- package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +7 -10
- package/components/ProductFiltersPro/ProductFiltersProNoResults.tsx +79 -0
- package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +5 -7
- package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +2 -4
- package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +11 -3
- 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 +7 -3
- package/components/ProductList/ProductList.graphql +8 -5
- package/components/ProductListCount/ProductListCount.tsx +3 -1
- package/components/ProductListFilters/ProductFilters.graphql +11 -2
- package/components/ProductListFilters/ProductListFilters.graphql +1 -1
- package/components/ProductListFilters/ProductListFilters.tsx +13 -19
- 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/ProductFilterTypes.graphql +8 -0
- package/components/ProductListItems/ProductListItemsBase.tsx +71 -30
- package/components/ProductListItems/filterTypes.tsx +14 -7
- package/components/ProductListItems/filteredProductList.tsx +44 -17
- package/components/ProductListItems/getFilterTypes.ts +33 -4
- package/components/ProductListItems/productListApplyCategoryDefaults.ts +50 -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/ProductSpecs/ProductSpecs.graphql +21 -1
- package/components/ProductSpecs/ProductSpecs.tsx +5 -11
- package/components/ProductSpecs/ProductSpecsAggregations.tsx +34 -0
- package/components/ProductSpecs/ProductSpecsCustomAttributes.tsx +45 -0
- package/components/ProductSpecs/ProductSpecsTypes.graphql +8 -0
- package/components/ProductStaticPaths/getProductStaticPaths.ts +1 -1
- package/components/ProductWeight/ProductWeight.tsx +12 -9
- package/components/index.ts +2 -0
- package/hooks/useProductLink.ts +4 -0
- package/hooks/useProductList.ts +148 -0
- package/hooks/useProductListLink.ts +6 -3
- package/index.ts +1 -0
- package/package.json +14 -14
@@ -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 { productLink } from '../../hooks/useProductLink'
|
10
|
+
import { productPageCategory } from '../ProductPageCategory/productPageCategory'
|
11
|
+
import { ProductPageBreadcrumbFragment } from './ProductPageBreadcrumb.gql'
|
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,5 +1,5 @@
|
|
1
1
|
fragment ProductSpecs on Products {
|
2
|
-
aggregations {
|
2
|
+
aggregations @skip(if: $useCustomAttributes) {
|
3
3
|
attribute_code
|
4
4
|
count
|
5
5
|
label
|
@@ -9,4 +9,24 @@ fragment ProductSpecs on Products {
|
|
9
9
|
value
|
10
10
|
}
|
11
11
|
}
|
12
|
+
items {
|
13
|
+
__typename
|
14
|
+
uid
|
15
|
+
...ProductListItem
|
16
|
+
custom_attributesV2(filters: { is_visible_on_front: true }) @include(if: $useCustomAttributes) {
|
17
|
+
items {
|
18
|
+
code
|
19
|
+
__typename
|
20
|
+
... on AttributeValue {
|
21
|
+
value
|
22
|
+
}
|
23
|
+
... on AttributeSelectedOptions {
|
24
|
+
selected_options {
|
25
|
+
label
|
26
|
+
value
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
12
32
|
}
|
@@ -1,6 +1,8 @@
|
|
1
1
|
import { responsiveVal, Row, SectionContainer, extendableComponent } from '@graphcommerce/next-ui'
|
2
2
|
import { Box, SxProps, Theme } from '@mui/material'
|
3
3
|
import { ProductSpecsFragment } from './ProductSpecs.gql'
|
4
|
+
import { ProductSpecsAggregations } from './ProductSpecsAggregations'
|
5
|
+
import { ProductSpecsCustomAttributes } from './ProductSpecsCustomAttributes'
|
4
6
|
|
5
7
|
export type ProductSpecsProps = ProductSpecsFragment & {
|
6
8
|
title?: string
|
@@ -13,7 +15,7 @@ const parts = ['root', 'specs', 'options'] as const
|
|
13
15
|
const { classes } = extendableComponent(name, parts)
|
14
16
|
|
15
17
|
export function ProductSpecs(props: ProductSpecsProps) {
|
16
|
-
const { aggregations, title, children, sx = [] } = props
|
18
|
+
const { aggregations, items, title, children, sx = [] } = props
|
17
19
|
const filter = ['price', 'category_id', 'size', 'new', 'sale', 'color']
|
18
20
|
const specs = aggregations?.filter(
|
19
21
|
(attr) => !filter.includes(attr?.attribute_code ?? '') && attr?.options?.[0]?.value !== '0',
|
@@ -46,16 +48,8 @@ export function ProductSpecs(props: ProductSpecsProps) {
|
|
46
48
|
},
|
47
49
|
})}
|
48
50
|
>
|
49
|
-
{
|
50
|
-
|
51
|
-
<div>{aggregation?.label}</div>
|
52
|
-
<Box className={classes.options} sx={{ display: 'grid', gridAutoFlow: 'row' }}>
|
53
|
-
{aggregation?.options?.map((option) => (
|
54
|
-
<span key={option?.value}>{option?.label === '1' ? 'Yes' : option?.label}</span>
|
55
|
-
))}
|
56
|
-
</Box>
|
57
|
-
</li>
|
58
|
-
))}
|
51
|
+
{aggregations && <ProductSpecsAggregations aggregations={aggregations} />}
|
52
|
+
{items && <ProductSpecsCustomAttributes items={items} />}
|
59
53
|
</Box>
|
60
54
|
{children}
|
61
55
|
</SectionContainer>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
import { extendableComponent } from '@graphcommerce/next-ui'
|
2
|
+
import { Box } from '@mui/material'
|
3
|
+
import { ProductSpecsFragment } from './ProductSpecs.gql'
|
4
|
+
|
5
|
+
const name = 'ProductSpecs' as const
|
6
|
+
const parts = ['root', 'specs', 'options'] as const
|
7
|
+
const { classes } = extendableComponent(name, parts)
|
8
|
+
|
9
|
+
export type ProductSpecsAggregationsProps = Pick<ProductSpecsFragment, 'aggregations'>
|
10
|
+
|
11
|
+
export function ProductSpecsAggregations(props: ProductSpecsAggregationsProps) {
|
12
|
+
const { aggregations } = props
|
13
|
+
const filter = ['price', 'category_id', 'size', 'new', 'sale', 'color']
|
14
|
+
const specs = aggregations?.filter(
|
15
|
+
(attr) => !filter.includes(attr?.attribute_code ?? '') && attr?.options?.[0]?.value !== '0',
|
16
|
+
)
|
17
|
+
|
18
|
+
if (specs?.length === 0) return null
|
19
|
+
|
20
|
+
return (
|
21
|
+
<>
|
22
|
+
{specs?.map((aggregation) => (
|
23
|
+
<li key={aggregation?.attribute_code}>
|
24
|
+
<div>{aggregation?.label}</div>
|
25
|
+
<Box className={classes.options} sx={{ display: 'grid', gridAutoFlow: 'row' }}>
|
26
|
+
{aggregation?.options?.map((option) => (
|
27
|
+
<span key={option?.value}>{option?.label === '1' ? 'Yes' : option?.label}</span>
|
28
|
+
))}
|
29
|
+
</Box>
|
30
|
+
</li>
|
31
|
+
))}
|
32
|
+
</>
|
33
|
+
)
|
34
|
+
}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import { useQuery } from '@graphcommerce/graphql'
|
2
|
+
import { extendableComponent, ListFormat } from '@graphcommerce/next-ui'
|
3
|
+
import { Box } from '@mui/material'
|
4
|
+
import { ProductSpecsFragment } from './ProductSpecs.gql'
|
5
|
+
import { ProductSpecsTypesDocument } from './ProductSpecsTypes.gql'
|
6
|
+
|
7
|
+
const name = 'ProductSpecs' as const
|
8
|
+
const parts = ['root', 'specs', 'options'] as const
|
9
|
+
const { classes } = extendableComponent(name, parts)
|
10
|
+
|
11
|
+
export type ProductSpecsCustomAttributesProps = Pick<ProductSpecsFragment, 'items'>
|
12
|
+
|
13
|
+
export function ProductSpecsCustomAttributes(props: ProductSpecsCustomAttributesProps) {
|
14
|
+
const { items } = props
|
15
|
+
|
16
|
+
const specs = items?.[0]?.custom_attributesV2?.items
|
17
|
+
|
18
|
+
const productSpecsTypes = useQuery(ProductSpecsTypesDocument)
|
19
|
+
|
20
|
+
if (items?.length === 0) return null
|
21
|
+
|
22
|
+
return (
|
23
|
+
<>
|
24
|
+
{specs?.map((item) => (
|
25
|
+
<li key={item?.code}>
|
26
|
+
<div>
|
27
|
+
{productSpecsTypes?.data?.attributesList?.items?.find(
|
28
|
+
(type) => type?.code === item?.code,
|
29
|
+
)?.label ?? item?.code}
|
30
|
+
</div>
|
31
|
+
<Box className={classes.options}>
|
32
|
+
{item?.__typename === 'AttributeSelectedOptions' && (
|
33
|
+
<ListFormat listStyle='long' type='unit'>
|
34
|
+
{item?.selected_options?.map((option) => (
|
35
|
+
<span key={option?.value}>{option?.label === '1' ? 'Yes' : option?.label}</span>
|
36
|
+
))}
|
37
|
+
</ListFormat>
|
38
|
+
)}
|
39
|
+
{item?.__typename === 'AttributeValue' && <span key={item?.value}>{item.value}</span>}
|
40
|
+
</Box>
|
41
|
+
</li>
|
42
|
+
))}
|
43
|
+
</>
|
44
|
+
)
|
45
|
+
}
|
@@ -22,7 +22,7 @@ export async function getProductStaticPaths(
|
|
22
22
|
const { data } = await query
|
23
23
|
const totalPages = data.products?.page_info?.total_pages ?? 1
|
24
24
|
|
25
|
-
if (totalPages > 1 &&
|
25
|
+
if (totalPages > 1 && options.limit !== true) {
|
26
26
|
for (let i = 2; i <= totalPages; i++) {
|
27
27
|
pages.push(
|
28
28
|
client.query({
|
@@ -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'
|
package/hooks/useProductLink.ts
CHANGED
@@ -4,6 +4,10 @@ export type ProductLinkProps = Omit<ProductLinkFragment, 'uid'>
|
|
4
4
|
|
5
5
|
export const productRoute = import.meta.graphCommerce.productRoute ?? '/p/'
|
6
6
|
|
7
|
+
export function productPath(urlKey: string) {
|
8
|
+
return `${productRoute}${urlKey}`
|
9
|
+
}
|
10
|
+
|
7
11
|
export function productLink(link: ProductLinkProps) {
|
8
12
|
return `${productRoute}${link.url_key}`
|
9
13
|
}
|