@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.
Files changed (94) hide show
  1. package/Api/ProductListItem.graphql +1 -2
  2. package/Api/ProductPageItem.graphql +1 -1
  3. package/CHANGELOG.md +278 -84
  4. package/Config.graphqls +13 -0
  5. package/components/AddProductsToCart/AddProductsToCartButton.tsx +17 -4
  6. package/components/AddProductsToCart/AddProductsToCartFab.tsx +7 -2
  7. package/components/AddProductsToCart/AddProductsToCartForm.tsx +31 -29
  8. package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +14 -63
  9. package/components/AddProductsToCart/AddProductsToCartSnackbarMessage.tsx +84 -0
  10. package/components/AddProductsToCart/UseAddProductsToCartAction.graphql +1 -1
  11. package/components/AddProductsToCart/findAddedItems.ts +1 -4
  12. package/components/AddProductsToCart/index.ts +1 -0
  13. package/components/AddProductsToCart/useAddProductsToCartAction.ts +2 -1
  14. package/components/AddProductsToCart/useFormAddProductsToCart.ts +1 -2
  15. package/components/JsonLdProduct/JsonLdProduct.graphql +1 -1
  16. package/components/JsonLdProduct/ProductPageJsonLd.tsx +1 -1
  17. package/components/ProductAddToCart/ProductAddToCart.tsx +6 -8
  18. package/components/ProductCustomizable/CustomizableCheckboxOption.tsx +3 -4
  19. package/components/ProductCustomizable/CustomizableMultipleOption.tsx +2 -2
  20. package/components/ProductCustomizable/CustomizableRadioOption.tsx +2 -2
  21. package/components/ProductCustomizable/ProductCustomizable.graphql +1 -1
  22. package/components/ProductCustomizable/index.ts +1 -0
  23. package/components/ProductCustomizable/productCustomizableSelectors.ts +59 -0
  24. package/components/ProductFiltersPro/PriceSlider.tsx +1 -2
  25. package/components/ProductFiltersPro/ProductFilterEqualChip.tsx +4 -5
  26. package/components/ProductFiltersPro/ProductFilterEqualSection.tsx +6 -7
  27. package/components/ProductFiltersPro/ProductFilterRangeChip.tsx +1 -1
  28. package/components/ProductFiltersPro/ProductFilterRangeSection.tsx +1 -1
  29. package/components/ProductFiltersPro/ProductFiltersPro.tsx +103 -19
  30. package/components/ProductFiltersPro/ProductFiltersProAggregations.tsx +41 -20
  31. package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +6 -10
  32. package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +18 -8
  33. package/components/ProductFiltersPro/ProductFiltersProCategorySection.tsx +130 -0
  34. package/components/ProductFiltersPro/ProductFiltersProChips.tsx +10 -8
  35. package/components/ProductFiltersPro/ProductFiltersProClearAll.tsx +4 -16
  36. package/components/ProductFiltersPro/ProductFiltersProLayoutSidebar.tsx +15 -7
  37. package/components/ProductFiltersPro/ProductFiltersProLimitChip.tsx +2 -8
  38. package/components/ProductFiltersPro/ProductFiltersProLimitSection.tsx +7 -10
  39. package/components/ProductFiltersPro/ProductFiltersProNoResults.tsx +79 -0
  40. package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +5 -7
  41. package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +2 -4
  42. package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +11 -3
  43. package/components/ProductFiltersPro/activeAggregations.ts +5 -9
  44. package/components/ProductFiltersPro/applyAggregationCount.ts +14 -8
  45. package/components/ProductFiltersPro/index.ts +9 -0
  46. package/components/ProductFiltersPro/{useClearAllFiltersHandler.ts → useProductFiltersProClearAllAction.ts} +1 -1
  47. package/components/ProductFiltersPro/useProductFiltersProHasFiltersApplied.ts +21 -0
  48. package/components/ProductFiltersPro/useProductFiltersProSort.tsx +7 -3
  49. package/components/ProductList/ProductList.graphql +8 -5
  50. package/components/ProductListCount/ProductListCount.tsx +3 -1
  51. package/components/ProductListFilters/ProductFilters.graphql +11 -2
  52. package/components/ProductListFilters/ProductListFilters.graphql +1 -1
  53. package/components/ProductListFilters/ProductListFilters.tsx +13 -19
  54. package/components/ProductListFiltersContainer/ProductListFiltersContainer.tsx +2 -4
  55. package/components/ProductListItem/ProductDiscountLabel.tsx +2 -3
  56. package/components/ProductListItem/ProductListItem.tsx +3 -3
  57. package/components/ProductListItem/ProductListItemTitleAndPrice.tsx +18 -15
  58. package/components/ProductListItems/ProductFilterTypes.graphql +8 -0
  59. package/components/ProductListItems/ProductListItemsBase.tsx +71 -30
  60. package/components/ProductListItems/filterTypes.tsx +14 -7
  61. package/components/ProductListItems/filteredProductList.tsx +44 -17
  62. package/components/ProductListItems/getFilterTypes.ts +33 -4
  63. package/components/ProductListItems/productListApplyCategoryDefaults.ts +50 -4
  64. package/components/ProductListItems/renderer.tsx +8 -2
  65. package/components/ProductListPagination/ProductListPagination.tsx +39 -20
  66. package/components/ProductListPrice/ProductListPrice.tsx +9 -4
  67. package/components/ProductListSuggestions/ProductListSuggestions.graphql +5 -0
  68. package/components/ProductListSuggestions/ProductListSuggestions.tsx +42 -0
  69. package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.graphql +3 -0
  70. package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +3 -0
  71. package/components/ProductPageBreadcrumb/ProductPageBreadcrumbs.tsx +40 -0
  72. package/components/ProductPageBreadcrumb/index.ts +1 -0
  73. package/components/ProductPageDescription/ComplexTextValue.graphql +1 -1
  74. package/components/ProductPageDescription/ProductPageDescription.tsx +1 -1
  75. package/components/ProductPageGallery/ProductImage.graphql +1 -0
  76. package/components/ProductPageGallery/ProductPageGallery.tsx +14 -8
  77. package/components/ProductPagePrice/ProductPagePrice.graphql +0 -6
  78. package/components/ProductPagePrice/ProductPagePrice.tsx +19 -12
  79. package/components/ProductPagePrice/ProductPagePriceTiers.tsx +4 -3
  80. package/components/ProductPagePrice/useCustomizableOptionPrice.ts +11 -53
  81. package/components/ProductShortDescription/ProductShortDescription.tsx +2 -0
  82. package/components/ProductSpecs/ProductSpecs.graphql +21 -1
  83. package/components/ProductSpecs/ProductSpecs.tsx +5 -11
  84. package/components/ProductSpecs/ProductSpecsAggregations.tsx +34 -0
  85. package/components/ProductSpecs/ProductSpecsCustomAttributes.tsx +45 -0
  86. package/components/ProductSpecs/ProductSpecsTypes.graphql +8 -0
  87. package/components/ProductStaticPaths/getProductStaticPaths.ts +1 -1
  88. package/components/ProductWeight/ProductWeight.tsx +12 -9
  89. package/components/index.ts +2 -0
  90. package/hooks/useProductLink.ts +4 -0
  91. package/hooks/useProductList.ts +148 -0
  92. package/hooks/useProductListLink.ts +6 -3
  93. package/index.ts +1 -0
  94. 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
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './ProductPageBreadcrumb.gql'
2
2
  export * from './ProductPageBreadcrumb'
3
+ export * from './ProductPageBreadcrumbs'
@@ -1,4 +1,4 @@
1
- fragment ComplexTextValue on ComplexTextValue @injectable {
1
+ fragment ComplexTextValue on ComplexTextValue {
2
2
  html
3
3
  __typename
4
4
  }
@@ -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}
@@ -1,4 +1,5 @@
1
1
  fragment ProductImage on ProductImage {
2
2
  url
3
3
  label
4
+ disabled
4
5
  }
@@ -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
- if (item.__typename === 'ProductImage')
28
- return { src: item.url ?? '', alt: item.label || undefined, width, height }
29
- return {
30
- src: '',
31
- alt: `{${item.__typename} not yet supported}`,
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
@@ -13,12 +13,6 @@ fragment ProductPagePrice on ProductInterface {
13
13
  final_price {
14
14
  ...Money
15
15
  }
16
- fixed_product_taxes {
17
- amount {
18
- ...Money
19
- }
20
- label
21
- }
22
16
  }
23
17
  }
24
18
  price_tiers {
@@ -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', ['root', 'discountPrice'] as const)
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
- {product.price_range.minimum_price.regular_price.value !== price.value && (
31
- <Box
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 {...product.price_range.minimum_price.regular_price} />
41
- </Box>
41
+ <Money {...regularPrice} />
42
+ </InContextMask>
42
43
  )}
43
- <Money {...price} value={priceValue} />
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 { Box, SxProps, Theme } from '@mui/material'
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
- <Box sx={sx}>
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
- </Box>
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 type { CustomizableAreaOptionFragment } from '../ProductCustomizable/CustomizableAreaOption.gql'
8
- import type { CustomizableCheckboxOptionFragment } from '../ProductCustomizable/CustomizableCheckboxOption.gql'
9
- import type { CustomizableDateOptionFragment } from '../ProductCustomizable/CustomizableDateOption.gql'
10
- import type { CustomizableDropDownOptionFragment } from '../ProductCustomizable/CustomizableDropDownOption.gql'
11
- import type { CustomizableFieldOptionFragment } from '../ProductCustomizable/CustomizableFieldOption.gql'
12
- import type { CustomizableFileOptionFragment } from '../ProductCustomizable/CustomizableFileOption.gql'
13
- import type { CustomizableMultipleOptionFragment } from '../ProductCustomizable/CustomizableMultipleOption.gql'
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
- (keyof MissingOptionValueSelectors extends never
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: Option, product: MoneyFragment) {
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 = { ...defaultSelectors, ...selectors }
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) => Option | Option[])
56
+ | ((option: AnyOption) => CustomizableProductOptionBase | CustomizableProductOptionBase[])
99
57
  const value = selector ? selector(productOption) : null
100
58
 
101
59
  if (!value) return 0
@@ -13,6 +13,8 @@ export function ProductShortDescription(props: ProductShortDescriptionProps) {
13
13
  const { product, sx = [] } = props
14
14
  const { short_description } = product
15
15
 
16
+ if (!short_description?.html) return null
17
+
16
18
  return (
17
19
  <Typography
18
20
  variant='body1'
@@ -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
- {specs?.map((aggregation) => (
50
- <li key={aggregation?.attribute_code}>
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
+ }
@@ -0,0 +1,8 @@
1
+ query ProductSpecsTypes {
2
+ attributesList(entityType: CATALOG_PRODUCT, filters: { is_visible_on_front: true }) {
3
+ items {
4
+ code
5
+ label
6
+ }
7
+ }
8
+ }
@@ -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 && import.meta.graphCommerce.limitSsg !== true) {
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 { useNumberFormat } from '@graphcommerce/next-ui'
3
+ import { UnitFormat, UnitFormatProps } from '@graphcommerce/next-ui'
4
4
  import { ProductWeightFragment } from './ProductWeight.gql'
5
5
 
6
- export function ProductWeight(props: ProductWeightFragment) {
7
- const { weight } = props
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
- const numberFormatter = useNumberFormat()
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
- {numberFormatter.format(weight)} {unit}
17
- </>
18
+ <UnitFormat unit={unit} {...rest}>
19
+ {product.weight}
20
+ </UnitFormat>
18
21
  )
19
22
  }
@@ -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'
@@ -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
  }