From 048eb4e58521dfc950f8ca33d81ab8d1f220431a Mon Sep 17 00:00:00 2001
From: "Embruch, Gerd" <gerd.embruch@uni-hamburg.de>
Date: Wed, 3 Jul 2024 09:50:37 +0200
Subject: [PATCH] addes signup; cleaned up

---
 README.md                                 |   6 +-
 README_tmp.html                           |  10 ++-
 src/components/form/FlatListEdit copy.jsx |   1 -
 src/components/form/FlatListEdit.jsx      |   2 -
 src/contexts/Auth/AuthState.jsx           |   1 -
 src/pages/User/ChangeMail.jsx             |   1 -
 src/pages/User/Login.jsx                  |   6 +-
 src/pages/User/Logout.jsx                 |   1 -
 src/pages/User/ResetPassword.jsx          |   1 -
 src/pages/User/Signup.jsx                 | 103 ++++++++++++++++++++++
 src/pages/User/Verify.jsx                 |  85 ++++++++++++++++++
 src/routes/Sitemap.jsx                    |   7 ++
 src/utils/AxiosConfig.js                  |   6 +-
 13 files changed, 211 insertions(+), 19 deletions(-)
 create mode 100644 src/pages/User/Signup.jsx
 create mode 100644 src/pages/User/Verify.jsx

diff --git a/README.md b/README.md
index badefbb..6f186f2 100644
--- a/README.md
+++ b/README.md
@@ -24,8 +24,10 @@ cp ./.env.template.local ./.env.production.local
 
 # Roadmap
 - [ ] complete pages
-  - [ ] sign up
+  - [ ] resend verification code
   - [ ] onboarding / RAGChat
+  - [ ] admin-login
+  - [ ] admin-page with LLM options
 - [ ] fix errors
-  - [ ] fix JWT renewal
+  - [ ] fix axios' JWT auto 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 65000c3..dca7ce2 100644
--- a/README_tmp.html
+++ b/README_tmp.html
@@ -391,14 +391,16 @@ cp ./.env.template.local ./.env.production.local
 <ul>
 <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="checkbox1"><label for="checkbox1">resend verification code</label></li>
 <li><input type="checkbox" id="checkbox2"><label for="checkbox2">onboarding / RAGChat</label></li>
+<li><input type="checkbox" id="checkbox3"><label for="checkbox3">admin-login</label></li>
+<li><input type="checkbox" id="checkbox4"><label for="checkbox4">admin-page with LLM options</label></li>
 </ul>
 </li>
-<li><input type="checkbox" id="checkbox3"><label for="checkbox3">fix errors</label>
+<li><input type="checkbox" id="checkbox5"><label for="checkbox5">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 &amp; submit on cleanLayout</label></li>
+<li><input type="checkbox" id="checkbox6"><label for="checkbox6">fix axios' JWT auto renewal</label></li>
+<li><input type="checkbox" id="checkbox7"><label for="checkbox7">check width of label &amp; submit on cleanLayout</label></li>
 </ul>
 </li>
 </ul>
diff --git a/src/components/form/FlatListEdit copy.jsx b/src/components/form/FlatListEdit copy.jsx
index f2e35c0..e3bbe15 100644
--- a/src/components/form/FlatListEdit copy.jsx	
+++ b/src/components/form/FlatListEdit copy.jsx	
@@ -60,7 +60,6 @@ function FlatListEdit({ methods, initialItems, fieldName, keyName, validator, ta
       const isMail = validator(input);
       return isMail.success ?? keyName;
     });
-    console.log('validInputs', validInputs);
 
     // handle invalid inputs
     // strip off valid entries from input to get remaining invalid inputs
diff --git a/src/components/form/FlatListEdit.jsx b/src/components/form/FlatListEdit.jsx
index 54a954c..bce29cd 100644
--- a/src/components/form/FlatListEdit.jsx
+++ b/src/components/form/FlatListEdit.jsx
@@ -49,7 +49,6 @@ function FlatListEdit({ initialItems = [], fieldName = '', tooltip = '', columns
     });
     // exit if no valid items found
     if (validInputs.length === 0) return;
-    console.log('validInputs', validInputs);
 
     // handle invalid inputs
     // strip off valid entries from input to get remaining invalid inputs
@@ -60,7 +59,6 @@ function FlatListEdit({ initialItems = [], fieldName = '', tooltip = '', columns
       // set error message on input
       methods.setError('addItem', { message: 'invalid entries remaining' });
     }
-    console.log('invalidInputs', invalidInputs);
 
 
     // handle valid inputs
diff --git a/src/contexts/Auth/AuthState.jsx b/src/contexts/Auth/AuthState.jsx
index ec1df97..fd36942 100755
--- a/src/contexts/Auth/AuthState.jsx
+++ b/src/contexts/Auth/AuthState.jsx
@@ -31,7 +31,6 @@ function AuthState({ children }) {
       credentials,
       { withCredentials: true }
     );
-    console.log;
     // set current user to login and merge accessToken into currentUser
     dispatchCurrentUser({ type: USER_ACTIONS.SET, payload: { ...result.data.record } });
     setAccessToken(result.data.token);
diff --git a/src/pages/User/ChangeMail.jsx b/src/pages/User/ChangeMail.jsx
index a546c8d..20cf1de 100644
--- a/src/pages/User/ChangeMail.jsx
+++ b/src/pages/User/ChangeMail.jsx
@@ -56,7 +56,6 @@ function ForgotPassword() {
       setFlashMsg(result.data?.message);
     } catch (error) {
       // catch the error
-      console.error(error);
       mergeBackendValidation(error.response.status, error.response.data, methods.setError);
     }
   }
diff --git a/src/pages/User/Login.jsx b/src/pages/User/Login.jsx
index cd81662..d8d73a6 100644
--- a/src/pages/User/Login.jsx
+++ b/src/pages/User/Login.jsx
@@ -82,7 +82,6 @@ function Login() {
       setFlashMsg(result.data?.message);
 
     } catch (err) {
-      console.log(err);
       // merge front & backend validation errors
       mergeBackendValidation(err.response.status, err.response.data, methods.setError);
     }
@@ -105,8 +104,9 @@ function Login() {
         </form>
       </FormProvider>
 
-      <div className="mt-4">
-        <Link to="/reset_password">Forgot your Password?</Link>
+      <div className="mt-4 flex justify-between">
+        <Link to="/reset_password">Reset Password</Link>
+        <Link to="/signup">Create an account</Link>
       </div>
     </>
   );
diff --git a/src/pages/User/Logout.jsx b/src/pages/User/Logout.jsx
index ba40eae..92358a3 100644
--- a/src/pages/User/Logout.jsx
+++ b/src/pages/User/Logout.jsx
@@ -28,7 +28,6 @@ function Logout() {
       // set flash mmessagesg
       setFlashMsg(result.data?.message);
     } catch (err) {
-      console.error(err);
       mergeBackendValidation(500, err);
     }
   }
diff --git a/src/pages/User/ResetPassword.jsx b/src/pages/User/ResetPassword.jsx
index 12fa32d..1950c9c 100644
--- a/src/pages/User/ResetPassword.jsx
+++ b/src/pages/User/ResetPassword.jsx
@@ -65,7 +65,6 @@ function ResetPasswordForm() {
       setFlashMsg(result.data?.message);
     } catch (error) {
       // catch the error
-      console.error(error);
       mergeBackendValidation(error.response.status, error.response.data, methods.setError);
     }
   }
diff --git a/src/pages/User/Signup.jsx b/src/pages/User/Signup.jsx
new file mode 100644
index 0000000..9823a68
--- /dev/null
+++ b/src/pages/User/Signup.jsx
@@ -0,0 +1,103 @@
+import React, { useEffect } from 'react';
+import { Link, useLocation, useNavigate } from 'react-router-dom';
+import { useAuth } from '../../contexts/Auth/AuthState';
+import { FormProvider, useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+import { z } from "zod";
+import { toast } from 'react-toastify';
+import { mergeBackendValidation, setFlashMsg } from '../../utils/ErrorHandling';
+import Input from '../../components/form/Input';
+import Submit from '../../components/form/Submit';
+import { Helmet } from 'react-helmet-async';
+import Heading from '../../components/font/Heading';
+import validator from 'validator';
+import api from '../../utils/AxiosConfig';
+
+function Signup() {
+
+  // #################################
+  // VALIDATION SCHEMA
+  // #################################
+  const schema = z.object({
+    name: z.string().min(1),
+    username: z.string().min(1),
+    email: z.string().email(),
+    password: z.string().refine((val) => val && validator.isStrongPassword(val), {
+      message: 'This value must be min 6 characters long and contain uppercase, lowercase, number, specialchar.',
+    }),
+    passwordConfirm: z.string(),
+  }).refine((data) => data.password === data.passwordConfirm, {
+    message: "Passwords don't match",
+    path: ["passwordConfirm"],
+  });
+  // #################################
+  // HOOKS
+  // #################################
+  // ### CONNECT AUTH CONTEXT
+  const { signup } = useAuth();
+  // ### MAKE USE OF NAVIGATION
+  const redirect = useNavigate();
+
+  // ### MAKE USE OF location state to fetch former requested page
+  const { state } = useLocation();
+  // ### PREPARE FORM
+  const methods = useForm({
+    resolver: zodResolver(schema),
+    mode: 'onBlur',
+    defaultValues: {
+      name: '',
+      username: '',
+      email: '',
+      password: '',
+      passwordConfirm: ''
+    }
+  });
+
+
+  // #################################
+  // FUNCTIONS
+  // #################################
+  // ### HANDLE SUBMITTING LOGIN FORM
+  async function handleSendForm(record) {
+    try {
+      // send data to API
+      const result = await api.post(`/users/signup`, record);
+      // if successfull redirect to login page
+      redirect(`/login`);
+      // FIX: flash message not dislayed
+      setFlashMsg(result.data?.message);
+
+    } catch (err) {
+      // merge front & backend validation errors
+      mergeBackendValidation(err.response.status, err.response.data, methods.setError);
+    }
+  }
+
+  // #################################
+  // OUTPUT
+  // #################################
+  return (
+    <>
+      {/* render page title */}
+      <Helmet><title>[{import.meta.env.VITE_APP_NAME}]</title></Helmet>
+
+      <Heading level="1">ZBH-Portal LogIn</Heading>
+      <FormProvider {...methods} >
+        <form onSubmit={methods.handleSubmit(handleSendForm)}>
+          <Input name='name' type='text' title='Name' className='h-16' autoFocus={true} />
+          <Input name='username' type='text' title='username' className='h-16' />
+          <Input name='email' type='mail' title='E-Mail' className='h-16' />
+          <Input name='password' type='password' title='password' className='h-16' />
+          <Input name='passwordConfirm' type='password' title='confirm password' className='h-16' />
+          <Submit value='Signup' />
+        </form>
+      </FormProvider>
+
+      <div className="mt-4">
+        <Link to="/login">Back to Login</Link>
+      </div>
+    </>
+  );
+}
+
+export default Signup;
\ No newline at end of file
diff --git a/src/pages/User/Verify.jsx b/src/pages/User/Verify.jsx
new file mode 100644
index 0000000..16de139
--- /dev/null
+++ b/src/pages/User/Verify.jsx
@@ -0,0 +1,85 @@
+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 Verify() {
+  // #################################
+  // VALIDATION SCHEMA
+  // #################################
+  // TODO limit file size via .env
+  // TODO check for file types
+  const schema = z.object({
+    token: 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/confirmverification`, inputs);
+      redirect('/login');
+      // 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">Verify Account</Heading>
+      <FormProvider {...methods} >
+        <form onSubmit={methods.handleSubmit(handleSendForm)}>
+          <Input name='token' type='text' title='confirm token' required={true} />
+          <Submit value='Verify' />
+        </form>
+      </FormProvider>
+
+      <div className="my-4 flex justify-between">
+        <Link to="/login">Back to Login</Link>
+      </div>
+    </>
+  );
+}
+
+export default React.memo(Verify);
\ No newline at end of file
diff --git a/src/routes/Sitemap.jsx b/src/routes/Sitemap.jsx
index 657f1dc..0b7664b 100644
--- a/src/routes/Sitemap.jsx
+++ b/src/routes/Sitemap.jsx
@@ -50,6 +50,13 @@ export const sitemap = [{
   }]
 }, {
   title: 'Others', element: <CleanLayout />, children: [
+    // SIGNUP
+    {
+      path: '/signup', children: [
+        { index: true, element: loadComponent('User/Signup') },
+        { path: ':token', element: loadComponent('User/Verify') }
+      ]
+    },
     // LOGIN
     { path: '/login', element: loadComponent('User/Login') },
     // FORGOT PASSWORD
diff --git a/src/utils/AxiosConfig.js b/src/utils/AxiosConfig.js
index 2310743..7e9e6a7 100755
--- a/src/utils/AxiosConfig.js
+++ b/src/utils/AxiosConfig.js
@@ -33,13 +33,13 @@ api.interceptors.response.use(
     return res;
   },
   async (err) => {
-    // console.log(err);
+    // console.log('JWT error: ', err);
     // save original request config
     const originalConfig = err.config;
     // if access denied and not a retry already
     // BUG: Infinit loop because _retry isn't set at runtime
-    // console.log(originalConfig);
-    // console.log(JSON.stringify(originalConfig));
+    // console.log('originalConfig: ',originalConfig);
+    // console.log('originalConfig: ',JSON.stringify(originalConfig));
     if (originalConfig && err?.response?.status === 403 && originalConfig._retry !== true) {
       // patch config to remember it's a retry
       originalConfig._retry = true;
-- 
GitLab