@graphcommerce/magento-customer 7.0.0-canary.13 → 7.0.0
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 +236 -2
- package/components/AccountSignInUpForm/AccountSignInUpForm.tsx +4 -1
- package/components/AddressFields/AddressFields.tsx +1 -1
- package/components/ApolloCustomerError/ApolloCustomerErrorAlert.tsx +0 -1
- package/components/ChangePasswordForm/ChangePasswordForm.tsx +44 -30
- package/components/CreateCustomerAddressForm/CreateCustomerAddress.graphql +2 -2
- package/components/CreateCustomerAddressForm/CreateCustomerAddressForm.tsx +2 -1
- package/components/CreateCustomerAddressForm/CustomerAddress.graphql +1 -0
- package/components/CustomerFab/CustomerFab.tsx +3 -0
- package/components/CustomerMenuFabItem/CustomerMenuFabItem.tsx +4 -2
- package/components/EditAddressForm/EditAddressForm.tsx +2 -0
- package/components/ForgotPasswordForm/ForgotPasswordForm.tsx +1 -0
- package/components/NameFields/NameFields.tsx +2 -1
- package/components/OrderCard/OrderCard.tsx +1 -1
- package/components/OrderDetails/OrderDetails.tsx +6 -18
- package/components/ResetPasswordForm/ResetPasswordForm.tsx +5 -9
- package/components/SessionDebugger/SessionDebugger.tsx +2 -1
- package/components/SignInForm/SignInForm.tsx +9 -10
- package/components/SignInForm/SignInFormInline.tsx +17 -17
- package/components/SignUpForm/SignUpForm.tsx +14 -27
- package/components/SignUpForm/SignUpFormInline.tsx +39 -52
- package/components/TrackingLink/TrackingLink.tsx +14 -5
- package/components/UpdateCustomerEmailForm/UpdateCustomerEmailForm.tsx +28 -21
- package/components/UpdateDefaultAddressForm/UpdateDefaultAddressForm.tsx +1 -0
- package/components/ValidatedPasswordElement/ValidatedPasswordElement.tsx +47 -0
- package/hooks/CustomerInfo.graphql +1 -0
- package/hooks/UseCustomerValidateToken.graphql +5 -0
- package/hooks/useCustomerValidateToken.ts +23 -0
- package/link/createCustomerTokenLink.ts +2 -7
- package/package.json +16 -15
- package/test/authentication.playwright.ts +1 -0
- package/link/onAuthenticationError.ts +0 -39
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { PasswordElement } from '@graphcommerce/ecommerce-ui'
|
|
1
2
|
import { useApolloClient } from '@graphcommerce/graphql'
|
|
2
3
|
import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
|
|
3
4
|
import { Button, FormRow, FormActions } from '@graphcommerce/next-ui'
|
|
4
5
|
import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
|
|
5
6
|
import { Trans } from '@lingui/react'
|
|
6
|
-
import { Box, FormControl, Link, SxProps,
|
|
7
|
+
import { Box, FormControl, Link, SxProps, Theme } from '@mui/material'
|
|
7
8
|
import { CustomerDocument } from '../../hooks'
|
|
8
9
|
import { ApolloCustomerErrorAlert } from '../ApolloCustomerError/ApolloCustomerErrorAlert'
|
|
9
10
|
import { SignInDocument } from './SignIn.gql'
|
|
@@ -12,7 +13,6 @@ type SignInFormProps = { email: string; sx?: SxProps<Theme> }
|
|
|
12
13
|
|
|
13
14
|
export function SignInForm(props: SignInFormProps) {
|
|
14
15
|
const { email, sx } = props
|
|
15
|
-
|
|
16
16
|
const client = useApolloClient()
|
|
17
17
|
const form = useFormGqlMutation(
|
|
18
18
|
SignInDocument,
|
|
@@ -32,7 +32,7 @@ export function SignInForm(props: SignInFormProps) {
|
|
|
32
32
|
{ errorPolicy: 'all' },
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
-
const {
|
|
35
|
+
const { handleSubmit, required, formState, error, control } = form
|
|
36
36
|
const [remainingError, authError] = graphqlErrorByCategory({
|
|
37
37
|
category: 'graphql-authentication',
|
|
38
38
|
error,
|
|
@@ -41,18 +41,19 @@ export function SignInForm(props: SignInFormProps) {
|
|
|
41
41
|
|
|
42
42
|
return (
|
|
43
43
|
<Box component='form' onSubmit={submitHandler} noValidate sx={sx}>
|
|
44
|
-
<FormRow>
|
|
45
|
-
<
|
|
46
|
-
key='password'
|
|
44
|
+
<FormRow sx={{ gridTemplateColumns: 'none' }}>
|
|
45
|
+
<PasswordElement
|
|
47
46
|
variant='outlined'
|
|
48
|
-
type='password'
|
|
49
47
|
error={!!formState.errors.password || !!authError}
|
|
48
|
+
control={control}
|
|
49
|
+
name='password'
|
|
50
50
|
label={<Trans id='Password' />}
|
|
51
51
|
autoFocus
|
|
52
52
|
autoComplete='current-password'
|
|
53
53
|
id='current-password'
|
|
54
54
|
required={required.password}
|
|
55
|
-
{
|
|
55
|
+
disabled={formState.isSubmitting}
|
|
56
|
+
helperText={!!formState.errors.password || authError?.message}
|
|
56
57
|
InputProps={{
|
|
57
58
|
endAdornment: (
|
|
58
59
|
<Link href='/account/forgot-password' underline='hover' sx={{ whiteSpace: 'nowrap' }}>
|
|
@@ -60,8 +61,6 @@ export function SignInForm(props: SignInFormProps) {
|
|
|
60
61
|
</Link>
|
|
61
62
|
),
|
|
62
63
|
}}
|
|
63
|
-
helperText={formState.errors.password?.message || authError?.message}
|
|
64
|
-
disabled={formState.isSubmitting}
|
|
65
64
|
/>
|
|
66
65
|
</FormRow>
|
|
67
66
|
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { PasswordElement } from '@graphcommerce/ecommerce-ui'
|
|
1
2
|
import { Button, extendableComponent } from '@graphcommerce/next-ui'
|
|
2
3
|
import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
|
|
3
4
|
import { Trans } from '@lingui/react'
|
|
4
|
-
import { Box,
|
|
5
|
+
import { Box, Link, SxProps, Theme } from '@mui/material'
|
|
5
6
|
import { SignInDocument, SignInMutationVariables } from './SignIn.gql'
|
|
6
7
|
|
|
7
8
|
type InlineSignInFormProps = Omit<SignInMutationVariables, 'password'> & {
|
|
@@ -12,13 +13,13 @@ type InlineSignInFormProps = Omit<SignInMutationVariables, 'password'> & {
|
|
|
12
13
|
const { classes } = extendableComponent('SignInFormInline', ['form', 'button'] as const)
|
|
13
14
|
|
|
14
15
|
export function SignInFormInline(props: InlineSignInFormProps) {
|
|
15
|
-
const { email, sx = [] } = props
|
|
16
|
+
const { email, children, sx = [] } = props
|
|
16
17
|
const form = useFormGqlMutation(
|
|
17
18
|
SignInDocument,
|
|
18
19
|
{ defaultValues: { email }, onBeforeSubmit: (values) => ({ ...values, email }) },
|
|
19
20
|
{ errorPolicy: 'all' },
|
|
20
21
|
)
|
|
21
|
-
const {
|
|
22
|
+
const { handleSubmit, required, formState, control } = form
|
|
22
23
|
const submitHandler = handleSubmit(() => {})
|
|
23
24
|
|
|
24
25
|
return (
|
|
@@ -41,35 +42,34 @@ export function SignInFormInline(props: InlineSignInFormProps) {
|
|
|
41
42
|
...(Array.isArray(sx) ? sx : [sx]),
|
|
42
43
|
]}
|
|
43
44
|
>
|
|
44
|
-
<
|
|
45
|
+
<PasswordElement
|
|
46
|
+
control={control}
|
|
45
47
|
variant='outlined'
|
|
46
|
-
|
|
47
|
-
error={!!formState.errors.password || !!error?.message}
|
|
48
|
+
name='password'
|
|
48
49
|
label={<Trans id='Password' />}
|
|
49
50
|
autoFocus
|
|
50
51
|
autoComplete='current-password'
|
|
51
52
|
id='current-password'
|
|
52
53
|
required={required.password}
|
|
53
|
-
{...muiRegister('password', { required: required.password })}
|
|
54
|
-
helperText={error?.message}
|
|
55
54
|
disabled={formState.isSubmitting}
|
|
56
55
|
InputProps={{
|
|
57
56
|
endAdornment: (
|
|
58
|
-
<
|
|
59
|
-
href='/account/forgot-password'
|
|
60
|
-
color='secondary'
|
|
61
|
-
variant='text'
|
|
62
|
-
className={classes.button}
|
|
63
|
-
sx={{ minWidth: 'max-content' }}
|
|
64
|
-
>
|
|
57
|
+
<Link href='/account/forgot-password' underline='hover' sx={{ whiteSpace: 'nowrap' }}>
|
|
65
58
|
<Trans id='Forgot password?' />
|
|
66
|
-
</
|
|
59
|
+
</Link>
|
|
67
60
|
),
|
|
68
61
|
}}
|
|
69
62
|
/>
|
|
70
|
-
<Button
|
|
63
|
+
<Button
|
|
64
|
+
type='submit'
|
|
65
|
+
loading={formState.isSubmitting}
|
|
66
|
+
color='secondary'
|
|
67
|
+
variant='pill'
|
|
68
|
+
sx={{ alignSelf: 'start', marginTop: (theme) => `calc(${theme.spacings.xxs} * .33)` }}
|
|
69
|
+
>
|
|
71
70
|
<Trans id='Sign in' />
|
|
72
71
|
</Button>
|
|
72
|
+
{children}
|
|
73
73
|
</Box>
|
|
74
74
|
)
|
|
75
75
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PasswordRepeatElement } from '@graphcommerce/ecommerce-ui'
|
|
2
2
|
import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
|
|
3
|
-
import { StoreConfigDocument } from '@graphcommerce/magento-store'
|
|
4
3
|
import { Button, FormActions, FormRow } from '@graphcommerce/next-ui'
|
|
5
4
|
import { useFormGqlMutation, useFormPersist } from '@graphcommerce/react-hook-form'
|
|
6
|
-
import { i18n } from '@lingui/core'
|
|
7
5
|
import { Trans } from '@lingui/react'
|
|
8
|
-
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
7
|
+
import { Alert, FormControlLabel, Switch } from '@mui/material'
|
|
9
8
|
import { ApolloCustomerErrorSnackbar } from '../ApolloCustomerError/ApolloCustomerErrorSnackbar'
|
|
10
9
|
import { NameFields } from '../NameFields/NameFields'
|
|
10
|
+
import { ValidatedPasswordElement } from '../ValidatedPasswordElement/ValidatedPasswordElement'
|
|
11
11
|
import { SignUpDocument, SignUpMutation, SignUpMutationVariables } from './SignUp.gql'
|
|
12
12
|
import { SignUpConfirmDocument } from './SignUpConfirm.gql'
|
|
13
13
|
|
|
@@ -18,8 +18,6 @@ const requireEmailValidation = import.meta.graphCommerce.customerRequireEmailCon
|
|
|
18
18
|
export function SignUpForm(props: SignUpFormProps) {
|
|
19
19
|
const { email } = props
|
|
20
20
|
|
|
21
|
-
const storeConfig = useQuery(StoreConfigDocument).data?.storeConfig
|
|
22
|
-
|
|
23
21
|
const Mutation = requireEmailValidation ? SignUpConfirmDocument : SignUpDocument
|
|
24
22
|
|
|
25
23
|
const form = useFormGqlMutation<
|
|
@@ -31,11 +29,10 @@ export function SignUpForm(props: SignUpFormProps) {
|
|
|
31
29
|
{ errorPolicy: 'all' },
|
|
32
30
|
)
|
|
33
31
|
|
|
34
|
-
const { muiRegister, handleSubmit, required,
|
|
32
|
+
const { muiRegister, handleSubmit, required, formState, error, control } = form
|
|
35
33
|
const [remainingError, inputError] = graphqlErrorByCategory({ category: 'graphql-input', error })
|
|
36
34
|
|
|
37
35
|
const submitHandler = handleSubmit(() => {})
|
|
38
|
-
const watchPassword = watch('password')
|
|
39
36
|
|
|
40
37
|
useFormPersist({ form, name: 'SignUp', exclude: ['password', 'confirmPassword'] })
|
|
41
38
|
|
|
@@ -50,37 +47,27 @@ export function SignUpForm(props: SignUpFormProps) {
|
|
|
50
47
|
return (
|
|
51
48
|
<form onSubmit={submitHandler} noValidate>
|
|
52
49
|
<FormRow>
|
|
53
|
-
<
|
|
50
|
+
<ValidatedPasswordElement
|
|
51
|
+
control={control}
|
|
52
|
+
name='password'
|
|
54
53
|
variant='outlined'
|
|
55
|
-
type='password'
|
|
56
54
|
error={!!formState.errors.password || !!inputError}
|
|
57
55
|
label={<Trans id='Password' />}
|
|
58
56
|
autoFocus
|
|
59
57
|
autoComplete='new-password'
|
|
60
58
|
required={required.password}
|
|
61
|
-
{...muiRegister('password', {
|
|
62
|
-
required: required.password,
|
|
63
|
-
minLength: {
|
|
64
|
-
value: Number(storeConfig?.minimum_password_length ?? 8),
|
|
65
|
-
message: i18n._(/* i18n */ 'Password must have at least 8 characters'),
|
|
66
|
-
},
|
|
67
|
-
})}
|
|
68
|
-
helperText={formState.errors.password?.message || inputError?.message}
|
|
69
59
|
disabled={formState.isSubmitting}
|
|
60
|
+
helperText={inputError?.message}
|
|
70
61
|
/>
|
|
71
|
-
<
|
|
62
|
+
<PasswordRepeatElement
|
|
63
|
+
control={control}
|
|
64
|
+
name='confirmPassword'
|
|
65
|
+
passwordFieldName='password'
|
|
72
66
|
variant='outlined'
|
|
73
|
-
|
|
74
|
-
error={!!formState.errors.confirmPassword}
|
|
67
|
+
error={!!formState.errors.confirmPassword || !!inputError}
|
|
75
68
|
label={<Trans id='Confirm password' />}
|
|
76
69
|
autoComplete='new-password'
|
|
77
70
|
required
|
|
78
|
-
{...muiRegister('confirmPassword', {
|
|
79
|
-
required: true,
|
|
80
|
-
validate: (value) =>
|
|
81
|
-
value === watchPassword || i18n._(/* i18n */ "Passwords don't match"),
|
|
82
|
-
})}
|
|
83
|
-
helperText={formState.errors.confirmPassword?.message}
|
|
84
71
|
disabled={formState.isSubmitting}
|
|
85
72
|
/>
|
|
86
73
|
</FormRow>
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ApolloErrorAlert, PasswordRepeatElement } from '@graphcommerce/ecommerce-ui'
|
|
2
|
+
import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
|
|
3
3
|
import { Button, extendableComponent, Form, FormRow } from '@graphcommerce/next-ui'
|
|
4
4
|
import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
|
|
5
|
-
import { i18n } from '@lingui/core'
|
|
6
5
|
import { Trans } from '@lingui/react'
|
|
7
|
-
import { Alert, Box
|
|
6
|
+
import { Alert, Box } from '@mui/material'
|
|
8
7
|
import React from 'react'
|
|
8
|
+
import { ValidatedPasswordElement } from '../ValidatedPasswordElement/ValidatedPasswordElement'
|
|
9
9
|
import { SignUpMutationVariables, SignUpMutation, SignUpDocument } from './SignUp.gql'
|
|
10
10
|
import { SignUpConfirmDocument } from './SignUpConfirm.gql'
|
|
11
11
|
|
|
@@ -25,37 +25,35 @@ const { classes } = extendableComponent('SignUpFormInline', [
|
|
|
25
25
|
|
|
26
26
|
const requireEmailValidation = import.meta.graphCommerce.customerRequireEmailConfirmation ?? false
|
|
27
27
|
|
|
28
|
-
export function SignUpFormInline({
|
|
29
|
-
email,
|
|
30
|
-
children,
|
|
31
|
-
firstname,
|
|
32
|
-
lastname,
|
|
33
|
-
onSubmitted = () => {},
|
|
34
|
-
}: SignUpFormInlineProps) {
|
|
28
|
+
export function SignUpFormInline(props: SignUpFormInlineProps) {
|
|
29
|
+
const { email, children, firstname, lastname, onSubmitted = () => {} } = props
|
|
35
30
|
const Mutation = requireEmailValidation ? SignUpConfirmDocument : SignUpDocument
|
|
36
31
|
|
|
37
32
|
const form = useFormGqlMutation<
|
|
38
33
|
SignUpMutation,
|
|
39
34
|
SignUpMutationVariables & { confirmPassword?: string }
|
|
40
|
-
>(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
35
|
+
>(
|
|
36
|
+
Mutation,
|
|
37
|
+
{
|
|
38
|
+
// todo(paales): This causes dirty data to be send to the backend.
|
|
39
|
+
defaultValues: {
|
|
40
|
+
email,
|
|
41
|
+
prefix: '-',
|
|
42
|
+
firstname: firstname ?? '-',
|
|
43
|
+
lastname: lastname ?? '-',
|
|
44
|
+
},
|
|
45
|
+
onBeforeSubmit: (values) => ({ ...values, email }),
|
|
46
|
+
onComplete: (result) => {
|
|
47
|
+
if (!result.errors) onSubmitted()
|
|
48
|
+
},
|
|
47
49
|
},
|
|
48
|
-
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
const { muiRegister, watch, handleSubmit, required, formState, error } = form
|
|
52
|
-
const submitHandler = handleSubmit(onSubmitted)
|
|
53
|
-
const watchPassword = watch('password')
|
|
54
|
-
|
|
55
|
-
const minPasswordLength = Number(
|
|
56
|
-
useQuery(StoreConfigDocument).data?.storeConfig?.minimum_password_length ?? 8,
|
|
50
|
+
{ errorPolicy: 'all' },
|
|
57
51
|
)
|
|
58
52
|
|
|
53
|
+
const { handleSubmit, formState, control, error, required } = form
|
|
54
|
+
const [remainingError, inputError] = graphqlErrorByCategory({ category: 'graphql-input', error })
|
|
55
|
+
const submitHandler = handleSubmit(() => {})
|
|
56
|
+
|
|
59
57
|
if (requireEmailValidation && form.formState.isSubmitSuccessful) {
|
|
60
58
|
return (
|
|
61
59
|
<Alert>
|
|
@@ -66,43 +64,31 @@ export function SignUpFormInline({
|
|
|
66
64
|
|
|
67
65
|
return (
|
|
68
66
|
<Form onSubmit={submitHandler} noValidate className={classes.form} sx={{ padding: 0 }}>
|
|
69
|
-
<FormRow
|
|
70
|
-
<
|
|
67
|
+
<FormRow className={classes.row} sx={{ padding: 0 }}>
|
|
68
|
+
<ValidatedPasswordElement
|
|
69
|
+
control={control}
|
|
70
|
+
name='password'
|
|
71
|
+
autoComplete='new-password'
|
|
71
72
|
variant='outlined'
|
|
72
|
-
type='password'
|
|
73
|
-
error={!!formState.errors.password || !!error?.message}
|
|
74
73
|
label={<Trans id='Password' />}
|
|
75
|
-
autoFocus
|
|
76
|
-
autoComplete='new-password'
|
|
77
|
-
id='new-password'
|
|
78
74
|
required={required.password}
|
|
79
|
-
{...muiRegister('password', {
|
|
80
|
-
required: required.password,
|
|
81
|
-
minLength: {
|
|
82
|
-
value: minPasswordLength,
|
|
83
|
-
message: i18n._(/* i18n */ 'Password must have at least 8 characters'),
|
|
84
|
-
},
|
|
85
|
-
})}
|
|
86
|
-
helperText={error?.message}
|
|
87
75
|
disabled={formState.isSubmitting}
|
|
76
|
+
error={!!inputError}
|
|
77
|
+
helperText={inputError?.message}
|
|
88
78
|
/>
|
|
89
|
-
<
|
|
79
|
+
<PasswordRepeatElement
|
|
80
|
+
control={control}
|
|
81
|
+
name='confirmPassword'
|
|
82
|
+
passwordFieldName='password'
|
|
83
|
+
autoComplete='new-password'
|
|
90
84
|
variant='outlined'
|
|
91
|
-
type='password'
|
|
92
|
-
error={!!formState.errors.confirmPassword || !!error?.message}
|
|
93
85
|
label={<Trans id='Confirm password' />}
|
|
94
|
-
autoComplete='new-password'
|
|
95
86
|
required
|
|
96
|
-
{...muiRegister('confirmPassword', {
|
|
97
|
-
required: true,
|
|
98
|
-
validate: (value) => value === watchPassword,
|
|
99
|
-
})}
|
|
100
|
-
helperText={!!formState.errors.confirmPassword && <Trans id='Passwords should match' />}
|
|
101
87
|
disabled={formState.isSubmitting}
|
|
102
88
|
/>
|
|
103
89
|
</FormRow>
|
|
104
90
|
|
|
105
|
-
<FormRow
|
|
91
|
+
<FormRow>
|
|
106
92
|
<FormRow
|
|
107
93
|
className={classes.buttonFormRow}
|
|
108
94
|
sx={(theme) => ({
|
|
@@ -126,6 +112,7 @@ export function SignUpFormInline({
|
|
|
126
112
|
</Box>
|
|
127
113
|
</FormRow>
|
|
128
114
|
</FormRow>
|
|
115
|
+
<ApolloErrorAlert error={remainingError} />
|
|
129
116
|
</Form>
|
|
130
117
|
)
|
|
131
118
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { IconSvg, iconLocation } from '@graphcommerce/next-ui'
|
|
2
2
|
import { Trans } from '@lingui/react'
|
|
3
|
-
import { Box, SxProps, Theme } from '@mui/material'
|
|
4
|
-
import React from 'react'
|
|
3
|
+
import { Box, Link, SxProps, Theme, Typography } from '@mui/material'
|
|
5
4
|
import { TrackingLinkFragment } from './TrackingLink.gql'
|
|
6
5
|
|
|
7
6
|
export type TrackingLinkProps = TrackingLinkFragment & { sx?: SxProps<Theme> }
|
|
@@ -9,6 +8,8 @@ export type TrackingLinkProps = TrackingLinkFragment & { sx?: SxProps<Theme> }
|
|
|
9
8
|
export function TrackingLink(props: TrackingLinkProps) {
|
|
10
9
|
const { number, sx = [] } = props
|
|
11
10
|
|
|
11
|
+
const validUrl = number?.startsWith('http://') || number?.startsWith('https://')
|
|
12
|
+
|
|
12
13
|
return (
|
|
13
14
|
<Box
|
|
14
15
|
className='TrackingLink-root'
|
|
@@ -21,12 +22,20 @@ export function TrackingLink(props: TrackingLinkProps) {
|
|
|
21
22
|
...(Array.isArray(sx) ? sx : [sx]),
|
|
22
23
|
]}
|
|
23
24
|
>
|
|
24
|
-
{number && (
|
|
25
|
-
|
|
25
|
+
{number && validUrl && (
|
|
26
|
+
<Link
|
|
27
|
+
onClick={(e) => e.stopPropagation()}
|
|
28
|
+
href={number}
|
|
29
|
+
target='_blank'
|
|
30
|
+
underline='hover'
|
|
31
|
+
sx={{ display: 'inline-flex', alignItems: 'center' }}
|
|
32
|
+
>
|
|
26
33
|
<IconSvg src={iconLocation} size='small' />
|
|
27
34
|
<Trans id='Follow order' />
|
|
28
|
-
|
|
35
|
+
</Link>
|
|
29
36
|
)}
|
|
37
|
+
|
|
38
|
+
{number && !validUrl && <Typography>{number}</Typography>}
|
|
30
39
|
</Box>
|
|
31
40
|
)
|
|
32
41
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { PasswordElement } from '@graphcommerce/ecommerce-ui'
|
|
2
|
+
import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
|
|
1
3
|
import {
|
|
2
4
|
Button,
|
|
3
5
|
Form,
|
|
@@ -9,8 +11,9 @@ import {
|
|
|
9
11
|
import { emailPattern, useFormGqlMutation } from '@graphcommerce/react-hook-form'
|
|
10
12
|
import { i18n } from '@lingui/core'
|
|
11
13
|
import { Trans } from '@lingui/react'
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
12
15
|
import { TextField } from '@mui/material'
|
|
13
|
-
import {
|
|
16
|
+
import { ApolloCustomerErrorSnackbar } from '../ApolloCustomerError'
|
|
14
17
|
import {
|
|
15
18
|
UpdateCustomerEmailDocument,
|
|
16
19
|
UpdateCustomerEmailMutation,
|
|
@@ -26,13 +29,20 @@ export function UpdateCustomerEmailForm(props: UpdateCustomerEmailFormProps) {
|
|
|
26
29
|
|
|
27
30
|
const form = useFormGqlMutation<
|
|
28
31
|
UpdateCustomerEmailMutation,
|
|
29
|
-
UpdateCustomerEmailMutationVariables & {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
32
|
+
UpdateCustomerEmailMutationVariables & { currentEmail?: string; confirmEmail?: string }
|
|
33
|
+
>(
|
|
34
|
+
UpdateCustomerEmailDocument,
|
|
35
|
+
{},
|
|
36
|
+
{
|
|
37
|
+
errorPolicy: 'all',
|
|
38
|
+
},
|
|
39
|
+
)
|
|
34
40
|
|
|
35
|
-
const { handleSubmit, error, required, formState, watch, muiRegister, reset } = form
|
|
41
|
+
const { handleSubmit, error, required, formState, watch, muiRegister, reset, control } = form
|
|
42
|
+
const [remainingError, authenticationError] = graphqlErrorByCategory({
|
|
43
|
+
category: 'graphql-authentication',
|
|
44
|
+
error,
|
|
45
|
+
})
|
|
36
46
|
const submit = handleSubmit(() => {
|
|
37
47
|
reset()
|
|
38
48
|
})
|
|
@@ -45,7 +55,7 @@ export function UpdateCustomerEmailForm(props: UpdateCustomerEmailFormProps) {
|
|
|
45
55
|
key='current-email'
|
|
46
56
|
variant='outlined'
|
|
47
57
|
type='text'
|
|
48
|
-
autoComplete='
|
|
58
|
+
autoComplete='email'
|
|
49
59
|
autoFocus
|
|
50
60
|
error={formState.isSubmitted && !!formState.errors.currentEmail}
|
|
51
61
|
helperText={formState.isSubmitted && formState.errors.currentEmail?.message}
|
|
@@ -67,8 +77,7 @@ export function UpdateCustomerEmailForm(props: UpdateCustomerEmailFormProps) {
|
|
|
67
77
|
key='email'
|
|
68
78
|
variant='outlined'
|
|
69
79
|
type='text'
|
|
70
|
-
autoComplete='
|
|
71
|
-
autoFocus
|
|
80
|
+
autoComplete='off'
|
|
72
81
|
error={formState.isSubmitted && !!formState.errors.email}
|
|
73
82
|
helperText={formState.isSubmitted && formState.errors.email?.message}
|
|
74
83
|
label={<Trans id='New email' />}
|
|
@@ -82,8 +91,7 @@ export function UpdateCustomerEmailForm(props: UpdateCustomerEmailFormProps) {
|
|
|
82
91
|
key='confirm-email'
|
|
83
92
|
variant='outlined'
|
|
84
93
|
type='text'
|
|
85
|
-
autoComplete='
|
|
86
|
-
autoFocus
|
|
94
|
+
autoComplete='off'
|
|
87
95
|
error={formState.isSubmitted && !!formState.errors.confirmEmail}
|
|
88
96
|
helperText={formState.isSubmitted && formState.errors.confirmEmail?.message}
|
|
89
97
|
label={<Trans id='Confirm new email' />}
|
|
@@ -96,21 +104,21 @@ export function UpdateCustomerEmailForm(props: UpdateCustomerEmailFormProps) {
|
|
|
96
104
|
</FormRow>
|
|
97
105
|
|
|
98
106
|
<FormRow>
|
|
99
|
-
<
|
|
107
|
+
<PasswordElement
|
|
108
|
+
control={control}
|
|
100
109
|
variant='outlined'
|
|
101
|
-
|
|
102
|
-
error={!!formState.errors.password}
|
|
110
|
+
name='password'
|
|
103
111
|
label={<Trans id='Password' />}
|
|
104
|
-
autoComplete='password'
|
|
112
|
+
autoComplete='current-password'
|
|
105
113
|
required={required.password}
|
|
106
|
-
{...muiRegister('password', {
|
|
107
|
-
required: required.password,
|
|
108
|
-
})}
|
|
109
|
-
helperText={formState.errors.password?.message}
|
|
110
114
|
disabled={formState.isSubmitting}
|
|
115
|
+
error={Boolean(authenticationError)}
|
|
116
|
+
helperText={authenticationError?.message}
|
|
111
117
|
/>
|
|
112
118
|
</FormRow>
|
|
113
119
|
|
|
120
|
+
<ApolloCustomerErrorSnackbar error={remainingError} />
|
|
121
|
+
|
|
114
122
|
<FormDivider />
|
|
115
123
|
<FormActions>
|
|
116
124
|
<Button
|
|
@@ -123,7 +131,6 @@ export function UpdateCustomerEmailForm(props: UpdateCustomerEmailFormProps) {
|
|
|
123
131
|
<Trans id='Save changes' />
|
|
124
132
|
</Button>
|
|
125
133
|
</FormActions>
|
|
126
|
-
<ApolloCustomerErrorAlert error={error} />
|
|
127
134
|
|
|
128
135
|
<MessageSnackbar sticky open={formState.isSubmitSuccessful && !error}>
|
|
129
136
|
<Trans id='Successfully updated email' />
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Controller, useFormAutoSubmit, useFormGqlMutation } from '@graphcommerce/react-hook-form'
|
|
2
2
|
import { Trans } from '@lingui/react'
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
|
3
4
|
import { FormControl, FormControlLabel, FormHelperText, Switch } from '@mui/material'
|
|
4
5
|
import React, { useEffect, useMemo } from 'react'
|
|
5
6
|
import { AccountAddressFragment } from '../AccountAddress/AccountAddress.gql'
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { PasswordElement, PasswordElementProps } from '@graphcommerce/ecommerce-ui'
|
|
2
|
+
import { useQuery } from '@graphcommerce/graphql'
|
|
3
|
+
import { StoreConfigDocument } from '@graphcommerce/magento-store'
|
|
4
|
+
import { FieldValues } from '@graphcommerce/react-hook-form'
|
|
5
|
+
import { i18n } from '@lingui/core'
|
|
6
|
+
|
|
7
|
+
export type ValidatedPasswordElementProps<T extends FieldValues> = PasswordElementProps<T>
|
|
8
|
+
|
|
9
|
+
export function ValidatedPasswordElement<TFieldValues extends FieldValues>(
|
|
10
|
+
props: PasswordElementProps<TFieldValues>,
|
|
11
|
+
): JSX.Element {
|
|
12
|
+
const { ...textFieldProps } = props
|
|
13
|
+
|
|
14
|
+
const storeConfig = useQuery(StoreConfigDocument).data?.storeConfig
|
|
15
|
+
const minPasswordLength = Number(storeConfig?.minimum_password_length) ?? 0
|
|
16
|
+
const passwordMinCharacterSets = Number(storeConfig?.required_character_classes_number) ?? 0
|
|
17
|
+
|
|
18
|
+
const validation: NonNullable<PasswordElementProps<TFieldValues>['validation']> = {}
|
|
19
|
+
|
|
20
|
+
validation.minLength = {
|
|
21
|
+
value: minPasswordLength,
|
|
22
|
+
message: i18n._(/* i18n */ 'Password must have at least {minPasswordLength} characters', {
|
|
23
|
+
minPasswordLength,
|
|
24
|
+
}),
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
validation.validate = (value: string) => {
|
|
28
|
+
const pass = value.trim()
|
|
29
|
+
let counter = 0
|
|
30
|
+
|
|
31
|
+
if (pass.match(/\d+/)) counter++
|
|
32
|
+
if (pass.match(/[a-z]+/)) counter++
|
|
33
|
+
if (pass.match(/[A-Z]+/)) counter++
|
|
34
|
+
if (pass.match(/[^a-zA-Z0-9]+/)) counter++
|
|
35
|
+
|
|
36
|
+
if (counter < passwordMinCharacterSets) {
|
|
37
|
+
return i18n._(
|
|
38
|
+
/* i18n */ 'Minimum of different classes of characters in password is {passwordMinCharacterSets}. Classes of characters: Lower Case, Upper Case, Digits, Special Characters.',
|
|
39
|
+
{ passwordMinCharacterSets },
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return <PasswordElement {...textFieldProps} validation={validation} />
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useApolloClient } from '@graphcommerce/graphql'
|
|
2
|
+
import { ErrorCategory } from '@graphcommerce/magento-graphql'
|
|
3
|
+
import { useRouter } from 'next/router'
|
|
4
|
+
import { UseCustomerValidateTokenDocument } from './UseCustomerValidateToken.gql'
|
|
5
|
+
import { useCustomerQuery } from './useCustomerQuery'
|
|
6
|
+
|
|
7
|
+
/** Validates the current customer token. This hook is supposed to be called only once. */
|
|
8
|
+
export function useCustomerValidateToken() {
|
|
9
|
+
const client = useApolloClient()
|
|
10
|
+
const router = useRouter()
|
|
11
|
+
|
|
12
|
+
useCustomerQuery(UseCustomerValidateTokenDocument, {
|
|
13
|
+
initialFetchPolicy: 'network-only',
|
|
14
|
+
onError: async ({ graphQLErrors }) => {
|
|
15
|
+
const category: ErrorCategory = 'graphql-authorization'
|
|
16
|
+
// If there is no authorization error, do nothing.
|
|
17
|
+
if (!graphQLErrors.some((e) => e.extensions?.category === category)) return
|
|
18
|
+
|
|
19
|
+
await client.clearStore()
|
|
20
|
+
router.reload()
|
|
21
|
+
},
|
|
22
|
+
})
|
|
23
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ApolloLink, ApolloCache, setContext } from '@graphcommerce/graphql/apollo'
|
|
1
|
+
import { setContext } from '@graphcommerce/graphql/apollo'
|
|
3
2
|
import { CustomerTokenDocument } from '../hooks'
|
|
4
|
-
import { onAuthorizationError } from './onAuthenticationError'
|
|
5
3
|
|
|
6
4
|
export const addTokenHeader = setContext((_, context) => {
|
|
7
5
|
if (!context.headers) context.headers = {}
|
|
@@ -17,7 +15,4 @@ export const addTokenHeader = setContext((_, context) => {
|
|
|
17
15
|
}
|
|
18
16
|
})
|
|
19
17
|
|
|
20
|
-
export const customerTokenLink =
|
|
21
|
-
|
|
22
|
-
/** Not really required anymore, you can use customerTokenLink directly */
|
|
23
|
-
export const createCustomerTokenLink = (_: ApolloCache<NormalizedCacheObject>) => customerTokenLink
|
|
18
|
+
export const customerTokenLink = addTokenHeader
|