@graphcommerce/magento-cart-items 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 CHANGED
@@ -1,6 +1,107 @@
1
1
  # Change Log
2
2
 
3
- ## 8.1.0-canary.2
3
+ ## 8.1.0-canary.22
4
+
5
+ ## 8.1.0-canary.21
6
+
7
+ ## 8.1.0-canary.20
8
+
9
+ ### Minor Changes
10
+
11
+ - [#2246](https://github.com/graphcommerce-org/graphcommerce/pull/2246) [`13524f9`](https://github.com/graphcommerce-org/graphcommerce/commit/13524f991a810c1679db49b3b8b4f04f90d0d6c1) - Added the ability to edit cart items with full support for all product types and custom options
12
+ ([@Jessevdpoel](https://github.com/Jessevdpoel))
13
+
14
+ ### Patch Changes
15
+
16
+ - [#2246](https://github.com/graphcommerce-org/graphcommerce/pull/2246) [`fc5c04d`](https://github.com/graphcommerce-org/graphcommerce/commit/fc5c04d4a2c0301be7d3cc983d9b31f6fcaf6fe6) - Create useRemoveItemFromCart hook to allow for reuse while keeping compatibility with plugins.
17
+ ([@Jessevdpoel](https://github.com/Jessevdpoel))
18
+
19
+ ## 8.1.0-canary.19
20
+
21
+ ## 8.1.0-canary.18
22
+
23
+ ## 8.1.0-canary.17
24
+
25
+ ### Minor Changes
26
+
27
+ - [#2209](https://github.com/graphcommerce-org/graphcommerce/pull/2209) [`2872cab`](https://github.com/graphcommerce-org/graphcommerce/commit/2872cabdca9ee4f0378fd411c6a633f71bb92f1f) - Removed useMediaQuery from the wishlist and cart ItemActionCard and replaced it with a new responsive size prop.
28
+ ([@Jessevdpoel](https://github.com/Jessevdpoel))
29
+
30
+ ## 8.1.0-canary.16
31
+
32
+ ## 8.1.0-canary.15
33
+
34
+ ## 8.1.0-canary.14
35
+
36
+ ## 8.1.0-canary.13
37
+
38
+ ## 8.1.0-canary.12
39
+
40
+ ## 8.1.0-canary.11
41
+
42
+ ## 8.1.0-canary.10
43
+
44
+ ## 8.1.0-canary.9
45
+
46
+ ## 8.1.0-canary.8
47
+
48
+ ## 8.1.0-canary.7
49
+
50
+ ## 8.1.0-canary.6
51
+
52
+ ## 8.1.0-canary.5
53
+
54
+ ## 8.0.6-canary.4
55
+
56
+ ## 8.0.6-canary.3
57
+
58
+ ## 8.0.6-canary.2
59
+
60
+ ## 8.0.6-canary.1
61
+
62
+ ## 8.0.6-canary.0
63
+
64
+ ## 8.0.5
65
+
66
+ ## 8.0.5-canary.10
67
+
68
+ ## 8.0.5-canary.9
69
+
70
+ ## 8.0.5-canary.8
71
+
72
+ ## 8.0.5-canary.7
73
+
74
+ ## 8.0.5-canary.6
75
+
76
+ ## 8.0.5-canary.5
77
+
78
+ ## 8.0.5-canary.4
79
+
80
+ ## 8.0.5-canary.3
81
+
82
+ ## 8.0.5-canary.2
83
+
84
+ ## 8.0.5-canary.1
85
+
86
+ ## 8.0.5-canary.0
87
+
88
+ ## 8.0.4
89
+
90
+ ## 8.0.4-canary.1
91
+
92
+ ## 8.0.4-canary.0
93
+
94
+ ## 8.0.3
95
+
96
+ ## 8.0.3-canary.6
97
+
98
+ ## 8.0.3-canary.5
99
+
100
+ ## 8.0.3-canary.4
101
+
102
+ ## 8.0.3-canary.3
103
+
104
+ ## 8.0.3-canary.2
4
105
 
5
106
  ## 8.0.3-canary.1
6
107
 
@@ -93,10 +93,7 @@ export function CartItem(props: CartItemProps) {
93
93
  color='default'
94
94
  badgeContent={
95
95
  <RemoveItemFromCartFab
96
- uid={uid}
97
- quantity={quantity}
98
- prices={prices}
99
- product={product}
96
+ {...props}
100
97
  fabProps={{ className: classes.badge }}
101
98
  sx={(theme) => ({
102
99
  '& > button': {
@@ -1,13 +1,15 @@
1
1
  import { Image } from '@graphcommerce/image'
2
2
  import { useDisplayInclTax } from '@graphcommerce/magento-cart/hooks'
3
+ import { ProductLinkProps } from '@graphcommerce/magento-product'
3
4
  import { Money } from '@graphcommerce/magento-store'
4
5
  import {
5
6
  ActionCard,
6
7
  ActionCardProps,
7
- responsiveVal,
8
8
  filterNonNullableKeys,
9
+ actionCardImageSizes,
9
10
  } from '@graphcommerce/next-ui'
10
- import { Box, Link } from '@mui/material'
11
+ import { Trans } from '@lingui/react'
12
+ import { Box, Button, Link } from '@mui/material'
11
13
  import { CartItemFragment } from '../../Api/CartItem.gql'
12
14
  import { RemoveItemFromCart } from '../RemoveItemFromCart/RemoveItemFromCart'
13
15
  import { UpdateItemQuantity } from '../UpdateItemQuantity/UpdateItemQuantity'
@@ -17,20 +19,12 @@ export type CartItemActionCardProps = { cartItem: CartItemFragment; readOnly?: b
17
19
  'value' | 'image' | 'price' | 'title' | 'action'
18
20
  >
19
21
 
20
- export const productImageSizes = {
21
- small: responsiveVal(60, 80),
22
- medium: responsiveVal(60, 80),
23
- large: responsiveVal(100, 120),
24
- }
25
-
26
- const typographySizes = {
27
- small: 'body2',
28
- medium: 'body1',
29
- large: 'subtitle1',
22
+ export function productEditLink(link: ProductLinkProps) {
23
+ return `/checkout/item/${link.url_key}`
30
24
  }
31
25
 
32
26
  export function CartItemActionCard(props: CartItemActionCardProps) {
33
- const { cartItem, sx = [], size = 'large', readOnly = false, ...rest } = props
27
+ const { cartItem, sx = [], size = 'responsive', readOnly = false, ...rest } = props
34
28
  const { uid, quantity, prices, errors, product } = cartItem
35
29
  const { name, thumbnail, url_key } = product
36
30
 
@@ -49,6 +43,11 @@ export function CartItemActionCard(props: CartItemActionCardProps) {
49
43
  price = prices?.price.value
50
44
  }
51
45
 
46
+ const hasOptions = !(
47
+ (cartItem.__typename === 'SimpleCartItem' || cartItem.__typename === 'VirtualCartItem') &&
48
+ cartItem.customizable_options.length === 0
49
+ )
50
+
52
51
  return (
53
52
  <ActionCard
54
53
  value={uid}
@@ -58,13 +57,16 @@ export function CartItemActionCard(props: CartItemActionCardProps) {
58
57
  px: 0,
59
58
  py: theme.spacings.xs,
60
59
  },
61
- '& .MuiBox-root': {
60
+ '& .ActionCard-rootInner': {
62
61
  justifyContent: 'space-between',
63
62
  alignItems: 'stretch',
64
63
  },
65
64
  '&.sizeSmall': {
66
65
  px: 0,
67
66
  },
67
+ '&.sizeResponsive': {
68
+ [theme.breakpoints.down('md')]: { px: 0 },
69
+ },
68
70
  '& .ActionCard-end': {
69
71
  justifyContent: readOnly ? 'center' : 'space-between',
70
72
  },
@@ -75,16 +77,11 @@ export function CartItemActionCard(props: CartItemActionCardProps) {
75
77
  alignSelf: 'flex-start',
76
78
  },
77
79
  '& .ActionCard-secondaryAction': {
78
- typography: typographySizes[size],
79
- display: 'flex',
80
- alignItems: 'center',
81
- color: 'text.secondary',
82
- mt: 1,
83
- gap: '10px',
84
- justifyContent: 'start',
80
+ display: 'grid',
81
+ rowGap: theme.spacings.xs,
82
+ justifyItems: 'start',
85
83
  },
86
84
  '& .ActionCard-price': {
87
- typography: typographySizes[size],
88
85
  pr: readOnly ? 0 : theme.spacings.xs,
89
86
  mb: { xs: 0.5, sm: 0 },
90
87
  },
@@ -97,13 +94,13 @@ export function CartItemActionCard(props: CartItemActionCardProps) {
97
94
  layout='fill'
98
95
  src={thumbnail?.url}
99
96
  sx={{
100
- width: productImageSizes[size],
101
- height: productImageSizes[size],
97
+ width: actionCardImageSizes[size],
98
+ height: actionCardImageSizes[size],
102
99
  display: 'block',
103
100
  borderRadius: 1,
104
101
  objectFit: 'contain',
105
102
  }}
106
- sizes={productImageSizes[size]}
103
+ sizes={actionCardImageSizes[size]}
107
104
  />
108
105
  )
109
106
  }
@@ -126,20 +123,38 @@ export function CartItemActionCard(props: CartItemActionCardProps) {
126
123
  }
127
124
  secondaryAction={
128
125
  <>
129
- {readOnly ? quantity : <UpdateItemQuantity uid={uid} quantity={quantity} />}
130
- {' ⨉ '}
126
+ <Box
127
+ sx={{
128
+ display: 'flex',
129
+ alignItems: 'center',
130
+ color: 'text.secondary',
131
+ mt: 1,
132
+ gap: '10px',
133
+ justifyContent: 'start',
134
+ }}
135
+ >
136
+ {readOnly ? quantity : <UpdateItemQuantity uid={uid} quantity={quantity} />}
137
+ {' ⨉ '}
131
138
 
132
- <Money value={price} currency={prices?.price.currency} />
139
+ <Money value={price} currency={prices?.price.currency} />
140
+ </Box>
141
+ {hasOptions && (
142
+ <Button
143
+ variant='inline'
144
+ color='secondary'
145
+ href={`${productEditLink(product)}?cartItemId=${uid}`}
146
+ >
147
+ <Trans id='Edit options' />
148
+ </Button>
149
+ )}
133
150
  </>
134
151
  }
135
152
  price={<Money {...(inclTaxes ? prices?.row_total_including_tax : prices?.row_total)} />}
136
153
  action={
137
154
  !readOnly && (
138
155
  <RemoveItemFromCart
139
- uid={uid}
140
- quantity={quantity}
141
- product={product}
142
- buttonProps={{ size }}
156
+ {...cartItem}
157
+ buttonProps={{ size: size === 'responsive' ? 'large' : size }}
143
158
  />
144
159
  )
145
160
  }
@@ -150,6 +165,7 @@ export function CartItemActionCard(props: CartItemActionCardProps) {
150
165
  </Box>
151
166
  ))}
152
167
  {...rest}
168
+ details={<>{rest.details}</>}
153
169
  />
154
170
  )
155
171
  }
@@ -1,5 +1,4 @@
1
1
  import { ActionCardLayout, ActionCardLayoutProps, nonNullable } from '@graphcommerce/next-ui'
2
- import { Theme, useMediaQuery } from '@mui/material'
3
2
  import { CartItemsFragment } from '../../Api/CartItems.gql'
4
3
  import {
5
4
  CartItemActionCard,
@@ -8,7 +7,13 @@ import {
8
7
 
9
8
  export type CartProps = Omit<ActionCardLayoutProps, 'className'> & {
10
9
  cart?: CartItemsFragment | null
10
+ /**
11
+ * @deprecated Not used anymore, please use the size prop
12
+ */
11
13
  sizeSm?: CartItemActionCardProps['size']
14
+ /**
15
+ * @deprecated Not used anymore, please use the size prop
16
+ */
12
17
  sizeMd?: CartItemActionCardProps['size']
13
18
  variant?: CartItemActionCardProps['variant']
14
19
  itemProps?: Omit<
@@ -31,32 +36,25 @@ export function CartItemsActionCards(props: CartProps) {
31
36
  children,
32
37
  layout = 'list',
33
38
  itemProps = {},
34
- sizeSm = 'medium',
35
- sizeMd = 'large',
36
39
  variant = 'default',
37
40
  ...remainingProps
38
41
  } = props
39
42
 
40
- const isMobile = useMediaQuery<Theme>((theme) => theme.breakpoints.down('md'), {
41
- defaultMatches: false,
42
- })
43
-
44
- const size = isMobile ? sizeSm : sizeMd
45
-
46
43
  if (!cart?.items?.length) return null
47
44
 
48
45
  return (
49
46
  <ActionCardLayout layout={layout} {...remainingProps}>
50
- {cart.items?.filter(nonNullable).map((item) => (
51
- <CartItemActionCard
52
- key={item.uid}
53
- cartItem={item}
54
- layout={layout}
55
- size={size}
56
- variant={variant}
57
- {...itemProps}
58
- />
59
- ))}
47
+ {cart.items
48
+ ?.filter(nonNullable)
49
+ .map((item) => (
50
+ <CartItemActionCard
51
+ key={item.uid}
52
+ cartItem={item}
53
+ layout={layout}
54
+ variant={variant}
55
+ {...itemProps}
56
+ />
57
+ ))}
60
58
  {children}
61
59
  </ActionCardLayout>
62
60
  )
@@ -0,0 +1,15 @@
1
+ import {
2
+ AddProductsToCartButton,
3
+ AddProductsToCartButtonProps,
4
+ } from '@graphcommerce/magento-product'
5
+ import { Trans } from '@lingui/react'
6
+
7
+ export function EditCartItemButton(props: AddProductsToCartButtonProps) {
8
+ const { children, ...rest } = props
9
+
10
+ return (
11
+ <AddProductsToCartButton color='secondary' {...rest}>
12
+ <Trans id='Save changes' />
13
+ </AddProductsToCartButton>
14
+ )
15
+ }
@@ -0,0 +1,8 @@
1
+ fragment EditCartItemForm on ProductInterface {
2
+ ...ProductCustomizable
3
+ ...ProductWeight
4
+ ...ProductPageItem
5
+ ...ConfigurableOptions
6
+ ...DownloadableProductOptions
7
+ ...BundleProductOptions
8
+ }
@@ -0,0 +1,59 @@
1
+ import { UseHistoryLink, useHistoryGo } from '@graphcommerce/framer-next-pages'
2
+ import {
3
+ useFormAddProductsToCart,
4
+ AddProductsToCartFormProps,
5
+ AddToCartItemSelector,
6
+ AddProductsToCartForm,
7
+ } from '@graphcommerce/magento-product'
8
+ import { useEffect } from 'react'
9
+ import {
10
+ UseRemoveItemFromCartProps,
11
+ useRemoveItemFromCart,
12
+ } from '../../../hooks/useRemoveItemFromCart'
13
+ import {
14
+ CartItemToCartItemInputProps,
15
+ cartItemToCartItemInput,
16
+ } from '../../../utils/cartItemToCartItemInput'
17
+
18
+ type EditInitProps = CartItemToCartItemInputProps & AddToCartItemSelector
19
+
20
+ function EditInit(props: EditInitProps) {
21
+ const { product, selectors, cartItem, index = 0 } = props
22
+ const { setValue } = useFormAddProductsToCart()
23
+
24
+ useEffect(() => {
25
+ const cartItemInput = cartItemToCartItemInput({ product, cartItem, selectors })
26
+ if (cartItemInput) setValue(`cartItems.${index}`, cartItemInput)
27
+ }, [cartItem, index, product, selectors, setValue])
28
+
29
+ return null
30
+ }
31
+
32
+ export type EditCartItemFormProps = CartItemToCartItemInputProps &
33
+ AddToCartItemSelector &
34
+ UseHistoryLink &
35
+ AddProductsToCartFormProps
36
+
37
+ export function EditCartItemForm(props: EditCartItemFormProps) {
38
+ const { product, cartItem, onBeforeSubmit, onComplete, index = 0, children, href } = props
39
+
40
+ const remove = useRemoveItemFromCart(cartItem as UseRemoveItemFromCartProps)
41
+ const goToCart = useHistoryGo({ href })
42
+
43
+ return (
44
+ <AddProductsToCartForm
45
+ {...props}
46
+ onBeforeSubmit={async (variables) => {
47
+ await remove.submit()
48
+ return onBeforeSubmit?.(variables) ?? variables
49
+ }}
50
+ onComplete={async (result, variables) => {
51
+ await goToCart()
52
+ return onComplete?.(result, variables)
53
+ }}
54
+ >
55
+ {children}
56
+ <EditInit product={product} cartItem={cartItem} index={index} />
57
+ </AddProductsToCartForm>
58
+ )
59
+ }
@@ -0,0 +1,3 @@
1
+ export * from './EditCartItemButton/EditCartItemButton'
2
+ export * from './EditCartItemForm/EditCartItemForm'
3
+ export * from './EditCartItemForm/EditCartItemForm.gql'
@@ -1,11 +1,13 @@
1
- import { ApolloCartErrorSnackbar, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
1
+ import { ApolloCartErrorSnackbar } from '@graphcommerce/magento-cart'
2
2
  import { Button, ButtonProps } from '@graphcommerce/next-ui'
3
3
  import { Trans } from '@lingui/react'
4
4
  import { SxProps, Theme, styled } from '@mui/material'
5
- import { CartItemFragment } from '../../Api/CartItem.gql'
6
- import { RemoveItemFromCartDocument } from './RemoveItemFromCart.gql'
5
+ import {
6
+ UseRemoveItemFromCartProps,
7
+ useRemoveItemFromCart,
8
+ } from '../../hooks/useRemoveItemFromCart'
7
9
 
8
- export type RemoveItemFromCartProps = Omit<CartItemFragment, '__typename'> & {
10
+ export type RemoveItemFromCartProps = UseRemoveItemFromCartProps & {
9
11
  sx?: SxProps<Theme>
10
12
  buttonProps?: Omit<ButtonProps, 'type' | 'loading'>
11
13
  }
@@ -14,12 +16,10 @@ const Form = styled('form')({})
14
16
 
15
17
  export function RemoveItemFromCart(props: RemoveItemFromCartProps) {
16
18
  const { uid, quantity, prices, buttonProps, ...formProps } = props
17
- const form = useFormGqlMutationCart(RemoveItemFromCartDocument, { defaultValues: { uid } })
18
- const { handleSubmit, formState, error } = form
19
- const submitHandler = handleSubmit(() => {})
20
19
 
20
+ const { submit, formState, error } = useRemoveItemFromCart(props)
21
21
  return (
22
- <Form noValidate onSubmit={submitHandler} {...formProps}>
22
+ <Form noValidate onSubmit={submit} {...formProps}>
23
23
  <Button
24
24
  variant='inline'
25
25
  color='secondary'
@@ -1,11 +1,13 @@
1
- import { ApolloCartErrorSnackbar, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
1
+ import { ApolloCartErrorSnackbar } from '@graphcommerce/magento-cart'
2
2
  import { Fab, FabProps, iconClose } from '@graphcommerce/next-ui'
3
3
  import { i18n } from '@lingui/core'
4
4
  import { SxProps, Theme, styled } from '@mui/material'
5
- import { CartItemFragment } from '../../Api/CartItem.gql'
6
- import { RemoveItemFromCartDocument } from './RemoveItemFromCart.gql'
5
+ import {
6
+ UseRemoveItemFromCartProps,
7
+ useRemoveItemFromCart,
8
+ } from '../../hooks/useRemoveItemFromCart'
7
9
 
8
- export type RemoveItemFromCartFabProps = Omit<CartItemFragment, '__typename'> & {
10
+ export type RemoveItemFromCartFabProps = UseRemoveItemFromCartProps & {
9
11
  sx?: SxProps<Theme>
10
12
  fabProps?: Omit<FabProps, 'type' | 'icon' | 'loading'>
11
13
  }
@@ -14,12 +16,11 @@ const Form = styled('form')({})
14
16
 
15
17
  export function RemoveItemFromCartFab(props: RemoveItemFromCartFabProps) {
16
18
  const { uid, quantity, prices, product, ...formProps } = props
17
- const form = useFormGqlMutationCart(RemoveItemFromCartDocument, { defaultValues: { uid } })
18
- const { handleSubmit, formState, error } = form
19
- const submitHandler = handleSubmit(() => {})
19
+
20
+ const { submit, formState, error } = useRemoveItemFromCart(props)
20
21
 
21
22
  return (
22
- <Form noValidate onSubmit={submitHandler} {...formProps}>
23
+ <Form noValidate onSubmit={submit} {...formProps}>
23
24
  <Fab
24
25
  aria-label={i18n._(/* i18n */ 'Remove Product')}
25
26
  size='small'
@@ -22,29 +22,20 @@ export function SelectedCustomizableOptions(props: SelectedCustomizableOptionPro
22
22
  return (
23
23
  <>
24
24
  {options.map((option) => (
25
- <Box>
26
- <Box key={option.customizable_option_uid} sx={{ color: 'text.primary' }}>
27
- {option.label}
28
- </Box>
25
+ <Box key={option.customizable_option_uid}>
26
+ <Box sx={{ color: 'text.primary' }}>{option.label}</Box>
29
27
  {option.values.filter(nonNullable).map((value) => (
30
28
  <Box
31
- key={option.customizable_option_uid}
29
+ key={value.customizable_option_value_uid}
32
30
  sx={(theme) => ({
33
31
  display: 'flex',
34
32
  gap: theme.spacings.xxs,
35
33
  flexDirection: 'row',
36
34
  })}
37
35
  >
38
- {value.label && (
39
- <span key={`${value.customizable_option_value_uid}_${value.label}`}>
40
- {value.label}
41
- </span>
42
- )}
36
+ {value.label && <span>{value.label}</span>}
43
37
  {value.price.value > 0 && productPrice && (
44
- <Box
45
- sx={(theme) => ({ position: 'absolute', right: theme.spacings.xs })}
46
- key={`${value.customizable_option_value_uid}_${value.price.value}`}
47
- >
38
+ <Box sx={(theme) => ({ position: 'absolute', right: theme.spacings.xs })}>
48
39
  <Money
49
40
  value={
50
41
  value.price.type === 'PERCENT'
@@ -0,0 +1,28 @@
1
+ import { useFormGqlMutationCart } from '@graphcommerce/magento-cart/hooks'
2
+ import { UseFormGraphQlOptions } from '@graphcommerce/react-hook-form'
3
+ import type { DistributedOmit } from 'type-fest'
4
+ import { CartItemFragment } from '../Api/CartItem.gql'
5
+ import {
6
+ RemoveItemFromCartMutation,
7
+ RemoveItemFromCartMutationVariables,
8
+ RemoveItemFromCartDocument,
9
+ } from '../components/RemoveItemFromCart/RemoveItemFromCart.gql'
10
+
11
+ export type UseRemoveItemFromCartProps = DistributedOmit<CartItemFragment, '__typename'> &
12
+ Omit<
13
+ UseFormGraphQlOptions<RemoveItemFromCartMutation, RemoveItemFromCartMutationVariables>,
14
+ 'errors'
15
+ >
16
+
17
+ export function useRemoveItemFromCart(props: UseRemoveItemFromCartProps) {
18
+ const { uid, errors, ...options } = props
19
+
20
+ const form = useFormGqlMutationCart(RemoveItemFromCartDocument, {
21
+ defaultValues: { uid },
22
+ ...options,
23
+ })
24
+
25
+ const { handleSubmit } = form
26
+ const submit = handleSubmit(() => {})
27
+ return { ...form, submit }
28
+ }
package/index.ts CHANGED
@@ -9,3 +9,5 @@ export * from './components/RemoveItemFromCart/RemoveItemFromCart.gql'
9
9
  export * from './components/RemoveItemFromCart/RemoveItemFromCartFab'
10
10
  export * from './components/SelectedCustomizableOptions/SelectedCustomizableOptions'
11
11
  export * from './components/UpdateItemQuantity/UpdateItemQuantity'
12
+ export * from './components/EditCartItem'
13
+ export * from './hooks/useRemoveItemFromCart'
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/magento-cart-items",
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": {
@@ -12,17 +12,18 @@
12
12
  }
13
13
  },
14
14
  "peerDependencies": {
15
- "@graphcommerce/eslint-config-pwa": "^8.1.0-canary.2",
16
- "@graphcommerce/graphql": "^8.1.0-canary.2",
17
- "@graphcommerce/image": "^8.1.0-canary.2",
18
- "@graphcommerce/magento-cart": "^8.1.0-canary.2",
19
- "@graphcommerce/magento-customer": "^8.1.0-canary.2",
20
- "@graphcommerce/magento-product": "^8.1.0-canary.2",
21
- "@graphcommerce/magento-store": "^8.1.0-canary.2",
22
- "@graphcommerce/next-ui": "^8.1.0-canary.2",
23
- "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.2",
24
- "@graphcommerce/react-hook-form": "^8.1.0-canary.2",
25
- "@graphcommerce/typescript-config-pwa": "^8.1.0-canary.2",
15
+ "@graphcommerce/eslint-config-pwa": "^8.1.0-canary.22",
16
+ "@graphcommerce/framer-next-pages": "^8.1.0-canary.22",
17
+ "@graphcommerce/graphql": "^8.1.0-canary.22",
18
+ "@graphcommerce/image": "^8.1.0-canary.22",
19
+ "@graphcommerce/magento-cart": "^8.1.0-canary.22",
20
+ "@graphcommerce/magento-customer": "^8.1.0-canary.22",
21
+ "@graphcommerce/magento-product": "^8.1.0-canary.22",
22
+ "@graphcommerce/magento-store": "^8.1.0-canary.22",
23
+ "@graphcommerce/next-ui": "^8.1.0-canary.22",
24
+ "@graphcommerce/prettier-config-pwa": "^8.1.0-canary.22",
25
+ "@graphcommerce/react-hook-form": "^8.1.0-canary.22",
26
+ "@graphcommerce/typescript-config-pwa": "^8.1.0-canary.22",
26
27
  "@lingui/core": "^4.2.1",
27
28
  "@lingui/macro": "^4.2.1",
28
29
  "@lingui/react": "^4.2.1",
@@ -31,5 +32,8 @@
31
32
  "next": "*",
32
33
  "react": "^18.2.0",
33
34
  "react-dom": "^18.2.0"
35
+ },
36
+ "devDependencies": {
37
+ "type-fest": "^4.15.0"
34
38
  }
35
39
  }
@@ -0,0 +1,117 @@
1
+ import {
2
+ AddProductsToCartFields,
3
+ AnyOption,
4
+ CustomizableProductOptionBase,
5
+ OptionValueSelector,
6
+ SelectorsProp,
7
+ productCustomizableSelectors,
8
+ } from '@graphcommerce/magento-product'
9
+ import { isTypename, filterNonNullableKeys, nonNullable } from '@graphcommerce/next-ui'
10
+ import { CartItemFragment } from '../Api/CartItem.gql'
11
+ import { EditCartItemFormFragment } from '../components/EditCartItem/EditCartItemForm/EditCartItemForm.gql'
12
+
13
+ type CartItemInput = AddProductsToCartFields['cartItems'][number]
14
+
15
+ export type CartItemToCartItemInputProps = {
16
+ product: EditCartItemFormFragment
17
+ cartItem: CartItemFragment
18
+ } & SelectorsProp
19
+
20
+ export function cartItemToCartItemInput(
21
+ props: CartItemToCartItemInputProps,
22
+ ): CartItemInput | undefined {
23
+ const { product, cartItem, selectors } = props
24
+
25
+ if (isTypename(product, ['GroupedProduct']) || !product.sku || !cartItem) return undefined
26
+
27
+ const allSelectors: OptionValueSelector = { ...productCustomizableSelectors, ...selectors }
28
+
29
+ const cartItemInput: CartItemInput = {
30
+ sku: product.sku,
31
+ quantity: cartItem.quantity,
32
+ customizable_options: {},
33
+ selected_options: [],
34
+ entered_options: [],
35
+ }
36
+
37
+ const cartItemCustomizableOptions = filterNonNullableKeys(
38
+ isTypename(cartItem, ['ConfigurableCartItem'])
39
+ ? cartItem.configurable_customizable
40
+ : cartItem.customizable_options,
41
+ )
42
+
43
+ if (cartItemCustomizableOptions.length > 0) {
44
+ product.options?.filter(nonNullable).forEach((productOption) => {
45
+ // @todo Date option: Magento's backend does not provide an ISO date string that can be used, only localized strings are available which can not be parsed.
46
+ // @todo File option: We do not support file options yet.
47
+ if (isTypename(productOption, ['CustomizableDateOption', 'CustomizableFileOption'])) return
48
+
49
+ const selector = allSelectors[productOption.__typename] as
50
+ | undefined
51
+ | ((option: AnyOption) => CustomizableProductOptionBase | CustomizableProductOptionBase[])
52
+ const possibleProductValues = selector ? selector(productOption) : null
53
+
54
+ const cartItemCustomizableOption = cartItemCustomizableOptions.find(
55
+ (option) => option?.customizable_option_uid === productOption.uid,
56
+ )
57
+
58
+ const cartItemCustomizableOptionValue = filterNonNullableKeys(
59
+ cartItemCustomizableOption?.values,
60
+ )
61
+ if (cartItemCustomizableOptionValue.length === 0) return
62
+
63
+ if (Array.isArray(possibleProductValues)) {
64
+ const value = cartItemCustomizableOptionValue.map((v) => v.customizable_option_value_uid)
65
+ if (!cartItemInput.customizable_options) cartItemInput.customizable_options = {}
66
+ cartItemInput.customizable_options[productOption.uid] = isTypename(productOption, [
67
+ 'CustomizableRadioOption',
68
+ 'CustomizableDropDownOption',
69
+ ])
70
+ ? value[0]
71
+ : value
72
+ } else {
73
+ const idx = (productOption.sort_order ?? 0) + 100
74
+
75
+ if (!cartItemInput.entered_options) cartItemInput.entered_options = []
76
+ cartItemInput.entered_options[idx] = {
77
+ uid: productOption.uid,
78
+ value: cartItemCustomizableOptionValue[0].value,
79
+ }
80
+ }
81
+ })
82
+ }
83
+
84
+ if (isTypename(cartItem, ['ConfigurableCartItem']) && cartItem.configurable_options) {
85
+ cartItemInput.selected_options = filterNonNullableKeys(cartItem.configurable_options).map(
86
+ (option) => option.configurable_product_option_value_uid,
87
+ )
88
+ }
89
+
90
+ if (isTypename(cartItem, ['BundleCartItem']) && isTypename(product, ['BundleProduct'])) {
91
+ filterNonNullableKeys(product.items).forEach((productBundleItem, i) => {
92
+ const cartItemBundleOption = cartItem.bundle_options.find(
93
+ (option) => option?.uid === productBundleItem?.uid,
94
+ )
95
+
96
+ if (!cartItemBundleOption) return
97
+
98
+ // todo multi select..
99
+ const idx = productBundleItem.position ?? 0 + 1000
100
+ const value = cartItemBundleOption.values[0]
101
+
102
+ if (!value) return
103
+ if (productBundleItem.options?.some((o) => o?.can_change_quantity)) {
104
+ if (!cartItemInput.entered_options) cartItemInput.entered_options = []
105
+ cartItemInput.entered_options[idx] = {
106
+ uid: value.uid,
107
+ value: `${value.quantity}`,
108
+ }
109
+ } else {
110
+ if (!cartItemInput.selected_options) cartItemInput.selected_options = []
111
+ cartItemInput.selected_options[idx] = value.uid
112
+ }
113
+ })
114
+ }
115
+
116
+ return cartItemInput
117
+ }