[frontend]: add reset password
This commit is contained in:
parent
857374be4e
commit
75930d105e
20
frontend/src/app/account/recovery/[token]/page.tsx
Normal file
20
frontend/src/app/account/recovery/[token]/page.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { NewPasswordForm } from '@/components/features/auth/forms/NewPasswordForm';
|
||||
import { Metadata } from 'next';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import React from 'react';
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const t = await getTranslations('auth.newPassword');
|
||||
|
||||
return {
|
||||
title: t('heading')
|
||||
}
|
||||
}
|
||||
|
||||
const NewPasswordPage = () => {
|
||||
return (
|
||||
<NewPasswordForm/>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewPasswordPage;
|
||||
20
frontend/src/app/account/recovery/page.tsx
Normal file
20
frontend/src/app/account/recovery/page.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { ResetPasswordForm } from '@/components/features/auth/forms/ResetPasswordForm';
|
||||
import { Metadata } from 'next';
|
||||
import { getTranslations } from 'next-intl/server';
|
||||
import React from 'react';
|
||||
|
||||
export async function generateMetadata(): Promise<Metadata> {
|
||||
const t = await getTranslations('auth.resetPassword');
|
||||
|
||||
return {
|
||||
title: t('heading')
|
||||
}
|
||||
}
|
||||
|
||||
const ResetPasswordPage = () => {
|
||||
return (
|
||||
<ResetPasswordForm/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPasswordPage;
|
||||
119
frontend/src/components/features/auth/forms/NewPasswordForm.tsx
Normal file
119
frontend/src/components/features/auth/forms/NewPasswordForm.tsx
Normal file
@ -0,0 +1,119 @@
|
||||
'use client'
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import { Button } from '@/components/ui/common/Button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel
|
||||
} from '@/components/ui/common/Form'
|
||||
import { Input } from '@/components/ui/common/Input'
|
||||
|
||||
import { useNewPasswordMutation } from '@/graphql/generated/output'
|
||||
|
||||
|
||||
import AuthWrapper from '../AuthWrapper'
|
||||
import { newPasswordSchema, TypeNewPasswordSchema } from '@/schemas/auth/new-password.schema'
|
||||
|
||||
export function NewPasswordForm() {
|
||||
const t = useTranslations('auth.newPassword')
|
||||
|
||||
const router = useRouter()
|
||||
const params = useParams<{ token: string }>()
|
||||
|
||||
const form = useForm<TypeNewPasswordSchema>({
|
||||
resolver: zodResolver(newPasswordSchema),
|
||||
defaultValues: {
|
||||
password: '',
|
||||
passwordRepeat: ''
|
||||
}
|
||||
})
|
||||
|
||||
const [newPassword, { loading: isLoadingNew }] = useNewPasswordMutation({
|
||||
onCompleted(data) {
|
||||
toast.success(t('successMessage'))
|
||||
router.push('/account/login')
|
||||
},
|
||||
onError() {
|
||||
toast.error(t('errorMessage'))
|
||||
}
|
||||
})
|
||||
|
||||
const { isValid } = form.formState
|
||||
|
||||
function onSubmit(data: TypeNewPasswordSchema) {
|
||||
newPassword({ variables: { data: { ...data, token: params.token } } })
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthWrapper
|
||||
heading={t('heading')}
|
||||
backButtonLabel={t('backButtonLabel')}
|
||||
backButtonHref='/account/login'
|
||||
>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className='grid gap-y-3'
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='password'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('passwordLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder='********'
|
||||
type='password'
|
||||
disabled={isLoadingNew}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('passwordDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='passwordRepeat'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{t('passwordRepeatLabel')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder='********'
|
||||
type='password'
|
||||
disabled={isLoadingNew}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('passwordRepeatDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
className='mt-2 w-full'
|
||||
disabled={!isValid || isLoadingNew}
|
||||
>
|
||||
{t('submitButton')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</AuthWrapper>
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
'use client'
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { CircleCheck } from 'lucide-react'
|
||||
import { useTranslations } from 'next-intl'
|
||||
import { useState } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { toast } from 'sonner'
|
||||
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertTitle
|
||||
} from '@/components/ui/common/Alert'
|
||||
import { Button } from '@/components/ui/common/Button'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel
|
||||
} from '@/components/ui/common/Form'
|
||||
import { Input } from '@/components/ui/common/Input'
|
||||
|
||||
import { useResetPasswordMutation } from '@/graphql/generated/output'
|
||||
|
||||
import {
|
||||
type TypeResetPasswordSchema,
|
||||
resetPasswordSchema
|
||||
} from '@/schemas/auth/reset-password.schema'
|
||||
|
||||
import AuthWrapper from '../AuthWrapper'
|
||||
|
||||
export function ResetPasswordForm() {
|
||||
const t = useTranslations('auth.resetPassword')
|
||||
|
||||
const [isSuccess, setIsSuccess] = useState(false)
|
||||
|
||||
const form = useForm<TypeResetPasswordSchema>({
|
||||
resolver: zodResolver(resetPasswordSchema),
|
||||
defaultValues: {
|
||||
email: ''
|
||||
}
|
||||
})
|
||||
|
||||
const [resetPassword, { loading: isLoadingReset }] =
|
||||
useResetPasswordMutation({
|
||||
onCompleted() {
|
||||
setIsSuccess(true)
|
||||
},
|
||||
onError() {
|
||||
toast.error(t('errorMessage'))
|
||||
}
|
||||
})
|
||||
|
||||
const { isValid } = form.formState
|
||||
|
||||
function onSubmit(data: TypeResetPasswordSchema) {
|
||||
void resetPassword({ variables: { data } })
|
||||
}
|
||||
|
||||
return (
|
||||
<AuthWrapper
|
||||
heading={t('heading')}
|
||||
backButtonLabel={t('backButtonLabel')}
|
||||
backButtonHref='/account/login'
|
||||
>
|
||||
{isSuccess ? (
|
||||
<Alert>
|
||||
<CircleCheck className='size-4' />
|
||||
<AlertTitle>{t('successAlertTitle')}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t('successAlertDescription')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className='grid gap-y-3'
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='email'
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('emailLabel')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder='john.doe@example.com'
|
||||
disabled={isLoadingReset}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
{t('emailDescription')}
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button
|
||||
className='mt-2 w-full'
|
||||
disabled={!isValid || isLoadingReset}
|
||||
>
|
||||
{t('submitButton')}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
</AuthWrapper>
|
||||
)
|
||||
}
|
||||
@ -605,6 +605,20 @@ export type LoginUserMutationVariables = Exact<{
|
||||
|
||||
export type LoginUserMutation = { __typename?: 'Mutation', loginUser: { __typename?: 'AuthModel', message?: string | null, user?: { __typename?: 'UserModel', id: string, name: string, email: string } | null } };
|
||||
|
||||
export type NewPasswordMutationVariables = Exact<{
|
||||
data: NewPasswordInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type NewPasswordMutation = { __typename?: 'Mutation', setNewPassword: boolean };
|
||||
|
||||
export type ResetPasswordMutationVariables = Exact<{
|
||||
data: ResetPasswordInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type ResetPasswordMutation = { __typename?: 'Mutation', resetPassword: boolean };
|
||||
|
||||
export type VerifyAccountMutationVariables = Exact<{
|
||||
data: VerificationInput;
|
||||
}>;
|
||||
@ -693,6 +707,68 @@ export function useLoginUserMutation(baseOptions?: Apollo.MutationHookOptions<Lo
|
||||
export type LoginUserMutationHookResult = ReturnType<typeof useLoginUserMutation>;
|
||||
export type LoginUserMutationResult = Apollo.MutationResult<LoginUserMutation>;
|
||||
export type LoginUserMutationOptions = Apollo.BaseMutationOptions<LoginUserMutation, LoginUserMutationVariables>;
|
||||
export const NewPasswordDocument = gql`
|
||||
mutation NewPassword($data: NewPasswordInput!) {
|
||||
setNewPassword(data: $data)
|
||||
}
|
||||
`;
|
||||
export type NewPasswordMutationFn = Apollo.MutationFunction<NewPasswordMutation, NewPasswordMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useNewPasswordMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useNewPasswordMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useNewPasswordMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [newPasswordMutation, { data, loading, error }] = useNewPasswordMutation({
|
||||
* variables: {
|
||||
* data: // value for 'data'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useNewPasswordMutation(baseOptions?: Apollo.MutationHookOptions<NewPasswordMutation, NewPasswordMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<NewPasswordMutation, NewPasswordMutationVariables>(NewPasswordDocument, options);
|
||||
}
|
||||
export type NewPasswordMutationHookResult = ReturnType<typeof useNewPasswordMutation>;
|
||||
export type NewPasswordMutationResult = Apollo.MutationResult<NewPasswordMutation>;
|
||||
export type NewPasswordMutationOptions = Apollo.BaseMutationOptions<NewPasswordMutation, NewPasswordMutationVariables>;
|
||||
export const ResetPasswordDocument = gql`
|
||||
mutation ResetPassword($data: ResetPasswordInput!) {
|
||||
resetPassword(data: $data)
|
||||
}
|
||||
`;
|
||||
export type ResetPasswordMutationFn = Apollo.MutationFunction<ResetPasswordMutation, ResetPasswordMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useResetPasswordMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useResetPasswordMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useResetPasswordMutation` returns a tuple that includes:
|
||||
* - A mutate function that you can call at any time to execute the mutation
|
||||
* - An object with fields that represent the current status of the mutation's execution
|
||||
*
|
||||
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
|
||||
*
|
||||
* @example
|
||||
* const [resetPasswordMutation, { data, loading, error }] = useResetPasswordMutation({
|
||||
* variables: {
|
||||
* data: // value for 'data'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useResetPasswordMutation(baseOptions?: Apollo.MutationHookOptions<ResetPasswordMutation, ResetPasswordMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<ResetPasswordMutation, ResetPasswordMutationVariables>(ResetPasswordDocument, options);
|
||||
}
|
||||
export type ResetPasswordMutationHookResult = ReturnType<typeof useResetPasswordMutation>;
|
||||
export type ResetPasswordMutationResult = Apollo.MutationResult<ResetPasswordMutation>;
|
||||
export type ResetPasswordMutationOptions = Apollo.BaseMutationOptions<ResetPasswordMutation, ResetPasswordMutationVariables>;
|
||||
export const VerifyAccountDocument = gql`
|
||||
mutation VerifyAccount($data: VerificationInput!) {
|
||||
verifyAccount(data: $data) {
|
||||
|
||||
3
frontend/src/graphql/metations/auth/NewPassword.graphql
Normal file
3
frontend/src/graphql/metations/auth/NewPassword.graphql
Normal file
@ -0,0 +1,3 @@
|
||||
mutation NewPassword($data: NewPasswordInput!) {
|
||||
setNewPassword(data: $data)
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
mutation ResetPassword($data: ResetPasswordInput!) {
|
||||
resetPassword(data: $data)
|
||||
}
|
||||
12
frontend/src/schemas/auth/new-password.schema.ts
Normal file
12
frontend/src/schemas/auth/new-password.schema.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const newPasswordSchema = z
|
||||
.object({
|
||||
password: z.string().min(8),
|
||||
passwordRepeat: z.string().min(8)
|
||||
})
|
||||
.refine(data => data.password === data.passwordRepeat, {
|
||||
path: ['passwordRepeat']
|
||||
})
|
||||
|
||||
export type TypeNewPasswordSchema = z.infer<typeof newPasswordSchema>
|
||||
7
frontend/src/schemas/auth/reset-password.schema.ts
Normal file
7
frontend/src/schemas/auth/reset-password.schema.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const resetPasswordSchema = z.object({
|
||||
email: z.string().email()
|
||||
})
|
||||
|
||||
export type TypeResetPasswordSchema = z.infer<typeof resetPasswordSchema>
|
||||
Loading…
x
Reference in New Issue
Block a user