@graphcommerce/magento-customer 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.
- package/CHANGELOG.md +14 -0
- package/components/AccountAddresses/AccountAddresses.tsx +9 -4
- package/components/AccountDeleteForm/AccountDeleteForm.tsx +14 -4
- package/components/AccountLatestOrder/AccountLatestOrder.tsx +1 -2
- package/components/AccountOrders/AccountOrders.tsx +1 -1
- package/components/CancelOrder/CancelOrderForm.tsx +2 -2
- package/components/CancelOrder/CancelOrderFragment.graphql +5 -0
- package/components/ChangeNameForm/ChangeNameForm.tsx +1 -0
- package/components/ContactForm/ContactUsConfig.graphql +3 -0
- package/components/CreditMemo/CreditMemo.graphql +43 -0
- package/components/CreditMemo/CreditMemoCard.graphql +12 -0
- package/components/CreditMemo/CreditMemoCard.tsx +66 -0
- package/components/CreditMemo/CreditMemoDetails.tsx +94 -0
- package/components/CreditMemo/CreditMemoItem.graphql +26 -0
- package/components/CreditMemo/CreditMemoItem.tsx +125 -0
- package/components/CreditMemo/CreditMemoItems.tsx +78 -0
- package/components/CreditMemo/CreditMemoTotals.tsx +122 -0
- package/components/CreditMemo/index.ts +8 -0
- package/components/CustomerForms/CustomerAttributeField.tsx +112 -0
- package/components/CustomerForms/CustomerAttributeMetadata.graphql +9 -0
- package/components/CustomerForms/CustomerUpdateForm.tsx +65 -0
- package/components/CustomerForms/UseCustomerCreateForm.graphql +7 -0
- package/components/CustomerForms/UseCustomerUpdateForm.graphql +7 -0
- package/components/CustomerForms/customerAttributeFieldHelpers.ts +127 -0
- package/components/CustomerForms/index.ts +8 -0
- package/components/CustomerForms/nameFieldset.tsx +32 -0
- package/components/CustomerForms/useCustomerCreateForm.ts +83 -0
- package/components/CustomerForms/useCustomerUpdateForm.ts +122 -0
- package/components/GuestOrderOverview/GuestOrderOverviewForm.tsx +28 -11
- package/components/Invoice/Invoice.graphql +13 -0
- package/components/Invoice/InvoiceCard.graphql +9 -0
- package/components/Invoice/InvoiceCard.tsx +66 -0
- package/components/Invoice/InvoiceDetails.tsx +94 -0
- package/components/Invoice/InvoiceItem.graphql +25 -0
- package/components/Invoice/InvoiceItem.tsx +125 -0
- package/components/Invoice/InvoiceItems.tsx +72 -0
- package/components/Invoice/InvoiceTotal.graphql +29 -0
- package/components/Invoice/InvoiceTotals.tsx +110 -0
- package/components/Invoice/index.ts +9 -0
- package/components/NameFields/NameFields.tsx +33 -18
- package/components/Order/OrderAdditional/OrderAdditional.graphql +16 -0
- package/components/Order/OrderAdditional/OrderAdditional.tsx +51 -0
- package/components/Order/OrderCard/OrderCard.graphql +13 -0
- package/components/{OrderCard → Order/OrderCard}/OrderCard.tsx +22 -18
- package/components/{OrderCardItem → Order/OrderCard}/OrderCardItem.graphql +1 -0
- package/components/Order/OrderComments/OrderComments.graphql +5 -0
- package/components/Order/OrderComments/SalesCommentItem.graphql +4 -0
- package/components/Order/OrderComments/SalesComments.tsx +28 -0
- package/components/Order/OrderDetails/OrderAddress.graphql +17 -0
- package/components/Order/OrderDetails/OrderDetails.graphql +38 -0
- package/components/{OrderDetails → Order/OrderDetails}/OrderDetails.tsx +70 -57
- package/components/Order/OrderDetails/ShippingHandling.graphql +19 -0
- package/components/Order/OrderDetails/TaxItem.graphql +7 -0
- package/components/{OrderItem → Order/OrderItem}/OrderItem.graphql +13 -0
- package/components/Order/OrderItem/OrderItem.tsx +144 -0
- package/components/Order/OrderItems/OrderItems.tsx +81 -0
- package/components/{OrderStateLabel → Order/OrderStateLabel}/OrderStateLabel.tsx +2 -2
- package/components/Order/OrderTotals/OrderTotal.graphql +32 -0
- package/components/Order/OrderTotals/OrderTotals.graphql +7 -0
- package/components/{OrderDetails → Order/OrderTotals}/OrderTotals.tsx +27 -12
- package/components/Order/index.ts +16 -0
- package/components/ReorderItems/ReorderItems.tsx +1 -1
- package/components/Shipment/Shipment.graphql +14 -0
- package/components/Shipment/ShipmentCard.graphql +8 -0
- package/components/Shipment/ShipmentCard.tsx +71 -0
- package/components/Shipment/ShipmentDetails.tsx +100 -0
- package/components/Shipment/ShipmentItem.graphql +19 -0
- package/components/Shipment/ShipmentItem.tsx +117 -0
- package/components/Shipment/ShipmentItems.tsx +72 -0
- package/components/Shipment/index.ts +7 -0
- package/components/SignUpForm/SignUpForm.tsx +51 -46
- package/components/WaitForCustomer/WaitForCustomer.tsx +12 -3
- package/components/index.ts +5 -8
- package/graphql/{AccountDashboardQueryFragment.graphql → fragments/AccountDashboardQueryFragment.graphql} +8 -3
- package/graphql/index.ts +10 -0
- package/graphql/queries/CreditMemoDetailPage.graphql +14 -0
- package/graphql/queries/InvoiceDetailPage.graphql +14 -0
- package/graphql/queries/OrderDetailPage.graphql +14 -0
- package/graphql/queries/ShipmentDetailPage.graphql +12 -0
- package/hooks/CustomerInfo.graphql +3 -3
- package/index.ts +1 -4
- package/package.json +14 -14
- package/utils/orderState.ts +1 -2
- package/components/OrderCard/OrderCard.graphql +0 -29
- package/components/OrderDetails/OrderDetails.graphql +0 -77
- package/components/OrderItem/OrderItem.tsx +0 -176
- package/components/OrderItems/OrderItems.tsx +0 -70
- package/graphql/OrderDetailPage.graphql +0 -10
- /package/components/{OrderItems → Order/OrderItems}/OrderItems.graphql +0 -0
- /package/components/{OrderStateLabel → Order/OrderStateLabel}/OrderStateLabel.graphql +0 -0
- /package/components/{OrderStateLabel → Order/OrderStateLabel}/OrderStateLabelInline.tsx +0 -0
- /package/graphql/{CustomerStoreConfig.graphql → inject/CustomerStoreConfig.graphql} +0 -0
- /package/graphql/{AccountDashboard.graphql → queries/AccountDashboard.graphql} +0 -0
- /package/graphql/{AccountDashboardAddresses.graphql → queries/AccountDashboardAddresses.graphql} +0 -0
- /package/graphql/{AccountDashboardCustomer.graphql → queries/AccountDashboardCustomer.graphql} +0 -0
- /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,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,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
|
+
}
|