From 21bd696a303edb72c14de9c8629957044f1bf47c Mon Sep 17 00:00:00 2001 From: SrJuggernaut Date: Wed, 24 Jan 2024 11:28:01 -0600 Subject: [PATCH] feat: recover password flow --- src/app/login/LoginForm.tsx | 4 + .../CreateRecoverPasswordForm.tsx | 101 +++++++++++++ .../ManageRecoverPassword.tsx | 28 ++++ .../UpdateRecoverPasswordForm.tsx | 138 ++++++++++++++++++ src/app/recover-password/page.tsx | 46 ++++++ 5 files changed, 317 insertions(+) create mode 100644 src/app/recover-password/CreateRecoverPasswordForm.tsx create mode 100644 src/app/recover-password/ManageRecoverPassword.tsx create mode 100644 src/app/recover-password/UpdateRecoverPasswordForm.tsx create mode 100644 src/app/recover-password/page.tsx diff --git a/src/app/login/LoginForm.tsx b/src/app/login/LoginForm.tsx index 15c5226..10fbd9c 100644 --- a/src/app/login/LoginForm.tsx +++ b/src/app/login/LoginForm.tsx @@ -12,6 +12,7 @@ import { nanoid } from '@reduxjs/toolkit' import { AppwriteException } from 'appwrite' import { login } from 'entgamers-database/frontend/session' import { useFormik } from 'formik' +import NextLink from 'next/link' import { useRouter } from 'next/navigation' import { useEffect, type FC } from 'react' import { object, string } from 'yup' @@ -105,6 +106,9 @@ const LoginForm: FC = () => { {formik.errors.password} )} + + Perdiste tu contraseña? Recuperala + + + + ) +} + +export default CreateRecoverPasswordForm diff --git a/src/app/recover-password/ManageRecoverPassword.tsx b/src/app/recover-password/ManageRecoverPassword.tsx new file mode 100644 index 0000000..79f8790 --- /dev/null +++ b/src/app/recover-password/ManageRecoverPassword.tsx @@ -0,0 +1,28 @@ +'use client' +import CreateRecoverPasswordForm from '@/app/recover-password/CreateRecoverPasswordForm' +import { useSearchParams } from 'next/navigation' +import { useEffect, useState, type FC } from 'react' +import UpdateRecoverPasswordForm, { type UpdateRecoverPasswordFormProps } from './UpdateRecoverPasswordForm' + +const ManageRecoverPassword: FC = () => { + const [recoverData, setRecoverData] = useState() + const searchParams = useSearchParams() + + useEffect(() => { + const userId = searchParams.get('userId') + const secret = searchParams.get('secret') + if (userId !== null && secret !== null) { + setRecoverData({ userId, secret }) + } + }, []) + + if (recoverData === undefined) { + return + } else { + return + } +} + +export default ManageRecoverPassword diff --git a/src/app/recover-password/UpdateRecoverPasswordForm.tsx b/src/app/recover-password/UpdateRecoverPasswordForm.tsx new file mode 100644 index 0000000..459343d --- /dev/null +++ b/src/app/recover-password/UpdateRecoverPasswordForm.tsx @@ -0,0 +1,138 @@ +import Button from '@/components/ui/Button' +import Typography from '@/components/ui/Typography' +import FormGroup from '@/components/ui/form/FormGroup' +import PasswordInput from '@/components/ui/form/PasswordInput' +import { useAppDispatch } from '@/hooks/useAppDispatch' +import { addAlert } from '@/state/feedbackSlice' +import { nanoid } from '@reduxjs/toolkit' +import { AppwriteException } from 'appwrite' +import { updatePasswordRecovery } from 'entgamers-database/frontend/session' +import { useFormik } from 'formik' +import { useRouter } from 'next/navigation' +import { type FC } from 'react' +import { object, ref, string } from 'yup' + +export interface UpdateRecoverPasswordFormProps { + userId: string + secret: string +} + +interface UpdateRecoverPasswordData extends UpdateRecoverPasswordFormProps { + password: string + confirmPassword: string +} + +const updateRecoverPasswordSchema = object({ + password: string() + .min(6, 'La contraseña debe tener al menos 6 caracteres') + .matches(/[a-z]/, 'La contraseña debe tener al menos una letra minúscula') + .matches(/[A-Z]/, 'La contraseña debe tener al menos una letra mayúscula') + .matches(/[0-9]/, 'La contraseña debe tener al menos un número') + .required('La contraseña es requerida'), + confirmPassword: string().oneOf([ref('password')], 'Las contraseñas no coinciden').required('La confirmación de la contraseña es requerida') +}) + +const UpdateRecoverPasswordForm: FC = (props) => { + const dispatch = useAppDispatch() + const router = useRouter() + + const formik = useFormik({ + initialValues: { + password: '', + confirmPassword: '', + userId: props.userId, + secret: props.secret + }, + onSubmit: async ({ confirmPassword, password, secret, userId }) => { + try { + await updatePasswordRecovery(userId, secret, password, confirmPassword) + dispatch(addAlert({ + id: nanoid(), + title: 'Contraseña actualizada', + message: 'Ahora puedes iniciar sesión', + severity: 'success' + })) + router.push('/login') + } catch (error) { + if (error instanceof AppwriteException) { + dispatch(addAlert({ + id: nanoid(), + message: error.message, + title: 'Error mientras se registraba', + severity: 'error' + })) + } else { + dispatch(addAlert({ + id: nanoid(), + message: 'Error desconocido', + title: 'Error mientras se solicitaba la recuperación de contraseña', + severity: 'error' + })) + } + } + }, + validationSchema: updateRecoverPasswordSchema, + isInitialValid: false + }) + return ( +
+ + + + {formik.touched.password !== undefined && formik.errors.password !== undefined + ? ( + + {formik.errors.password} + + ) + : ( + + Escribe tu nueva contraseña + + ) + } + + + + + {formik.touched.confirmPassword !== undefined && formik.errors.confirmPassword !== undefined && ( + + {formik.errors.confirmPassword} + + )} + + + + +
+ ) +} + +export default UpdateRecoverPasswordForm diff --git a/src/app/recover-password/page.tsx b/src/app/recover-password/page.tsx new file mode 100644 index 0000000..04534e0 --- /dev/null +++ b/src/app/recover-password/page.tsx @@ -0,0 +1,46 @@ +import ManageRecoverPassword from '@/app/recover-password/ManageRecoverPassword' +import Typography from '@/components/ui/Typography' +import { css, cx } from '@/styled-system/css' +import { Center } from '@/styled-system/jsx' +import { container } from '@/styled-system/patterns' +import { card } from '@/styled-system/recipes' +import { type FC } from 'react' + +const page: FC = () => { + return ( +
+
+
+ Recuperar contraseña +
+
+ +
+
+
+ ) +} + +export default page