@graphcommerce/magento-customer 9.1.0-canary.18 → 9.1.0-canary.20

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 (96) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/components/AccountAddresses/AccountAddresses.tsx +9 -4
  3. package/components/AccountDeleteForm/AccountDeleteForm.tsx +14 -4
  4. package/components/AccountLatestOrder/AccountLatestOrder.tsx +1 -2
  5. package/components/AccountOrders/AccountOrders.tsx +1 -1
  6. package/components/CancelOrder/CancelOrderForm.tsx +2 -2
  7. package/components/CancelOrder/CancelOrderFragment.graphql +5 -0
  8. package/components/ChangeNameForm/ChangeNameForm.tsx +1 -0
  9. package/components/ContactForm/ContactUsConfig.graphql +3 -0
  10. package/components/CreditMemo/CreditMemo.graphql +43 -0
  11. package/components/CreditMemo/CreditMemoCard.graphql +12 -0
  12. package/components/CreditMemo/CreditMemoCard.tsx +66 -0
  13. package/components/CreditMemo/CreditMemoDetails.tsx +94 -0
  14. package/components/CreditMemo/CreditMemoItem.graphql +26 -0
  15. package/components/CreditMemo/CreditMemoItem.tsx +125 -0
  16. package/components/CreditMemo/CreditMemoItems.tsx +78 -0
  17. package/components/CreditMemo/CreditMemoTotals.tsx +122 -0
  18. package/components/CreditMemo/index.ts +8 -0
  19. package/components/CustomerForms/CustomerAttributeField.tsx +112 -0
  20. package/components/CustomerForms/CustomerAttributeMetadata.graphql +9 -0
  21. package/components/CustomerForms/CustomerUpdateForm.tsx +65 -0
  22. package/components/CustomerForms/UseCustomerCreateForm.graphql +7 -0
  23. package/components/CustomerForms/UseCustomerUpdateForm.graphql +7 -0
  24. package/components/CustomerForms/customerAttributeFieldHelpers.ts +127 -0
  25. package/components/CustomerForms/index.ts +8 -0
  26. package/components/CustomerForms/nameFieldset.tsx +32 -0
  27. package/components/CustomerForms/useCustomerCreateForm.ts +83 -0
  28. package/components/CustomerForms/useCustomerUpdateForm.ts +122 -0
  29. package/components/GuestOrderOverview/GuestOrderOverviewForm.tsx +28 -11
  30. package/components/Invoice/Invoice.graphql +13 -0
  31. package/components/Invoice/InvoiceCard.graphql +9 -0
  32. package/components/Invoice/InvoiceCard.tsx +66 -0
  33. package/components/Invoice/InvoiceDetails.tsx +94 -0
  34. package/components/Invoice/InvoiceItem.graphql +25 -0
  35. package/components/Invoice/InvoiceItem.tsx +125 -0
  36. package/components/Invoice/InvoiceItems.tsx +72 -0
  37. package/components/Invoice/InvoiceTotal.graphql +29 -0
  38. package/components/Invoice/InvoiceTotals.tsx +110 -0
  39. package/components/Invoice/index.ts +9 -0
  40. package/components/NameFields/NameFields.tsx +33 -18
  41. package/components/Order/OrderAdditional/OrderAdditional.graphql +16 -0
  42. package/components/Order/OrderAdditional/OrderAdditional.tsx +51 -0
  43. package/components/Order/OrderCard/OrderCard.graphql +13 -0
  44. package/components/{OrderCard → Order/OrderCard}/OrderCard.tsx +22 -18
  45. package/components/{OrderCardItem → Order/OrderCard}/OrderCardItem.graphql +1 -0
  46. package/components/Order/OrderComments/OrderComments.graphql +5 -0
  47. package/components/Order/OrderComments/SalesCommentItem.graphql +4 -0
  48. package/components/Order/OrderComments/SalesComments.tsx +28 -0
  49. package/components/Order/OrderDetails/OrderAddress.graphql +17 -0
  50. package/components/Order/OrderDetails/OrderDetails.graphql +38 -0
  51. package/components/{OrderDetails → Order/OrderDetails}/OrderDetails.tsx +70 -57
  52. package/components/Order/OrderDetails/ShippingHandling.graphql +19 -0
  53. package/components/Order/OrderDetails/TaxItem.graphql +7 -0
  54. package/components/{OrderItem → Order/OrderItem}/OrderItem.graphql +13 -0
  55. package/components/Order/OrderItem/OrderItem.tsx +144 -0
  56. package/components/Order/OrderItems/OrderItems.tsx +81 -0
  57. package/components/{OrderStateLabel → Order/OrderStateLabel}/OrderStateLabel.tsx +2 -2
  58. package/components/Order/OrderTotals/OrderTotal.graphql +32 -0
  59. package/components/Order/OrderTotals/OrderTotals.graphql +7 -0
  60. package/components/{OrderDetails → Order/OrderTotals}/OrderTotals.tsx +27 -12
  61. package/components/Order/index.ts +16 -0
  62. package/components/ReorderItems/ReorderItems.tsx +1 -1
  63. package/components/Shipment/Shipment.graphql +14 -0
  64. package/components/Shipment/ShipmentCard.graphql +8 -0
  65. package/components/Shipment/ShipmentCard.tsx +71 -0
  66. package/components/Shipment/ShipmentDetails.tsx +100 -0
  67. package/components/Shipment/ShipmentItem.graphql +19 -0
  68. package/components/Shipment/ShipmentItem.tsx +117 -0
  69. package/components/Shipment/ShipmentItems.tsx +72 -0
  70. package/components/Shipment/index.ts +7 -0
  71. package/components/SignUpForm/SignUpForm.tsx +51 -46
  72. package/components/WaitForCustomer/WaitForCustomer.tsx +12 -3
  73. package/components/index.ts +5 -8
  74. package/graphql/{AccountDashboardQueryFragment.graphql → fragments/AccountDashboardQueryFragment.graphql} +8 -3
  75. package/graphql/index.ts +10 -0
  76. package/graphql/queries/CreditMemoDetailPage.graphql +14 -0
  77. package/graphql/queries/InvoiceDetailPage.graphql +14 -0
  78. package/graphql/queries/OrderDetailPage.graphql +14 -0
  79. package/graphql/queries/ShipmentDetailPage.graphql +12 -0
  80. package/hooks/CustomerInfo.graphql +3 -3
  81. package/index.ts +1 -4
  82. package/package.json +14 -14
  83. package/utils/orderState.ts +1 -2
  84. package/components/OrderCard/OrderCard.graphql +0 -29
  85. package/components/OrderDetails/OrderDetails.graphql +0 -77
  86. package/components/OrderItem/OrderItem.tsx +0 -176
  87. package/components/OrderItems/OrderItems.tsx +0 -70
  88. package/graphql/OrderDetailPage.graphql +0 -10
  89. /package/components/{OrderItems → Order/OrderItems}/OrderItems.graphql +0 -0
  90. /package/components/{OrderStateLabel → Order/OrderStateLabel}/OrderStateLabel.graphql +0 -0
  91. /package/components/{OrderStateLabel → Order/OrderStateLabel}/OrderStateLabelInline.tsx +0 -0
  92. /package/graphql/{CustomerStoreConfig.graphql → inject/CustomerStoreConfig.graphql} +0 -0
  93. /package/graphql/{AccountDashboard.graphql → queries/AccountDashboard.graphql} +0 -0
  94. /package/graphql/{AccountDashboardAddresses.graphql → queries/AccountDashboardAddresses.graphql} +0 -0
  95. /package/graphql/{AccountDashboardCustomer.graphql → queries/AccountDashboardCustomer.graphql} +0 -0
  96. /package/graphql/{AccountDashboardOrders.graphql → queries/AccountDashboardOrders.graphql} +0 -0
@@ -0,0 +1,122 @@
1
+ import { Money } from '@graphcommerce/magento-store'
2
+ import { breakpointVal, extendableComponent, sxx } from '@graphcommerce/next-ui'
3
+ import { Trans } from '@lingui/macro'
4
+ import type { SxProps, Theme } from '@mui/material'
5
+ import { Box, Divider, lighten, Typography } from '@mui/material'
6
+ import type { CreditMemoFragment } from './CreditMemo.gql'
7
+
8
+ export type CreditMemoTotalsProps = {
9
+ creditMemo: CreditMemoFragment
10
+ sx?: SxProps<Theme>
11
+ }
12
+
13
+ const componentName = 'CreditMemoTotals'
14
+ const parts = ['totalsContainer', 'totalsRow', 'totalsDivider', 'totalsVat'] as const
15
+ const { classes } = extendableComponent(componentName, parts)
16
+
17
+ export function CreditMemoTotals(props: CreditMemoTotalsProps) {
18
+ const { creditMemo, sx = [] } = props
19
+ const { total } = creditMemo
20
+
21
+ if (!total) return null
22
+
23
+ return (
24
+ <Box
25
+ className={classes.totalsContainer}
26
+ sx={sxx(
27
+ (theme) => ({
28
+ my: theme.spacings.md,
29
+ ...breakpointVal(
30
+ 'borderRadius',
31
+ theme.shape.borderRadius * 3,
32
+ theme.shape.borderRadius * 5,
33
+ theme.breakpoints.values,
34
+ ),
35
+ background:
36
+ theme.palette.mode === 'light'
37
+ ? theme.palette.background.default
38
+ : lighten(theme.palette.background.default, 0.15),
39
+ padding: `${theme.spacings.xs} ${theme.spacings.sm}`,
40
+ }),
41
+ sx,
42
+ )}
43
+ >
44
+ <Box className={classes.totalsRow} sx={{ display: 'flex', justifyContent: 'space-between' }}>
45
+ <Typography>
46
+ <Trans>Subtotal</Trans>
47
+ </Typography>
48
+ <Money {...total.subtotal} />
49
+ </Box>
50
+
51
+ {total.discounts?.map((discount) => (
52
+ <Box
53
+ className={classes.totalsRow}
54
+ sx={{ display: 'flex', justifyContent: 'space-between' }}
55
+ key={`discount-${discount?.label}`}
56
+ >
57
+ <Typography>{discount?.label}</Typography>
58
+ {discount?.amount && (
59
+ <Money {...discount.amount} value={(discount.amount.value ?? 0) * -1} />
60
+ )}
61
+ </Box>
62
+ ))}
63
+
64
+ {total.total_shipping && (
65
+ <Box
66
+ className={classes.totalsRow}
67
+ sx={{ display: 'flex', justifyContent: 'space-between' }}
68
+ >
69
+ <Typography>
70
+ <Trans>Shipping</Trans>
71
+ </Typography>
72
+ <Money {...total.total_shipping} />
73
+ </Box>
74
+ )}
75
+
76
+ {total.adjustment && (
77
+ <Box
78
+ className={classes.totalsRow}
79
+ sx={{ display: 'flex', justifyContent: 'space-between' }}
80
+ >
81
+ <Typography>
82
+ <Trans>Adjustment</Trans>
83
+ </Typography>
84
+ <Money {...total.adjustment} />
85
+ </Box>
86
+ )}
87
+
88
+ <Divider sx={(theme) => ({ my: theme.spacings.xxs })} />
89
+
90
+ <Box
91
+ className={classes.totalsRow}
92
+ sx={(theme) => ({
93
+ display: 'flex',
94
+ justifyContent: 'space-between',
95
+ color: theme.palette.primary.main,
96
+ })}
97
+ >
98
+ <Typography>
99
+ <Trans>Refund total</Trans>
100
+ </Typography>
101
+ <Money {...total.grand_total} />
102
+ </Box>
103
+
104
+ {total.taxes?.map((tax) => (
105
+ <Box
106
+ key={tax?.title}
107
+ className={classes.totalsVat}
108
+ sx={(theme) => ({
109
+ display: 'flex',
110
+ justifyContent: 'space-between',
111
+ color: theme.palette.text.disabled,
112
+ })}
113
+ >
114
+ <Typography>
115
+ <Trans>Including {tax?.title}</Trans>
116
+ </Typography>
117
+ <Money {...tax?.amount} />
118
+ </Box>
119
+ ))}
120
+ </Box>
121
+ )
122
+ }
@@ -0,0 +1,8 @@
1
+ export * from './CreditMemo.gql'
2
+ export * from './CreditMemoCard'
3
+ export * from './CreditMemoCard.gql'
4
+ export * from './CreditMemoDetails'
5
+ export * from './CreditMemoItem'
6
+ export * from './CreditMemoItem.gql'
7
+ export * from './CreditMemoItems'
8
+ export * from './CreditMemoTotals'
@@ -0,0 +1,112 @@
1
+ import { ActionCardListForm, SwitchElement, TextFieldElement } from '@graphcommerce/ecommerce-ui'
2
+ import type { CustomAttributeMetadata } from '@graphcommerce/magento-store'
3
+ import type { ActionCardProps } from '@graphcommerce/next-ui'
4
+ import { ActionCard, filterNonNullableKeys, sxx } from '@graphcommerce/next-ui'
5
+ import type { Control, FieldPath, FieldValues } from '@graphcommerce/react-hook-form'
6
+ import { t } from '@lingui/macro'
7
+ import { MenuItem, type SxProps, type Theme } from '@mui/material'
8
+ import {
9
+ customerAttributeInputType,
10
+ customerAttributeValidationRules,
11
+ } from './customerAttributeFieldHelpers'
12
+
13
+ function assertUnreachable(renderer: never): never {
14
+ throw new Error(`Please define a valid renderer for ${renderer}`)
15
+ }
16
+
17
+ export type CustomerAttributeFieldProps<TFieldValues extends FieldValues = FieldValues> = {
18
+ control: Control<TFieldValues>
19
+ metadata: CustomAttributeMetadata<'CustomerAttributeMetadata'>
20
+ helperText?: string
21
+ disabled?: boolean
22
+ sx?: SxProps<Theme>
23
+ }
24
+
25
+ export function CustomerAttributeField<TFieldValues extends FieldValues = FieldValues>(
26
+ props: CustomerAttributeFieldProps<TFieldValues>,
27
+ ) {
28
+ const { control, metadata, helperText, disabled, sx } = props
29
+ const { label, code, is_required, is_unique, options, frontend_input, gridArea, name } = metadata
30
+
31
+ const fieldName = name as FieldPath<TFieldValues>
32
+
33
+ const frontendInput = frontend_input ?? 'UNDEFINED'
34
+ switch (frontendInput) {
35
+ case 'TEXT':
36
+ case 'TEXTAREA':
37
+ case 'SELECT':
38
+ case 'DATE':
39
+ case 'DATETIME':
40
+ case 'MULTILINE':
41
+ return (
42
+ <TextFieldElement<TFieldValues>
43
+ key={code}
44
+ label={label}
45
+ control={control}
46
+ name={fieldName}
47
+ type={customerAttributeInputType(metadata)}
48
+ required={is_required}
49
+ rules={customerAttributeValidationRules(metadata)}
50
+ multiline={frontendInput === 'TEXTAREA'}
51
+ minRows={frontendInput === 'TEXTAREA' ? 4 : undefined}
52
+ select={frontend_input === 'SELECT'}
53
+ InputLabelProps={{
54
+ shrink: frontendInput === 'DATE' || frontendInput === 'DATETIME',
55
+ }}
56
+ disabled={disabled}
57
+ helperText={is_unique ? t`${label} must be unique` : helperText}
58
+ sx={sxx(sx, { gridArea })}
59
+ data-field={gridArea}
60
+ >
61
+ {options.map((option) => (
62
+ <MenuItem key={option?.value} value={option?.value}>
63
+ {option?.label}
64
+ </MenuItem>
65
+ ))}
66
+ </TextFieldElement>
67
+ )
68
+ case 'BOOLEAN':
69
+ return (
70
+ <SwitchElement<TFieldValues>
71
+ key={code}
72
+ label={label}
73
+ control={control}
74
+ name={fieldName}
75
+ required={is_required}
76
+ rules={customerAttributeValidationRules(metadata)}
77
+ disabled={disabled}
78
+ sx={sxx(sx, { gridArea })}
79
+ data-field={gridArea}
80
+ />
81
+ )
82
+ case 'MULTISELECT':
83
+ return (
84
+ <ActionCardListForm<ActionCardProps, TFieldValues>
85
+ key={code}
86
+ control={control}
87
+ name={fieldName}
88
+ required={is_required}
89
+ multiple
90
+ render={ActionCard}
91
+ items={filterNonNullableKeys(metadata.options).map((option) => ({
92
+ value: option.value,
93
+ title: option.label,
94
+ }))}
95
+ sx={sxx(sx, { gridArea })}
96
+ data-field={gridArea}
97
+ />
98
+ )
99
+ case 'FILE':
100
+ case 'GALLERY':
101
+ case 'HIDDEN':
102
+ case 'IMAGE':
103
+ case 'MEDIA_IMAGE':
104
+ case 'PRICE':
105
+ case 'UNDEFINED':
106
+ case 'WEIGHT':
107
+ console.log(`${code}:${frontendInput} is not implemented`)
108
+ return null
109
+ default:
110
+ return assertUnreachable(frontendInput)
111
+ }
112
+ }
@@ -0,0 +1,9 @@
1
+ fragment CustomerAttributeMetadata on CustomerAttributeMetadata
2
+ @inject(into: ["CustomAttributeMetadata"]) {
3
+ input_filter
4
+ multiline_count
5
+ validate_rules {
6
+ name
7
+ value
8
+ }
9
+ }
@@ -0,0 +1,65 @@
1
+ import { ApolloErrorSnackbar } from '@graphcommerce/ecommerce-ui'
2
+ import {
3
+ AttributesFormAutoLayout,
4
+ type AttributeFormAutoLayoutProps,
5
+ } from '@graphcommerce/magento-store'
6
+ import { Button, FormActions, type ButtonProps } from '@graphcommerce/next-ui'
7
+ import { Trans } from '@lingui/react'
8
+ import { styled } from '@mui/material'
9
+ import type { ComponentProps } from 'react'
10
+ import { CustomerAttributeField } from './CustomerAttributeField'
11
+ import { nameFieldset } from './nameFieldset'
12
+ import {
13
+ useCustomerUpdateForm,
14
+ type UpdateCustomerFormValues,
15
+ type UseCustomerUpdateFormConfig,
16
+ } from './useCustomerUpdateForm'
17
+
18
+ const Form = styled('form')({})
19
+
20
+ export type CustomerUpdateFormProps = Pick<
21
+ AttributeFormAutoLayoutProps<UpdateCustomerFormValues, 'CustomerAttributeMetadata'>,
22
+ 'fieldsets' | 'render'
23
+ > & {
24
+ slotProps?: {
25
+ form: Omit<ComponentProps<typeof Form>, 'onSubmit' | 'noValidate'>
26
+ formLayout?: Omit<
27
+ AttributeFormAutoLayoutProps<UpdateCustomerFormValues, 'CustomerAttributeMetadata'>,
28
+ 'control' | 'attributes'
29
+ >
30
+ formActions?: ComponentProps<typeof FormActions>
31
+ button?: Omit<ButtonProps, 'type' | 'loading'>
32
+ }
33
+ } & UseCustomerUpdateFormConfig
34
+
35
+ export function CustomerUpdateForm(props: CustomerUpdateFormProps) {
36
+ const { slotProps, fieldsets, render, ...config } = props
37
+
38
+ const { control, handleSubmit, formState, error, attributes } = useCustomerUpdateForm(config)
39
+ const submit = handleSubmit(() => {})
40
+
41
+ return (
42
+ <Form onSubmit={submit} noValidate {...slotProps?.form}>
43
+ <AttributesFormAutoLayout
44
+ attributes={attributes}
45
+ control={control}
46
+ render={render ?? CustomerAttributeField}
47
+ fieldsets={fieldsets ?? [nameFieldset(attributes)]}
48
+ {...slotProps?.formLayout}
49
+ />
50
+ <FormActions {...slotProps?.formActions}>
51
+ <Button
52
+ type='submit'
53
+ color='primary'
54
+ variant='pill'
55
+ size='large'
56
+ loading={formState.isSubmitting}
57
+ {...slotProps?.button}
58
+ >
59
+ <Trans id='Save changes' />
60
+ </Button>
61
+ </FormActions>
62
+ <ApolloErrorSnackbar error={error} />
63
+ </Form>
64
+ )
65
+ }
@@ -0,0 +1,7 @@
1
+ mutation UseCustomerCreateForm($input: CustomerCreateInput!) {
2
+ createCustomerV2(input: $input) {
3
+ customer {
4
+ ...CustomerInfo
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,7 @@
1
+ mutation UseCustomerUpdateForm($input: CustomerUpdateInput!) {
2
+ updateCustomerV2(input: $input) {
3
+ customer {
4
+ ...CustomerInfo
5
+ }
6
+ }
7
+ }
@@ -0,0 +1,127 @@
1
+ import type { CustomAttributeMetadata } from '@graphcommerce/magento-store'
2
+ import { filterNonNullableKeys } from '@graphcommerce/next-ui'
3
+ import type { ControllerProps, FieldPath, FieldValues } from '@graphcommerce/react-hook-form'
4
+ import { t } from '@lingui/macro'
5
+ import type { HTMLInputTypeAttribute } from 'react'
6
+
7
+ export type InputValidationValue =
8
+ | 'alphanumeric'
9
+ | 'alphanum-with-spaces'
10
+ | 'numeric'
11
+ | 'alpha'
12
+ | 'url'
13
+ | 'email'
14
+ | 'length'
15
+ | 'date'
16
+
17
+ function assertUnreachable(renderer: never): never {
18
+ throw new Error(`Please define a valid renderer for ${renderer}`)
19
+ }
20
+
21
+ export function customerAttributeValidationRules<
22
+ TFieldValues extends FieldValues,
23
+ TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
24
+ >(
25
+ metadata: CustomAttributeMetadata<'CustomerAttributeMetadata'>,
26
+ ): ControllerProps<TFieldValues, TName>['rules'] {
27
+ const { label, is_required, validate_rules } = metadata
28
+
29
+ return filterNonNullableKeys(validate_rules, ['name', 'value']).reduce<
30
+ ControllerProps<TFieldValues, TName>['rules']
31
+ >(
32
+ (current, validateRule) => {
33
+ const inputValidation = (validateRule.value ?? 'length') as InputValidationValue
34
+
35
+ switch (validateRule.name) {
36
+ case 'MAX_TEXT_LENGTH':
37
+ return {
38
+ ...current,
39
+ maxLength: {
40
+ value: parseInt(validateRule.value, 10),
41
+ message: t`The maximum length is ${validateRule.value}`,
42
+ },
43
+ }
44
+ case 'MIN_TEXT_LENGTH':
45
+ return {
46
+ ...current,
47
+ minLength: {
48
+ value: parseInt(validateRule.value, 10),
49
+ message: t`The minimum length is ${validateRule.value}`,
50
+ },
51
+ }
52
+ case 'INPUT_VALIDATION':
53
+ switch (inputValidation) {
54
+ case 'email':
55
+ case 'length':
56
+ case 'date':
57
+ // Handled by setting the input type of the field
58
+ return current
59
+ case 'alpha':
60
+ return {
61
+ ...current,
62
+ pattern: { value: /^[A-Za-z]+$/, message: t`Only alpha values allowed` },
63
+ }
64
+ case 'alphanumeric':
65
+ return {
66
+ ...current,
67
+ pattern: { value: /^[A-Za-z0-9]+$/, message: t`Only alphanumeric values allowed` },
68
+ }
69
+ case 'numeric':
70
+ return {
71
+ ...current,
72
+ pattern: { value: /^[0-9]+$/, message: t`Only numeric values allowed` },
73
+ }
74
+ case 'alphanum-with-spaces':
75
+ return {
76
+ ...current,
77
+ pattern: {
78
+ value: /^[A-Za-z0-9\s]+$/,
79
+ message: t`Only alphanumeric values with spaces allowed`,
80
+ },
81
+ }
82
+ case 'url':
83
+ return {
84
+ ...current,
85
+ pattern: {
86
+ value: /^(https?):\/\/[^\s/$.?#].[^\s]*$/,
87
+ message: t`Please enter a valid URL`,
88
+ },
89
+ }
90
+ default:
91
+ return assertUnreachable(inputValidation)
92
+ }
93
+ case 'FILE_EXTENSIONS':
94
+ case 'MAX_IMAGE_HEIGHT':
95
+ case 'MAX_IMAGE_WIDTH':
96
+ case 'MAX_FILE_SIZE':
97
+ case 'DATE_RANGE_MAX':
98
+ case 'DATE_RANGE_MIN':
99
+ return current
100
+ default:
101
+ return assertUnreachable(validateRule.name)
102
+ }
103
+ },
104
+ {
105
+ required: {
106
+ value: is_required,
107
+ message: t`${label ?? t`This field`} is required`,
108
+ },
109
+ },
110
+ )
111
+ }
112
+
113
+ export function customerAttributeInputType(
114
+ metadata: CustomAttributeMetadata<'CustomerAttributeMetadata'>,
115
+ ): React.HTMLInputTypeAttribute {
116
+ if (metadata.frontend_input === 'DATE') return 'date'
117
+ if (metadata.frontend_input === 'DATETIME') return 'datetime-local'
118
+
119
+ const inputValidation =
120
+ metadata.__typename === 'CustomerAttributeMetadata' &&
121
+ (metadata.validate_rules?.find((r) => r?.name === 'INPUT_VALIDATION')?.value as
122
+ | InputValidationValue
123
+ | undefined)
124
+ let type = 'text'
125
+ if (inputValidation === 'email') type = 'email'
126
+ return type
127
+ }
@@ -0,0 +1,8 @@
1
+ export * from './CustomerAttributeField'
2
+ export * from './CustomerAttributeMetadata.gql'
3
+ export * from './CustomerUpdateForm'
4
+ export * from './nameFieldset'
5
+ export * from './useCustomerCreateForm'
6
+ export * from './UseCustomerCreateForm.gql'
7
+ export * from './useCustomerUpdateForm'
8
+ export * from './UseCustomerUpdateForm.gql'
@@ -0,0 +1,32 @@
1
+ import {
2
+ extractAttributes,
3
+ type AttributeFormAutoLayoutFieldset,
4
+ type CustomAttributeMetadata,
5
+ } from '@graphcommerce/magento-store'
6
+ import { Trans } from '@lingui/macro'
7
+
8
+ export function nameFieldset(
9
+ attributes: CustomAttributeMetadata[],
10
+ withLabel = true,
11
+ ): AttributeFormAutoLayoutFieldset {
12
+ const nameFields = extractAttributes(attributes, [
13
+ 'prefix',
14
+ 'firstname',
15
+ 'middlename',
16
+ 'lastname',
17
+ 'suffix',
18
+ ])[0].map((f) => f.code)
19
+
20
+ const additional = extractAttributes(attributes, ['dob', 'gender'])[0].map((f) => f.code)
21
+
22
+ return {
23
+ label: withLabel ? <Trans id='Name'>Name</Trans> : undefined,
24
+ gridAreas: [...nameFields, ...additional],
25
+ // xs is shown in one column by default
26
+ sx: {
27
+ gridTemplateAreas: {
28
+ md: [`"${nameFields.join(' ')}"`, `"${additional.join(' ')}"`].join(' '),
29
+ },
30
+ },
31
+ }
32
+ }
@@ -0,0 +1,83 @@
1
+ import {
2
+ CustomAttributesField_to_AttributeValueInputs,
3
+ useAttributesForm,
4
+ type CustomAttributeMetadata,
5
+ type CustomAttributesFormValues,
6
+ type UseAttributesFormConfig,
7
+ } from '@graphcommerce/magento-store'
8
+ import { useFormGqlMutation, type UseFormGraphQlOptions } from '@graphcommerce/react-hook-form'
9
+ import type { MutationHookOptions } from '@apollo/client'
10
+ import {
11
+ UseCustomerCreateFormDocument,
12
+ type UseCustomerCreateFormMutation,
13
+ type UseCustomerCreateFormMutationVariables,
14
+ } from './UseCustomerCreateForm.gql'
15
+
16
+ export type CreateCustomerFormValues = UseCustomerCreateFormMutationVariables &
17
+ CustomAttributesFormValues & { confirmPassword?: string }
18
+
19
+ /** Used for onBeforeSubmit of useFormGqlMutation */
20
+ export function CreateCustomerFormValues_to_CreateCustomerVariables(
21
+ attributes: CustomAttributeMetadata<'CustomerAttributeMetadata'>[],
22
+ values: CreateCustomerFormValues,
23
+ ): UseCustomerCreateFormMutationVariables {
24
+ const { ...custom_attributes } = values.custom_attributes ?? {}
25
+
26
+ return {
27
+ input: {
28
+ ...values.input,
29
+ gender: values.input.gender ? Number(values.input.gender) : undefined,
30
+ custom_attributes: [
31
+ ...(values.input.custom_attributes ?? []),
32
+ ...CustomAttributesField_to_AttributeValueInputs(attributes, custom_attributes),
33
+ ],
34
+ },
35
+ }
36
+ }
37
+
38
+ export type UseCustomerCreateFormConfig = Omit<
39
+ UseAttributesFormConfig<'CustomerAttributeMetadata'>,
40
+ 'formCode' | 'typename'
41
+ >
42
+
43
+ export function useCustomerCreateForm(
44
+ attributeFormConfig?: UseCustomerCreateFormConfig,
45
+ useFormGqlOptions?: UseFormGraphQlOptions<
46
+ UseCustomerCreateFormMutation,
47
+ CreateCustomerFormValues
48
+ >,
49
+ mutationOptions?: MutationHookOptions<UseCustomerCreateFormMutation, CreateCustomerFormValues>,
50
+ ) {
51
+ const attributes = useAttributesForm({
52
+ formCode: 'customer_account_create',
53
+ typename: 'CustomerAttributeMetadata',
54
+ attributeToName: {
55
+ email: 'input.email',
56
+ dob: 'input.date_of_birth',
57
+ firstname: 'input.firstname',
58
+ lastname: 'input.lastname',
59
+ prefix: 'input.prefix',
60
+ middlename: 'input.middlename',
61
+ suffix: 'input.suffix',
62
+ gender: 'input.gender',
63
+ password: 'input.password',
64
+ },
65
+ ...attributeFormConfig,
66
+ })
67
+
68
+ // console.log(customer, CustomerInfo_to_UpdateCustomerFormValues(attributes, customer))
69
+ const form = useFormGqlMutation<UseCustomerCreateFormMutation, CreateCustomerFormValues>(
70
+ UseCustomerCreateFormDocument,
71
+ {
72
+ ...useFormGqlOptions,
73
+ onBeforeSubmit: async (values, f) => {
74
+ const result = (await useFormGqlOptions?.onBeforeSubmit?.(values, f)) ?? values
75
+ if (result === false) return false
76
+ return CreateCustomerFormValues_to_CreateCustomerVariables(attributes, result)
77
+ },
78
+ },
79
+ mutationOptions,
80
+ )
81
+
82
+ return { ...form, attributes }
83
+ }