@graphcommerce/magento-customer 6.2.0-canary.43 → 6.2.0-canary.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Change Log
2
2
 
3
+ ## 6.2.0-canary.45
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1962](https://github.com/graphcommerce-org/graphcommerce/pull/1962) [`518b6ca24`](https://github.com/graphcommerce-org/graphcommerce/commit/518b6ca248fc94624dc06eb02de5b3eac0fc9483) - Created a new `<ValidatedPasswordElement/>` which validates according to Magento's validation groups and implement on all locations. Move remaining password fields to `<PasswordElement />` ([@carlocarels90](https://github.com/carlocarels90))
8
+
9
+ ## 6.2.0-canary.44
10
+
3
11
  ## 6.2.0-canary.43
4
12
 
5
13
  ## 6.2.0-canary.42
@@ -1,3 +1,9 @@
1
+ import {
2
+ ApolloErrorSnackbar,
3
+ PasswordElement,
4
+ PasswordRepeatElement,
5
+ } from '@graphcommerce/ecommerce-ui'
6
+ import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
1
7
  import {
2
8
  Form,
3
9
  FormActions,
@@ -7,10 +13,8 @@ import {
7
13
  Button,
8
14
  } from '@graphcommerce/next-ui'
9
15
  import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
10
- import { i18n } from '@lingui/core'
11
16
  import { Trans } from '@lingui/react'
12
- import { TextField } from '@mui/material'
13
- import { ApolloCustomerErrorAlert } from '../ApolloCustomerError/ApolloCustomerErrorAlert'
17
+ import { ValidatedPasswordElement } from '../ValidatedPasswordElement/ValidatedPasswordElement'
14
18
  import {
15
19
  ChangePasswordDocument,
16
20
  ChangePasswordMutation,
@@ -21,55 +25,61 @@ export function ChangePasswordForm() {
21
25
  const form = useFormGqlMutation<
22
26
  ChangePasswordMutation,
23
27
  ChangePasswordMutationVariables & { confirmPassword?: string }
24
- >(ChangePasswordDocument)
25
- const { muiRegister, handleSubmit, required, watch, data, formState, error } = form
28
+ >(ChangePasswordDocument, {}, { errorPolicy: 'all' })
29
+ const { handleSubmit, required, data, formState, error, control } = form
30
+ const [remainingError0, authenticationError] = graphqlErrorByCategory({
31
+ category: 'graphql-authentication',
32
+ error,
33
+ })
34
+ const [remainingError, inputError] = graphqlErrorByCategory({
35
+ category: 'graphql-input',
36
+ error: remainingError0,
37
+ })
38
+
26
39
  const submitHandler = handleSubmit(() => {})
27
- const pass = watch('newPassword')
40
+
41
+ const showSuccess = !formState.isSubmitting && formState.isSubmitSuccessful && !error?.message
28
42
 
29
43
  return (
30
44
  <Form onSubmit={submitHandler} noValidate>
31
45
  <FormRow>
32
- <TextField
46
+ <PasswordElement
47
+ control={control}
48
+ name='currentPassword'
33
49
  variant='outlined'
34
- type='password'
35
- error={!!formState.errors.currentPassword}
50
+ autoComplete='current-password'
36
51
  label={<Trans id='Current Password' />}
37
52
  required={required.currentPassword}
38
- {...muiRegister('currentPassword', { required: required.currentPassword })}
39
- helperText={formState.errors.currentPassword?.message}
40
53
  disabled={formState.isSubmitting}
54
+ error={Boolean(authenticationError)}
55
+ helperText={authenticationError?.message}
41
56
  />
42
57
  </FormRow>
43
58
 
44
59
  <FormRow>
45
- <TextField
60
+ <ValidatedPasswordElement
61
+ control={control}
62
+ name='newPassword'
46
63
  variant='outlined'
47
- type='password'
48
- error={!!formState.errors.newPassword}
64
+ autoComplete='new-password'
49
65
  label={<Trans id='New password' />}
50
66
  required={required.newPassword}
51
- {...muiRegister('newPassword', { required: required.newPassword })}
52
- helperText={formState.errors.newPassword?.message}
53
67
  disabled={formState.isSubmitting}
68
+ error={Boolean(inputError)}
69
+ helperText={inputError?.message}
54
70
  />
55
-
56
- <TextField
71
+ <PasswordRepeatElement
72
+ control={control}
73
+ name='confirmPassword'
74
+ passwordFieldName='newPassword'
75
+ autoComplete='new-password'
57
76
  variant='outlined'
58
- type='password'
59
- error={!!formState.errors.confirmPassword}
60
77
  label={<Trans id='Confirm password' />}
61
78
  required
62
- {...muiRegister('confirmPassword', {
63
- required: true,
64
- validate: (value) => value === pass || i18n._(/* i18n */ "Passwords don't match"),
65
- })}
66
- helperText={formState.errors.confirmPassword?.message}
67
79
  disabled={formState.isSubmitting}
68
80
  />
69
81
  </FormRow>
70
82
 
71
- <ApolloCustomerErrorAlert error={error} />
72
-
73
83
  <FormDivider />
74
84
 
75
85
  <FormActions>
@@ -84,9 +94,13 @@ export function ChangePasswordForm() {
84
94
  </Button>
85
95
  </FormActions>
86
96
 
87
- <MessageSnackbar sticky open={Boolean(formState.isSubmitSuccessful && data)}>
88
- <Trans id='Successfully changed password' />
89
- </MessageSnackbar>
97
+ <ApolloErrorSnackbar error={remainingError} />
98
+
99
+ {showSuccess && (
100
+ <MessageSnackbar open={showSuccess} sticky variant='pill'>
101
+ <Trans id='Successfully changed password' />
102
+ </MessageSnackbar>
103
+ )}
90
104
  </Form>
91
105
  )
92
106
  }
@@ -1,14 +1,10 @@
1
- import {
2
- PasswordElement,
3
- PasswordRepeatElement,
4
- TextFieldElement,
5
- } from '@graphcommerce/ecommerce-ui'
1
+ import { PasswordRepeatElement, TextFieldElement } from '@graphcommerce/ecommerce-ui'
6
2
  import { Button, Form, FormActions, FormRow } from '@graphcommerce/next-ui'
7
3
  import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
8
4
  import { Trans } from '@lingui/react'
9
- import { TextField } from '@mui/material'
10
5
  import { useRouter } from 'next/router'
11
6
  import { ApolloCustomerErrorAlert } from '../ApolloCustomerError/ApolloCustomerErrorAlert'
7
+ import { ValidatedPasswordElement } from '../ValidatedPasswordElement/ValidatedPasswordElement'
12
8
  import {
13
9
  ResetPasswordDocument,
14
10
  ResetPasswordMutation,
@@ -59,11 +55,11 @@ export function ResetPasswordForm(props: ResetPasswordFormProps) {
59
55
  />
60
56
  </FormRow>
61
57
  <FormRow>
62
- <PasswordElement
58
+ <ValidatedPasswordElement
63
59
  control={control}
64
60
  name='newPassword'
61
+ autoComplete='new-password'
65
62
  variant='outlined'
66
- type='password'
67
63
  label={<Trans id='New password' />}
68
64
  required
69
65
  disabled={formState.isSubmitting}
@@ -71,9 +67,9 @@ export function ResetPasswordForm(props: ResetPasswordFormProps) {
71
67
  <PasswordRepeatElement
72
68
  control={control}
73
69
  name='confirmPassword'
70
+ autoComplete='new-password'
74
71
  passwordFieldName='newPassword'
75
72
  variant='outlined'
76
- type='password'
77
73
  label={<Trans id='Confirm password' />}
78
74
  required
79
75
  disabled={formState.isSubmitting}
@@ -1,9 +1,18 @@
1
+ import { PasswordElement } from '@graphcommerce/ecommerce-ui'
1
2
  import { useApolloClient } from '@graphcommerce/graphql'
2
3
  import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
3
- import { Button, FormRow, FormActions } from '@graphcommerce/next-ui'
4
+ import {
5
+ Button,
6
+ FormRow,
7
+ FormActions,
8
+ iconEyeCrossed,
9
+ iconEye,
10
+ IconSvg,
11
+ } from '@graphcommerce/next-ui'
4
12
  import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
5
13
  import { Trans } from '@lingui/react'
6
- import { Box, FormControl, Link, SxProps, TextField, Theme } from '@mui/material'
14
+ import { Box, FormControl, IconButton, InputAdornment, Link, SxProps, Theme } from '@mui/material'
15
+ import { MouseEvent, useState } from 'react'
7
16
  import { CustomerDocument } from '../../hooks'
8
17
  import { ApolloCustomerErrorAlert } from '../ApolloCustomerError/ApolloCustomerErrorAlert'
9
18
  import { SignInDocument } from './SignIn.gql'
@@ -12,7 +21,6 @@ type SignInFormProps = { email: string; sx?: SxProps<Theme> }
12
21
 
13
22
  export function SignInForm(props: SignInFormProps) {
14
23
  const { email, sx } = props
15
-
16
24
  const client = useApolloClient()
17
25
  const form = useFormGqlMutation(
18
26
  SignInDocument,
@@ -32,7 +40,7 @@ export function SignInForm(props: SignInFormProps) {
32
40
  { errorPolicy: 'all' },
33
41
  )
34
42
 
35
- const { muiRegister, handleSubmit, required, formState, error } = form
43
+ const { handleSubmit, required, formState, error, control } = form
36
44
  const [remainingError, authError] = graphqlErrorByCategory({
37
45
  category: 'graphql-authentication',
38
46
  error,
@@ -41,18 +49,19 @@ export function SignInForm(props: SignInFormProps) {
41
49
 
42
50
  return (
43
51
  <Box component='form' onSubmit={submitHandler} noValidate sx={sx}>
44
- <FormRow>
45
- <TextField
46
- key='password'
52
+ <FormRow sx={{ gridTemplateColumns: 'none' }}>
53
+ <PasswordElement
47
54
  variant='outlined'
48
- type='password'
49
55
  error={!!formState.errors.password || !!authError}
56
+ control={control}
57
+ name='password'
50
58
  label={<Trans id='Password' />}
51
59
  autoFocus
52
60
  autoComplete='current-password'
53
61
  id='current-password'
54
62
  required={required.password}
55
- {...muiRegister('password', { required: required.password })}
63
+ disabled={formState.isSubmitting}
64
+ helperText={!!formState.errors.password || authError?.message}
56
65
  InputProps={{
57
66
  endAdornment: (
58
67
  <Link href='/account/forgot-password' underline='hover' sx={{ whiteSpace: 'nowrap' }}>
@@ -60,8 +69,6 @@ export function SignInForm(props: SignInFormProps) {
60
69
  </Link>
61
70
  ),
62
71
  }}
63
- helperText={formState.errors.password?.message || authError?.message}
64
- disabled={formState.isSubmitting}
65
72
  />
66
73
  </FormRow>
67
74
 
@@ -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, SxProps, TextField, Theme } from '@mui/material'
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 { muiRegister, handleSubmit, required, formState, error } = form
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
- <TextField
45
+ <PasswordElement
46
+ control={control}
45
47
  variant='outlined'
46
- type='password'
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
- <Button
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
- </Button>
59
+ </Link>
67
60
  ),
68
61
  }}
69
62
  />
70
- <Button type='submit' loading={formState.isSubmitting} color='secondary' variant='pill'>
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,15 +1,14 @@
1
- import { useQuery } from '@graphcommerce/graphql'
1
+ import { PasswordElement, 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
- import { Alert, FormControlLabel, Switch, TextField } from '@mui/material'
6
+ import { Alert, FormControlLabel, Switch } from '@mui/material'
9
7
  import { ApolloCustomerErrorSnackbar } from '../ApolloCustomerError/ApolloCustomerErrorSnackbar'
10
8
  import { NameFields } from '../NameFields/NameFields'
11
9
  import { SignUpDocument, SignUpMutation, SignUpMutationVariables } from './SignUp.gql'
12
10
  import { SignUpConfirmDocument } from './SignUpConfirm.gql'
11
+ import { ValidatedPasswordElement } from '../ValidatedPasswordElement/ValidatedPasswordElement'
13
12
 
14
13
  type SignUpFormProps = { email: string }
15
14
 
@@ -18,8 +17,6 @@ const requireEmailValidation = import.meta.graphCommerce.customerRequireEmailCon
18
17
  export function SignUpForm(props: SignUpFormProps) {
19
18
  const { email } = props
20
19
 
21
- const storeConfig = useQuery(StoreConfigDocument).data?.storeConfig
22
-
23
20
  const Mutation = requireEmailValidation ? SignUpConfirmDocument : SignUpDocument
24
21
 
25
22
  const form = useFormGqlMutation<
@@ -31,11 +28,10 @@ export function SignUpForm(props: SignUpFormProps) {
31
28
  { errorPolicy: 'all' },
32
29
  )
33
30
 
34
- const { muiRegister, handleSubmit, required, watch, formState, error } = form
31
+ const { muiRegister, handleSubmit, required, formState, error, control } = form
35
32
  const [remainingError, inputError] = graphqlErrorByCategory({ category: 'graphql-input', error })
36
33
 
37
34
  const submitHandler = handleSubmit(() => {})
38
- const watchPassword = watch('password')
39
35
 
40
36
  useFormPersist({ form, name: 'SignUp', exclude: ['password', 'confirmPassword'] })
41
37
 
@@ -50,37 +46,27 @@ export function SignUpForm(props: SignUpFormProps) {
50
46
  return (
51
47
  <form onSubmit={submitHandler} noValidate>
52
48
  <FormRow>
53
- <TextField
49
+ <ValidatedPasswordElement
50
+ control={control}
51
+ name='password'
54
52
  variant='outlined'
55
- type='password'
56
53
  error={!!formState.errors.password || !!inputError}
57
54
  label={<Trans id='Password' />}
58
55
  autoFocus
59
56
  autoComplete='new-password'
60
57
  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
58
  disabled={formState.isSubmitting}
59
+ helperText={inputError?.message}
70
60
  />
71
- <TextField
61
+ <PasswordRepeatElement
62
+ control={control}
63
+ name='confirmPassword'
64
+ passwordFieldName='password'
72
65
  variant='outlined'
73
- type='password'
74
- error={!!formState.errors.confirmPassword}
66
+ error={!!formState.errors.confirmPassword || !!inputError}
75
67
  label={<Trans id='Confirm password' />}
76
68
  autoComplete='new-password'
77
69
  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
70
  disabled={formState.isSubmitting}
85
71
  />
86
72
  </FormRow>
@@ -1,14 +1,17 @@
1
- import { ApolloErrorAlert } from '@graphcommerce/ecommerce-ui'
2
- import { useQuery } from '@graphcommerce/graphql'
3
- import { StoreConfigDocument } from '@graphcommerce/magento-store'
1
+ import {
2
+ ApolloErrorAlert,
3
+ PasswordElement,
4
+ PasswordRepeatElement,
5
+ } from '@graphcommerce/ecommerce-ui'
6
+ import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
4
7
  import { Button, extendableComponent, Form, FormRow } from '@graphcommerce/next-ui'
5
8
  import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
6
- import { i18n } from '@lingui/core'
7
9
  import { Trans } from '@lingui/react'
8
- import { Alert, Box, TextField } from '@mui/material'
10
+ import { Alert, Box } from '@mui/material'
9
11
  import React from 'react'
10
12
  import { SignUpMutationVariables, SignUpMutation, SignUpDocument } from './SignUp.gql'
11
13
  import { SignUpConfirmDocument } from './SignUpConfirm.gql'
14
+ import { ValidatedPasswordElement } from '../ValidatedPasswordElement/ValidatedPasswordElement'
12
15
 
13
16
  type SignUpFormInlineProps = Pick<SignUpMutationVariables, 'email'> & {
14
17
  children?: React.ReactNode
@@ -26,13 +29,8 @@ const { classes } = extendableComponent('SignUpFormInline', [
26
29
 
27
30
  const requireEmailValidation = import.meta.graphCommerce.customerRequireEmailConfirmation ?? false
28
31
 
29
- export function SignUpFormInline({
30
- email,
31
- children,
32
- firstname,
33
- lastname,
34
- onSubmitted = () => {},
35
- }: SignUpFormInlineProps) {
32
+ export function SignUpFormInline(props: SignUpFormInlineProps) {
33
+ const { email, children, firstname, lastname, onSubmitted = () => {} } = props
36
34
  const Mutation = requireEmailValidation ? SignUpConfirmDocument : SignUpDocument
37
35
 
38
36
  const form = useFormGqlMutation<
@@ -56,13 +54,9 @@ export function SignUpFormInline({
56
54
  { errorPolicy: 'all' },
57
55
  )
58
56
 
59
- const { muiRegister, watch, handleSubmit, required, formState } = form
57
+ const { handleSubmit, formState, control, error, required } = form
58
+ const [remainingError, inputError] = graphqlErrorByCategory({ category: 'graphql-input', error })
60
59
  const submitHandler = handleSubmit(() => {})
61
- const watchPassword = watch('password')
62
-
63
- const minPasswordLength = Number(
64
- useQuery(StoreConfigDocument).data?.storeConfig?.minimum_password_length ?? 8,
65
- )
66
60
 
67
61
  if (requireEmailValidation && form.formState.isSubmitSuccessful) {
68
62
  return (
@@ -75,36 +69,25 @@ export function SignUpFormInline({
75
69
  return (
76
70
  <Form onSubmit={submitHandler} noValidate className={classes.form} sx={{ padding: 0 }}>
77
71
  <FormRow className={classes.row} sx={{ padding: 0 }}>
78
- <TextField
72
+ <ValidatedPasswordElement
73
+ control={control}
74
+ name='password'
75
+ autoComplete='new-password'
79
76
  variant='outlined'
80
- type='password'
81
- error={!!formState.errors.password || !!form.error}
82
77
  label={<Trans id='Password' />}
83
- autoFocus
84
- autoComplete='new-password'
85
- id='new-password'
86
78
  required={required.password}
87
- {...muiRegister('password', {
88
- required: required.password,
89
- minLength: {
90
- value: minPasswordLength,
91
- message: i18n._(/* i18n */ 'Password must have at least 8 characters'),
92
- },
93
- })}
94
79
  disabled={formState.isSubmitting}
80
+ error={!!inputError}
81
+ helperText={inputError?.message}
95
82
  />
96
- <TextField
83
+ <PasswordRepeatElement
84
+ control={control}
85
+ name='confirmPassword'
86
+ passwordFieldName='password'
87
+ autoComplete='new-password'
97
88
  variant='outlined'
98
- type='password'
99
- error={!!formState.errors.confirmPassword || !!form.error}
100
89
  label={<Trans id='Confirm password' />}
101
- autoComplete='new-password'
102
90
  required
103
- {...muiRegister('confirmPassword', {
104
- required: true,
105
- validate: (value) => value === watchPassword,
106
- })}
107
- helperText={!!formState.errors.confirmPassword && <Trans id='Passwords should match' />}
108
91
  disabled={formState.isSubmitting}
109
92
  />
110
93
  </FormRow>
@@ -133,7 +116,7 @@ export function SignUpFormInline({
133
116
  </Box>
134
117
  </FormRow>
135
118
  </FormRow>
136
- <ApolloErrorAlert error={form.error} />
119
+ <ApolloErrorAlert error={remainingError} />
137
120
  </Form>
138
121
  )
139
122
  }
@@ -1,3 +1,4 @@
1
+ import { PasswordElement } from '@graphcommerce/ecommerce-ui'
1
2
  import {
2
3
  Button,
3
4
  Form,
@@ -16,6 +17,8 @@ import {
16
17
  UpdateCustomerEmailMutation,
17
18
  UpdateCustomerEmailMutationVariables,
18
19
  } from './UpdateCustomerEmail.gql'
20
+ import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
21
+ import { ApolloCustomerErrorSnackbar } from '../ApolloCustomerError'
19
22
 
20
23
  type UpdateCustomerEmailFormProps = {
21
24
  email: string
@@ -26,13 +29,20 @@ export function UpdateCustomerEmailForm(props: UpdateCustomerEmailFormProps) {
26
29
 
27
30
  const form = useFormGqlMutation<
28
31
  UpdateCustomerEmailMutation,
29
- UpdateCustomerEmailMutationVariables & {
30
- currentEmail?: string
31
- confirmEmail?: string
32
- }
33
- >(UpdateCustomerEmailDocument)
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='currentEmail'
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='email'
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='confirmEmail'
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
- <TextField
107
+ <PasswordElement
108
+ control={control}
100
109
  variant='outlined'
101
- type='password'
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' />
@@ -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
+ }
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@graphcommerce/magento-customer",
3
3
  "homepage": "https://www.graphcommerce.org/",
4
4
  "repository": "github:graphcommerce-org/graphcommerce",
5
- "version": "6.2.0-canary.43",
5
+ "version": "6.2.0-canary.45",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,20 +12,20 @@
12
12
  }
13
13
  },
14
14
  "devDependencies": {
15
- "@graphcommerce/eslint-config-pwa": "6.2.0-canary.43",
16
- "@graphcommerce/prettier-config-pwa": "6.2.0-canary.43",
17
- "@graphcommerce/typescript-config-pwa": "6.2.0-canary.43"
15
+ "@graphcommerce/eslint-config-pwa": "6.2.0-canary.45",
16
+ "@graphcommerce/prettier-config-pwa": "6.2.0-canary.45",
17
+ "@graphcommerce/typescript-config-pwa": "6.2.0-canary.45"
18
18
  },
19
19
  "dependencies": {
20
- "@graphcommerce/ecommerce-ui": "6.2.0-canary.43",
21
- "@graphcommerce/framer-utils": "6.2.0-canary.43",
22
- "@graphcommerce/graphql": "6.2.0-canary.43",
23
- "@graphcommerce/graphql-mesh": "6.2.0-canary.43",
24
- "@graphcommerce/image": "6.2.0-canary.43",
25
- "@graphcommerce/magento-graphql": "6.2.0-canary.43",
26
- "@graphcommerce/magento-store": "6.2.0-canary.43",
27
- "@graphcommerce/next-ui": "6.2.0-canary.43",
28
- "@graphcommerce/react-hook-form": "6.2.0-canary.43"
20
+ "@graphcommerce/ecommerce-ui": "6.2.0-canary.45",
21
+ "@graphcommerce/framer-utils": "6.2.0-canary.45",
22
+ "@graphcommerce/graphql": "6.2.0-canary.45",
23
+ "@graphcommerce/graphql-mesh": "6.2.0-canary.45",
24
+ "@graphcommerce/image": "6.2.0-canary.45",
25
+ "@graphcommerce/magento-graphql": "6.2.0-canary.45",
26
+ "@graphcommerce/magento-store": "6.2.0-canary.45",
27
+ "@graphcommerce/next-ui": "6.2.0-canary.45",
28
+ "@graphcommerce/react-hook-form": "6.2.0-canary.45"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@lingui/react": "^4.2.1",