From a76c94aea489b5ae4ebddb7b3465d7431a45755c Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Tue, 4 Mar 2025 11:33:11 +0100 Subject: [PATCH 01/12] ILEX-96 add username field --- src/components/Auth/Register.jsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/components/Auth/Register.jsx b/src/components/Auth/Register.jsx index 848a79cf..158605ae 100644 --- a/src/components/Auth/Register.jsx +++ b/src/components/Auth/Register.jsx @@ -18,6 +18,7 @@ const Register = () => { const [formData, setFormData] = React.useState({ firstName: "", lastName: "", + username: "", email: "", password: "", organization: "", @@ -32,6 +33,7 @@ const Register = () => { const response = await handleRegister( formData.firstName, formData.lastName, + formData.username, formData.email, formData.password, formData.organization @@ -86,6 +88,16 @@ const Register = () => { error={!!errors.lastName} helperText={errors.lastName} /> + + setFormData({ ...formData, username: e.target.value }) + } + error={!!errors.username} + helperText={errors.username} + /> Date: Tue, 4 Mar 2025 11:34:55 +0100 Subject: [PATCH 02/12] ILEX-96 accept username in api call --- src/api/endpoints/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/endpoints/index.ts b/src/api/endpoints/index.ts index cf250c3c..9a158a63 100644 --- a/src/api/endpoints/index.ts +++ b/src/api/endpoints/index.ts @@ -364,6 +364,7 @@ export const handleLogin = async (email: string, password: string) => { export const handleRegister = async ( firstName: string, lastName: string, + username: string, email: string, password: string, organization: string @@ -373,6 +374,7 @@ export const handleRegister = async ( const response = await postOpsUserNew({ firstName, lastName, + username, email, password, organization, From 27331123898a1fd59ae34234ef22be3bba73ef91 Mon Sep 17 00:00:00 2001 From: ddelpiano Date: Tue, 25 Mar 2025 14:02:45 +0100 Subject: [PATCH 03/12] popup implementation for login and registration --- src/components/Auth/Login.jsx | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index 5aeed8b9..d6ebeb24 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -22,6 +22,26 @@ const Login = () => { password: "", }); + React.useEffect(() => { + let eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; + let eventer = window[eventMethod]; + let messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; + eventer(messageEvent, function (e) { + // For Aigul, this is where you will get the response code/status and the user data + console.log(e); + // Expect something similar to this object below, you can use this one already to set the user data + // eslint-disable-next-line no-unused-vars + let response = { + code: "200", + status: "200", + username: "johndoe", + email: "johndoe@gmail.com", + token: "1234567890", + orcid: "0000-0000-0000-0000", + }; + }); + }, []); + const handleInputChange = (e) => { const { name, value } = e.target; setFormData((prev) => ({ @@ -40,8 +60,10 @@ const Login = () => { }; const handleOrcidSignIn = () => { - const orcidSignInUrl = `https://uri.olympiangods.org/u/ops/orcid-login`; - window.location.href = orcidSignInUrl; + // For Aigul, customise the url so that the base url is stored in a setting file, + // same for the routes we are calling. + const orcidSignInUrl = `http://127.0.0.1:8606/u/ops/orcid-new`; + window.open(orcidSignInUrl, "Orcid Sign In", "width=600,height=800").focus(); }; return ( @@ -103,7 +125,9 @@ const Login = () => { - or + + or + From 1bd9cbb2daa853c0779a4e395fa36dc71463c0f6 Mon Sep 17 00:00:00 2001 From: ddelpiano Date: Tue, 25 Mar 2025 14:41:43 +0100 Subject: [PATCH 04/12] login is based on username, not email --- src/components/Auth/Login.jsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index d6ebeb24..b4e79ceb 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -15,10 +15,11 @@ import { CheckedIcon, UncheckedIcon, OrcidIcon } from "../../Icons"; import FormField from "./UI/Formfield"; import PasswordField from "./UI/PasswordField"; import { handleLogin } from "../../api/endpoints/index"; +import { API_CONFIG } from "../../config"; const Login = () => { const [formData, setFormData] = React.useState({ - email: "", + username: "", password: "", }); @@ -52,7 +53,7 @@ const Login = () => { const loginUser = async () => { try { - await handleLogin(formData.email, formData.password); + await handleLogin(formData.username, formData.password); console.log("Login successful"); } catch (error) { console.error("Login error:", error); @@ -62,7 +63,7 @@ const Login = () => { const handleOrcidSignIn = () => { // For Aigul, customise the url so that the base url is stored in a setting file, // same for the routes we are calling. - const orcidSignInUrl = `http://127.0.0.1:8606/u/ops/orcid-new`; + const orcidSignInUrl = API_CONFIG.OLYMPIAN_GODS + API_CONFIG.REAL_API.LOGIN_ORCID; window.open(orcidSignInUrl, "Orcid Sign In", "width=600,height=800").focus(); }; @@ -78,11 +79,11 @@ const Login = () => {
Date: Tue, 25 Mar 2025 14:42:25 +0100 Subject: [PATCH 05/12] some changes to organise the code --- src/api/endpoints/index.ts | 11 ++++++----- src/api/endpoints/interLexURIStructureAPI.ts | 5 +---- src/config.js | 4 ++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/api/endpoints/index.ts b/src/api/endpoints/index.ts index 9a158a63..1d456972 100644 --- a/src/api/endpoints/index.ts +++ b/src/api/endpoints/index.ts @@ -346,15 +346,16 @@ export const addMessateToVariantDiscussion = async (group, variantID, message) = }); } -export const handleLogin = async (email: string, password: string) => { +export const handleLogin = async (username: string, password: string) => { try { const { postOpsUserLogin } = useApi() const response = await postOpsUserLogin({ - email, - password, + data: { + username, + password, + }, }); console.log("Login successful:", response); - return response.data; } catch (error) { console.error("Login failed:", error); throw error; @@ -413,4 +414,4 @@ export const handleRecoverUser = async (email: string) => { console.error("Recover user failed:", error); throw error; } -}; \ No newline at end of file +}; diff --git a/src/api/endpoints/interLexURIStructureAPI.ts b/src/api/endpoints/interLexURIStructureAPI.ts index 2b79b428..46014efe 100644 --- a/src/api/endpoints/interLexURIStructureAPI.ts +++ b/src/api/endpoints/interLexURIStructureAPI.ts @@ -489,6 +489,7 @@ export const postOpsUserLogin = ( return customInstance( + // TODO change this using config url and api route {url: `https://uri.olympiangods.org/u/ops/user-login`, method: 'POST' }, options); @@ -8746,7 +8747,3 @@ export const useGetEndpointsVersions = { const [formData, setFormData] = React.useState({ username: "", password: "", }); + const [errors, setErrors] = React.useState({}); + const { setUserData } = React.useContext(GlobalDataContext); + const navigate = useNavigate(); React.useEffect(() => { let eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; let eventer = window[eventMethod]; let messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; eventer(messageEvent, function (e) { - // For Aigul, this is where you will get the response code/status and the user data console.log(e); - // Expect something similar to this object below, you can use this one already to set the user data + const { status, message, userData } = e.data; // eslint-disable-next-line no-unused-vars let response = { code: "200", @@ -40,6 +49,20 @@ const Login = () => { token: "1234567890", orcid: "0000-0000-0000-0000", }; + if(status === 200) { + setUserData({ username: response.username, email: response.email, id: orcid }) + navigate("/") + } else if (status === 401) { + setErrors((prevErrors) => ({ + ...prevErrors, + auth: "Invalid username or password. Please try again", + })); + } else { + setErrors((prevErrors) => ({ + ...prevErrors, + auth: message || "An unknown error occurred. Please try again", + })); + } }); }, []); @@ -53,16 +76,26 @@ const Login = () => { const loginUser = async () => { try { - await handleLogin(formData.username, formData.password); - console.log("Login successful"); + await schema.validate(formData, { abortEarly: false }) + setErrors({}) + + try { + await handleLogin(formData.username || "", formData.password || "") + } catch (e) { + console.log("error: ", e) + } } catch (error) { - console.error("Login error:", error); + const newErrors = {} + error.inner.forEach((e) => { + if (e.path) { + newErrors[e.path] = e.message + } + }) + setErrors(newErrors) } }; const handleOrcidSignIn = () => { - // For Aigul, customise the url so that the base url is stored in a setting file, - // same for the routes we are calling. const orcidSignInUrl = API_CONFIG.OLYMPIAN_GODS + API_CONFIG.REAL_API.LOGIN_ORCID; window.open(orcidSignInUrl, "Orcid Sign In", "width=600,height=800").focus(); }; @@ -77,6 +110,7 @@ const Login = () => { Log in to your account Welcome! Please enter your details. + {errors.auth && {errors.auth}} { placeholder="Enter your username" value={formData.username} onChange={handleInputChange} + errorMessage={errors.username} /> { helperText="Required" value={formData.password} onChange={handleInputChange} + errorMessage={errors.password} /> { +const FormField = ({ xs = 12, label, helperText, placeholder, name, value, onChange, errorMessage }) => { return ( @@ -14,7 +15,17 @@ const FormField = ({ xs = 12, label, helperText, placeholder, name, value, onCha name={name} // Pass the name prop value={value} onChange={onChange} + autoComplete="off" + error={errorMessage} + endAdornment={ + errorMessage && ( + + + + ) + } /> + {errorMessage && {`${errorMessage.charAt(0).toUpperCase() + errorMessage.slice(1)}`}} ); @@ -28,6 +39,7 @@ FormField.propTypes = { name: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, + errorMessage: PropTypes.string, }; FormField.defaultProps = { diff --git a/src/components/Auth/UI/PasswordField.jsx b/src/components/Auth/UI/PasswordField.jsx index d6d02545..54682505 100644 --- a/src/components/Auth/UI/PasswordField.jsx +++ b/src/components/Auth/UI/PasswordField.jsx @@ -8,11 +8,13 @@ import { InputAdornment, IconButton, Grid, + Typography } from "@mui/material"; import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined"; +import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined'; -const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, onChange }) => { +const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, onChange, errorMessage }) => { const [showPassword, setShowPassword] = useState(false); const handleClickShowPassword = () => setShowPassword((show) => !show); @@ -32,22 +34,29 @@ const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, o name={name} // Pass the name prop value={value} onChange={onChange} + error={errorMessage} + autoComplete="off" endAdornment={ - - - {showPassword ? ( - + + + {errorMessage ? : <> + {showPassword ? ( + ) : ( - - )} - - + + )} + + } + + } /> + {errorMessage && {`${errorMessage.charAt(0).toUpperCase() + errorMessage.slice(1)}`}} ); @@ -61,6 +70,7 @@ PasswordField.propTypes = { name: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, + errorMessage: PropTypes.string, }; PasswordField.defaultProps = { From b5bdb61fc042bd578d1efe05d0d5fef3b3529ace Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Tue, 25 Mar 2025 19:12:01 +0100 Subject: [PATCH 07/12] Revert "ILEX-96 handle status code and add form validation" This reverts commit 708802b46cf0a7b1898113e74c494f2ebbbde159. --- package.json | 11 +++-- src/components/Auth/Login.jsx | 52 ++++-------------------- src/components/Auth/UI/Formfield.jsx | 16 +------- src/components/Auth/UI/PasswordField.jsx | 36 ++++++---------- 4 files changed, 28 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index 9e91387d..3ed59959 100644 --- a/package.json +++ b/package.json @@ -17,18 +17,17 @@ "@mui/lab": "^5.0.0-alpha.170", "@mui/material": "^5.15.15", "@mui/x-tree-view": "^7.5.0", - "body-parser": "^1.20.3", - "cors": "^2.8.5", + "body-parser" : "^1.20.3", + "cors" : "^2.8.5", "d3": "^7.1.1", - "date-fns": "^3.6.0", "dotenv": "16.4.7", - "express": "^4.21.2", + "date-fns": "^3.6.0", + "express" : "^4.21.2", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.23.1", "react-rte": "^0.16.5", - "react-syntax-highlighter": "^15.5.0", - "yup": "^1.6.1" + "react-syntax-highlighter": "^15.5.0" }, "devDependencies": { "@faker-js/faker": "^8.4.1", diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index 70dd070a..b4e79ceb 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -10,36 +10,27 @@ import { } from "@mui/material"; import { ArrowBack } from "@mui/icons-material"; import Checkbox from "@mui/material/Checkbox"; -import { Link, useNavigate } from "react-router-dom"; +import { Link } from "react-router-dom"; import { CheckedIcon, UncheckedIcon, OrcidIcon } from "../../Icons"; import FormField from "./UI/Formfield"; import PasswordField from "./UI/PasswordField"; import { handleLogin } from "../../api/endpoints/index"; import { API_CONFIG } from "../../config"; -import { GlobalDataContext } from "../../contexts/DataContext"; -import * as yup from "yup"; - -const schema = yup.object().shape({ - username: yup.string().required().min(3), - password: yup.string().required().min(6), -}); const Login = () => { const [formData, setFormData] = React.useState({ username: "", password: "", }); - const [errors, setErrors] = React.useState({}); - const { setUserData } = React.useContext(GlobalDataContext); - const navigate = useNavigate(); React.useEffect(() => { let eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; let eventer = window[eventMethod]; let messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; eventer(messageEvent, function (e) { + // For Aigul, this is where you will get the response code/status and the user data console.log(e); - const { status, message, userData } = e.data; + // Expect something similar to this object below, you can use this one already to set the user data // eslint-disable-next-line no-unused-vars let response = { code: "200", @@ -49,20 +40,6 @@ const Login = () => { token: "1234567890", orcid: "0000-0000-0000-0000", }; - if(status === 200) { - setUserData({ username: response.username, email: response.email, id: orcid }) - navigate("/") - } else if (status === 401) { - setErrors((prevErrors) => ({ - ...prevErrors, - auth: "Invalid username or password. Please try again", - })); - } else { - setErrors((prevErrors) => ({ - ...prevErrors, - auth: message || "An unknown error occurred. Please try again", - })); - } }); }, []); @@ -76,26 +53,16 @@ const Login = () => { const loginUser = async () => { try { - await schema.validate(formData, { abortEarly: false }) - setErrors({}) - - try { - await handleLogin(formData.username || "", formData.password || "") - } catch (e) { - console.log("error: ", e) - } + await handleLogin(formData.username, formData.password); + console.log("Login successful"); } catch (error) { - const newErrors = {} - error.inner.forEach((e) => { - if (e.path) { - newErrors[e.path] = e.message - } - }) - setErrors(newErrors) + console.error("Login error:", error); } }; const handleOrcidSignIn = () => { + // For Aigul, customise the url so that the base url is stored in a setting file, + // same for the routes we are calling. const orcidSignInUrl = API_CONFIG.OLYMPIAN_GODS + API_CONFIG.REAL_API.LOGIN_ORCID; window.open(orcidSignInUrl, "Orcid Sign In", "width=600,height=800").focus(); }; @@ -110,7 +77,6 @@ const Login = () => { Log in to your account Welcome! Please enter your details. - {errors.auth && {errors.auth}} { placeholder="Enter your username" value={formData.username} onChange={handleInputChange} - errorMessage={errors.username} /> { helperText="Required" value={formData.password} onChange={handleInputChange} - errorMessage={errors.password} /> { +const FormField = ({ xs = 12, label, helperText, placeholder, name, value, onChange }) => { return ( @@ -15,17 +14,7 @@ const FormField = ({ xs = 12, label, helperText, placeholder, name, value, onCha name={name} // Pass the name prop value={value} onChange={onChange} - autoComplete="off" - error={errorMessage} - endAdornment={ - errorMessage && ( - - - - ) - } /> - {errorMessage && {`${errorMessage.charAt(0).toUpperCase() + errorMessage.slice(1)}`}} ); @@ -39,7 +28,6 @@ FormField.propTypes = { name: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, - errorMessage: PropTypes.string, }; FormField.defaultProps = { diff --git a/src/components/Auth/UI/PasswordField.jsx b/src/components/Auth/UI/PasswordField.jsx index 54682505..d6d02545 100644 --- a/src/components/Auth/UI/PasswordField.jsx +++ b/src/components/Auth/UI/PasswordField.jsx @@ -8,13 +8,11 @@ import { InputAdornment, IconButton, Grid, - Typography } from "@mui/material"; import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined"; -import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined'; -const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, onChange, errorMessage }) => { +const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, onChange }) => { const [showPassword, setShowPassword] = useState(false); const handleClickShowPassword = () => setShowPassword((show) => !show); @@ -34,29 +32,22 @@ const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, o name={name} // Pass the name prop value={value} onChange={onChange} - error={errorMessage} - autoComplete="off" endAdornment={ - - - {errorMessage ? : <> - {showPassword ? ( - + + + {showPassword ? ( + ) : ( - - )} - - } - - + + )} + + } /> - {errorMessage && {`${errorMessage.charAt(0).toUpperCase() + errorMessage.slice(1)}`}} ); @@ -70,7 +61,6 @@ PasswordField.propTypes = { name: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, - errorMessage: PropTypes.string, }; PasswordField.defaultProps = { From 53811620c02d3e970dc68b3f943fb872b3914b68 Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Tue, 25 Mar 2025 20:24:46 +0100 Subject: [PATCH 08/12] ILEX-96 add validation and add status code check --- src/components/Auth/Login.jsx | 56 ++++++++++++++++++------ src/components/Auth/UI/Formfield.jsx | 18 ++++++-- src/components/Auth/UI/PasswordField.jsx | 38 ++++++++++------ 3 files changed, 82 insertions(+), 30 deletions(-) diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index b4e79ceb..c9c84cfa 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -10,28 +10,36 @@ import { } from "@mui/material"; import { ArrowBack } from "@mui/icons-material"; import Checkbox from "@mui/material/Checkbox"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { CheckedIcon, UncheckedIcon, OrcidIcon } from "../../Icons"; import FormField from "./UI/Formfield"; import PasswordField from "./UI/PasswordField"; import { handleLogin } from "../../api/endpoints/index"; import { API_CONFIG } from "../../config"; +import { GlobalDataContext } from "../../contexts/DataContext"; +import * as yup from "yup"; + +const schema = yup.object().shape({ + username: yup.string().required().min(3), + password: yup.string().required().min(6), +}); const Login = () => { const [formData, setFormData] = React.useState({ username: "", password: "", }); + const [errors, setErrors] = React.useState({}); + const { setUserData } = React.useContext(GlobalDataContext); + const navigate = useNavigate(); React.useEffect(() => { let eventMethod = window.addEventListener ? "addEventListener" : "attachEvent"; let eventer = window[eventMethod]; let messageEvent = eventMethod === "attachEvent" ? "onmessage" : "message"; eventer(messageEvent, function (e) { - // For Aigul, this is where you will get the response code/status and the user data console.log(e); - // Expect something similar to this object below, you can use this one already to set the user data - // eslint-disable-next-line no-unused-vars + let response = { code: "200", status: "200", @@ -40,29 +48,48 @@ const Login = () => { token: "1234567890", orcid: "0000-0000-0000-0000", }; + + if(response.code === 200) { + setUserData({ username: response.username, email: response.email, id: response.orcid }); + navigate("/"); + } else if (response.code === 401) { + setErrors((prevErrors) => ({ + ...prevErrors, + auth: "Invalid username or password. Please try again", + })); + } else { + setErrors((prevErrors) => ({ + ...prevErrors, + auth: "An unknown error occurred. Please try again", + })); + } }); + + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const handleInputChange = (e) => { const { name, value } = e.target; - setFormData((prev) => ({ - ...prev, - [name]: value, - })); + setFormData((prev) => ({ ...prev, [name]: value })); }; const loginUser = async () => { try { - await handleLogin(formData.username, formData.password); - console.log("Login successful"); + await schema.validate(formData, { abortEarly: false }) + setErrors({}) + await handleLogin(formData.username, formData.password) } catch (error) { - console.error("Login error:", error); + const newErrors = {} + error.inner.forEach((e) => { + if (e.path) { + newErrors[e.path] = e.message + } + }) + setErrors(newErrors) } }; const handleOrcidSignIn = () => { - // For Aigul, customise the url so that the base url is stored in a setting file, - // same for the routes we are calling. const orcidSignInUrl = API_CONFIG.OLYMPIAN_GODS + API_CONFIG.REAL_API.LOGIN_ORCID; window.open(orcidSignInUrl, "Orcid Sign In", "width=600,height=800").focus(); }; @@ -77,6 +104,7 @@ const Login = () => { Log in to your account Welcome! Please enter your details. + {errors.auth && {errors.auth}} { placeholder="Enter your username" value={formData.username} onChange={handleInputChange} + errorMessage={errors.username} /> { helperText="Required" value={formData.password} onChange={handleInputChange} + errorMessage={errors.password} /> { +const FormField = ({ xs = 12, label, helperText, placeholder, name, value, onChange, errorMessage }) => { return ( @@ -14,7 +15,17 @@ const FormField = ({ xs = 12, label, helperText, placeholder, name, value, onCha name={name} // Pass the name prop value={value} onChange={onChange} + autoComplete="off" + error={errorMessage} + endAdornment={ + errorMessage && ( + + + + ) + } /> + {errorMessage && {`${errorMessage.charAt(0).toUpperCase() + errorMessage.slice(1)}`}} ); @@ -28,6 +39,7 @@ FormField.propTypes = { name: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, + errorMessage: PropTypes.string, }; FormField.defaultProps = { @@ -35,4 +47,4 @@ FormField.defaultProps = { placeholder: "", }; -export default FormField; +export default FormField; \ No newline at end of file diff --git a/src/components/Auth/UI/PasswordField.jsx b/src/components/Auth/UI/PasswordField.jsx index d6d02545..011f8d4c 100644 --- a/src/components/Auth/UI/PasswordField.jsx +++ b/src/components/Auth/UI/PasswordField.jsx @@ -8,11 +8,13 @@ import { InputAdornment, IconButton, Grid, + Typography } from "@mui/material"; import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined"; +import ErrorOutlineOutlinedIcon from '@mui/icons-material/ErrorOutlineOutlined'; -const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, onChange }) => { +const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, onChange, errorMessage }) => { const [showPassword, setShowPassword] = useState(false); const handleClickShowPassword = () => setShowPassword((show) => !show); @@ -32,22 +34,29 @@ const PasswordField = ({ xs = 12, label, placeholder, helperText, name, value, o name={name} // Pass the name prop value={value} onChange={onChange} + error={errorMessage} + autoComplete="off" endAdornment={ - - - {showPassword ? ( - + + + {errorMessage ? : <> + {showPassword ? ( + ) : ( - - )} - - + + )} + + } + + } /> + {errorMessage && {`${errorMessage.charAt(0).toUpperCase() + errorMessage.slice(1)}`}} ); @@ -61,6 +70,7 @@ PasswordField.propTypes = { name: PropTypes.string.isRequired, value: PropTypes.string.isRequired, onChange: PropTypes.func.isRequired, + errorMessage: PropTypes.string, }; PasswordField.defaultProps = { @@ -68,4 +78,4 @@ PasswordField.defaultProps = { helperText: "", }; -export default PasswordField; +export default PasswordField; \ No newline at end of file From b7fde1d65f1be58da75310fbc1a4ec3ba64cb1a2 Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Tue, 25 Mar 2025 20:49:41 +0100 Subject: [PATCH 09/12] ILEX-96 add form validation in register page --- src/components/Auth/Login.jsx | 16 ++++----- src/components/Auth/Register.jsx | 56 ++++++++++++++++++++++---------- 2 files changed, 47 insertions(+), 25 deletions(-) diff --git a/src/components/Auth/Login.jsx b/src/components/Auth/Login.jsx index c9c84cfa..d91232a5 100644 --- a/src/components/Auth/Login.jsx +++ b/src/components/Auth/Login.jsx @@ -7,6 +7,7 @@ import { Grid, Paper, Typography, + Alert, } from "@mui/material"; import { ArrowBack } from "@mui/icons-material"; import Checkbox from "@mui/material/Checkbox"; @@ -77,15 +78,14 @@ const Login = () => { try { await schema.validate(formData, { abortEarly: false }) setErrors({}) + await handleLogin(formData.username, formData.password) } catch (error) { - const newErrors = {} - error.inner.forEach((e) => { - if (e.path) { - newErrors[e.path] = e.message - } - }) - setErrors(newErrors) + console.error("Login error:", error); + setErrors((prevErrors) => ({ + ...prevErrors, + auth: "An unknown error occurred. Please try again", + })); } }; @@ -103,8 +103,8 @@ const Login = () => { Log in to your account Welcome! Please enter your details. + {errors.auth && {errors.auth}} - {errors.auth && {errors.auth}} { const [formData, setFormData] = React.useState({ @@ -25,11 +35,13 @@ const Register = () => { }); const [errors, setErrors] = React.useState({}); - const [errorMessage, setErrorMessage] = React.useState(""); const navigate = useNavigate(); const registerUser = async () => { try { + await schema.validate(formData, { abortEarly: false }) + setErrors({}) + const response = await handleRegister( formData.firstName, formData.lastName, @@ -41,14 +53,24 @@ const Register = () => { if (response.status === 200) { navigate("/"); + } else if(response.status === 401) { + setErrors((prevErrors) => ({ + ...prevErrors, + auth: "Invalid data. Please try again", + })); } else { const errorData = await response.json(); - setErrors(errorData.errors || {}); - setErrorMessage(errorData.message || "Something went wrong."); + setErrors((prevErrors) => ({ + ...prevErrors, + auth: errorData.message || "An unknown error occurred. Please try again", + })); } } catch (error) { console.error("Registration error:", error); - setErrorMessage("An unexpected error occurred. Please try again."); + setErrors((prevErrors) => ({ + ...prevErrors, + auth: "An unknown error occurred. Please try again", + })); } }; @@ -62,7 +84,7 @@ const Register = () => { Register a new account and join - {errorMessage && {errorMessage}} + {errors.auth && {errors.auth}} @@ -74,8 +96,8 @@ const Register = () => { onChange={(e) => setFormData({ ...formData, firstName: e.target.value }) } - error={!!errors.firstName} - helperText={errors.firstName} + errorMessage={errors.firstName} + helperText="Required" /> { onChange={(e) => setFormData({ ...formData, lastName: e.target.value }) } - error={!!errors.lastName} - helperText={errors.lastName} + errorMessage={errors.lastName} + helperText="Required" /> { onChange={(e) => setFormData({ ...formData, username: e.target.value }) } - error={!!errors.username} - helperText={errors.username} + errorMessage={errors.username} + helperText="Required" /> { onChange={(e) => setFormData({ ...formData, email: e.target.value }) } - error={!!errors.email} - helperText={errors.email} + errorMessage={errors.email} + helperText="Required" /> { onChange={(e) => setFormData({ ...formData, password: e.target.value }) } - error={!!errors.password} - helperText={errors.password} + errorMessage={errors.password} + helperText="Required" /> { onChange={(e) => setFormData({ ...formData, organization: e.target.value }) } - error={!!errors.organization} - helperText={errors.organization} + errorMessage={errors.organization} + helperText="Required" /> From 69e0fab2197c0913e9474fb48db494546901f1f4 Mon Sep 17 00:00:00 2001 From: Aiga115 Date: Tue, 25 Mar 2025 21:14:29 +0100 Subject: [PATCH 10/12] ILEX-96 connect user to topbar --- src/components/Header/index.jsx | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/components/Header/index.jsx b/src/components/Header/index.jsx index 20adcfbf..e9ee8b88 100644 --- a/src/components/Header/index.jsx +++ b/src/components/Header/index.jsx @@ -34,6 +34,7 @@ import ListItemButton from '@mui/material/ListItemButton'; import { GlobalDataContext } from "../../contexts/DataContext"; import EditBulkTermsDialog from "../Dashboard/EditBulkTerms/EditBulkTermsDialog"; import ModeEditOutlineOutlinedIcon from "@mui/icons-material/ModeEditOutlineOutlined"; +import PersonOutlineIcon from '@mui/icons-material/PersonOutline'; import { vars } from "../../theme/variables"; const { gray200, white, gray100, gray600 } = vars; @@ -101,6 +102,17 @@ const styles = { keyBoardInfo: { borderRadius: '0.25rem', pointerEvents: 'none', background: gray100, color: gray600, fontSize: '0.875rem', lineHeight: '142.857%', p: '0.125rem 0.5rem' + }, + + avatar: { + border: '0.0469rem solid rgba(0,0,0,0.08)', + width: '2.5rem', + height: '2.5rem', + '& .MuiSvgIcon-root': { + width: '1.5rem', + height: '1.5rem', + fontSize: '1.5rem' + } } } @@ -297,8 +309,8 @@ const Header = ({ isLoggedIn = true }) => { {!isLoggedIn ? ( - - + +