@graphcommerce/magento-product 8.1.0-canary.2 → 8.1.0-canary.22

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 (43) hide show
  1. package/CHANGELOG.md +135 -1
  2. package/components/AddProductsToCart/AddProductsToCartButton.tsx +1 -0
  3. package/components/AddProductsToCart/AddProductsToCartForm.tsx +7 -2
  4. package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +13 -14
  5. package/components/AddProductsToCart/findAddedItems.ts +81 -0
  6. package/components/AddProductsToCart/index.ts +3 -0
  7. package/components/AddProductsToCart/useAddProductsToCartAction.ts +6 -3
  8. package/components/AddProductsToCart/useFormAddProductsToCart.ts +1 -2
  9. package/components/JsonLdProduct/JsonLdProductOffer.graphql +2 -3
  10. package/components/JsonLdProduct/ProductPageJsonLd.tsx +9 -4
  11. package/components/JsonLdProduct/index.ts +1 -0
  12. package/components/ProductAddToCart/ProductAddToCart.tsx +6 -4
  13. package/components/ProductCustomizable/CustomizableAreaOption.tsx +41 -7
  14. package/components/ProductCustomizable/CustomizableDateOption.tsx +60 -7
  15. package/components/ProductCustomizable/CustomizableDropDownOption.tsx +63 -15
  16. package/components/ProductCustomizable/CustomizableFieldOption.tsx +40 -4
  17. package/components/ProductCustomizable/index.ts +1 -0
  18. package/components/ProductCustomizable/productCustomizableSelectors.ts +59 -0
  19. package/components/ProductFiltersPro/ProductFiltersPro.tsx +25 -10
  20. package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +6 -2
  21. package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +6 -2
  22. package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +9 -28
  23. package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +15 -0
  24. package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +7 -32
  25. package/components/ProductFiltersPro/useProductFiltersProSort.tsx +74 -0
  26. package/components/ProductListItems/CategoryDefault.graphql +5 -0
  27. package/components/ProductListItems/ProductListItemsBase.tsx +1 -1
  28. package/components/ProductListItems/filterTypes.tsx +1 -1
  29. package/components/ProductListItems/filteredProductList.tsx +1 -1
  30. package/components/ProductListItems/productListApplyCategoryDefaults.ts +28 -0
  31. package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +5 -3
  32. package/components/ProductPageMeta/ProductPageMeta.graphql +1 -1
  33. package/components/ProductPagePrice/ProductPagePrice.graphql +3 -0
  34. package/components/ProductPagePrice/ProductPagePrice.tsx +11 -4
  35. package/components/ProductPagePrice/useCustomizableOptionPrice.ts +85 -0
  36. package/components/ProductShortDescription/ProductShortDescription.tsx +2 -0
  37. package/components/ProductStaticPaths/getProductStaticPaths.ts +2 -3
  38. package/components/ProductStaticPaths/getSitemapPaths.ts +3 -0
  39. package/components/index.ts +2 -0
  40. package/hooks/useProductListLink.ts +10 -5
  41. package/hooks/useProductListLinkReplace.ts +3 -0
  42. package/package.json +13 -13
  43. package/tsconfig.json +1 -1
@@ -1,12 +1,17 @@
1
1
  import { useWatch } from '@graphcommerce/ecommerce-ui'
2
2
  import { Money } from '@graphcommerce/magento-store'
3
+ import { extendableComponent } from '@graphcommerce/next-ui'
4
+ import { Box } from '@mui/material'
3
5
  import { AddToCartItemSelector, useFormAddProductsToCart } from '../AddProductsToCart'
4
6
  import { ProductPagePriceFragment } from './ProductPagePrice.gql'
5
7
  import { getProductTierPrice } from './getProductTierPrice'
6
- import { extendableComponent } from '@graphcommerce/next-ui'
7
- import { Box } from '@mui/material'
8
+ import {
9
+ UseCustomizableOptionPriceProps,
10
+ useCustomizableOptionPrice,
11
+ } from './useCustomizableOptionPrice'
8
12
 
9
- export type ProductPagePriceProps = { product: ProductPagePriceFragment } & AddToCartItemSelector
13
+ export type ProductPagePriceProps = { product: ProductPagePriceFragment } & AddToCartItemSelector &
14
+ UseCustomizableOptionPriceProps
10
15
 
11
16
  const { classes } = extendableComponent('ProductPagePrice', ['root', 'discountPrice'] as const)
12
17
 
@@ -18,6 +23,8 @@ export function ProductPagePrice(props: ProductPagePriceProps) {
18
23
  const price =
19
24
  getProductTierPrice(product, quantity) ?? product.price_range.minimum_price.final_price
20
25
 
26
+ const priceValue = useCustomizableOptionPrice(props)
27
+
21
28
  return (
22
29
  <>
23
30
  {product.price_range.minimum_price.regular_price.value !== price.value && (
@@ -33,7 +40,7 @@ export function ProductPagePrice(props: ProductPagePriceProps) {
33
40
  <Money {...product.price_range.minimum_price.regular_price} />
34
41
  </Box>
35
42
  )}
36
- <Money {...price} />
43
+ <Money {...price} value={priceValue} />
37
44
  </>
38
45
  )
39
46
  }
@@ -0,0 +1,85 @@
1
+ import { useWatch } from '@graphcommerce/ecommerce-ui'
2
+ import { MoneyFragment } from '@graphcommerce/magento-store'
3
+ import { filterNonNullableKeys, isTypename, nonNullable } from '@graphcommerce/next-ui'
4
+ import { AddToCartItemSelector, useFormAddProductsToCart } from '../AddProductsToCart'
5
+ import {
6
+ productCustomizableSelectors,
7
+ CustomizableProductOptionBase,
8
+ OptionValueSelector,
9
+ AnyOption,
10
+ SelectorsProp,
11
+ } from '../ProductCustomizable/productCustomizableSelectors'
12
+ import { ProductPagePriceFragment } from './ProductPagePrice.gql'
13
+ import { getProductTierPrice } from './getProductTierPrice'
14
+
15
+ export type UseCustomizableOptionPriceProps = {
16
+ product: ProductPagePriceFragment
17
+ } & AddToCartItemSelector &
18
+ SelectorsProp
19
+
20
+ function calcOptionPrice(option: CustomizableProductOptionBase, product: MoneyFragment) {
21
+ if (!option?.price) return 0
22
+ switch (option.price_type) {
23
+ case 'DYNAMIC':
24
+ case 'FIXED':
25
+ return option.price
26
+ case 'PERCENT':
27
+ return (product?.value ?? 0) * (option.price / 100)
28
+ }
29
+
30
+ return 0
31
+ }
32
+
33
+ export function useCustomizableOptionPrice(props: UseCustomizableOptionPriceProps) {
34
+ const { product, selectors, index = 0 } = props
35
+
36
+ const { control } = useFormAddProductsToCart()
37
+ const cartItem = useWatch({ control, name: `cartItems.${index}` }) ?? {}
38
+ const price =
39
+ getProductTierPrice(product, cartItem?.quantity) ??
40
+ product.price_range.minimum_price.final_price
41
+
42
+ const allSelectors: OptionValueSelector = { ...productCustomizableSelectors, ...selectors }
43
+
44
+ if (isTypename(product, ['GroupedProduct'])) return price.value
45
+ if (!product.options || product.options.length === 0) return price.value
46
+
47
+ const finalPrice = product.options.filter(nonNullable).reduce((optionPrice, productOption) => {
48
+ const isCustomizable = Boolean(cartItem.customizable_options?.[productOption.uid])
49
+ const isEntered = Boolean(
50
+ cartItem.entered_options?.find((o) => productOption.uid && o?.uid && o?.value),
51
+ )
52
+ if (!isCustomizable && !isEntered) return optionPrice
53
+
54
+ const selector = allSelectors[productOption.__typename] as
55
+ | undefined
56
+ | ((option: AnyOption) => CustomizableProductOptionBase | CustomizableProductOptionBase[])
57
+ const value = selector ? selector(productOption) : null
58
+
59
+ if (!value) return 0
60
+
61
+ // If the option can have multiple values
62
+ if (Array.isArray(value)) {
63
+ return (
64
+ optionPrice +
65
+ filterNonNullableKeys(value)
66
+ .filter(
67
+ (v) =>
68
+ cartItem.customizable_options?.[productOption.uid] &&
69
+ cartItem.customizable_options?.[productOption.uid].includes(v.uid),
70
+ )
71
+ .reduce((p, v) => p + calcOptionPrice(v, price), 0)
72
+ )
73
+ }
74
+
75
+ // If the option can have a single value entered.
76
+ if (
77
+ cartItem.entered_options?.filter((v) => v?.uid === productOption.uid && v.value).length !== 0
78
+ )
79
+ return optionPrice + calcOptionPrice(value, price)
80
+
81
+ return optionPrice
82
+ }, price.value ?? 0)
83
+
84
+ return finalPrice
85
+ }
@@ -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'
@@ -11,7 +11,7 @@ export type ProductTypenames = NonNullable<
11
11
  export async function getProductStaticPaths(
12
12
  client: ApolloClient<NormalizedCacheObject>,
13
13
  locale: string,
14
- typename?: ProductTypenames,
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 import.meta.graphCommerce.limitSsg ? paths.slice(0, 1) : paths
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 },
@@ -13,6 +13,8 @@ export * from './ProductListItems/filterTypes'
13
13
  export * from './ProductListItems/getFilterTypes'
14
14
  export * from './ProductListItems/ProductListItems.gql'
15
15
  export * from './ProductListItems/ProductListItemsBase'
16
+ export * from './ProductListItems/productListApplyCategoryDefaults'
17
+ export * from './ProductListItems/CategoryDefault.gql'
16
18
  export * from './ProductListItems/ProductListParamsProvider'
17
19
  export * from './ProductListItems/renderer'
18
20
  export * from './ProductListLink/ProductListLink'
@@ -2,11 +2,13 @@ import {
2
2
  isFilterTypeEqual,
3
3
  isFilterTypeMatch,
4
4
  isFilterTypeRange,
5
+ ProductFilterParams,
5
6
  ProductListParams,
7
+ toFilterParams,
6
8
  } from '../components/ProductListItems/filterTypes'
7
9
 
8
- export function productListLink(props: ProductListParams): string {
9
- const { url, sort, currentPage, pageSize, filters: incoming } = props
10
+ export function productListLinkFromFilter(props: ProductFilterParams): string {
11
+ const { url, sort, dir, currentPage, pageSize, filters: incoming } = props
10
12
  const isSearch = url.startsWith('search')
11
13
  const filters = isSearch ? incoming : { ...incoming, category_uid: undefined }
12
14
  const uid = incoming?.category_uid?.eq || incoming?.category_uid?.in?.[0]
@@ -19,9 +21,8 @@ export function productListLink(props: ProductListParams): string {
19
21
 
20
22
  // todo(paales): How should the URL look like with multiple sorts?
21
23
  // Something like: /sort/position,price/dir/asc,asc
22
- const [sortBy] = Object.keys(sort)
23
- if (sort && sortBy) query += `/sort/${sortBy}`
24
- if (sort && sortBy && sort[sortBy] && sort[sortBy] === 'DESC') query += `/dir/desc`
24
+ if (sort) query += `/sort/${sort}`
25
+ if (dir) query += `/dir/desc`
25
26
  if (pageSize) query += `/page-size/${pageSize}`
26
27
 
27
28
  // Apply filters
@@ -42,6 +43,10 @@ export function productListLink(props: ProductListParams): string {
42
43
  return query ? `/${url}${paginateSort}/q${query}` : `/${url}${paginateSort}`
43
44
  }
44
45
 
46
+ export function productListLink(props: ProductListParams): string {
47
+ return productListLinkFromFilter(toFilterParams(props))
48
+ }
49
+
45
50
  export function useProductListLink(props: ProductListParams): string {
46
51
  return productListLink({ ...props, url: `${props.url}` })
47
52
  }
@@ -9,6 +9,9 @@ type UseProductLinkPushProps = {
9
9
  scroll?: boolean
10
10
  }
11
11
 
12
+ /**
13
+ * @deprecated replaced by custom function inside ProductFiltersPro
14
+ */
12
15
  export function useProductListLinkReplace(props?: UseProductLinkPushProps) {
13
16
  const { setParams } = useProductListParamsContext()
14
17
  const router = useRouter()
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/magento-product",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "8.1.0-canary.2",
5
+ "version": "8.1.0-canary.22",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -18,18 +18,18 @@
18
18
  "typescript": "5.3.3"
19
19
  },
20
20
  "peerDependencies": {
21
- "@graphcommerce/ecommerce-ui": "^8.1.0-canary.2",
22
- "@graphcommerce/eslint-config-pwa": "^8.1.0-canary.2",
23
- "@graphcommerce/framer-next-pages": "^8.1.0-canary.2",
24
- "@graphcommerce/framer-scroller": "^8.1.0-canary.2",
25
- "@graphcommerce/graphql": "^8.1.0-canary.2",
26
- "@graphcommerce/graphql-mesh": "^8.1.0-canary.2",
27
- "@graphcommerce/image": "^8.1.0-canary.2",
28
- "@graphcommerce/magento-cart": "^8.1.0-canary.2",
29
- "@graphcommerce/magento-store": "^8.1.0-canary.2",
30
- "@graphcommerce/next-ui": "^8.1.0-canary.2",
31
- "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.2",
32
- "@graphcommerce/typescript-config-pwa": "^8.1.0-canary.2",
21
+ "@graphcommerce/ecommerce-ui": "^8.1.0-canary.22",
22
+ "@graphcommerce/eslint-config-pwa": "^8.1.0-canary.22",
23
+ "@graphcommerce/framer-next-pages": "^8.1.0-canary.22",
24
+ "@graphcommerce/framer-scroller": "^8.1.0-canary.22",
25
+ "@graphcommerce/graphql": "^8.1.0-canary.22",
26
+ "@graphcommerce/graphql-mesh": "^8.1.0-canary.22",
27
+ "@graphcommerce/image": "^8.1.0-canary.22",
28
+ "@graphcommerce/magento-cart": "^8.1.0-canary.22",
29
+ "@graphcommerce/magento-store": "^8.1.0-canary.22",
30
+ "@graphcommerce/next-ui": "^8.1.0-canary.22",
31
+ "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.22",
32
+ "@graphcommerce/typescript-config-pwa": "^8.1.0-canary.22",
33
33
  "@lingui/core": "^4.2.1",
34
34
  "@lingui/macro": "^4.2.1",
35
35
  "@lingui/react": "^4.2.1",
package/tsconfig.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "exclude": ["**/node_modules", "**/.*/"],
3
3
  "include": ["**/*.ts", "**/*.tsx"],
4
- "extends": "@graphcommerce/typescript-config-pwa/nextjs.json"
4
+ "extends": "@graphcommerce/typescript-config-pwa/nextjs.json",
5
5
  }