@graphcommerce/magento-customer 3.0.1

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 (78) hide show
  1. package/CHANGELOG.md +294 -0
  2. package/components/AccountAddress/AccountAddress.gql.ts +4 -0
  3. package/components/AccountAddress/AccountAddress.graphql +6 -0
  4. package/components/AccountAddress/index.tsx +59 -0
  5. package/components/AccountAddresses/AccountAddresses.gql.ts +4 -0
  6. package/components/AccountAddresses/AccountAddresses.graphql +5 -0
  7. package/components/AccountAddresses/UpdateDefaultAddress.gql.ts +14 -0
  8. package/components/AccountAddresses/UpdateDefaultAddress.graphql +14 -0
  9. package/components/AccountAddresses/index.tsx +101 -0
  10. package/components/AddressFields/index.tsx +189 -0
  11. package/components/AddressMultiLine/index.tsx +68 -0
  12. package/components/AddressSingleLine/index.tsx +34 -0
  13. package/components/ApolloCustomerError/ApolloCustomerErrorAlert.tsx +21 -0
  14. package/components/ApolloCustomerError/ApolloCustomerErrorFullPage.tsx +46 -0
  15. package/components/ChangeNameForm/UpdateCustomerName.gql.ts +14 -0
  16. package/components/ChangeNameForm/UpdateCustomerName.graphql +9 -0
  17. package/components/ChangeNameForm/index.tsx +66 -0
  18. package/components/ChangePasswordForm/ChangePassword.gql.ts +13 -0
  19. package/components/ChangePasswordForm/ChangePassword.graphql +5 -0
  20. package/components/ChangePasswordForm/ChangePasswordForm.tsx +93 -0
  21. package/components/CreateCustomerAddressForm/CreateCustomerAddress.gql.ts +28 -0
  22. package/components/CreateCustomerAddressForm/CreateCustomerAddress.graphql +41 -0
  23. package/components/CreateCustomerAddressForm/CustomerAddress.gql.ts +4 -0
  24. package/components/CreateCustomerAddressForm/CustomerAddress.graphql +22 -0
  25. package/components/CreateCustomerAddressForm/CustomerAddressEdit.gql.ts +4 -0
  26. package/components/CreateCustomerAddressForm/CustomerAddressEdit.graphql +4 -0
  27. package/components/CreateCustomerAddressForm/index.tsx +98 -0
  28. package/components/CustomerFab/index.tsx +55 -0
  29. package/components/CustomerMenuFabItem/index.tsx +68 -0
  30. package/components/DeleteCustomerAddressForm/DeleteCustomerAddressForm.gql.ts +12 -0
  31. package/components/DeleteCustomerAddressForm/DeleteCustomerAddressForm.graphql +3 -0
  32. package/components/DeleteCustomerAddressForm/index.tsx +46 -0
  33. package/components/EditAddressForm/UpdateCustomerAddress.gql.ts +29 -0
  34. package/components/EditAddressForm/UpdateCustomerAddress.graphql +43 -0
  35. package/components/EditAddressForm/index.tsx +127 -0
  36. package/components/ForgotPasswordForm/ForgotPassword.gql.ts +12 -0
  37. package/components/ForgotPasswordForm/ForgotPassword.graphql +3 -0
  38. package/components/ForgotPasswordForm/ForgotPasswordForm.tsx +73 -0
  39. package/components/InlineAccount/InlineAccount.gql.ts +12 -0
  40. package/components/InlineAccount/InlineAccount.graphql +10 -0
  41. package/components/InlineAccount/index.tsx +149 -0
  42. package/components/NameFields/index.tsx +89 -0
  43. package/components/ResetPasswordForm/ResetPassword.gql.ts +14 -0
  44. package/components/ResetPasswordForm/ResetPassword.graphql +3 -0
  45. package/components/ResetPasswordForm/index.tsx +98 -0
  46. package/components/SignInForm/SignIn.gql.ts +13 -0
  47. package/components/SignInForm/SignIn.graphql +5 -0
  48. package/components/SignInForm/SignInForm.tsx +96 -0
  49. package/components/SignInForm/SignInFormInline.tsx +68 -0
  50. package/components/SignOutForm/SignOutForm.gql.ts +10 -0
  51. package/components/SignOutForm/SignOutForm.graphql +5 -0
  52. package/components/SignOutForm/index.tsx +30 -0
  53. package/components/SignUpForm/SignUp.gql.ts +22 -0
  54. package/components/SignUpForm/SignUp.graphql +36 -0
  55. package/components/SignUpForm/SignUpForm.tsx +90 -0
  56. package/components/SignUpForm/SignUpFormInline.tsx +113 -0
  57. package/components/UpdateCustomerEmailForm/UpdateCustomerEmail.gql.ts +13 -0
  58. package/components/UpdateCustomerEmailForm/UpdateCustomerEmail.graphql +7 -0
  59. package/components/UpdateCustomerEmailForm/index.tsx +133 -0
  60. package/components/UpdateDefaultAddressForm/index.tsx +91 -0
  61. package/components/index.ts +29 -0
  62. package/hooks/Customer.gql.ts +10 -0
  63. package/hooks/Customer.graphql +5 -0
  64. package/hooks/CustomerInfo.gql.ts +4 -0
  65. package/hooks/CustomerInfo.graphql +20 -0
  66. package/hooks/CustomerToken.gql.ts +10 -0
  67. package/hooks/CustomerToken.graphql +8 -0
  68. package/hooks/CustomerToken.graphqls +8 -0
  69. package/hooks/IsEmailAvailable.gql.ts +12 -0
  70. package/hooks/IsEmailAvailable.graphql +5 -0
  71. package/hooks/index.ts +7 -0
  72. package/hooks/useExtractCustomerErrors.tsx +42 -0
  73. package/hooks/useFormIsEmailAvailable.tsx +69 -0
  74. package/index.ts +4 -0
  75. package/next-env.d.ts +4 -0
  76. package/package.json +41 -0
  77. package/tsconfig.json +5 -0
  78. package/typePolicies.ts +91 -0
@@ -0,0 +1,73 @@
1
+ import { makeStyles, TextField, Theme } from '@material-ui/core'
2
+ import { Alert } from '@material-ui/lab'
3
+ import { Button, Form, FormActions, FormRow } from '@graphcommerce/next-ui'
4
+ import { emailPattern, useFormGqlMutation } from '@graphcommerce/react-hook-form'
5
+ import React from 'react'
6
+ import ApolloCustomerErrorAlert from '../ApolloCustomerError/ApolloCustomerErrorAlert'
7
+ import {
8
+ ForgotPasswordDocument,
9
+ ForgotPasswordMutation,
10
+ ForgotPasswordMutationVariables,
11
+ } from './ForgotPassword.gql'
12
+
13
+ const useStyles = makeStyles(
14
+ (theme: Theme) => ({
15
+ alert: {
16
+ marginTop: theme.spacings.md,
17
+ marginBottom: theme.spacings.sm,
18
+ },
19
+ }),
20
+ { name: 'ForgotPasswordForm' },
21
+ )
22
+
23
+ export default function ForgotPasswordForm() {
24
+ const classes = useStyles()
25
+ const form = useFormGqlMutation<ForgotPasswordMutation, ForgotPasswordMutationVariables>(
26
+ ForgotPasswordDocument,
27
+ )
28
+ const { muiRegister, handleSubmit, required, data, formState, error } = form
29
+ const submitHandler = handleSubmit(() => {})
30
+
31
+ if (formState.isSubmitSuccessful && data) {
32
+ return (
33
+ <Alert severity='success' variant='standard' className={classes.alert}>
34
+ We&apos;ve send a password reset link to your account!
35
+ </Alert>
36
+ )
37
+ }
38
+
39
+ return (
40
+ <Form onSubmit={submitHandler} noValidate>
41
+ <FormRow>
42
+ <TextField
43
+ variant='outlined'
44
+ type='text'
45
+ error={!!formState.errors.email}
46
+ label='Email'
47
+ required={required.email}
48
+ {...muiRegister('email', {
49
+ required: required.email,
50
+ pattern: { value: emailPattern, message: 'Invalid email address' },
51
+ })}
52
+ helperText={formState.errors.email?.message}
53
+ disabled={formState.isSubmitting}
54
+ />
55
+ </FormRow>
56
+
57
+ <ApolloCustomerErrorAlert error={error} />
58
+
59
+ <FormActions>
60
+ <Button
61
+ type='submit'
62
+ loading={formState.isSubmitting}
63
+ color='primary'
64
+ variant='contained'
65
+ size='large'
66
+ text='bold'
67
+ >
68
+ Send password reset email
69
+ </Button>
70
+ </FormActions>
71
+ </Form>
72
+ )
73
+ }
@@ -0,0 +1,12 @@
1
+ /* eslint-disable */
2
+ import * as Types from '@graphcommerce/graphql';
3
+
4
+ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
5
+
6
+ export const InlineAccountDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"InlineAccount"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"cartId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cart"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"cart_id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"cartId"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"shipping_addresses"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"CartAddressInterface"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"Field","name":{"kind":"Name","value":"firstname"}},{"kind":"Field","name":{"kind":"Name","value":"lastname"}},{"kind":"Field","name":{"kind":"Name","value":"city"}},{"kind":"Field","name":{"kind":"Name","value":"company"}},{"kind":"Field","name":{"kind":"Name","value":"country"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"label"}}]}},{"kind":"Field","name":{"kind":"Name","value":"postcode"}},{"kind":"Field","name":{"kind":"Name","value":"region"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"code"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"region_id"}}]}},{"kind":"Field","name":{"kind":"Name","value":"street"}},{"kind":"Field","name":{"kind":"Name","value":"telephone"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]} as unknown as DocumentNode<InlineAccountQuery, InlineAccountQueryVariables>;
7
+ export type InlineAccountQueryVariables = Types.Exact<{
8
+ cartId: Types.Scalars['String'];
9
+ }>;
10
+
11
+
12
+ export type InlineAccountQuery = { cart?: Types.Maybe<{ __typename: 'Cart', id: string, email?: Types.Maybe<string>, shipping_addresses: Array<Types.Maybe<{ __typename: 'ShippingCartAddress', firstname: string, lastname: string, city: string, company?: Types.Maybe<string>, postcode?: Types.Maybe<string>, street: Array<Types.Maybe<string>>, telephone: string, country: { code: string, label: string }, region?: Types.Maybe<{ code?: Types.Maybe<string>, label?: Types.Maybe<string>, region_id?: Types.Maybe<number> }> }>> }> };
@@ -0,0 +1,10 @@
1
+ query InlineAccount($cartId: String!) {
2
+ cart(cart_id: $cartId) {
3
+ __typename
4
+ id
5
+ shipping_addresses {
6
+ ...CartAddress
7
+ }
8
+ email
9
+ }
10
+ }
@@ -0,0 +1,149 @@
1
+ import { useQuery } from '@apollo/client'
2
+ import { makeStyles, TextField, Theme, Typography } from '@material-ui/core'
3
+ import { useCartQuery } from '@graphcommerce/magento-cart'
4
+ import Button from '@graphcommerce/next-ui/Button'
5
+ import FormRow from '@graphcommerce/next-ui/Form/FormRow'
6
+ import { UseStyles } from '@graphcommerce/next-ui/Styles'
7
+ import React, { useState } from 'react'
8
+ import { CustomerTokenDocument, IsEmailAvailableDocument } from '../../hooks'
9
+ import { InlineAccountDocument } from './InlineAccount.gql'
10
+ import SignUpFormInline from '../SignUpForm/SignUpFormInline'
11
+
12
+ const useStyles = makeStyles(
13
+ (theme: Theme) => ({
14
+ root: {
15
+ borderRadius: 4,
16
+ border: `1px solid ${theme.palette.divider}`,
17
+ padding: theme.spacings.md,
18
+ },
19
+ innerContainer: {
20
+ display: 'flex',
21
+ justifyContent: 'space-between',
22
+ flexDirection: 'column',
23
+ alignItems: 'flex-start',
24
+ gap: 32,
25
+ [theme.breakpoints.up('sm')]: {
26
+ alignItems: 'flex-end',
27
+ flexDirection: 'unset',
28
+ gap: 0,
29
+ },
30
+ },
31
+ form: {
32
+ marginTop: theme.spacings.sm,
33
+ },
34
+ button: {
35
+ minWidth: 160,
36
+ },
37
+ title: {
38
+ paddingBottom: 8,
39
+ },
40
+ }),
41
+ { name: 'InlineAccount' },
42
+ )
43
+
44
+ type InlineAccountProps = {
45
+ title?: React.ReactNode
46
+ description?: React.ReactNode
47
+ accountHref: string
48
+ } & UseStyles<typeof useStyles>
49
+
50
+ export default function InlineAccount(props: InlineAccountProps) {
51
+ const { title, description, accountHref } = props
52
+ const classes = useStyles(props)
53
+
54
+ const [toggled, setToggled] = useState<boolean>(false)
55
+
56
+ const { loading, data } = useCartQuery(InlineAccountDocument)
57
+ const cart = data?.cart
58
+
59
+ const { data: customerTokenData } = useQuery(CustomerTokenDocument)
60
+ const { data: isEmailAvailableData } = useQuery(IsEmailAvailableDocument, {
61
+ variables: {
62
+ email: cart?.email ?? '',
63
+ },
64
+ })
65
+
66
+ const { firstname, lastname } = cart?.shipping_addresses?.[0] ?? {}
67
+ const signedIn = Boolean(
68
+ customerTokenData?.customerToken && customerTokenData?.customerToken.valid,
69
+ )
70
+ const canSignUp = isEmailAvailableData?.isEmailAvailable?.is_email_available === true
71
+
72
+ if (!canSignUp) return <></>
73
+
74
+ return (
75
+ <div>
76
+ <div key='signupaccount' className={classes.root}>
77
+ {!signedIn && canSignUp && (
78
+ <>
79
+ <div className={classes.innerContainer}>
80
+ <div>
81
+ <Typography variant='h4' className={classes.title}>
82
+ {title ?? 'No account yet?'}
83
+ </Typography>
84
+ {description ?? 'You can track your order status and much more!'}
85
+ </div>
86
+ <div>
87
+ {!toggled && (
88
+ <Button
89
+ variant='pill'
90
+ color='secondary'
91
+ text='bold'
92
+ loading={loading}
93
+ onClick={() => setToggled(!toggled)}
94
+ className={classes.button}
95
+ >
96
+ Create an account
97
+ </Button>
98
+ )}
99
+ </div>
100
+ </div>
101
+ {cart?.email && toggled && (
102
+ <div className={classes.form}>
103
+ <FormRow>
104
+ <TextField
105
+ variant='outlined'
106
+ type='email'
107
+ label='Email'
108
+ value={cart?.email}
109
+ InputProps={{
110
+ readOnly: true,
111
+ }}
112
+ />
113
+ </FormRow>
114
+ <SignUpFormInline
115
+ firstname={firstname ?? ''}
116
+ lastname={lastname}
117
+ email={cart?.email}
118
+ onSubmitted={() => setToggled(false)}
119
+ />
120
+ </div>
121
+ )}
122
+ </>
123
+ )}
124
+
125
+ {signedIn && (
126
+ <div className={classes.innerContainer}>
127
+ <div>
128
+ <Typography variant='h4' className={classes.title}>
129
+ {title ?? 'Have an account?'}
130
+ </Typography>
131
+ {description ?? 'You can find your order history in your account!'}
132
+ </div>
133
+ <div>
134
+ <Button
135
+ variant='pill'
136
+ color='secondary'
137
+ text='bold'
138
+ href={accountHref}
139
+ className={classes.button}
140
+ >
141
+ My account
142
+ </Button>
143
+ </div>
144
+ </div>
145
+ )}
146
+ </div>
147
+ </div>
148
+ )
149
+ }
@@ -0,0 +1,89 @@
1
+ import { MenuItem, TextField } from '@material-ui/core'
2
+ import { FormRow, InputCheckmark } from '@graphcommerce/next-ui'
3
+ import { assertFormGqlOperation, Controller, UseFormReturn } from '@graphcommerce/react-hook-form'
4
+ import React from 'react'
5
+
6
+ type NameFieldValues = {
7
+ firstname?: string
8
+ lastname?: string
9
+ prefix?: string
10
+ }
11
+
12
+ type NameFieldProps = {
13
+ form: UseFormReturn<any>
14
+ readOnly?: boolean
15
+ prefix?: boolean
16
+ }
17
+
18
+ export default function NameFields(props: NameFieldProps) {
19
+ const { prefix, form, readOnly } = props
20
+ assertFormGqlOperation<NameFieldValues>(form)
21
+
22
+ const { control, formState, muiRegister, required, valid } = form
23
+
24
+ return (
25
+ <>
26
+ <FormRow>
27
+ {prefix && (
28
+ <Controller
29
+ defaultValue='Dhr.'
30
+ control={control}
31
+ name='prefix'
32
+ render={({ field: { ref, onChange, ...field }, fieldState }) => (
33
+ <TextField
34
+ variant='outlined'
35
+ select
36
+ error={!!fieldState.error}
37
+ label='Prefix'
38
+ required={!!required?.prefix}
39
+ helperText={fieldState.error?.message}
40
+ onChange={(e) => onChange(e.target.value)}
41
+ inputRef={ref}
42
+ InputProps={{
43
+ readOnly,
44
+ endAdornment: <InputCheckmark show={valid.prefix} />,
45
+ }}
46
+ {...field}
47
+ >
48
+ {['Dhr.', 'Mevr.'].map((option) => (
49
+ <MenuItem key={option} value={option}>
50
+ {option}
51
+ </MenuItem>
52
+ ))}
53
+ </TextField>
54
+ )}
55
+ />
56
+ )}
57
+ </FormRow>
58
+
59
+ <FormRow>
60
+ <TextField
61
+ variant='outlined'
62
+ type='text'
63
+ label='First Name'
64
+ required={!!required}
65
+ error={!!formState.errors.firstname}
66
+ helperText={formState.isSubmitted && formState.errors.firstname?.message}
67
+ InputProps={{
68
+ readOnly,
69
+ endAdornment: <InputCheckmark show={valid.firstname} />,
70
+ }}
71
+ {...muiRegister('firstname', { required: required?.firstname })}
72
+ />
73
+ <TextField
74
+ variant='outlined'
75
+ type='text'
76
+ error={!!formState.errors.lastname}
77
+ label='Last Name'
78
+ required={!!required?.lastname}
79
+ helperText={formState.isSubmitted && formState.errors.lastname?.message}
80
+ InputProps={{
81
+ readOnly,
82
+ endAdornment: <InputCheckmark show={valid.lastname} />,
83
+ }}
84
+ {...muiRegister('lastname', { required: required?.lastname })}
85
+ />
86
+ </FormRow>
87
+ </>
88
+ )
89
+ }
@@ -0,0 +1,14 @@
1
+ /* eslint-disable */
2
+ import * as Types from '@graphcommerce/graphql';
3
+
4
+ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
5
+
6
+ export const ResetPasswordDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ResetPassword"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"resetPasswordToken"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"resetPassword"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"Argument","name":{"kind":"Name","value":"resetPasswordToken"},"value":{"kind":"Variable","name":{"kind":"Name","value":"resetPasswordToken"}}},{"kind":"Argument","name":{"kind":"Name","value":"newPassword"},"value":{"kind":"Variable","name":{"kind":"Name","value":"newPassword"}}}]}]}}]} as unknown as DocumentNode<ResetPasswordMutation, ResetPasswordMutationVariables>;
7
+ export type ResetPasswordMutationVariables = Types.Exact<{
8
+ email: Types.Scalars['String'];
9
+ resetPasswordToken: Types.Scalars['String'];
10
+ newPassword: Types.Scalars['String'];
11
+ }>;
12
+
13
+
14
+ export type ResetPasswordMutation = { resetPassword?: Types.Maybe<boolean> };
@@ -0,0 +1,3 @@
1
+ mutation ResetPassword($email: String!, $resetPasswordToken: String!, $newPassword: String!) {
2
+ resetPassword(email: $email, resetPasswordToken: $resetPasswordToken, newPassword: $newPassword)
3
+ }
@@ -0,0 +1,98 @@
1
+ import { TextField } from '@material-ui/core'
2
+ import { Button, Form, FormActions, FormRow } from '@graphcommerce/next-ui'
3
+ import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
4
+ import { useRouter } from 'next/router'
5
+ import React from 'react'
6
+ import ApolloCustomerErrorAlert from '../ApolloCustomerError/ApolloCustomerErrorAlert'
7
+ import {
8
+ ResetPasswordDocument,
9
+ ResetPasswordMutation,
10
+ ResetPasswordMutationVariables,
11
+ } from './ResetPassword.gql'
12
+
13
+ type ResetPasswordFormProps = {
14
+ token: string
15
+ }
16
+
17
+ export default function ResetPasswordForm(props: ResetPasswordFormProps) {
18
+ const { token } = props
19
+
20
+ const form = useFormGqlMutation<
21
+ ResetPasswordMutation,
22
+ ResetPasswordMutationVariables & { confirmPassword?: string }
23
+ >(ResetPasswordDocument, {
24
+ onBeforeSubmit: (formData) => ({
25
+ ...formData,
26
+ resetPasswordToken: token,
27
+ }),
28
+ })
29
+
30
+ const router = useRouter()
31
+ const { muiRegister, handleSubmit, required, watch, data, formState, error } = form
32
+ const submitHandler = handleSubmit(() => {})
33
+ const newPass = watch('newPassword')
34
+
35
+ if (formState.isSubmitSuccessful && data) {
36
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
37
+ router.replace(`${window.location.href.split('?')[0]}?success=1`)
38
+ }
39
+
40
+ return (
41
+ <Form onSubmit={submitHandler} noValidate>
42
+ <FormRow>
43
+ <TextField
44
+ variant='outlined'
45
+ type='email'
46
+ error={!!formState.errors.email}
47
+ label='Email'
48
+ required={required.email}
49
+ {...muiRegister('email', { required: required.email })}
50
+ helperText={formState.errors.email?.message}
51
+ disabled={formState.isSubmitting}
52
+ />
53
+ </FormRow>
54
+ <FormRow>
55
+ <TextField
56
+ variant='outlined'
57
+ type='password'
58
+ error={!!formState.errors.newPassword}
59
+ label='New password'
60
+ required={required.newPassword}
61
+ {...muiRegister('newPassword', { required: required.newPassword })}
62
+ helperText={formState.errors.newPassword?.message}
63
+ disabled={formState.isSubmitting}
64
+ />
65
+ </FormRow>
66
+ <FormRow>
67
+ <TextField
68
+ variant='outlined'
69
+ type='password'
70
+ error={!!formState.errors.confirmPassword}
71
+ label='Confirm password'
72
+ required
73
+ {...muiRegister('confirmPassword', {
74
+ required: true,
75
+ validate: (value) => value === newPass || "Passwords don't match",
76
+ })}
77
+ helperText={formState.errors.confirmPassword?.message}
78
+ disabled={formState.isSubmitting}
79
+ />
80
+ </FormRow>
81
+
82
+ <ApolloCustomerErrorAlert error={error} />
83
+
84
+ <FormActions>
85
+ <Button
86
+ type='submit'
87
+ loading={formState.isSubmitting}
88
+ color='primary'
89
+ variant='contained'
90
+ size='large'
91
+ text='bold'
92
+ >
93
+ Save new password
94
+ </Button>
95
+ </FormActions>
96
+ </Form>
97
+ )
98
+ }
@@ -0,0 +1,13 @@
1
+ /* eslint-disable */
2
+ import * as Types from '@graphcommerce/graphql';
3
+
4
+ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
5
+
6
+ export const SignInDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SignIn"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"email"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"password"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"generateCustomerToken"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"email"},"value":{"kind":"Variable","name":{"kind":"Name","value":"email"}}},{"kind":"Argument","name":{"kind":"Name","value":"password"},"value":{"kind":"Variable","name":{"kind":"Name","value":"password"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"token"}}]}}]}}]} as unknown as DocumentNode<SignInMutation, SignInMutationVariables>;
7
+ export type SignInMutationVariables = Types.Exact<{
8
+ email: Types.Scalars['String'];
9
+ password: Types.Scalars['String'];
10
+ }>;
11
+
12
+
13
+ export type SignInMutation = { generateCustomerToken?: Types.Maybe<{ token?: Types.Maybe<string> }> };
@@ -0,0 +1,5 @@
1
+ mutation SignIn($email: String!, $password: String!) {
2
+ generateCustomerToken(email: $email, password: $password) {
3
+ token
4
+ }
5
+ }
@@ -0,0 +1,96 @@
1
+ import { useQuery } from '@apollo/client'
2
+ import { FormControl, Link, makeStyles, TextField, Theme } from '@material-ui/core'
3
+ import { Alert } from '@material-ui/lab'
4
+ import { graphqlErrorByCategory } from '@graphcommerce/magento-graphql'
5
+ import { FormRow, Button, FormActions } from '@graphcommerce/next-ui'
6
+ import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
7
+ import PageLink from 'next/link'
8
+ import React from 'react'
9
+ import { CustomerTokenDocument } from '../../hooks'
10
+ import ApolloCustomerErrorAlert from '../ApolloCustomerError/ApolloCustomerErrorAlert'
11
+ import { SignInDocument } from './SignIn.gql'
12
+
13
+ const useStyles = makeStyles(
14
+ (theme: Theme) => ({
15
+ forgotPass: {
16
+ whiteSpace: 'nowrap',
17
+ },
18
+ }),
19
+ { name: 'SignIn' },
20
+ )
21
+
22
+ type SignInFormProps = { email: string }
23
+
24
+ export default function SignInForm(props: SignInFormProps) {
25
+ const { email } = props
26
+ const classes = useStyles()
27
+ const { data } = useQuery(CustomerTokenDocument)
28
+ const form = useFormGqlMutation(
29
+ SignInDocument,
30
+ { defaultValues: { email } },
31
+ { errorPolicy: 'all' },
32
+ )
33
+
34
+ const { muiRegister, handleSubmit, required, formState, error } = form
35
+ const [remainingError, authError] = graphqlErrorByCategory({
36
+ category: 'graphql-authentication',
37
+ error,
38
+ })
39
+ const submitHandler = handleSubmit(() => {})
40
+
41
+ const requireAuth = Boolean(data?.customerToken && !data?.customerToken.valid)
42
+
43
+ return (
44
+ <form onSubmit={submitHandler} noValidate>
45
+ {requireAuth && (
46
+ <Alert severity='error' variant='standard'>
47
+ Your session has expired, please reauthenticate
48
+ </Alert>
49
+ )}
50
+
51
+ <FormRow>
52
+ <TextField
53
+ key='password'
54
+ variant='outlined'
55
+ type='password'
56
+ error={!!formState.errors.password || !!authError}
57
+ label='Password'
58
+ autoFocus
59
+ autoComplete='current-password'
60
+ id='current-password'
61
+ required={required.password}
62
+ {...muiRegister('password', { required: required.password })}
63
+ FormHelperTextProps={{
64
+ className: classes.forgotPass,
65
+ }}
66
+ InputProps={{
67
+ endAdornment: (
68
+ <PageLink href='/account/forgot-password' key='forgot-password' passHref>
69
+ <Link className={classes.forgotPass}>Forgot password?</Link>
70
+ </PageLink>
71
+ ),
72
+ }}
73
+ helperText={formState.errors.password?.message || authError?.message}
74
+ disabled={formState.isSubmitting}
75
+ />
76
+ </FormRow>
77
+
78
+ <ApolloCustomerErrorAlert error={remainingError} key='error' />
79
+
80
+ <FormActions>
81
+ <FormControl>
82
+ <Button
83
+ type='submit'
84
+ loading={formState.isSubmitting}
85
+ color='primary'
86
+ variant='contained'
87
+ size='large'
88
+ text='bold'
89
+ >
90
+ Log In
91
+ </Button>
92
+ </FormControl>
93
+ </FormActions>
94
+ </form>
95
+ )
96
+ }
@@ -0,0 +1,68 @@
1
+ import { makeStyles, TextField, Theme } from '@material-ui/core'
2
+ import { Button, Form } from '@graphcommerce/next-ui'
3
+ import { useFormGqlMutation } from '@graphcommerce/react-hook-form'
4
+ import PageLink from 'next/link'
5
+ import React, { PropsWithChildren } from 'react'
6
+ import { SignInDocument, SignInMutationVariables } from './SignIn.gql'
7
+
8
+ const useStyles = makeStyles(
9
+ (theme: Theme) => ({
10
+ form: {
11
+ display: 'grid',
12
+ alignItems: 'center',
13
+ gridRowGap: theme.spacings.sm,
14
+ gridColumnGap: theme.spacings.xs,
15
+ gridTemplateColumns: '1fr',
16
+ [theme.breakpoints.up('sm')]: {
17
+ gridTemplateColumns: '5fr 1fr',
18
+ },
19
+ },
20
+ button: {
21
+ minWidth: 'max-content',
22
+ },
23
+ }),
24
+ { name: 'SignInFormInline' },
25
+ )
26
+
27
+ type InlineSignInFormProps = Omit<SignInMutationVariables, 'password'>
28
+
29
+ export default function SignInFormInline({ email }: PropsWithChildren<InlineSignInFormProps>) {
30
+ const classes = useStyles()
31
+ const form = useFormGqlMutation(
32
+ SignInDocument,
33
+ { defaultValues: { email } },
34
+ { errorPolicy: 'all' },
35
+ )
36
+ const { muiRegister, handleSubmit, required, formState, error } = form
37
+ const submitHandler = handleSubmit(() => {})
38
+
39
+ return (
40
+ <form onSubmit={submitHandler} noValidate className={classes.form}>
41
+ <TextField
42
+ variant='outlined'
43
+ type='password'
44
+ error={!!formState.errors.password || !!error?.message}
45
+ label='Password'
46
+ autoFocus
47
+ autoComplete='current-password'
48
+ id='current-password'
49
+ required={required.password}
50
+ {...muiRegister('password', { required: required.password })}
51
+ helperText={error?.message}
52
+ disabled={formState.isSubmitting}
53
+ InputProps={{
54
+ endAdornment: (
55
+ <PageLink href='/account/forgot-password' key='forgot-password' passHref>
56
+ <Button color='secondary' variant='text' className={classes.button}>
57
+ Forgot password?
58
+ </Button>
59
+ </PageLink>
60
+ ),
61
+ }}
62
+ />
63
+ <Button type='submit' loading={formState.isSubmitting} color='secondary' variant='pill'>
64
+ Sign in
65
+ </Button>
66
+ </form>
67
+ )
68
+ }
@@ -0,0 +1,10 @@
1
+ /* eslint-disable */
2
+ import * as Types from '@graphcommerce/graphql';
3
+
4
+ import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
5
+
6
+ export const SignOutFormDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SignOutForm"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"revokeCustomerToken"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"result"}}]}}]}}]} as unknown as DocumentNode<SignOutFormMutation, SignOutFormMutationVariables>;
7
+ export type SignOutFormMutationVariables = Types.Exact<{ [key: string]: never; }>;
8
+
9
+
10
+ export type SignOutFormMutation = { revokeCustomerToken?: Types.Maybe<{ result: boolean }> };
@@ -0,0 +1,5 @@
1
+ mutation SignOutForm {
2
+ revokeCustomerToken {
3
+ result
4
+ }
5
+ }