@graphcommerce/magento-customer 7.0.0-canary.21 → 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.
Files changed (29) hide show
  1. package/CHANGELOG.md +211 -1
  2. package/components/AccountSignInUpForm/AccountSignInUpForm.tsx +4 -1
  3. package/components/AddressFields/AddressFields.tsx +1 -1
  4. package/components/ApolloCustomerError/ApolloCustomerErrorAlert.tsx +0 -1
  5. package/components/ChangePasswordForm/ChangePasswordForm.tsx +44 -30
  6. package/components/CreateCustomerAddressForm/CreateCustomerAddress.graphql +2 -2
  7. package/components/CreateCustomerAddressForm/CreateCustomerAddressForm.tsx +2 -1
  8. package/components/CreateCustomerAddressForm/CustomerAddress.graphql +1 -0
  9. package/components/CustomerFab/CustomerFab.tsx +3 -0
  10. package/components/EditAddressForm/EditAddressForm.tsx +2 -0
  11. package/components/ForgotPasswordForm/ForgotPasswordForm.tsx +1 -0
  12. package/components/NameFields/NameFields.tsx +2 -1
  13. package/components/OrderCard/OrderCard.tsx +1 -1
  14. package/components/ResetPasswordForm/ResetPasswordForm.tsx +5 -9
  15. package/components/SessionDebugger/SessionDebugger.tsx +2 -1
  16. package/components/SignInForm/SignInForm.tsx +9 -10
  17. package/components/SignInForm/SignInFormInline.tsx +17 -17
  18. package/components/SignUpForm/SignUpForm.tsx +14 -27
  19. package/components/SignUpForm/SignUpFormInline.tsx +39 -52
  20. package/components/UpdateCustomerEmailForm/UpdateCustomerEmailForm.tsx +28 -21
  21. package/components/UpdateDefaultAddressForm/UpdateDefaultAddressForm.tsx +1 -0
  22. package/components/ValidatedPasswordElement/ValidatedPasswordElement.tsx +47 -0
  23. package/hooks/CustomerInfo.graphql +1 -0
  24. package/hooks/UseCustomerValidateToken.graphql +5 -0
  25. package/hooks/useCustomerValidateToken.ts +23 -0
  26. package/link/createCustomerTokenLink.ts +2 -7
  27. package/package.json +16 -15
  28. package/test/authentication.playwright.ts +1 -0
  29. package/link/onAuthenticationError.ts +0 -39
package/CHANGELOG.md CHANGED
@@ -1,6 +1,216 @@
1
1
  # Change Log
2
2
 
3
- ## 7.0.0-canary.21
3
+ ## 7.0.0
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
+ - [#1926](https://github.com/graphcommerce-org/graphcommerce/pull/1926) [`ab8877fdb`](https://github.com/graphcommerce-org/graphcommerce/commit/ab8877fdb6147960ce656d28306d719e92f6de68) - Made the follow order link in the order card & order details a working `<Link />` if provided from magento backend. ([@JoshuaS98](https://github.com/JoshuaS98))
10
+
11
+ ### Patch Changes
12
+
13
+ - [#1916](https://github.com/graphcommerce-org/graphcommerce/pull/1916) [`97ebc19af`](https://github.com/graphcommerce-org/graphcommerce/commit/97ebc19aff093bf57d24d009e96661ad43926fd6) - The customer's token would be invalidated if any authorization error occured. Now only scoped to customer queries and mutations, potentially reducing the amount of random logouts. ([@paales](https://github.com/paales))
14
+
15
+ - [#1952](https://github.com/graphcommerce-org/graphcommerce/pull/1952) [`f1fe4f598`](https://github.com/graphcommerce-org/graphcommerce/commit/f1fe4f5986cee1f7c8313152e43691ed939c8f21) - Enable password fields when there is an error and user input correction is required. ([@carlocarels90](https://github.com/carlocarels90))
16
+
17
+ - [#1914](https://github.com/graphcommerce-org/graphcommerce/pull/1914) [`38d6c4888`](https://github.com/graphcommerce-org/graphcommerce/commit/38d6c488850013b36cae9f388996039219c1327e) - Errors in the cart didn't allow for recovery from the faulty state ([@paales](https://github.com/paales))
18
+
19
+ - [#1930](https://github.com/graphcommerce-org/graphcommerce/pull/1930) [`c8d023e9e`](https://github.com/graphcommerce-org/graphcommerce/commit/c8d023e9e874131cd9f8fe192b1fca5fe1a26ee3) - Fix the 'close menu' on search and add the option to secondary menu items ([@StefanAngenent](https://github.com/StefanAngenent))
20
+
21
+ - [#2006](https://github.com/graphcommerce-org/graphcommerce/pull/2006) [`80b60cb40`](https://github.com/graphcommerce-org/graphcommerce/commit/80b60cb404882260bd0e8184f3e54f4720925c96) - The Billing address was set to the shipping address when a default billing address was available and made prefix optional when creating an address ([@Jessevdpoel](https://github.com/Jessevdpoel))
22
+
23
+ - [#1942](https://github.com/graphcommerce-org/graphcommerce/pull/1942) [`21b0d0c48`](https://github.com/graphcommerce-org/graphcommerce/commit/21b0d0c48603343c09f287978bf051140e9be912) - Customer's session is now revalidated when a previous session is detected on pageload, making sure the customer is still logged in. ([@paales](https://github.com/paales))
24
+
25
+ - [#1972](https://github.com/graphcommerce-org/graphcommerce/pull/1972) [`cda89820d`](https://github.com/graphcommerce-org/graphcommerce/commit/cda89820dc50e2019a26239b7450863d8c862bfb) - Ensure correct width for CircularProgress and fix misalignment in rotate animation ([@carlocarels90](https://github.com/carlocarels90))
26
+
27
+ - [#2016](https://github.com/graphcommerce-org/graphcommerce/pull/2016) [`227ddcee8`](https://github.com/graphcommerce-org/graphcommerce/commit/227ddcee8808715928371c1f3a4c7925032df0ef) - Made cardProps on CustomerAddressActionCards overridable. ([@Jessevdpoel](https://github.com/Jessevdpoel))
28
+
29
+ - [#1969](https://github.com/graphcommerce-org/graphcommerce/pull/1969) [`838322a97`](https://github.com/graphcommerce-org/graphcommerce/commit/838322a97c7ef1b8aa919196e756da381904bf04) - Fixing the Internal Server Error if the third address line is empty. ([@action-simon](https://github.com/action-simon))
30
+
31
+ ## 6.2.0-canary.98
32
+
33
+ ## 6.2.0-canary.97
34
+
35
+ ## 6.2.0-canary.96
36
+
37
+ ## 6.2.0-canary.95
38
+
39
+ ## 6.2.0-canary.94
40
+
41
+ ## 6.2.0-canary.93
42
+
43
+ ## 6.2.0-canary.92
44
+
45
+ ## 6.2.0-canary.91
46
+
47
+ ## 6.2.0-canary.90
48
+
49
+ ## 6.2.0-canary.89
50
+
51
+ ## 6.2.0-canary.88
52
+
53
+ ## 6.2.0-canary.87
54
+
55
+ ## 6.2.0-canary.86
56
+
57
+ ## 6.2.0-canary.85
58
+
59
+ ## 6.2.0-canary.84
60
+
61
+ ## 6.2.0-canary.83
62
+
63
+ ## 6.2.0-canary.82
64
+
65
+ ## 6.2.0-canary.81
66
+
67
+ ## 6.2.0-canary.80
68
+
69
+ ## 6.2.0-canary.79
70
+
71
+ ## 6.2.0-canary.78
72
+
73
+ ## 6.2.0-canary.77
74
+
75
+ ## 6.2.0-canary.76
76
+
77
+ ## 6.2.0-canary.75
78
+
79
+ ### Patch Changes
80
+
81
+ - [#2016](https://github.com/graphcommerce-org/graphcommerce/pull/2016) [`227ddcee8`](https://github.com/graphcommerce-org/graphcommerce/commit/227ddcee8808715928371c1f3a4c7925032df0ef) - Made cardProps on CustomerAddressActionCards overridable. ([@Jessevdpoel](https://github.com/Jessevdpoel))
82
+
83
+ ## 6.2.0-canary.74
84
+
85
+ ### Patch Changes
86
+
87
+ - [#2006](https://github.com/graphcommerce-org/graphcommerce/pull/2006) [`80b60cb40`](https://github.com/graphcommerce-org/graphcommerce/commit/80b60cb404882260bd0e8184f3e54f4720925c96) - Bugfix for unintentionally overriding the billing address even if a default address was set. Also made prefix optional when creating an address ([@Jessevdpoel](https://github.com/Jessevdpoel))
88
+
89
+ ## 6.2.0-canary.73
90
+
91
+ ## 6.2.0-canary.72
92
+
93
+ ## 6.2.0-canary.71
94
+
95
+ ## 6.2.0-canary.70
96
+
97
+ ## 6.2.0-canary.69
98
+
99
+ ## 6.2.0-canary.68
100
+
101
+ ## 6.2.0-canary.67
102
+
103
+ ## 6.2.0-canary.66
104
+
105
+ ## 6.2.0-canary.65
106
+
107
+ ## 6.2.0-canary.64
108
+
109
+ ## 6.2.0-canary.63
110
+
111
+ ## 6.2.0-canary.62
112
+
113
+ ## 6.2.0-canary.61
114
+
115
+ ## 6.2.0-canary.60
116
+
117
+ ## 6.2.0-canary.59
118
+
119
+ ## 6.2.0-canary.58
120
+
121
+ ## 6.2.0-canary.57
122
+
123
+ ## 6.2.0-canary.56
124
+
125
+ ## 6.2.0-canary.55
126
+
127
+ ## 6.2.0-canary.54
128
+
129
+ ## 6.2.0-canary.53
130
+
131
+ ## 6.2.0-canary.52
132
+
133
+ ### Patch Changes
134
+
135
+ - [#1972](https://github.com/graphcommerce-org/graphcommerce/pull/1972) [`cda89820d`](https://github.com/graphcommerce-org/graphcommerce/commit/cda89820dc50e2019a26239b7450863d8c862bfb) - Ensure correct width for CircularProgress and fix misalignment in rotate animation ([@carlocarels90](https://github.com/carlocarels90))
136
+
137
+ ## 6.2.0-canary.51
138
+
139
+ ## 6.2.0-canary.50
140
+
141
+ ## 6.2.0-canary.49
142
+
143
+ ## 6.2.0-canary.48
144
+
145
+ ## 6.2.0-canary.47
146
+
147
+ ### Patch Changes
148
+
149
+ - [#1969](https://github.com/graphcommerce-org/graphcommerce/pull/1969) [`838322a97`](https://github.com/graphcommerce-org/graphcommerce/commit/838322a97c7ef1b8aa919196e756da381904bf04) - Fixing the Internal Server Error if the third address line is empty. ([@action-simon](https://github.com/action-simon))
150
+
151
+ ## 6.2.0-canary.46
152
+
153
+ ## 6.2.0-canary.45
154
+
155
+ ### Minor Changes
156
+
157
+ - [#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))
158
+
159
+ ## 6.2.0-canary.44
160
+
161
+ ## 6.2.0-canary.43
162
+
163
+ ## 6.2.0-canary.42
164
+
165
+ ## 6.2.0-canary.41
166
+
167
+ ## 6.2.0-canary.40
168
+
169
+ ## 6.2.0-canary.39
170
+
171
+ ## 6.2.0-canary.38
172
+
173
+ ## 6.2.0-canary.37
174
+
175
+ ### Patch Changes
176
+
177
+ - [#1952](https://github.com/graphcommerce-org/graphcommerce/pull/1952) [`f1fe4f598`](https://github.com/graphcommerce-org/graphcommerce/commit/f1fe4f5986cee1f7c8313152e43691ed939c8f21) - enable password fields when there is an error and user input correction is required. ([@carlocarels90](https://github.com/carlocarels90))
178
+
179
+ ## 6.2.0-canary.36
180
+
181
+ ## 6.2.0-canary.35
182
+
183
+ ## 6.2.0-canary.34
184
+
185
+ ## 6.2.0-canary.33
186
+
187
+ ## 6.2.0-canary.32
188
+
189
+ ## 6.2.0-canary.31
190
+
191
+ ## 6.2.0-canary.30
192
+
193
+ ### Patch Changes
194
+
195
+ - [#1942](https://github.com/graphcommerce-org/graphcommerce/pull/1942) [`21b0d0c48`](https://github.com/graphcommerce-org/graphcommerce/commit/21b0d0c48603343c09f287978bf051140e9be912) - Customer's session is now revalidated when a previous session is detected on pageload, making sure the customer is still logged in. ([@paales](https://github.com/paales))
196
+
197
+ ## 6.2.0-canary.29
198
+
199
+ ## 6.2.0-canary.28
200
+
201
+ ## 6.2.0-canary.27
202
+
203
+ ## 6.2.0-canary.26
204
+
205
+ ## 6.2.0-canary.25
206
+
207
+ ## 6.2.0-canary.24
208
+
209
+ ## 6.2.0-canary.23
210
+
211
+ ## 6.2.0-canary.22
212
+
213
+ ## 6.2.0-canary.21
4
214
 
5
215
  ## 6.2.0-canary.20
6
216
 
@@ -8,6 +8,7 @@ import {
8
8
  } from '@graphcommerce/next-ui'
9
9
  import { emailPattern } from '@graphcommerce/react-hook-form'
10
10
  import { Trans } from '@lingui/react'
11
+ // eslint-disable-next-line @typescript-eslint/no-restricted-imports
11
12
  import { Box, CircularProgress, Link, SxProps, TextField, Theme, Typography } from '@mui/material'
12
13
  import router from 'next/router'
13
14
  import { CustomerDocument, useFormIsEmailAvailable } from '../../hooks'
@@ -118,7 +119,9 @@ export function AccountSignInUpForm(props: AccountSignInUpFormProps) {
118
119
  pattern: { value: emailPattern, message: '' },
119
120
  })}
120
121
  InputProps={{
121
- endAdornment: formState.isSubmitting && <CircularProgress />,
122
+ endAdornment: formState.isSubmitting && (
123
+ <CircularProgress sx={{ display: 'inline-flex' }} />
124
+ ),
122
125
  readOnly: !!customerQuery.data?.customer?.email,
123
126
  }}
124
127
  />
@@ -21,8 +21,8 @@ export type AddressFieldValues = {
21
21
  city?: string
22
22
  }
23
23
 
24
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
24
  export type AddressFieldsProps = {
25
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
26
26
  form: UseFormReturn<any>
27
27
  readOnly?: boolean
28
28
  countryFirst?: boolean
@@ -1,7 +1,6 @@
1
1
  import { ApolloErrorAlert, ApolloErrorAlertProps } from '@graphcommerce/ecommerce-ui'
2
2
  import { Button } from '@graphcommerce/next-ui'
3
3
  import { Trans } from '@lingui/react'
4
- import { Link } from '@mui/material'
5
4
  import { useCustomerSession } from '../../hooks/useCustomerSession'
6
5
  import { useAuthorizationErrorMasked } from './useAuthorizationErrorMasked'
7
6
 
@@ -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, 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,5 +1,5 @@
1
1
  mutation CreateCustomerAddress(
2
- $prefix: String!
2
+ $prefix: String
3
3
  $firstname: String!
4
4
  $middlename: String
5
5
  $lastname: String!
@@ -7,7 +7,7 @@ mutation CreateCustomerAddress(
7
7
  $telephone: String!
8
8
  $street: String!
9
9
  $houseNumber: String!
10
- $addition: String
10
+ $addition: String = ""
11
11
  $city: String!
12
12
  $postcode: String!
13
13
  $region: CustomerAddressRegionInput!
@@ -12,6 +12,7 @@ import {
12
12
  import { phonePattern, useFormGqlMutation } from '@graphcommerce/react-hook-form'
13
13
  import { i18n } from '@lingui/core'
14
14
  import { Trans } from '@lingui/react'
15
+ // eslint-disable-next-line @typescript-eslint/no-restricted-imports
15
16
  import { TextField } from '@mui/material'
16
17
  import { useRouter } from 'next/router'
17
18
  import { AddressFields } from '../AddressFields/AddressFields'
@@ -43,7 +44,7 @@ export function CreateCustomerAddressForm() {
43
44
  {},
44
45
  }
45
46
  },
46
- onComplete: (e) => {
47
+ onComplete: () => {
47
48
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
48
49
  router.push(`/account/addresses`)
49
50
  },
@@ -20,4 +20,5 @@ fragment CustomerAddress on CustomerAddress {
20
20
 
21
21
  vat_id
22
22
  default_shipping
23
+ default_billing
23
24
  }
@@ -8,6 +8,7 @@ import { i18n } from '@lingui/core'
8
8
  import { Fab, FabProps as FabPropsType, NoSsr, SxProps, Theme } from '@mui/material'
9
9
  import React from 'react'
10
10
  import { useCustomerSession, UseCustomerSessionReturn } from '../../hooks'
11
+ import { useCustomerValidateToken } from '../../hooks/useCustomerValidateToken'
11
12
 
12
13
  type CustomerFabContentProps = {
13
14
  icon?: React.ReactNode
@@ -53,6 +54,8 @@ export type CustomerFabProps = Omit<CustomerFabContentProps, 'session'>
53
54
  export function CustomerFab(props: CustomerFabProps) {
54
55
  const session = useCustomerSession()
55
56
 
57
+ useCustomerValidateToken()
58
+
56
59
  return (
57
60
  <NoSsr fallback={<CustomerFabContent {...props} />}>
58
61
  <CustomerFabContent session={session} {...props} />
@@ -1,3 +1,4 @@
1
+ // eslint-disable-next-line import/no-extraneous-dependencies
1
2
  import { useGo, usePageContext } from '@graphcommerce/framer-next-pages'
2
3
  import { useQuery } from '@graphcommerce/graphql'
3
4
  import { CountryRegionsDocument } from '@graphcommerce/magento-store'
@@ -12,6 +13,7 @@ import {
12
13
  import { phonePattern, useFormGqlMutation } from '@graphcommerce/react-hook-form'
13
14
  import { i18n } from '@lingui/core'
14
15
  import { Trans } from '@lingui/react'
16
+ // eslint-disable-next-line @typescript-eslint/no-restricted-imports
15
17
  import { SxProps, TextField, Theme } from '@mui/material'
16
18
  import { AccountAddressFragment } from '../AccountAddress/AccountAddress.gql'
17
19
  import { AddressFields } from '../AddressFields/AddressFields'
@@ -2,6 +2,7 @@ import { Button, Form, FormActions, FormRow } from '@graphcommerce/next-ui'
2
2
  import { emailPattern, useFormGqlMutation } from '@graphcommerce/react-hook-form'
3
3
  import { i18n } from '@lingui/core'
4
4
  import { Trans } from '@lingui/react'
5
+ // eslint-disable-next-line @typescript-eslint/no-restricted-imports
5
6
  import { TextField, Alert, SxProps, Theme } from '@mui/material'
6
7
  import { ApolloCustomerErrorAlert } from '../ApolloCustomerError/ApolloCustomerErrorAlert'
7
8
  import {
@@ -3,6 +3,7 @@ import { FormRow, InputCheckmark } from '@graphcommerce/next-ui'
3
3
  import { assertFormGqlOperation, Controller, UseFormReturn } from '@graphcommerce/react-hook-form'
4
4
  import { i18n } from '@lingui/core'
5
5
  import { Trans } from '@lingui/react'
6
+ // eslint-disable-next-line @typescript-eslint/no-restricted-imports
6
7
  import { MenuItem, TextField } from '@mui/material'
7
8
  import React from 'react'
8
9
 
@@ -28,7 +29,7 @@ export function NameFields(props: NameFieldProps) {
28
29
  const { prefix, form, readOnly, prefixes = [mr, mrs, other] } = props
29
30
  assertFormGqlOperation<NameFieldValues>(form)
30
31
 
31
- const { control, formState, muiRegister, required, valid } = form
32
+ const { control, required, valid } = form
32
33
 
33
34
  return (
34
35
  <>
@@ -1,12 +1,12 @@
1
1
  import { Money } from '@graphcommerce/magento-store'
2
2
  import { extendableComponent, NextLink, useDateTimeFormat } from '@graphcommerce/next-ui'
3
+ import { Trans } from '@lingui/react'
3
4
  import { Box, styled, SxProps, Theme, Skeleton, ListItemButton } from '@mui/material'
4
5
  import { UseOrderCardItemImages } from '../../hooks/useOrderCardItemImages'
5
6
  import { OrderCardItemImage } from '../OrderCardItemImage/OrderCardItemImage'
6
7
  import { OrderStateLabel } from '../OrderStateLabel/OrderStateLabel'
7
8
  import { TrackingLink } from '../TrackingLink/TrackingLink'
8
9
  import { OrderCardFragment } from './OrderCard.gql'
9
- import { Trans } from '@lingui/react'
10
10
 
11
11
  type OrderCardProps = Partial<OrderCardFragment> & {
12
12
  loading?: boolean
@@ -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,6 +1,7 @@
1
+ /* eslint-disable no-console */
1
2
  import { useApolloClient } from '@graphcommerce/graphql'
2
- import { CustomerTokenDocument } from '@graphcommerce/magento-customer'
3
3
  import { Button } from '@mui/material'
4
+ import { CustomerTokenDocument } from '../../hooks/CustomerToken.gql'
4
5
 
5
6
  export function SessionDebugger() {
6
7
  const client = useApolloClient()
@@ -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, TextField, Theme } from '@mui/material'
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 { muiRegister, handleSubmit, required, formState, error } = form
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
- <TextField
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
- {...muiRegister('password', { required: required.password })}
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, 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,13 +1,13 @@
1
- import { useQuery } from '@graphcommerce/graphql'
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
- import { Alert, FormControlLabel, Switch, TextField } from '@mui/material'
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, watch, formState, error } = form
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
- <TextField
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
- <TextField
62
+ <PasswordRepeatElement
63
+ control={control}
64
+ name='confirmPassword'
65
+ passwordFieldName='password'
72
66
  variant='outlined'
73
- type='password'
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 { useQuery } from '@graphcommerce/graphql'
2
- import { StoreConfigDocument } from '@graphcommerce/magento-store'
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, TextField } from '@mui/material'
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
- >(Mutation, {
41
- // todo(paales): This causes dirty data to be send to the backend.
42
- defaultValues: {
43
- email,
44
- prefix: '-',
45
- firstname: firstname ?? '-',
46
- lastname: lastname ?? '-',
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
- onBeforeSubmit: (values) => ({ ...values, email }),
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 key='inline-signup' className={classes.row} sx={{ padding: 0 }}>
70
- <TextField
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
- <TextField
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 key='signup-submit'>
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,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 { ApolloCustomerErrorAlert } from '../ApolloCustomerError/ApolloCustomerErrorAlert'
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
- 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' />
@@ -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
+ }
@@ -2,6 +2,7 @@ fragment CustomerInfo on Customer {
2
2
  default_billing
3
3
  default_shipping
4
4
  addresses {
5
+ default_billing
5
6
  ...CustomerAddress
6
7
  }
7
8
  email
@@ -0,0 +1,5 @@
1
+ query UseCustomerValidateToken {
2
+ customer {
3
+ email
4
+ }
5
+ }
@@ -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 { NormalizedCacheObject } from '@graphcommerce/graphql'
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 = ApolloLink.from([addTokenHeader, onAuthorizationError])
21
-
22
- /** Not really required anymore, you can use customerTokenLink directly */
23
- export const createCustomerTokenLink = (_: ApolloCache<NormalizedCacheObject>) => customerTokenLink
18
+ export const customerTokenLink = addTokenHeader
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": "7.0.0-canary.21",
5
+ "version": "7.0.0",
6
6
  "sideEffects": false,
7
7
  "prettier": "@graphcommerce/prettier-config-pwa",
8
8
  "eslintConfig": {
@@ -12,24 +12,25 @@
12
12
  }
13
13
  },
14
14
  "devDependencies": {
15
- "@graphcommerce/eslint-config-pwa": "7.0.0-canary.21",
16
- "@graphcommerce/prettier-config-pwa": "7.0.0-canary.21",
17
- "@graphcommerce/typescript-config-pwa": "7.0.0-canary.21"
15
+ "@graphcommerce/eslint-config-pwa": "7.0.0",
16
+ "@graphcommerce/prettier-config-pwa": "7.0.0",
17
+ "@graphcommerce/typescript-config-pwa": "7.0.0"
18
18
  },
19
19
  "dependencies": {
20
- "@graphcommerce/ecommerce-ui": "7.0.0-canary.21",
21
- "@graphcommerce/framer-utils": "7.0.0-canary.21",
22
- "@graphcommerce/graphql": "7.0.0-canary.21",
23
- "@graphcommerce/graphql-mesh": "7.0.0-canary.21",
24
- "@graphcommerce/image": "7.0.0-canary.21",
25
- "@graphcommerce/magento-graphql": "7.0.0-canary.21",
26
- "@graphcommerce/magento-store": "7.0.0-canary.21",
27
- "@graphcommerce/next-ui": "7.0.0-canary.21",
28
- "@graphcommerce/react-hook-form": "7.0.0-canary.21"
20
+ "@graphcommerce/ecommerce-ui": "7.0.0",
21
+ "@graphcommerce/framer-utils": "7.0.0",
22
+ "@graphcommerce/graphql": "7.0.0",
23
+ "@graphcommerce/graphql-mesh": "7.0.0",
24
+ "@graphcommerce/image": "7.0.0",
25
+ "@graphcommerce/magento-graphql": "7.0.0",
26
+ "@graphcommerce/magento-store": "7.0.0",
27
+ "@graphcommerce/next-ui": "7.0.0",
28
+ "@graphcommerce/react-hook-form": "7.0.0"
29
29
  },
30
30
  "peerDependencies": {
31
- "@lingui/react": "^3.13.2",
32
- "@lingui/core": "^3.13.2",
31
+ "@lingui/react": "^4.2.1",
32
+ "@lingui/core": "^4.2.1",
33
+ "@lingui/macro": "^4.2.1",
33
34
  "@mui/material": "^5.10.16",
34
35
  "framer-motion": "^10.0.0",
35
36
  "graphql": "^16.0.0",
@@ -1,3 +1,4 @@
1
+ /* eslint-disable import/no-extraneous-dependencies */
1
2
  /* eslint-disable @typescript-eslint/no-non-null-assertion */
2
3
  import { waitForGraphQlResponse } from '@graphcommerce/graphql/test/apolloClient.fixture'
3
4
  import { SignUpDocument, SignUpMutation } from '@graphcommerce/magento-customer/components'
@@ -1,39 +0,0 @@
1
- import { InMemoryCache } from '@graphcommerce/graphql'
2
- import { onError } from '@graphcommerce/graphql/apollo'
3
- import { CustomerTokenDocument } from '../hooks/CustomerToken.gql'
4
-
5
- function invalidateToken(cache: InMemoryCache) {
6
- const res = cache.readQuery({
7
- query: CustomerTokenDocument,
8
- })
9
-
10
- if (res?.customerToken?.valid) {
11
- // Write arbitrary old token to document
12
- cache.writeQuery({
13
- query: CustomerTokenDocument,
14
- data: {
15
- customerToken: {
16
- ...res.customerToken,
17
- token: null,
18
- createdAt: new Date('2000').toUTCString(),
19
- valid: false,
20
- },
21
- },
22
- broadcast: true,
23
- })
24
- }
25
- }
26
-
27
- export const onAuthorizationError = onError(({ graphQLErrors, operation }) => {
28
- const { cache } = operation.getContext()
29
- if (graphQLErrors) {
30
- for (const err of graphQLErrors) {
31
- const isCustomerError = err.path?.join('.').toLowerCase().includes('customer')
32
- if (err.extensions?.category === 'graphql-authorization' && isCustomerError) {
33
- // Modify the operation context with a new token
34
- invalidateToken(cache as InMemoryCache)
35
- break
36
- }
37
- }
38
- }
39
- })