@graphcommerce/magento-product 9.1.0-canary.18 → 9.1.0-canary.19

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 (34) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/components/AddProductsToCart/AddProductsToCartForm.tsx +20 -45
  3. package/components/AddProductsToCart/useFormAddProductsToCart.ts +16 -1
  4. package/components/ProductCustomizable/CustomizableAreaOption.tsx +22 -37
  5. package/components/ProductCustomizable/CustomizableCheckboxOption.tsx +61 -41
  6. package/components/ProductCustomizable/CustomizableDateOption.graphql +1 -0
  7. package/components/ProductCustomizable/CustomizableDateOption.tsx +42 -87
  8. package/components/ProductCustomizable/CustomizableDropDownOption.tsx +13 -5
  9. package/components/ProductCustomizable/CustomizableFieldOption.tsx +22 -40
  10. package/components/ProductCustomizable/CustomizableMultipleOption.tsx +54 -35
  11. package/components/ProductCustomizable/CustomizablePrice.tsx +40 -0
  12. package/components/ProductCustomizable/CustomizableRadioOption.tsx +56 -36
  13. package/components/ProductCustomizable/ProductCustomizable.graphql +1 -0
  14. package/components/ProductCustomizable/ProductCustomizable.tsx +10 -2
  15. package/components/ProductCustomizable/productCustomizableSelectors.ts +5 -7
  16. package/components/ProductListItem/ProductDiscountLabel.tsx +1 -1
  17. package/components/ProductListItem/ProductListItem.tsx +5 -2
  18. package/components/ProductListItem/ProductNewLabel.tsx +36 -0
  19. package/components/ProductListItems/renderer.tsx +1 -1
  20. package/components/ProductPageDescription/ProductPageDescription.tsx +10 -1
  21. package/components/ProductPageGallery/ProductPageGallery.tsx +22 -19
  22. package/components/ProductPageGallery/ProductVideo.graphql +1 -0
  23. package/components/ProductPageGallery/ProductVideo.tsx +169 -0
  24. package/components/ProductPageMeta/ProductPageMeta.graphql +1 -0
  25. package/components/ProductPageMeta/ProductPageMeta.tsx +2 -0
  26. package/components/ProductPagePrice/ProductPagePrice.graphql +1 -10
  27. package/components/ProductPagePrice/ProductPrice.graphql +12 -0
  28. package/components/ProductPagePrice/getProductTierPrice.ts +3 -5
  29. package/components/ProductPagePrice/useCustomizableOptionPrice.ts +38 -29
  30. package/{Api → graphql/fragments}/ProductListItem.graphql +2 -0
  31. package/graphql/index.ts +2 -0
  32. package/index.ts +1 -2
  33. package/package.json +13 -13
  34. /package/{Api → graphql/fragments}/ProductPageItem.graphql +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,29 @@
1
1
  # Change Log
2
2
 
3
+ ## 9.1.0-canary.19
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`89e785d`](https://github.com/graphcommerce-org/graphcommerce/commit/89e785de9d62c2f6cf6b2885da72ff63b16fc70d) - Added support for TIME and DATE for the customizable options. Added required stars. ([@paales](https://github.com/paales))
8
+
9
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`7565120`](https://github.com/graphcommerce-org/graphcommerce/commit/756512031642371609258fba322a7f3a4845a17b) - Customizable Product Options wouldn't be properly selected because the parent woudln't rerender anymore. ([@paales](https://github.com/paales))
10
+
11
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`37c00d8`](https://github.com/graphcommerce-org/graphcommerce/commit/37c00d80419b209850457559d7b7eca4101f5705) - Forward productListRenderer for all locations that can be rendered by pagebuilder ([@paales](https://github.com/paales))
12
+
13
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`e9da6a9`](https://github.com/graphcommerce-org/graphcommerce/commit/e9da6a9e55a9344a1f8ef8f1f20060af2bb38ee9) - Added support for video's on the product page. ([@paales](https://github.com/paales))
14
+
15
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`1e38811`](https://github.com/graphcommerce-org/graphcommerce/commit/1e3881177065548165b7141a29cff8ab27692b25) - Added support for meta_keyword for products and categories ([@paales](https://github.com/paales))
16
+
17
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`b497116`](https://github.com/graphcommerce-org/graphcommerce/commit/b497116798e26419950982a6a9d05932a9e99961) - Make sure CustomizableOptions are sorted correctly ([@paales](https://github.com/paales))
18
+
19
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`89e785d`](https://github.com/graphcommerce-org/graphcommerce/commit/89e785de9d62c2f6cf6b2885da72ff63b16fc70d) - Created a CustomizablePrice component that will highlight the price, to prevent duplicating logic and preventing rerenders. ([@paales](https://github.com/paales))
20
+
21
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`722763f`](https://github.com/graphcommerce-org/graphcommerce/commit/722763f01f9c4726126d5a30919bdcd25929a330) - Support for new_from_date and new_to_date labels ([@paales](https://github.com/paales))
22
+
23
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`35fdadd`](https://github.com/graphcommerce-org/graphcommerce/commit/35fdadd8896619a2c84e91e39279f5928c0c9007) - Refactored the price calculation of customizable options on the product page so required options are correctly handled. ([@paales](https://github.com/paales))
24
+
25
+ - [#2499](https://github.com/graphcommerce-org/graphcommerce/pull/2499) [`35fdadd`](https://github.com/graphcommerce-org/graphcommerce/commit/35fdadd8896619a2c84e91e39279f5928c0c9007) - Renamed customizable_options_entered to entered_options_record and customizable_options to selected_options_record ([@paales](https://github.com/paales))
26
+
3
27
  ## 9.1.0-canary.18
4
28
 
5
29
  ## 9.1.0-canary.17
@@ -3,7 +3,6 @@ import type { ApolloQueryResult } from '@graphcommerce/graphql'
3
3
  import { useApolloClient } from '@graphcommerce/graphql'
4
4
  import type { CrosssellsQuery } from '@graphcommerce/magento-cart'
5
5
  import { CrosssellsDocument, useFormGqlMutationCart } from '@graphcommerce/magento-cart'
6
- import type { ErrorSnackbarProps, MessageSnackbarProps } from '@graphcommerce/next-ui'
7
6
  import { nonNullable } from '@graphcommerce/next-ui'
8
7
  import type { SxProps, Theme } from '@mui/material'
9
8
  import { Box } from '@mui/material'
@@ -22,13 +21,6 @@ export type AddProductsToCartFormProps = {
22
21
  sx?: SxProps<Theme>
23
22
  redirect?: RedirectType
24
23
  snackbarProps?: AddProductsToCartSnackbarProps
25
-
26
- /** @deprecated Use snackbarProps.errorSnackbar instead */
27
- errorSnackbar?: Omit<ErrorSnackbarProps, 'open'>
28
- /** @deprecated Use snackbarProps.successSnackbar instead */
29
- successSnackbar?: Omit<MessageSnackbarProps, 'open' | 'action'>
30
- /** @deprecated Use snackbarProps.disableSuccessSnackbar instead */
31
- disableSuccessSnackbar?: boolean
32
24
  } & UseFormGraphQlOptions<AddProductsToCartMutation, AddProductsToCartFields>
33
25
 
34
26
  const name = 'AddProductsToCartForm'
@@ -45,17 +37,7 @@ const name = 'AddProductsToCartForm'
45
37
  * - Redirects the user to the cart/checkout/added page after successful submission.
46
38
  */
47
39
  export function AddProductsToCartForm(props: AddProductsToCartFormProps) {
48
- let {
49
- children,
50
- redirect,
51
- onComplete,
52
- sx,
53
- disableSuccessSnackbar,
54
- errorSnackbar,
55
- successSnackbar,
56
- snackbarProps,
57
- ...formProps
58
- } = props
40
+ let { children, redirect, onComplete, sx, snackbarProps, ...formProps } = props
59
41
  const router = useRouter()
60
42
  const client = useApolloClient()
61
43
  const crosssellsQuery = useRef<Promise<ApolloQueryResult<CrosssellsQuery>>>()
@@ -78,26 +60,24 @@ export function AddProductsToCartForm(props: AddProductsToCartFormProps) {
78
60
  cartId,
79
61
  cartItems: cartItems
80
62
  .filter((cartItem) => cartItem.sku && cartItem.quantity !== 0)
81
- .map(({ customizable_options, ...cartItem }) => {
82
- const options = Object.values(customizable_options ?? {})
83
- .flat(1)
84
- .filter(Boolean)
85
-
86
- return {
87
- ...cartItem,
88
- quantity: cartItem.quantity || 1,
89
- selected_options: [
90
- ...(cartItem.selected_options ?? []).filter(Boolean),
91
- ...options,
92
- ],
93
- entered_options: [
94
- ...(cartItem.entered_options
95
- ?.filter((option) => option?.value)
96
- .filter(nonNullable)
97
- .map((option) => ({ uid: option.uid, value: `${option?.value}` })) ?? []),
98
- ],
99
- }
100
- }),
63
+ .map(({ selected_options_record = {}, entered_options_record = {}, ...cartItem }) => ({
64
+ ...cartItem,
65
+ quantity: cartItem.quantity || 1,
66
+ selected_options: [
67
+ ...(cartItem.selected_options ?? []).filter(nonNullable),
68
+ ...Object.values(selected_options_record).flat(1).filter(nonNullable),
69
+ ],
70
+ entered_options: [
71
+ ...(cartItem.entered_options ?? []).filter(nonNullable),
72
+ ...Object.entries(entered_options_record).map(([uid, value]) => {
73
+ if (value instanceof Date) {
74
+ const dateValue = value.toISOString().replace(/.000Z/, '').replace('T', ' ')
75
+ return { uid, value: dateValue }
76
+ }
77
+ return { uid, value: value.toString() }
78
+ }),
79
+ ],
80
+ })),
101
81
  }
102
82
 
103
83
  const sku = requestData.cartItems[requestData.cartItems.length - 1]?.sku
@@ -147,12 +127,7 @@ export function AddProductsToCartForm(props: AddProductsToCartFormProps) {
147
127
  <Box component='form' onSubmit={submit} noValidate sx={sx} className={name}>
148
128
  {children}
149
129
  </Box>
150
- <AddProductsToCartSnackbar
151
- errorSnackbar={errorSnackbar}
152
- successSnackbar={successSnackbar}
153
- disableSuccessSnackbar={disableSuccessSnackbar}
154
- {...snackbarProps}
155
- />
130
+ <AddProductsToCartSnackbar {...snackbarProps} />
156
131
  </AddProductsToCartContext.Provider>
157
132
  )
158
133
  }
@@ -1,4 +1,5 @@
1
1
  import type { UseFormGqlMutationReturn } from '@graphcommerce/ecommerce-ui'
2
+ import type { EnteredOptionInput } from '@graphcommerce/graphql-mesh'
2
3
  import { createContext, useContext } from 'react'
3
4
  import type { LiteralUnion, Simplify } from 'type-fest'
4
5
  import type {
@@ -10,7 +11,21 @@ export type RedirectType = LiteralUnion<'added' | undefined | false, `/${string}
10
11
 
11
12
  type Item = Simplify<
12
13
  AddProductsToCartMutationVariables['cartItems'][number] & {
13
- customizable_options?: Record<string, string | string[]>
14
+ /**
15
+ * The value of the selected_options_record values will be added to the selected_options array.
16
+ *
17
+ * This format exists to prevent name collisions and without having to select by index in the
18
+ * selected_options array.
19
+ */
20
+ selected_options_record?: Record<string, string | string[]>
21
+ /**
22
+ * The value of the entered_options_record entries will be coverted to entries for the
23
+ * entered_options array.
24
+ *
25
+ * This format exists to prevent name collisions and without having to select by index in the
26
+ * entered_options array.
27
+ */
28
+ entered_options_record?: Record<string, string | number | Date>
14
29
  }
15
30
  >
16
31
 
@@ -1,4 +1,4 @@
1
- import { TextFieldElement } from '@graphcommerce/ecommerce-ui'
1
+ import { TextFieldElement, useWatch } from '@graphcommerce/ecommerce-ui'
2
2
  import type { CurrencyEnum } from '@graphcommerce/graphql-mesh'
3
3
  import { Money } from '@graphcommerce/magento-store'
4
4
  import type { TypeRenderer } from '@graphcommerce/next-ui'
@@ -7,11 +7,11 @@ import { i18n } from '@lingui/core'
7
7
  import { Box } from '@mui/material'
8
8
  import React from 'react'
9
9
  import { useFormAddProductsToCart } from '../AddProductsToCart'
10
+ import { CustomizablePrice } from './CustomizablePrice'
10
11
  import type { ProductCustomizableFragment } from './ProductCustomizable.gql'
11
12
 
12
13
  export type OptionTypeRenderer = TypeRenderer<
13
14
  NonNullable<NonNullable<ProductCustomizableFragment['options']>[number]> & {
14
- optionIndex: number
15
15
  index: number
16
16
  currency: CurrencyEnum
17
17
  productPrice: number
@@ -23,55 +23,40 @@ export type CustomizableAreaOptionProps = React.ComponentProps<
23
23
  >
24
24
 
25
25
  export function CustomizableAreaOption(props: CustomizableAreaOptionProps) {
26
- const { uid, areaValue, required, optionIndex, index, title, currency, productPrice } = props
26
+ const { uid, areaValue, required, index, title, currency, productPrice } = props
27
27
  const maxLength = areaValue?.max_characters ?? undefined
28
- const { control, register, getValues } = useFormAddProductsToCart()
28
+ const { control } = useFormAddProductsToCart()
29
29
 
30
+ const name = `cartItems.${index}.entered_options_record.${uid}` as const
30
31
  if (!areaValue) return null
31
32
 
32
33
  return (
33
34
  <Box>
34
- <input
35
- type='hidden'
36
- {...register(`cartItems.${index}.entered_options.${optionIndex}.uid`)}
37
- value={uid}
35
+ <SectionHeader
36
+ labelLeft={
37
+ <>
38
+ {title} {required && ' *'}
39
+ </>
40
+ }
41
+ sx={{ mt: 0 }}
38
42
  />
39
- <SectionHeader labelLeft={title} sx={{ mt: 0 }} />
40
43
  <TextFieldElement
41
44
  sx={{ width: '100%' }}
42
45
  color='primary'
43
46
  multiline
44
47
  minRows={3}
45
48
  control={control}
46
- name={`cartItems.${index}.entered_options.${optionIndex}.value`}
49
+ name={name}
47
50
  InputProps={{
48
- endAdornment:
49
- areaValue.price === 0
50
- ? null
51
- : areaValue.price && (
52
- <Box
53
- sx={{
54
- display: 'flex',
55
- typography: 'body1',
56
- '&.sizeMedium': { typographty: 'subtitle1' },
57
- '&.sizeLarge': { typography: 'h6' },
58
- color: getValues(`cartItems.${index}.entered_options.${optionIndex}.value`)
59
- ? 'text.primary'
60
- : 'text.secondary',
61
- }}
62
- >
63
- {/* Change fontFamily so the + is properly outlined */}
64
- <span style={{ fontFamily: 'arial', paddingTop: '1px' }}>+{'\u00A0'}</span>
65
- <Money
66
- value={
67
- areaValue.price_type === 'PERCENT'
68
- ? productPrice * (areaValue.price / 100)
69
- : areaValue.price
70
- }
71
- currency={currency}
72
- />
73
- </Box>
74
- ),
51
+ endAdornment: (
52
+ <CustomizablePrice
53
+ name={name}
54
+ price_type={areaValue.price_type}
55
+ productPrice={productPrice}
56
+ currency={currency}
57
+ value={areaValue.price}
58
+ />
59
+ ),
75
60
  }}
76
61
  required={Boolean(required)}
77
62
  rules={{ maxLength }}
@@ -1,25 +1,71 @@
1
1
  import { ActionCardListForm } from '@graphcommerce/ecommerce-ui'
2
2
  import { Money } from '@graphcommerce/magento-store'
3
3
  import type { ActionCardProps } from '@graphcommerce/next-ui'
4
- import { ActionCard, filterNonNullableKeys, SectionHeader } from '@graphcommerce/next-ui'
4
+ import { ActionCard, nonNullable, SectionHeader } from '@graphcommerce/next-ui'
5
5
  import { i18n } from '@lingui/core'
6
6
  import { Box, Checkbox } from '@mui/material'
7
7
  import { useFormAddProductsToCart } from '../AddProductsToCart'
8
8
  import type { OptionTypeRenderer } from './CustomizableAreaOption'
9
+ import type { CustomizableCheckboxOptionFragment } from './CustomizableCheckboxOption.gql'
9
10
 
10
11
  export type CustomizableCheckboxOptionProps = React.ComponentProps<
11
12
  OptionTypeRenderer['CustomizableCheckboxOption']
12
13
  >
13
14
 
14
- export function CustomizableCheckboxOption(props: CustomizableCheckboxOptionProps) {
15
- const { uid, required, index, title: label, checkboxValue, currency, productPrice } = props
16
- const { control, getValues } = useFormAddProductsToCart()
15
+ type CheckboxActionCardProps = Pick<CustomizableCheckboxOptionProps, 'productPrice' | 'currency'> &
16
+ Pick<ActionCardProps, 'value' | 'selected'> & {
17
+ option: NonNullable<NonNullable<CustomizableCheckboxOptionFragment['checkboxValue']>[number]>
18
+ }
19
+
20
+ function CustomizableCheckboxActionCard(props: CheckboxActionCardProps) {
21
+ const { productPrice, currency, value, selected, option, ...rest } = props
22
+ const { title, price, price_type } = option
23
+
24
+ return (
25
+ <ActionCard
26
+ {...rest}
27
+ value={value}
28
+ selected={selected}
29
+ title={
30
+ <Box sx={{ display: 'flex', alignItems: 'center' }}>
31
+ <Checkbox checked={selected} /> {title}
32
+ </Box>
33
+ }
34
+ price={
35
+ price === 0
36
+ ? null
37
+ : price && (
38
+ <Box
39
+ sx={{
40
+ color: selected ? 'text.primary' : 'text.secondary',
41
+ }}
42
+ >
43
+ <span style={{ fontFamily: 'arial' }}>{'+ '}</span>
44
+ <Money
45
+ value={price_type === 'PERCENT' ? productPrice * (price / 100) : price}
46
+ currency={currency}
47
+ />
48
+ </Box>
49
+ )
50
+ }
51
+ />
52
+ )
53
+ }
17
54
 
18
- const allSelected = getValues(`cartItems.${index}.customizable_options.${uid}`) || []
55
+ export function CustomizableCheckboxOption(props: CustomizableCheckboxOptionProps) {
56
+ const { uid, required, index, title: label, checkboxValue, productPrice, currency } = props
57
+ const { control } = useFormAddProductsToCart()
19
58
 
20
59
  return (
21
60
  <Box>
22
- <SectionHeader labelLeft={label} sx={{ mt: 0 }} />
61
+ <SectionHeader
62
+ labelLeft={
63
+ <>
64
+ {label} {required && ' *'}
65
+ </>
66
+ }
67
+ sx={{ mt: 0 }}
68
+ />
23
69
  <ActionCardListForm
24
70
  sx={(theme) => ({
25
71
  mt: theme.spacings.xxs,
@@ -31,41 +77,15 @@ export function CustomizableCheckboxOption(props: CustomizableCheckboxOptionProp
31
77
  ? i18n._(/* i18n*/ 'Please select a value for ‘{label}’', { label })
32
78
  : false,
33
79
  }}
34
- render={ActionCard}
35
- name={`cartItems.${index}.customizable_options.${uid}`}
36
- items={filterNonNullableKeys(checkboxValue, ['title']).map(
37
- (checkboxVal) =>
38
- ({
39
- value: checkboxVal.uid,
40
- title: (
41
- <Box sx={{ display: 'flex', alignItems: 'center' }}>
42
- <Checkbox checked={allSelected.includes(checkboxVal.uid)} /> {checkboxVal.title}
43
- </Box>
44
- ),
45
- price:
46
- checkboxVal.price === 0
47
- ? null
48
- : checkboxVal.price && (
49
- <Box
50
- sx={{
51
- color: allSelected.includes(checkboxVal.uid)
52
- ? 'text.primary'
53
- : 'text.secondary',
54
- }}
55
- >
56
- <span style={{ fontFamily: 'arial' }}>{'+ '}</span>
57
- <Money
58
- value={
59
- checkboxVal.price_type === 'PERCENT'
60
- ? productPrice * (checkboxVal.price / 100)
61
- : checkboxVal.price
62
- }
63
- currency={currency}
64
- />
65
- </Box>
66
- ),
67
- }) satisfies ActionCardProps,
68
- )}
80
+ render={CustomizableCheckboxActionCard}
81
+ name={`cartItems.${index}.selected_options_record.${uid}`}
82
+ items={(checkboxValue ?? []).filter(nonNullable).map((checkboxVal) => ({
83
+ productPrice,
84
+ currency,
85
+ title: checkboxVal.title ?? '',
86
+ value: checkboxVal.uid,
87
+ option: checkboxVal,
88
+ }))}
69
89
  errorMessage=''
70
90
  />
71
91
  </Box>
@@ -1,6 +1,7 @@
1
1
  fragment CustomizableDateOption on CustomizableDateOption {
2
2
  product_sku
3
3
  dateValue: value {
4
+ type
4
5
  price
5
6
  price_type
6
7
  sku
@@ -1,10 +1,11 @@
1
1
  import { TextFieldElement } from '@graphcommerce/ecommerce-ui'
2
- import { Money } from '@graphcommerce/magento-store'
2
+ import type { CustomizableDateTypeEnum } from '@graphcommerce/graphql-mesh'
3
3
  import { SectionHeader } from '@graphcommerce/next-ui'
4
- import { Trans } from '@lingui/react'
4
+ import { t } from '@lingui/macro'
5
5
  import { Box } from '@mui/material'
6
6
  import { useFormAddProductsToCart } from '../AddProductsToCart'
7
7
  import type { OptionTypeRenderer } from './CustomizableAreaOption'
8
+ import { CustomizablePrice } from './CustomizablePrice'
8
9
 
9
10
  export type CustomizableDateOptionProps = React.ComponentProps<
10
11
  OptionTypeRenderer['CustomizableDateOption']
@@ -13,111 +14,65 @@ export type CustomizableDateOptionProps = React.ComponentProps<
13
14
  maxDate?: Date
14
15
  }
15
16
 
16
- export function CustomizableDateOption(props: CustomizableDateOptionProps) {
17
- const {
18
- uid,
19
- required,
20
- optionIndex,
21
- index,
22
- title,
23
- minDate = new Date('1950-11-12T00:00'),
24
- maxDate = new Date('9999-11-12T00:00'),
25
- dateValue,
26
- currency,
27
- productPrice,
28
- } = props
29
- const {
30
- register,
31
- setValue,
32
- setError,
33
- getFieldState,
34
- clearErrors,
35
- control,
36
- resetField,
37
- getValues,
38
- } = useFormAddProductsToCart()
17
+ function getInputType(
18
+ type: CustomizableDateTypeEnum | null | undefined,
19
+ ): React.HTMLInputTypeAttribute {
20
+ if (type === 'DATE') return 'date'
21
+ if (type === 'TIME') return 'time'
22
+ return 'datetime-local'
23
+ }
39
24
 
40
- const { invalid } = getFieldState(`cartItems.${index}.entered_options.${optionIndex}.value`)
25
+ export function CustomizableDateOption(props: CustomizableDateOptionProps) {
26
+ const { uid, required, index, title, minDate, maxDate, dateValue, currency, productPrice } = props
27
+ const { control } = useFormAddProductsToCart()
41
28
 
42
- minDate.setSeconds(0, 0)
43
- maxDate.setSeconds(0, 0)
29
+ const name = `cartItems.${index}.entered_options_record.${uid}` as const
44
30
  if (!dateValue) return null
45
31
 
46
- const price =
47
- dateValue.price === 0
48
- ? null
49
- : dateValue.price && (
50
- <Box
51
- sx={{
52
- display: 'flex',
53
- typography: 'body1',
54
- '&.sizeMedium': { typographty: 'subtitle1' },
55
- '&.sizeLarge': { typography: 'h6' },
56
- color:
57
- dateValue.uid ===
58
- getValues(`cartItems.${index}.entered_options.${optionIndex}.value`)
59
- ? 'text.primary'
60
- : 'text.secondary',
61
- }}
62
- >
63
- {/* Change fontFamily so the + is properly outlined */}
64
- <span style={{ fontFamily: 'arial' }}>+{'\u00A0'}</span>
65
- <Money
66
- value={
67
- dateValue.price_type === 'PERCENT'
68
- ? productPrice * (dateValue.price / 100)
69
- : dateValue.price
70
- }
71
- currency={currency}
72
- />
73
- </Box>
74
- )
32
+ minDate?.setSeconds(0, 0)
33
+ maxDate?.setSeconds(0, 0)
34
+
75
35
  return (
76
36
  <Box>
77
- <input
78
- type='hidden'
79
- {...register(`cartItems.${index}.entered_options.${optionIndex}.uid`)}
80
- value={uid}
81
- />
82
-
83
37
  <SectionHeader labelLeft={title} sx={{ mt: 0 }} />
84
38
  <TextFieldElement
85
39
  control={control}
86
- name={`cartItems.${index}.entered_options.${optionIndex}.value`}
40
+ name={name}
87
41
  sx={{
88
42
  width: '100%',
89
- '& input[type="datetime-local"]::-webkit-calendar-picker-indicator': {
43
+ '& ::-webkit-calendar-picker-indicator': {
90
44
  filter: (theme) => (theme.palette.mode === 'dark' ? 'invert(100%)' : 'none'),
91
45
  mr: '10px',
92
46
  },
93
47
  }}
94
- defaultValue=''
95
48
  required={!!required}
96
- error={invalid}
97
- helperText={invalid ? <Trans id='Invalid date' /> : ''}
98
- type='datetime-local'
49
+ type={getInputType(dateValue.type)}
99
50
  InputProps={{
100
- endAdornment: price,
51
+ endAdornment: (
52
+ <CustomizablePrice
53
+ name={name}
54
+ price_type={dateValue.price_type}
55
+ currency={currency}
56
+ value={dateValue.price}
57
+ productPrice={productPrice}
58
+ />
59
+ ),
101
60
  inputProps: {
102
- min: minDate.toISOString().replace(/:00.000Z/, ''),
103
- max: maxDate.toISOString().replace(/:00.000Z/, ''),
61
+ min: minDate?.toISOString().replace(/.000Z/, ''),
62
+ max: maxDate?.toISOString().replace(/.000Z/, ''),
104
63
  },
105
64
  }}
106
- onChange={(data) => {
107
- const selectedDate = new Date(data.currentTarget.value)
108
- if (selectedDate < minDate || selectedDate > maxDate) {
109
- setError(`cartItems.${index}.entered_options.${optionIndex}.value`, {
110
- message: 'Invalid date',
111
- })
112
- } else {
113
- clearErrors(`cartItems.${index}.entered_options.${optionIndex}.value`)
114
- }
115
- if (data.currentTarget.value)
116
- setValue(
117
- `cartItems.${index}.entered_options.${optionIndex}.value`,
118
- `${data.currentTarget.value.replace('T', ' ')}:00`,
119
- )
120
- else resetField(`cartItems.${index}.entered_options.${optionIndex}.value`)
65
+ rules={{
66
+ validate: (value) => {
67
+ if (!(value instanceof Date)) return true
68
+
69
+ if (minDate && value < minDate)
70
+ return t`Date must be after ${minDate.toLocaleDateString()}`
71
+ if (maxDate && value > maxDate)
72
+ return t`Date must be before ${maxDate.toLocaleDateString()}`
73
+
74
+ return true
75
+ },
121
76
  }}
122
77
  />
123
78
  </Box>
@@ -1,6 +1,7 @@
1
1
  import { useController } from '@graphcommerce/ecommerce-ui'
2
2
  import { Money } from '@graphcommerce/magento-store'
3
3
  import { filterNonNullableKeys, SectionHeader } from '@graphcommerce/next-ui'
4
+ import { i18n } from '@lingui/core'
4
5
  import { Box, MenuItem, TextField } from '@mui/material'
5
6
  import { useFormAddProductsToCart } from '../AddProductsToCart'
6
7
  import type { OptionTypeRenderer } from './CustomizableAreaOption'
@@ -17,9 +18,11 @@ export function CustomizableDropDownOption(props: CustomizableDropDownOptionProp
17
18
  field: { onChange, value, ref, ...field },
18
19
  fieldState: { invalid, error },
19
20
  } = useController({
20
- name: `cartItems.${index}.customizable_options.${uid}`,
21
+ name: `cartItems.${index}.selected_options_record.${uid}`,
21
22
  rules: {
22
- required: Boolean(required),
23
+ required: required
24
+ ? i18n._(/* i18n*/ 'Please select a value for ‘{label}’', { label: title })
25
+ : false,
23
26
  },
24
27
  control,
25
28
  defaultValue: '',
@@ -27,8 +30,14 @@ export function CustomizableDropDownOption(props: CustomizableDropDownOptionProp
27
30
 
28
31
  return (
29
32
  <Box>
30
- <SectionHeader labelLeft={title} sx={{ mt: 0 }} />
31
-
33
+ <SectionHeader
34
+ labelLeft={
35
+ <>
36
+ {title} {required && ' *'}
37
+ </>
38
+ }
39
+ sx={{ mt: 0 }}
40
+ />
32
41
  <TextField
33
42
  sx={{
34
43
  width: '100%',
@@ -44,7 +53,6 @@ export function CustomizableDropDownOption(props: CustomizableDropDownOptionProp
44
53
  inputRef={ref}
45
54
  onChange={(event) => onChange(event.target.value)}
46
55
  select
47
- required={Boolean(required)}
48
56
  error={invalid}
49
57
  helperText={error?.message}
50
58
  >