@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.
- package/CHANGELOG.md +135 -1
- package/components/AddProductsToCart/AddProductsToCartButton.tsx +1 -0
- package/components/AddProductsToCart/AddProductsToCartForm.tsx +7 -2
- package/components/AddProductsToCart/AddProductsToCartSnackbar.tsx +13 -14
- package/components/AddProductsToCart/findAddedItems.ts +81 -0
- package/components/AddProductsToCart/index.ts +3 -0
- package/components/AddProductsToCart/useAddProductsToCartAction.ts +6 -3
- package/components/AddProductsToCart/useFormAddProductsToCart.ts +1 -2
- package/components/JsonLdProduct/JsonLdProductOffer.graphql +2 -3
- package/components/JsonLdProduct/ProductPageJsonLd.tsx +9 -4
- package/components/JsonLdProduct/index.ts +1 -0
- package/components/ProductAddToCart/ProductAddToCart.tsx +6 -4
- package/components/ProductCustomizable/CustomizableAreaOption.tsx +41 -7
- package/components/ProductCustomizable/CustomizableDateOption.tsx +60 -7
- package/components/ProductCustomizable/CustomizableDropDownOption.tsx +63 -15
- package/components/ProductCustomizable/CustomizableFieldOption.tsx +40 -4
- package/components/ProductCustomizable/index.ts +1 -0
- package/components/ProductCustomizable/productCustomizableSelectors.ts +59 -0
- package/components/ProductFiltersPro/ProductFiltersPro.tsx +25 -10
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersChip.tsx +6 -2
- package/components/ProductFiltersPro/ProductFiltersProAllFiltersSidebar.tsx +6 -2
- package/components/ProductFiltersPro/ProductFiltersProSortChip.tsx +9 -28
- package/components/ProductFiltersPro/ProductFiltersProSortDirectionArrow.tsx +15 -0
- package/components/ProductFiltersPro/ProductFiltersProSortSection.tsx +7 -32
- package/components/ProductFiltersPro/useProductFiltersProSort.tsx +74 -0
- package/components/ProductListItems/CategoryDefault.graphql +5 -0
- package/components/ProductListItems/ProductListItemsBase.tsx +1 -1
- package/components/ProductListItems/filterTypes.tsx +1 -1
- package/components/ProductListItems/filteredProductList.tsx +1 -1
- package/components/ProductListItems/productListApplyCategoryDefaults.ts +28 -0
- package/components/ProductPageBreadcrumb/ProductPageBreadcrumb.tsx +5 -3
- package/components/ProductPageMeta/ProductPageMeta.graphql +1 -1
- package/components/ProductPagePrice/ProductPagePrice.graphql +3 -0
- package/components/ProductPagePrice/ProductPagePrice.tsx +11 -4
- package/components/ProductPagePrice/useCustomizableOptionPrice.ts +85 -0
- package/components/ProductShortDescription/ProductShortDescription.tsx +2 -0
- package/components/ProductStaticPaths/getProductStaticPaths.ts +2 -3
- package/components/ProductStaticPaths/getSitemapPaths.ts +3 -0
- package/components/index.ts +2 -0
- package/hooks/useProductListLink.ts +10 -5
- package/hooks/useProductListLinkReplace.ts +3 -0
- package/package.json +13 -13
- 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 {
|
7
|
-
|
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
|
+
}
|
@@ -11,7 +11,7 @@ export type ProductTypenames = NonNullable<
|
|
11
11
|
export async function getProductStaticPaths(
|
12
12
|
client: ApolloClient<NormalizedCacheObject>,
|
13
13
|
locale: string,
|
14
|
-
|
14
|
+
options: { limit?: boolean } = { limit: import.meta.graphCommerce.limitSsg || false },
|
15
15
|
) {
|
16
16
|
const query = client.query({
|
17
17
|
query: ProductStaticPathsDocument,
|
@@ -36,8 +36,7 @@ export async function getProductStaticPaths(
|
|
36
36
|
const paths: Return['paths'] = (await Promise.all(pages))
|
37
37
|
.map((q) => q.data.products?.items)
|
38
38
|
.flat(1)
|
39
|
-
.filter((item) => (typename ? item?.__typename === typename : true))
|
40
39
|
.map((p) => ({ params: { url: `${p?.url_key}` }, locale }))
|
41
40
|
|
42
|
-
return
|
41
|
+
return options.limit ? paths.slice(0, 1) : paths
|
43
42
|
}
|
@@ -3,6 +3,9 @@ import { canonicalize, nonNullable } from '@graphcommerce/next-ui'
|
|
3
3
|
import { productLink } from '../../hooks/useProductLink'
|
4
4
|
import { ProductStaticPathsDocument } from './ProductStaticPaths.gql'
|
5
5
|
|
6
|
+
/**
|
7
|
+
* @deprecated Not used anymore, use `getProductStaticPaths` instead.
|
8
|
+
*/
|
6
9
|
export async function getSitemapPaths(
|
7
10
|
client: ApolloClient<NormalizedCacheObject>,
|
8
11
|
ctx: { locale?: string; defaultLocale?: string },
|
package/components/index.ts
CHANGED
@@ -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
|
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
|
-
|
23
|
-
if (
|
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.
|
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.
|
22
|
-
"@graphcommerce/eslint-config-pwa": "^8.1.0-canary.
|
23
|
-
"@graphcommerce/framer-next-pages": "^8.1.0-canary.
|
24
|
-
"@graphcommerce/framer-scroller": "^8.1.0-canary.
|
25
|
-
"@graphcommerce/graphql": "^8.1.0-canary.
|
26
|
-
"@graphcommerce/graphql-mesh": "^8.1.0-canary.
|
27
|
-
"@graphcommerce/image": "^8.1.0-canary.
|
28
|
-
"@graphcommerce/magento-cart": "^8.1.0-canary.
|
29
|
-
"@graphcommerce/magento-store": "^8.1.0-canary.
|
30
|
-
"@graphcommerce/next-ui": "^8.1.0-canary.
|
31
|
-
"@graphcommerce/prettier-config-pwa": "^8.1.0-canary.
|
32
|
-
"@graphcommerce/typescript-config-pwa": "^8.1.0-canary.
|
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