@graphcommerce/magento-product 4.7.2 → 4.7.3

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.
@@ -4,8 +4,7 @@ fragment ProductListItem on ProductInterface @injectable {
4
4
  sku
5
5
  name
6
6
  small_image {
7
- url
8
- label
7
+ ...ProductImage
9
8
  }
10
9
  price_range {
11
10
  minimum_price {
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Change Log
2
2
 
3
+ ## 4.7.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1675](https://github.com/graphcommerce-org/graphcommerce/pull/1675) [`1b1504c9b`](https://github.com/graphcommerce-org/graphcommerce/commit/1b1504c9b0e51f2787bce91e1ff1940f540411d6) Thanks [@paales](https://github.com/paales)! - Added crosssel functionality
8
+
9
+ - Updated dependencies [[`9e630670f`](https://github.com/graphcommerce-org/graphcommerce/commit/9e630670ff6c952ab7b938d890b5509804985cf3), [`cf3518499`](https://github.com/graphcommerce-org/graphcommerce/commit/cf351849999ad6fe73ce2bb258098a7dd301d517), [`81f31d1e5`](https://github.com/graphcommerce-org/graphcommerce/commit/81f31d1e54397368088a4289aaddd29facfceeef), [`2e9fa5984`](https://github.com/graphcommerce-org/graphcommerce/commit/2e9fa5984a07ff14fc1b3a4f62189a26e8e3ecdd), [`adf13069a`](https://github.com/graphcommerce-org/graphcommerce/commit/adf13069af6460c960276b402237371c12fc6dec), [`a8905d263`](https://github.com/graphcommerce-org/graphcommerce/commit/a8905d263273cb9322583d5759a5fdc66eceb8e4), [`1b1504c9b`](https://github.com/graphcommerce-org/graphcommerce/commit/1b1504c9b0e51f2787bce91e1ff1940f540411d6), [`8a34f8081`](https://github.com/graphcommerce-org/graphcommerce/commit/8a34f808186274a6fe1d4f309472f1a9c6d00efd), [`3dde492ad`](https://github.com/graphcommerce-org/graphcommerce/commit/3dde492ad3a49d96481eeb7453fb305d0017b1a5)]:
10
+ - @graphcommerce/next-ui@4.28.1
11
+ - @graphcommerce/graphql@3.5.0
12
+ - @graphcommerce/framer-scroller@2.1.41
13
+ - @graphcommerce/magento-cart@4.9.0
14
+ - @graphcommerce/ecommerce-ui@1.5.4
15
+ - @graphcommerce/magento-store@4.3.2
16
+ - @graphcommerce/framer-next-pages@3.3.2
17
+ - @graphcommerce/image@3.1.10
18
+
3
19
  ## 4.7.2
4
20
 
5
21
  ### Patch Changes
@@ -1,16 +1,23 @@
1
1
  import { UseFormGqlMutationReturn, UseFormGraphQlOptions } from '@graphcommerce/ecommerce-ui'
2
2
  import { useFormGqlMutationCart } from '@graphcommerce/magento-cart'
3
3
  import { Box, SxProps, Theme } from '@mui/material'
4
- import { createContext, useContext, useMemo } from 'react'
4
+ import { useRouter } from 'next/router'
5
+ import { createContext, useContext, useEffect } from 'react'
5
6
  import {
6
7
  AddProductsToCartDocument,
7
8
  AddProductsToCartMutation,
8
9
  AddProductsToCartMutationVariables,
9
10
  } from './AddProductsToCart.gql'
10
11
 
12
+ type RedirectType = 'added' | 'cart' | 'checkout' | undefined
13
+
14
+ type AddProductsToCartFormState = AddProductsToCartMutationVariables & {
15
+ redirect?: RedirectType
16
+ }
17
+
11
18
  type AddProductsToCartContextType = UseFormGqlMutationReturn<
12
19
  AddProductsToCartMutation,
13
- AddProductsToCartMutationVariables
20
+ AddProductsToCartFormState
14
21
  >
15
22
 
16
23
  export const addProductsToCartContext = createContext(
@@ -20,29 +27,57 @@ export const addProductsToCartContext = createContext(
20
27
  type AddProductsToCartFormProps = {
21
28
  children: React.ReactNode
22
29
  sx?: SxProps<Theme>
30
+ redirect?: RedirectType
23
31
  } & Omit<
24
- UseFormGraphQlOptions<AddProductsToCartMutation, AddProductsToCartMutationVariables>,
32
+ UseFormGraphQlOptions<AddProductsToCartMutation, AddProductsToCartFormState>,
25
33
  'onBeforeSubmit'
26
34
  >
27
35
 
28
36
  export function AddProductsToCartForm(props: AddProductsToCartFormProps) {
29
- const { children, defaultValues, sx, ...formProps } = props
30
- const form = useFormGqlMutationCart(AddProductsToCartDocument, {
31
- defaultValues,
37
+ const { children, defaultValues, redirect, onComplete, sx, ...formProps } = props
38
+ const router = useRouter()
39
+
40
+ const form = useFormGqlMutationCart<AddProductsToCartMutation, AddProductsToCartFormState>(
41
+ AddProductsToCartDocument,
42
+ {
43
+ defaultValues: {
44
+ ...defaultValues,
45
+ redirect,
46
+ },
47
+
48
+ // We're stripping out incomplete entered options.
49
+ onBeforeSubmit: ({ cartId, cartItems }) => ({
50
+ cartId,
51
+ cartItems: cartItems
52
+ .filter((cartItem) => cartItem.sku)
53
+ .map((cartItem) => ({
54
+ ...cartItem,
55
+ selected_options: cartItem.selected_options?.filter(Boolean),
56
+ entered_options: cartItem.entered_options?.filter((option) => option?.value),
57
+ })),
58
+ }),
59
+ onComplete: async (result, variables) => {
60
+ await onComplete?.(result, variables)
61
+
62
+ if (
63
+ result.data?.addProductsToCart?.user_errors?.length ||
64
+ result.errors?.length ||
65
+ !redirect
66
+ )
67
+ return
68
+
69
+ if (redirect === 'checkout') await router.push('/checkout')
70
+ if (redirect === 'added') await router.push({ pathname: '/checkout/added' })
71
+ if (redirect === 'cart') await router.push({ pathname: '/cart' })
72
+ },
73
+ ...formProps,
74
+ },
75
+ )
32
76
 
33
- // We're stripping out incomplete entered options.
34
- onBeforeSubmit: ({ cartId, cartItems }) => ({
35
- cartId,
36
- cartItems: cartItems
37
- .filter((cartItem) => cartItem.sku)
38
- .map((cartItem) => ({
39
- ...cartItem,
40
- selected_options: cartItem.selected_options?.filter(Boolean),
41
- entered_options: cartItem.entered_options?.filter((option) => option?.value),
42
- })),
43
- }),
44
- ...formProps,
45
- })
77
+ // When the value changes, update the value in the form
78
+ useEffect(() => {
79
+ if (form.getValues('redirect') !== redirect) form.setValue('redirect', redirect)
80
+ }, [form, redirect])
46
81
 
47
82
  const submit = form.handleSubmit(() => {})
48
83
 
@@ -14,7 +14,14 @@ type AddToCartMessageProps = { name?: string | null }
14
14
 
15
15
  export function AddProductsToCartSnackbar(props: AddToCartMessageProps) {
16
16
  const { name } = props
17
- const { formState, error, data } = useFormAddProductsToCart()
17
+ const { formState, error, data, getValues } = useFormAddProductsToCart()
18
+
19
+ const showSuccess =
20
+ !formState.isSubmitting &&
21
+ formState.isSubmitSuccessful &&
22
+ !error?.message &&
23
+ !data?.addProductsToCart?.user_errors?.length &&
24
+ !getValues('redirect')
18
25
 
19
26
  return (
20
27
  <>
@@ -34,12 +41,7 @@ export function AddProductsToCartSnackbar(props: AddToCartMessageProps) {
34
41
  </ErrorSnackbar>
35
42
 
36
43
  <MessageSnackbar
37
- open={
38
- !formState.isSubmitting &&
39
- formState.isSubmitSuccessful &&
40
- !error?.message &&
41
- !data?.addProductsToCart?.user_errors?.length
42
- }
44
+ open={showSuccess}
43
45
  variant='pill'
44
46
  action={
45
47
  <PageLink href='/cart' passHref>
@@ -1,5 +1,6 @@
1
1
  import { SelectElement, TextFieldElement } from '@graphcommerce/ecommerce-ui'
2
2
  import { filterNonNullableKeys, RenderType, TypeRenderer } from '@graphcommerce/next-ui'
3
+ import React from 'react'
3
4
  import { useFormAddProductsToCart } from '../AddProductsToCart'
4
5
  import { ProductCustomizableFragment } from './ProductCustomizable.gql'
5
6
 
@@ -10,7 +11,9 @@ type OptionTypeRenderer = TypeRenderer<
10
11
  }
11
12
  >
12
13
 
13
- const CustomizableAreaOption: OptionTypeRenderer['CustomizableAreaOption'] = (props) => {
14
+ const CustomizableAreaOption = React.memo<
15
+ React.ComponentProps<OptionTypeRenderer['CustomizableAreaOption']>
16
+ >((props) => {
14
17
  const { uid, areaValue, required, optionIndex, index, title } = props
15
18
  const maxLength = areaValue?.max_characters ?? undefined
16
19
  const { control, register } = useFormAddProductsToCart()
@@ -34,9 +37,11 @@ const CustomizableAreaOption: OptionTypeRenderer['CustomizableAreaOption'] = (pr
34
37
  />
35
38
  </>
36
39
  )
37
- }
40
+ })
38
41
 
39
- const CustomizableDropDownOption: OptionTypeRenderer['CustomizableDropDownOption'] = (props) => {
42
+ const CustomizableDropDownOption = React.memo<
43
+ React.ComponentProps<OptionTypeRenderer['CustomizableDropDownOption']>
44
+ >((props) => {
40
45
  const { uid, required, optionIndex, index, title, dropdownValue } = props
41
46
  const { control, register } = useFormAddProductsToCart()
42
47
 
@@ -52,6 +57,7 @@ const CustomizableDropDownOption: OptionTypeRenderer['CustomizableDropDownOption
52
57
  name={`cartItems.${index}.entered_options.${optionIndex}.value`}
53
58
  label={title}
54
59
  required={Boolean(required)}
60
+ defaultValue=''
55
61
  options={filterNonNullableKeys(dropdownValue, ['title']).map((option) => ({
56
62
  id: option.uid,
57
63
  label: option.title,
@@ -59,7 +65,7 @@ const CustomizableDropDownOption: OptionTypeRenderer['CustomizableDropDownOption
59
65
  />
60
66
  </>
61
67
  )
62
- }
68
+ })
63
69
 
64
70
  const renderer: OptionTypeRenderer = {
65
71
  CustomizableAreaOption,
@@ -84,7 +90,7 @@ export function ProductCustomizable(props: ProductCustomizableProps) {
84
90
  key={option.uid}
85
91
  renderer={renderer}
86
92
  {...option}
87
- optionIndex={option.sort_order - 1}
93
+ optionIndex={option.sort_order + 100}
88
94
  index={index}
89
95
  />
90
96
  ))}
@@ -6,7 +6,16 @@ import {
6
6
  breakpointVal,
7
7
  } from '@graphcommerce/next-ui'
8
8
  import { Trans } from '@lingui/react'
9
- import { ButtonBase, Typography, Box, styled, SxProps, Theme } from '@mui/material'
9
+ import {
10
+ ButtonBase,
11
+ Typography,
12
+ Box,
13
+ styled,
14
+ SxProps,
15
+ Theme,
16
+ ButtonBaseProps,
17
+ useEventCallback,
18
+ } from '@mui/material'
10
19
  import PageLink from 'next/link'
11
20
  import React, { PropsWithChildren } from 'react'
12
21
  import { ProductListItemFragment } from '../../Api/ProductListItem.gql'
@@ -40,16 +49,15 @@ type StyleProps = {
40
49
  imageOnly?: boolean
41
50
  }
42
51
 
43
- type BaseProps = PropsWithChildren<
44
- { subTitle?: React.ReactNode } & StyleProps &
45
- OverlayAreas &
46
- ProductListItemFragment &
47
- Pick<ImageProps, 'loading' | 'sizes' | 'dontReportWronglySizedImages'>
48
- >
52
+ type BaseProps = { subTitle?: React.ReactNode; children?: React.ReactNode } & StyleProps &
53
+ OverlayAreas &
54
+ ProductListItemFragment &
55
+ Pick<ImageProps, 'loading' | 'sizes' | 'dontReportWronglySizedImages'>
49
56
 
50
57
  export type ProductListItemProps = BaseProps & {
51
58
  sx?: SxProps<Theme>
52
59
  titleComponent?: React.ElementType
60
+ onClick?: (event: React.MouseEvent<HTMLAnchorElement>, item: ProductListItemFragment) => void
53
61
  }
54
62
 
55
63
  const StyledImage = styled(Image)({})
@@ -72,8 +80,13 @@ export function ProductListItem(props: ProductListItemProps) {
72
80
  aspectRatio = [4, 3],
73
81
  titleComponent = 'h2',
74
82
  sx = [],
83
+ onClick,
75
84
  } = props
76
85
 
86
+ const handleClick = useEventCallback((e: React.MouseEvent<HTMLAnchorElement>) =>
87
+ onClick?.(e, props),
88
+ )
89
+
77
90
  const productLink = useProductLink(props)
78
91
  const discount = Math.floor(price_range.minimum_price.discount?.percent_off ?? 0)
79
92
 
@@ -98,6 +111,7 @@ export function ProductListItem(props: ProductListItemProps) {
98
111
  ...(Array.isArray(sx) ? sx : [sx]),
99
112
  ]}
100
113
  className={classes.root}
114
+ onClick={onClick ? handleClick : undefined}
101
115
  >
102
116
  <Box
103
117
  sx={(theme) => ({
@@ -12,12 +12,19 @@ export type ProductItemsGridProps = {
12
12
  renderers: ProductListItemRenderer
13
13
  loadingEager?: number
14
14
  size?: 'normal' | 'small'
15
- titleComponent?: React.ElementType
16
15
  sx?: BoxProps['sx']
17
- }
16
+ } & Pick<ProductListItemProps, 'onClick' | 'titleComponent'>
18
17
 
19
18
  export function ProductListItemsBase(props: ProductItemsGridProps) {
20
- const { items, sx = [], renderers, loadingEager = 0, size = 'normal', titleComponent } = props
19
+ const {
20
+ items,
21
+ sx = [],
22
+ renderers,
23
+ loadingEager = 0,
24
+ size = 'normal',
25
+ titleComponent,
26
+ onClick,
27
+ } = props
21
28
 
22
29
  return (
23
30
  <Box
@@ -41,14 +48,15 @@ export function ProductListItemsBase(props: ProductItemsGridProps) {
41
48
  <RenderType
42
49
  key={item.uid ?? ''}
43
50
  renderer={renderers}
44
- {...item}
45
- loading={loadingEager > idx ? 'eager' : 'lazy'}
46
51
  sizes={
47
52
  size === 'small'
48
53
  ? { 0: '100vw', 354: '50vw', 675: '30vw', 1255: '23vw', 1500: '337px' }
49
54
  : { 0: '100vw', 367: '48vw', 994: '30vw', 1590: '23vw', 1920: '443px' }
50
55
  }
56
+ {...item}
57
+ loading={loadingEager > idx ? 'eager' : 'lazy'}
51
58
  titleComponent={titleComponent}
59
+ onClick={onClick}
52
60
  noReport
53
61
  />
54
62
  ) : null,
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": "4.7.2",
5
+ "version": "4.7.3",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -19,15 +19,15 @@
19
19
  "type-fest": "^2.12.2"
20
20
  },
21
21
  "dependencies": {
22
- "@graphcommerce/ecommerce-ui": "1.5.3",
23
- "@graphcommerce/framer-scroller": "2.1.40",
24
- "@graphcommerce/framer-next-pages": "3.3.1",
25
- "@graphcommerce/graphql": "3.4.8",
22
+ "@graphcommerce/ecommerce-ui": "1.5.4",
23
+ "@graphcommerce/framer-scroller": "2.1.41",
24
+ "@graphcommerce/framer-next-pages": "3.3.2",
25
+ "@graphcommerce/graphql": "3.5.0",
26
26
  "@graphcommerce/graphql-mesh": "4.2.0",
27
- "@graphcommerce/image": "3.1.9",
28
- "@graphcommerce/magento-cart": "4.8.7",
29
- "@graphcommerce/magento-store": "4.3.1",
30
- "@graphcommerce/next-ui": "4.28.0",
27
+ "@graphcommerce/image": "3.1.10",
28
+ "@graphcommerce/magento-cart": "4.9.0",
29
+ "@graphcommerce/magento-store": "4.3.2",
30
+ "@graphcommerce/next-ui": "4.28.1",
31
31
  "schema-dts": "^1.1.0"
32
32
  },
33
33
  "peerDependencies": {