mirror of
https://github.com/vercel/commerce.git
synced 2025-07-22 12:24:18 +00:00
Merge pull request #73 from KieIO/feature/m2-forgot-password
Feature/m2 forgot password
This commit is contained in:
39
framework/vendure/schema.d.ts
vendored
39
framework/vendure/schema.d.ts
vendored
@@ -1,3 +1,5 @@
|
||||
import { ResetPassword } from './schema.d';
|
||||
import { requestPasswordReset } from '@framework/schema';
|
||||
import { FacetValue } from './schema.d';
|
||||
export type Maybe<T> = T | null
|
||||
export type Exact<T extends { [key: string]: unknown }> = {
|
||||
@@ -3126,6 +3128,36 @@ export type LoginMutation = { __typename?: 'Mutation' } & {
|
||||
>)
|
||||
}
|
||||
|
||||
export type ResetPasswordMutation = { __typename?: 'Mutation' } & {
|
||||
resetPassword:
|
||||
| ({ __typename: 'CurrentUser' } & Pick<CurrentUser, 'id'>)
|
||||
| ({ __typename: 'PasswordResetTokenInvalidError' } & Pick<
|
||||
PasswordResetTokenInvalidError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
| ({ __typename: 'PasswordResetTokenExpiredError' } & Pick<
|
||||
PasswordResetTokenExpiredError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
|
||||
NativeAuthStrategyError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
}
|
||||
|
||||
export type SignupMutation = { __typename?: 'Mutation' } & {
|
||||
registerCustomerAccount:
|
||||
| ({ __typename: 'Success' } & Pick<Success, 'success'>)
|
||||
| ({ __typename: 'MissingPasswordError' } & Pick<
|
||||
MissingPasswordError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
| ({ __typename: 'NativeAuthStrategyError' } & Pick<
|
||||
NativeAuthStrategyError,
|
||||
'errorCode' | 'message'
|
||||
>)
|
||||
}
|
||||
|
||||
export type VerifyCustomerAccountVariables = Exact<{
|
||||
token: Scalars['String']
|
||||
password?: Maybe<Scalars['String']>
|
||||
@@ -3179,8 +3211,9 @@ export type SignupMutationVariables = Exact<{
|
||||
input: RegisterCustomerInput
|
||||
}>
|
||||
|
||||
export type SignupMutation = { __typename?: 'Mutation' } & {
|
||||
registerCustomerAccount:
|
||||
|
||||
export type RequestPasswordReset = { __typename?: 'Mutation' } & {
|
||||
requestPasswordReset:
|
||||
| ({ __typename: 'Success' } & Pick<Success, 'success'>)
|
||||
| ({ __typename: 'MissingPasswordError' } & Pick<
|
||||
MissingPasswordError,
|
||||
@@ -3192,6 +3225,8 @@ export type SignupMutation = { __typename?: 'Mutation' } & {
|
||||
>)
|
||||
}
|
||||
|
||||
|
||||
|
||||
export type ActiveCustomerQueryVariables = Exact<{ [key: string]: never }>
|
||||
|
||||
export type ActiveCustomerQuery = { __typename?: 'Query' } & {
|
||||
|
@@ -0,0 +1,14 @@
|
||||
export const requestPasswordReset = /* GraphQL */ `
|
||||
mutation RequestPasswordReset($emailAddress: String!) {
|
||||
requestPasswordReset(emailAddress: $emailAddress) {
|
||||
__typename
|
||||
...on Success{
|
||||
success
|
||||
}
|
||||
...on ErrorResult{
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
15
framework/vendure/utils/mutations/reset-password-mutation.ts
Normal file
15
framework/vendure/utils/mutations/reset-password-mutation.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export const resetPasswordMutation = /* GraphQL */ `
|
||||
mutation resetPassword($token: String!,$password: String!){
|
||||
resetPassword(token: $token,password: $password){
|
||||
__typename
|
||||
...on CurrentUser{
|
||||
id
|
||||
identifier
|
||||
}
|
||||
...on ErrorResult{
|
||||
errorCode
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
10
pages/forgot-password.tsx
Normal file
10
pages/forgot-password.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FormForgot, Layout } from 'src/components/common'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<FormForgot/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
NotFound.Layout = Layout
|
10
pages/reset-password.tsx
Normal file
10
pages/reset-password.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import { FormResetPassword, Layout } from 'src/components/common'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div>
|
||||
<FormResetPassword/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
NotFound.Layout = Layout
|
@@ -0,0 +1,22 @@
|
||||
@import '../../../../styles/utilities';
|
||||
.formAuthen{
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
padding: 4rem 0 ;
|
||||
.title{
|
||||
@apply font-heading heading-3;
|
||||
padding: 0 1.6rem 0 0.8rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.bottom {
|
||||
@apply flex justify-between items-center;
|
||||
margin: 4rem auto;
|
||||
.remembered {
|
||||
@apply font-bold cursor-pointer;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
.socialAuthen{
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
import { Form, Formik } from 'formik';
|
||||
import React, { useRef } from 'react';
|
||||
import { ButtonCommon, InputFiledInForm } from 'src/components/common';
|
||||
import { useModalCommon } from 'src/components/hooks';
|
||||
import useRequestPasswordReset from 'src/components/hooks/auth/useRequestPasswordReset';
|
||||
import { CustomInputCommon } from 'src/utils/type.utils';
|
||||
import * as Yup from 'yup';
|
||||
import ModalAuthenticate from '../../ModalAuthenticate/ModalAuthenticate';
|
||||
import { default as s, default as styles } from './FormForgot.module.scss';
|
||||
import { useMessage } from 'src/components/contexts'
|
||||
import { LANGUAGE } from 'src/utils/language.utils'
|
||||
|
||||
interface Props {
|
||||
|
||||
}
|
||||
const DisplayingErrorMessagesSchema = Yup.object().shape({
|
||||
email: Yup.string().email('Your email was wrong').required('Required')
|
||||
})
|
||||
|
||||
const FormForgot = ({ }: Props) => {
|
||||
const {requestPassword} = useRequestPasswordReset();
|
||||
const { showMessageSuccess, showMessageError } = useMessage();
|
||||
|
||||
const emailRef = useRef<CustomInputCommon>(null);
|
||||
|
||||
const { visible: visibleModalAuthen,closeModal: closeModalAuthen, openModal: openModalAuthen } = useModalCommon({ initialValue: false });
|
||||
|
||||
const onForgot = (values: { email: string }) => {
|
||||
requestPassword({email: values.email},onForgotPasswordCallBack);
|
||||
}
|
||||
|
||||
const onForgotPasswordCallBack = (isSuccess: boolean, message?: string) => {
|
||||
if (isSuccess) {
|
||||
showMessageSuccess("Request forgot password successfully. Please verify your email to login.")
|
||||
} else {
|
||||
showMessageError(message || LANGUAGE.MESSAGE.ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={s.formAuthen}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<div className={s.title}>Forgot Password</div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
email: '',
|
||||
}}
|
||||
validationSchema={DisplayingErrorMessagesSchema}
|
||||
onSubmit={onForgot}
|
||||
>
|
||||
{({ errors, touched, isValid, submitForm }) => (
|
||||
<Form className="u-form">
|
||||
<div className="body">
|
||||
<InputFiledInForm
|
||||
name="email"
|
||||
placeholder="Email Address"
|
||||
ref={emailRef}
|
||||
error={
|
||||
touched.email && errors.email
|
||||
? errors.email.toString()
|
||||
: ''
|
||||
}
|
||||
isShowIconSuccess={touched.email && !errors.email}
|
||||
onEnter={isValid ? submitForm : undefined}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<div className={styles.remembered} onClick={openModalAuthen}>
|
||||
I Remembered My Password?
|
||||
</div>
|
||||
<ButtonCommon HTMLType='submit' size="large">
|
||||
Reset Password
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
<ModalAuthenticate visible={visibleModalAuthen} closeModal={closeModalAuthen} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
export default FormForgot;
|
@@ -0,0 +1,27 @@
|
||||
@import '../../../../styles/utilities';
|
||||
.formAuthen{
|
||||
width: 50%;
|
||||
margin: 0 auto;
|
||||
padding: 4rem 0 ;
|
||||
.title{
|
||||
@apply font-heading heading-3;
|
||||
padding: 0 1.6rem 0 0.8rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.passwordNote {
|
||||
@apply text-center caption text-label;
|
||||
margin-top: 0.8rem;
|
||||
}
|
||||
.bottom {
|
||||
@apply flex justify-center items-center;
|
||||
margin: 4rem auto;
|
||||
.remembered {
|
||||
@apply font-bold cursor-pointer;
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
.confirmPassword{
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
import { Form, Formik } from 'formik';
|
||||
import React, { useRef } from 'react';
|
||||
import { ButtonCommon, InputPasswordFiledInForm } from 'src/components/common';
|
||||
import { useMessage } from 'src/components/contexts';
|
||||
import useRequestPasswordReset from 'src/components/hooks/auth/useRequestPasswordReset';
|
||||
import { LANGUAGE } from 'src/utils/language.utils';
|
||||
import { CustomInputCommon } from 'src/utils/type.utils';
|
||||
import * as Yup from 'yup';
|
||||
import { useRouter } from 'next/router'
|
||||
import { default as s, default as styles } from './FormResetPassword.module.scss';
|
||||
import { useResetPassword } from 'src/components/hooks/auth';
|
||||
|
||||
interface Props {
|
||||
|
||||
}
|
||||
const DisplayingErrorMessagesSchema = Yup.object().shape({
|
||||
password: Yup.string()
|
||||
.matches(
|
||||
/^(?=.{8,})(?=.*[a-z])(?=.*[A-Z])((?=.*[0-9!@#$%^&*()\-_=+{};:,<.>]){1}).*$/,
|
||||
'Must contain 8 characters with at least 1 uppercase and 1 lowercase letter and either 1 number or 1 special character.'
|
||||
)
|
||||
.max(30, 'Password is too long')
|
||||
.required('Required'),
|
||||
confirmPassword: Yup.string()
|
||||
.label('Password Confirm')
|
||||
.required()
|
||||
.oneOf([Yup.ref('password')], 'Passwords does not match'),
|
||||
})
|
||||
|
||||
const FormResetPassword = ({ }: Props) => {
|
||||
const router = useRouter();
|
||||
|
||||
const {resetPassword} = useResetPassword();
|
||||
|
||||
const { showMessageSuccess, showMessageError } = useMessage();
|
||||
|
||||
const onReset = (values: {password: string }) => {
|
||||
const { token } = router.query;
|
||||
resetPassword({token:token,password: values.password},onResetPasswordCallBack);
|
||||
}
|
||||
|
||||
const onResetPasswordCallBack = (isSuccess: boolean, message?: string) => {
|
||||
if (isSuccess) {
|
||||
showMessageSuccess("Reset password successfully. Please to login.")
|
||||
} else {
|
||||
showMessageError(message || LANGUAGE.MESSAGE.ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={s.formAuthen}>
|
||||
<div className={s.inner}>
|
||||
<div className={s.body}>
|
||||
<div className={s.title}>Reset Password</div>
|
||||
<Formik
|
||||
initialValues={{
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
}}
|
||||
validationSchema={DisplayingErrorMessagesSchema}
|
||||
onSubmit={onReset}
|
||||
>
|
||||
{({ errors, touched, isValid, submitForm }) => (
|
||||
<Form className="u-form">
|
||||
<div>
|
||||
<InputPasswordFiledInForm
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
error={
|
||||
touched.password && errors.password
|
||||
? errors.password.toString()
|
||||
: ''
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={s.confirmPassword}>
|
||||
<InputPasswordFiledInForm
|
||||
name="confirmPassword"
|
||||
placeholder="Password confirm"
|
||||
error={
|
||||
touched.confirmPassword && errors.confirmPassword
|
||||
? errors.confirmPassword.toString()
|
||||
: ''
|
||||
}
|
||||
onEnter={isValid ? submitForm : undefined}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className={styles.passwordNote}>
|
||||
Must contain 8 characters with at least 1 uppercase and 1
|
||||
lowercase letter and either 1 number or 1 special character.
|
||||
</div>
|
||||
<div className={styles.bottom}>
|
||||
<ButtonCommon HTMLType='submit' size="large">
|
||||
Change Password
|
||||
</ButtonCommon>
|
||||
</div>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export default FormResetPassword;
|
@@ -58,6 +58,10 @@ const HeaderMenu = memo(
|
||||
onClick: openModalRegister,
|
||||
name: 'Create account',
|
||||
},
|
||||
{
|
||||
link: '/forgot-password',
|
||||
name: 'Forgot Password',
|
||||
},
|
||||
],
|
||||
[openModalLogin, openModalRegister]
|
||||
)
|
||||
|
@@ -51,6 +51,8 @@ export { default as LayoutCheckout} from './LayoutCheckout/LayoutCheckout'
|
||||
export { default as InputPasswordFiledInForm} from './InputPasswordFiledInForm/InputPasswordFiledInForm'
|
||||
export { default as InputFiledInForm} from './InputFiledInForm/InputFiledInForm'
|
||||
export { default as MessageCommon} from './MessageCommon/MessageCommon'
|
||||
export { default as FormForgot} from './ForgotPassword/FormForgot/FormForgot'
|
||||
export { default as FormResetPassword} from './ForgotPassword/FormResetPassword/FormResetPassword'
|
||||
export { default as ProductCardSkeleton} from './ProductCardSkeleton/ProductCardSkeleton'
|
||||
export { default as ListProductCardSkeleton} from './ListProductCardSkeleton/ListProductCardSkeleton'
|
||||
|
||||
|
@@ -3,4 +3,6 @@ export { default as useLogin } from './useLogin'
|
||||
export { default as useLogout } from './useLogout'
|
||||
export { default as useVerifyCustomer } from './useVerifyCustomer'
|
||||
export { default as useActiveCustomer } from './useActiveCustomer'
|
||||
export { default as useRequestPasswordReset } from './useRequestPasswordReset'
|
||||
export { default as useResetPassword } from './useResetPassword'
|
||||
|
||||
|
50
src/components/hooks/auth/useRequestPasswordReset.tsx
Normal file
50
src/components/hooks/auth/useRequestPasswordReset.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { useState } from 'react'
|
||||
import useActiveCustomer from './useActiveCustomer'
|
||||
import fetcher from 'src/utils/fetcher'
|
||||
import { CommonError } from 'src/domains/interfaces/CommonError'
|
||||
import { requestPasswordReset } from '@framework/utils/mutations/request-password-reset-mutation'
|
||||
import { RequestPasswordReset } from '@framework/schema'
|
||||
|
||||
interface ForgotPassword {
|
||||
email: string
|
||||
}
|
||||
|
||||
const useRequestPasswordReset = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
// const { mutate } = useActiveCustomer()
|
||||
|
||||
const requestPassword = (
|
||||
{email}: ForgotPassword,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
fetcher<RequestPasswordReset>({
|
||||
query: requestPasswordReset,
|
||||
variables: {
|
||||
emailAddress: email
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.requestPasswordReset.__typename !== 'Success') {
|
||||
throw CommonError.create(
|
||||
data.requestPasswordReset.message,
|
||||
data.requestPasswordReset.errorCode
|
||||
)
|
||||
}
|
||||
// mutate()
|
||||
fCallBack(true)
|
||||
return data
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error)
|
||||
fCallBack(false, error.message)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
||||
return { loading, requestPassword, error }
|
||||
}
|
||||
|
||||
export default useRequestPasswordReset
|
52
src/components/hooks/auth/useResetPassword.tsx
Normal file
52
src/components/hooks/auth/useResetPassword.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { useState } from 'react'
|
||||
import useActiveCustomer from './useActiveCustomer'
|
||||
import fetcher from 'src/utils/fetcher'
|
||||
import { CommonError } from 'src/domains/interfaces/CommonError'
|
||||
import { resetPasswordMutation } from '@framework/utils/mutations/reset-password-mutation'
|
||||
import { ResetPasswordMutation } from '@framework/schema'
|
||||
|
||||
interface Props {
|
||||
token?: string| string[] ,
|
||||
password:string
|
||||
}
|
||||
|
||||
const useResetPassword = () => {
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
// const { mutate } = useActiveCustomer()
|
||||
|
||||
const resetPassword = (
|
||||
{token,password}: Props,
|
||||
fCallBack: (isSuccess: boolean, message?: string) => void
|
||||
) => {
|
||||
setError(null)
|
||||
setLoading(true)
|
||||
fetcher<ResetPasswordMutation>({
|
||||
query: resetPasswordMutation,
|
||||
variables: {
|
||||
token: token,
|
||||
password:password
|
||||
},
|
||||
})
|
||||
.then((data) => {
|
||||
if (data.resetPassword.__typename !== 'CurrentUser') {
|
||||
throw CommonError.create(
|
||||
data.resetPassword.message,
|
||||
data.resetPassword.errorCode
|
||||
)
|
||||
}
|
||||
// mutate()
|
||||
fCallBack(true)
|
||||
return data
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error)
|
||||
fCallBack(false, error.message)
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}
|
||||
|
||||
return { loading, resetPassword, error }
|
||||
}
|
||||
|
||||
export default useResetPassword
|
Reference in New Issue
Block a user