From 1eb8a09a1561a4ff5144afbf521ab6ec8adde85c Mon Sep 17 00:00:00 2001 From: "Embruch, Gerd" <gerd.embruch@uni-hamburg.de> Date: Tue, 2 Jul 2024 15:39:03 +0200 Subject: [PATCH] added change email --- README.md | 8 ++- README_tmp.html | 14 ++++- src/contexts/Auth/AuthState.jsx | 6 ++ src/pages/User/ChangeMail.jsx | 88 ++++++++++++++++++++++++++++ src/pages/User/ForgotPassword.jsx | 7 ++- src/pages/User/Login.jsx | 2 +- src/pages/User/RequestChangeMail.jsx | 73 +++++++++++++++++++++++ src/pages/User/ResetPassword.jsx | 8 +-- src/routes/Sitemap.jsx | 25 +++++++- src/utils/AxiosConfig.js | 4 +- 10 files changed, 218 insertions(+), 17 deletions(-) create mode 100644 src/pages/User/ChangeMail.jsx create mode 100644 src/pages/User/RequestChangeMail.jsx diff --git a/README.md b/README.md index 33d5cc0..badefbb 100644 --- a/README.md +++ b/README.md @@ -23,5 +23,9 @@ cp ./.env.template.local ./.env.production.local - [PM2](https://pm2.keymetrics.io/) # Roadmap -- [ ] complete pages incl. password request and RAGChat -- [ ] fix errors \ No newline at end of file +- [ ] complete pages + - [ ] sign up + - [ ] onboarding / RAGChat +- [ ] fix errors + - [ ] fix JWT renewal + - [ ] check width of label & submit on cleanLayout \ No newline at end of file diff --git a/README_tmp.html b/README_tmp.html index b3a3df1..65000c3 100644 --- a/README_tmp.html +++ b/README_tmp.html @@ -389,8 +389,18 @@ cp ./.env.template.local ./.env.production.local </ul> <h1 id="roadmap">Roadmap</h1> <ul> -<li><input type="checkbox" id="checkbox0"><label for="checkbox0">complete pages incl. password request and RAGChat</label></li> -<li><input type="checkbox" id="checkbox1"><label for="checkbox1">fix errors</label></li> +<li><input type="checkbox" id="checkbox0"><label for="checkbox0">complete pages</label> +<ul> +<li><input type="checkbox" id="checkbox1"><label for="checkbox1">sign up</label></li> +<li><input type="checkbox" id="checkbox2"><label for="checkbox2">onboarding / RAGChat</label></li> +</ul> +</li> +<li><input type="checkbox" id="checkbox3"><label for="checkbox3">fix errors</label> +<ul> +<li><input type="checkbox" id="checkbox4"><label for="checkbox4">fix JWT renewal</label></li> +<li><input type="checkbox" id="checkbox5"><label for="checkbox5">check width of label & submit on cleanLayout</label></li> +</ul> +</li> </ul> </body> diff --git a/src/contexts/Auth/AuthState.jsx b/src/contexts/Auth/AuthState.jsx index 74e01ae..ec1df97 100755 --- a/src/contexts/Auth/AuthState.jsx +++ b/src/contexts/Auth/AuthState.jsx @@ -54,6 +54,11 @@ function AuthState({ children }) { return api.post('/users/requestpasswordreset', { email }); } + // ### REQUEST PASSWORD RESET + function requestEmailReset(email) { + return api.post('/users/requestemailchange', { email }); + } + // ### RETURN return ( <AuthContext.Provider @@ -62,6 +67,7 @@ function AuthState({ children }) { currentUser, logout, requestPasswordReset, + requestEmailReset, USER_ACTIONS, dispatchCurrentUser }}> diff --git a/src/pages/User/ChangeMail.jsx b/src/pages/User/ChangeMail.jsx new file mode 100644 index 0000000..a546c8d --- /dev/null +++ b/src/pages/User/ChangeMail.jsx @@ -0,0 +1,88 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import React, { useRef, useState } from 'react'; +import { Helmet } from 'react-helmet-async'; +import { FormProvider, useForm, useFormContext } from 'react-hook-form'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import { z } from 'zod'; +import Input from '../../components/form/Input'; +import Submit from '../../components/form/Submit'; +import { useAuth } from '../../contexts/Auth/AuthState'; +import { mergeBackendValidation, setFlashMsg } from '../../utils/ErrorHandling'; +import Heading from '../../components/font/Heading'; +import api from '../../utils/AxiosConfig'; + +function ForgotPassword() { + // ################################# + // VALIDATION SCHEMA + // ################################# + // TODO limit file size via .env + // TODO check for file types + const schema = z.object({ + token: z.string().min(1), + password: z.string().min(1), + }); + + // ################################# + // HOOKS + // ################################# + // FETCH TOKEN FROM URL + const { token } = useParams(); + + // SET FORM + const methods = useForm({ + resolver: zodResolver(schema), + mode: 'onSubmit', + defaultValues: { + token: token, + } + }); + + // ### ENABLE REDIRECTIONS + const redirect = useNavigate(); + + // ################################# + // FUNCTIONS + // ################################# + + // ### HANDLE SUBMITTING FORM + async function handleSendForm(inputs) { + + // TRY UPDATE + try { + // send data + const result = await api.post(`/users/confirmemailchange`, inputs); + redirect('/change_email'); + // set flash message + setFlashMsg(result.data?.message); + } catch (error) { + // catch the error + console.error(error); + mergeBackendValidation(error.response.status, error.response.data, methods.setError); + } + } + + // ################################# + // OUTPUT + // ################################# + return ( + <> + {/* render page title */} + <Helmet><title>[{import.meta.env.VITE_APP_NAME}] change E-Mail</title></Helmet> + + <Heading level="1">e-mail reset</Heading> + <FormProvider {...methods} > + <form onSubmit={methods.handleSubmit(handleSendForm)}> + <Input name='token' type='text' title='confirm token' required={true} /> + <Input name='password' type='password' title='current password' autoFocus={true} required={true} /> + <Submit value='submit' /> + </form> + </FormProvider> + + <div className="my-4 flex justify-between"> + <Link to="/change_email">Back to request</Link> + </div> + </> + ); +} + +export default React.memo(ForgotPassword); \ No newline at end of file diff --git a/src/pages/User/ForgotPassword.jsx b/src/pages/User/ForgotPassword.jsx index 09c7ec4..3d84cb4 100644 --- a/src/pages/User/ForgotPassword.jsx +++ b/src/pages/User/ForgotPassword.jsx @@ -1,7 +1,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import React, { useRef, useState } from 'react'; import { Helmet } from 'react-helmet-async'; -import { FormProvider, useForm } from 'react-hook-form'; +import { FormProvider, useForm, useFormContext } from 'react-hook-form'; import { Link } from 'react-router-dom'; import { z } from 'zod'; import Input from '../../components/form/Input'; @@ -57,14 +57,15 @@ function ForgotPassword() { <Heading level="1">request password reset</Heading> <FormProvider {...methods} > - <form onSubmit={methods.handleSubmit(handleSendForm)} noValidate> + <form onSubmit={methods.handleSubmit(handleSendForm)}> <Input name='email' type='mail' title='E-Mail' className='h-16' required={true} /> <Submit value='request' /> </form> </FormProvider> - <div className="my-4"> + <div className="my-4 flex justify-between"> <Link to="/login">Back to Login</Link> + {methods.formState.isSubmitted && <Link to="/reset_password/-">Proceed</Link>} </div> </> ); diff --git a/src/pages/User/Login.jsx b/src/pages/User/Login.jsx index bfefe51..cd81662 100644 --- a/src/pages/User/Login.jsx +++ b/src/pages/User/Login.jsx @@ -106,7 +106,7 @@ function Login() { </FormProvider> <div className="mt-4"> - <Link to="/forgot_password">Forgot your Password?</Link> + <Link to="/reset_password">Forgot your Password?</Link> </div> </> ); diff --git a/src/pages/User/RequestChangeMail.jsx b/src/pages/User/RequestChangeMail.jsx new file mode 100644 index 0000000..dfef9ef --- /dev/null +++ b/src/pages/User/RequestChangeMail.jsx @@ -0,0 +1,73 @@ +import { zodResolver } from '@hookform/resolvers/zod'; +import React from 'react'; +import { Helmet } from 'react-helmet-async'; +import { FormProvider, useForm } from 'react-hook-form'; +import { Link } from 'react-router-dom'; +import { z } from 'zod'; +import Input from '../../components/form/Input'; +import Submit from '../../components/form/Submit'; +import { useAuth } from '../../contexts/Auth/AuthState'; +import { mergeBackendValidation, setFlashMsg } from '../../utils/ErrorHandling'; +import Heading from '../../components/font/Heading'; + +function RequestChangeMail() { + // ################################# + // VALIDATION SCHEMA + // ################################# + // TODO limit file size via .env + // TODO check for file types + const schema = z.object({ + email: z.string().email(), + }); + + // ################################# + // HOOKS + // ################################# + const methods = useForm({ + resolver: zodResolver(schema), + mode: 'onSubmit' + }); + + // ################################# + // FUNCTIONS + // ################################# + // ### IMPORT FUNCTION FROM AuthContext + const { requestEmailReset } = useAuth(); + + // ### HANDLE SUBMITTING FORM + async function handleSendForm(inputs) { + try { + // send data to login function + const result = await requestEmailReset(inputs.email); + // set flash message + setFlashMsg(result.data?.message); + } catch (error) { + // catch the error + mergeBackendValidation(error.response.status, error.response.data, methods.setError); + } + } + + // ################################# + // OUTPUT + // ################################# + return ( + <> + {/* render page title */} + <Helmet><title>[{import.meta.env.VITE_APP_NAME}] change E-Mail</title></Helmet> + + <Heading level="1">request e-mail reset</Heading> + <FormProvider {...methods} > + <form onSubmit={methods.handleSubmit(handleSendForm)}> + <Input name='email' type='mail' title='new E-Mail address' required={true} /> + <Submit value='request' /> + </form> + </FormProvider> + + <div className="my-4 flex justify-between"> + <Link to="/change_email/-">I already recieved a token</Link> + </div> + </> + ); +} + +export default React.memo(RequestChangeMail); \ No newline at end of file diff --git a/src/pages/User/ResetPassword.jsx b/src/pages/User/ResetPassword.jsx index e52c9f8..12fa32d 100644 --- a/src/pages/User/ResetPassword.jsx +++ b/src/pages/User/ResetPassword.jsx @@ -32,7 +32,7 @@ function ResetPasswordForm() { // ################################# // ### CONNECT AUTH CONTEXT const { logout } = useAuth(); - + // FETCH TOKEN FROM URL const { token } = useParams(); // ### PREPARE FORM @@ -44,7 +44,7 @@ function ResetPasswordForm() { } }); - // ### ENABLE Redirections + // ### ENABLE REDIRECTIONS const redirect = useNavigate(); // ################################# @@ -57,8 +57,6 @@ function ResetPasswordForm() { // TRY UPDATE try { - // prepare form data - inputs.token = token; // send data const result = await api.post(`/users/confirmpasswordreset`, inputs); await logout(); @@ -93,7 +91,7 @@ function ResetPasswordForm() { </FormProvider> <div className="mt-4"> - <Link to="/login">Login</Link> + <Link to="/login">Back to Login</Link> </div> </> ); diff --git a/src/routes/Sitemap.jsx b/src/routes/Sitemap.jsx index 359bd04..657f1dc 100644 --- a/src/routes/Sitemap.jsx +++ b/src/routes/Sitemap.jsx @@ -23,8 +23,29 @@ export const sitemap = [{ element: loadComponent('Onboarding/Onboarding', true, true), handle: { crumb: () => <Link to="/onboarding">Onboarding</Link> } }, + // REQUEST CHANGE EMAIL ADDRESS + { + title: 'Change eMail', + path: '/change_email', + handle: { crumb: () => <Link to="/change_email">Change eMail</Link> }, + children: [ + { index: true, element: loadComponent('User/RequestChangeMail') }, + // CHANGE EMAIL ADDRESS + { + title: 'Change eMail', + path: ':token', + hidden: true, + element: loadComponent('User/ChangeMail') + } + ] + }, // LOGOUT - { title: 'Logout', path: '/logout', element: loadComponent('User/Logout', true, true), handle: { crumb: () => <Link to="/profile/logout">Logout</Link> } } + { + title: 'Logout', + path: '/logout', + element: loadComponent('User/Logout', true, true), + handle: { crumb: () => <Link to="/profile/logout">Logout</Link> } + } ] }] }, { @@ -32,7 +53,7 @@ export const sitemap = [{ // LOGIN { path: '/login', element: loadComponent('User/Login') }, // FORGOT PASSWORD - { path: '/forgot_password', element: loadComponent('User/ForgotPassword') }, + { path: '/reset_password', element: loadComponent('User/ForgotPassword') }, // RESET PASSWORD { path: '/reset_password/:token', element: loadComponent('User/ResetPassword') }, // ERROR diff --git a/src/utils/AxiosConfig.js b/src/utils/AxiosConfig.js index 98abfd7..2310743 100755 --- a/src/utils/AxiosConfig.js +++ b/src/utils/AxiosConfig.js @@ -47,8 +47,8 @@ api.interceptors.response.use( // console.log('retry', err.code, originalConfig._retry); // refresh access token try { - const result = await api.post( - '/auth/token', + const result = await api.get( + '/users/refreshjwt', {}, { withCredentials: true } ); -- GitLab