From 025bee3c7da97ef460280320626adceaf7228bd8 Mon Sep 17 00:00:00 2001 From: Sean Ryan Date: Fri, 19 Apr 2024 14:51:08 -0700 Subject: [PATCH 1/4] Sean 4/19 created MUIType interface in Interfaces.ts > > Co-author-by: Heather Pfeiffer Co-author-by: Jesse Wowczuk Co-author-by: Sean Ryan Co-author-by: Austin Alvarez --- app/src/interfaces/Interfaces.ts | 14 ++++++++++++++ package.json | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/interfaces/Interfaces.ts b/app/src/interfaces/Interfaces.ts index ee9e04ed..3df07aa1 100644 --- a/app/src/interfaces/Interfaces.ts +++ b/app/src/interfaces/Interfaces.ts @@ -101,6 +101,20 @@ export interface HTMLType { framework: string; nestable: boolean; } +export interface MUIType { + id: number; + tag: string; + name: string; + style: any; + placeHolderShort: string | React.JSX.Element; + placeHolderLong: string; + // ? == optional type part of icon, cant comment out icon and it works + // Icon?: any; + icon?: any; + framework: string; + nestable: boolean; + imports: any[]; +} export interface DragItem extends DragObjectWithType { newInstance: boolean; instanceType: string; diff --git a/package.json b/package.json index 3bb49d25..9688ef3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "reactype", - "version": "20.0.0 by Yameng Zhang, Brian Yan, Cyrus Burns, John Wage", + "version": "21.0.0 by Austin Alvarez, Heather Pfeiffer, Sean Ryan, Jesse Wowczuk, Zack Vandiver", "description": "Prototyping tool for React/Typescript Applications.", "private": true, "main": "app/.electron/main.js", From e92beed1f900fd70b7bf22290b89b86673cae822 Mon Sep 17 00:00:00 2001 From: Sean Ryan Date: Mon, 22 Apr 2024 12:10:06 -0700 Subject: [PATCH 2/4] fixed forgot password component; updated syntax for signup and login components; added update password endpoint and associated middleware --- __tests__/userAuth.test.ts | 56 ++++ app/src/components/login/FBPassWord.tsx | 367 ++++++++++++++---------- app/src/components/login/SignIn.tsx | 313 +++++++++++--------- app/src/components/login/SignUp.tsx | 204 ++++++------- app/src/helperFunctions/auth.ts | 352 ++++++++++++++++++++--- app/src/interfaces/Interfaces.ts | 26 ++ package-lock.json | 180 +++++++++++- package.json | 1 + server/controllers/userController.ts | 41 ++- server/interfaces.ts | 1 + server/server.ts | 13 +- 11 files changed, 1091 insertions(+), 463 deletions(-) diff --git a/__tests__/userAuth.test.ts b/__tests__/userAuth.test.ts index 4a3d8c90..c8b032de 100644 --- a/__tests__/userAuth.test.ts +++ b/__tests__/userAuth.test.ts @@ -147,4 +147,60 @@ describe('User Authentication tests', () => { .then((res) => expect(res.text).toBe('"Incorrect Password"')); }); }); + + describe('/updatePassword', () => { + describe('POST', () => { + //testing update password + const testUsername = `supertest${Date.now()}`; + const testPassword = `password${Date.now()}`; + it('responds with status 200 and json string on valid password update (Success)', () => { + return request(app) + .post('/updatePassword') + .set('Content-Type', 'application/json') + .send({ + username: testUsername, + password: testPassword + }) + .expect(200) + .then((res) => expect(res.body.message).toBe('Success')); // might need to be res.text instead of res.body.message + }); + + it('responds with status 400 and json string if no password is provided (Password is required.)', () => { + return request(app) + .post('/updatePassword') + .send({ username: testUsername }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(400) + .then((res) => expect(res.body.error).toBe('Password is required.')); + }); + + it('responds with status 404 and json string if user is not found (User not found.)', () => { + return request(app) + .post('/updatePassword') + .send({ username: 'doesntexist', password: 'fakepassword' }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(404) + .then((res) => expect(res.body.error).toBe('User not found.')); + }); + + it('responds with status 400 and json string the provided password is the same as the current password (New password must be different from the current password.)', () => { + return request(app) + .post('/updatePassword') + .send({ + username: testUsername, + password: testPassword + }) + .set('Accept', 'application/json') + .expect('Content-Type', /json/) + .expect(400) + .then((res) => + expect(res.body.error).toBe( + 'New password must be different from the current password.' + ) + ); + }); + }); + }); }); diff --git a/app/src/components/login/FBPassWord.tsx b/app/src/components/login/FBPassWord.tsx index 717b855d..f594abee 100644 --- a/app/src/components/login/FBPassWord.tsx +++ b/app/src/components/login/FBPassWord.tsx @@ -1,11 +1,18 @@ import React, { useState, MouseEvent } from 'react'; import { LoginInt } from '../../interfaces/Interfaces'; +import { SigninDark } from '../../../../app/src/public/styles/theme'; import { Link as RouteLink, withRouter, - RouteComponentProps + RouteComponentProps, + useHistory } from 'react-router-dom'; -import { newUserIsCreated } from '../../helperFunctions/auth'; +import { + validateInputs, + handleChange, + resetErrorValidation, + updatePassword +} from '../../helperFunctions/auth'; import Avatar from '@mui/material/Avatar'; import Button from '@mui/material/Button'; import CssBaseline from '@mui/material/CssBaseline'; @@ -13,8 +20,18 @@ import TextField from '@mui/material/TextField'; import Grid from '@mui/material/Grid'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import makeStyles from '@mui/styles/makeStyles'; +import { styled } from '@mui/material/styles'; import Container from '@mui/material/Container'; +import AssignmentIcon from '@mui/icons-material/Assignment'; +import { + StyledEngineProvider, + Theme, + ThemeProvider +} from '@mui/material/styles'; + +declare module '@mui/styles/defaultTheme' { + interface DefaultTheme extends Theme {} +} function Copyright() { return ( @@ -26,181 +43,219 @@ function Copyright() { ); } -const useStyles = makeStyles((theme) => ({ - paper: { - marginTop: theme.spacing(8), - display: 'flex', - flexDirection: 'column', - alignItems: 'center' - }, - avatar: { - margin: theme.spacing(1), - backgroundColor: '#3EC1AC' - }, - form: { - width: '100%', // Fix IE 11 issue. - marginTop: theme.spacing(3) - }, - submit: { - margin: theme.spacing(3, 0, 2) - }, - root: { - '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': { - borderColor: '#3EC1AC' - } +const StyledPaper = styled(Box)(({ theme }) => ({ + marginTop: theme.spacing(8), + display: 'flex', + flexDirection: 'column', + alignItems: 'center' +})); + +const StyledAvatar = styled(Avatar)(({ theme }) => ({ + margin: theme.spacing(1), + backgroundColor: 'white' +})); + +const StyledForm = styled('form')(({ theme }) => ({ + width: '100%', // Fix IE 11 issue. + marginTop: theme.spacing(3) +})); + +const StyledButton = styled(Button)(({ theme }) => ({ + margin: theme.spacing(3, 0, 2) +})); + +const StyledTextField = styled(TextField)(({ theme }) => ({ + '& .MuiOutlinedInput-root.Mui-focused .MuiOutlinedInput-notchedOutline': { + borderColor: '#white' } })); -const SignUp: React.FC = (props) => { - const classes = useStyles(); +const FBPassWord: React.FC = () => { + const history = useHistory(); + const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [passwordVerify, setPasswordVerify] = useState(''); + const [invalidUserMsg, setInvalidUserMsg] = useState(''); const [invalidPasswordMsg, setInvalidPasswordMsg] = useState(''); const [invalidVerifyPasswordMsg, setInvalidVerifyPasswordMsg] = useState(''); + const [invalidUser, setInvalidUser] = useState(false); const [invalidPassword, setInvalidPassword] = useState(false); const [invalidVerifyPassword, setInvalidVerifyPassword] = useState(false); - const handleChange = (e: React.ChangeEvent) => { - let inputVal = e.target.value; - switch (e.target.name) { - case 'password': - setPassword(inputVal); - break; - case 'passwordVerify': - setPasswordVerify(inputVal); - break; - } + // define error setters to pass to resetErrorValidation function + const errorSetters = { + setInvalidUser, + setInvalidUserMsg, + setInvalidPassword, + setInvalidPasswordMsg, + setInvalidVerifyPassword, + setInvalidVerifyPasswordMsg + }; + // define handle change setters to pass to handleChange function + const handleChangeSetters = { + setUsername, + setPassword, + setPasswordVerify }; - const handleSignUp = (e: React.MouseEvent) => { - e.preventDefault(); - const email = props.location.state.email; - // Reset Error Validation - setInvalidPasswordMsg(''); - setInvalidVerifyPasswordMsg(''); - setInvalidPassword(false); - setInvalidVerifyPassword(false); - - if (password === '') { - setInvalidPassword(true); - setInvalidPasswordMsg('No Password Entered'); - return; - } else if (password.length < 8) { - setInvalidPassword(true); - setInvalidPasswordMsg('Minimum 8 Characters'); - return; - } else if ( - !/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/i.test( - password - ) - ) { - setInvalidPassword(true); - setInvalidPasswordMsg('Minimum 1 Letter, Number, and Special Character'); - return; - } else if (password !== passwordVerify) { - setInvalidPassword(true); - setInvalidVerifyPassword(true); - setInvalidPasswordMsg('Verification Failed'); - setInvalidVerifyPasswordMsg('Verification Failed'); - setPasswordVerify(''); - return; - } else { - setInvalidPassword(false); - } + /** + * Handles input changes for form fields and updates the state accordingly. + * This function delegates to the `handleChange` function, passing the event + * and the `handleChangeSetters` for updating the specific state tied to the input fields. + * @param {React.ChangeEvent} e - The event object that triggered the change. + */ + const handleInputChange = (e: React.ChangeEvent): void => { + handleChange(e, handleChangeSetters); + }; - if (password !== passwordVerify) { - setInvalidPassword(true); - setInvalidVerifyPassword(true); - setInvalidPasswordMsg('Verification Failed'); - setInvalidVerifyPasswordMsg('Verification Failed'); - setPasswordVerify(''); + /** + * Handles the form submission for user password change. Prevents the default form submission behavior, + * resets any previous validation errors, and, if the input validation passes, attempts to update the user's password. + * Upon successful password update, the user is redirected to the login page. If the update fails or validation fails, + * appropriate error messages are displayed. + * @param {React.FormEvent} e - The event object that triggered the form submission, + * used to prevent the default form behavior. + * @returns {void} Nothing is returned from this function as it handles redirection or error display internally. + */ + const handleUpdatePassword = async (e: React.FormEvent) => { + e.preventDefault(); + resetErrorValidation(errorSetters); // Reset validation errors before a new password update attempt. + const isValid = validateInputs({ + username, + password, + passwordVerify, + errorSetters + }); // Validate Inputs using Auth helper function + if (!isValid) { + console.log('Validation failed, not updating password.'); return; - } else { - setInvalidVerifyPassword(false); } - - // get username and email from FB - newUserIsCreated(email, email, password).then((userCreated) => { - if (userCreated === 'Success') { - props.history.push('/'); + try { + const isUpdated = await updatePassword(username, password); + console.log(isUpdated); + if (isUpdated === 'Success') { + history.push('/login'); } else { + console.log( + 'Update password failed: Unknown or unhandled error', + isUpdated + ); } - }); + } catch (err) { + console.error( + 'Error during password updating in handleUpdatePassword:', + err + ); + } }; return ( - - -
- - - - - Please enter in your new password - -
- - - - - - - - - - + - - - - Already have an account? Sign In - - - -
-
- - - -
+ + + + + + + + + + Please Enter In Your New Password + + + + + + + + + + + + + + + Update Password + + + + + + Already have an account? + Sign In + + + + + + + + + + + + ); }; -export default withRouter(SignUp); +export default withRouter(FBPassWord); diff --git a/app/src/components/login/SignIn.tsx b/app/src/components/login/SignIn.tsx index b68766d2..ac1b83b7 100644 --- a/app/src/components/login/SignIn.tsx +++ b/app/src/components/login/SignIn.tsx @@ -1,17 +1,26 @@ import React, { useCallback, useEffect, useState } from 'react'; -import { RouteComponentProps, Link as RouteLink } from 'react-router-dom'; +import { + RouteComponentProps, + Link as RouteLink, + useHistory +} from 'react-router-dom'; import { SigninDark } from '../../../../app/src/public/styles/theme'; import { StyledEngineProvider, Theme, ThemeProvider } from '@mui/material/styles'; -import { useDispatch } from 'react-redux'; +import { styled } from '@mui/material/styles'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import { LoginInt } from '../../interfaces/Interfaces'; import serverConfig from '../../serverConfig.js'; import makeStyles from '@mui/styles/makeStyles'; -import { sessionIsCreated } from '../../helperFunctions/auth'; +import { + sessionIsCreated, + handleChange, + resetErrorValidation, + validateInputs +} from '../../helperFunctions/auth'; import { Divider, Box, @@ -19,7 +28,6 @@ import { Button, Container, CssBaseline, - Grid, TextField, Typography } from '@mui/material'; @@ -62,17 +70,27 @@ const useStyles = makeStyles((theme) => ({ } })); -const SignIn: React.FC = (props) => { +const StyledForm = styled('form')(({ theme }) => ({ + width: '100%', // Fix IE 11 issue. + marginTop: theme.spacing(3) +})); + +const SignIn: React.FC = () => { const classes = useStyles(); - const dispatch = useDispatch(); + const history = useHistory(); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [invalidUserMsg, setInvalidUserMsg] = useState(''); - const [invalidPassMsg, setInvalidPassMsg] = useState(''); + const [invalidPassMsg, setInvalidPasswordMsg] = useState(''); const [invalidUser, setInvalidUser] = useState(false); - const [invalidPass, setInvalidPass] = useState(false); + const [invalidPass, setInvalidPassword] = useState(false); + /** + * Periodically checks for specific cookies and manages session state based on their presence. + * If a specific cookie is found, it stores the session ID in local storage and redirects to the home page. + * The check stops once the necessary cookie is found or if a session ID already exists in local storage. + */ useEffect(() => { const githubCookie = setInterval(() => { window.api?.setCookie(); @@ -80,7 +98,7 @@ const SignIn: React.FC = (props) => { if (cookie[0]) { window.localStorage.setItem('ssid', cookie[0].value); clearInterval(githubCookie); - props.history.push('/'); + history.push('/'); } else if (window.localStorage.getItem('ssid')) { clearInterval(githubCookie); } @@ -88,55 +106,80 @@ const SignIn: React.FC = (props) => { }, 2000); }, []); - const handleChange = (e: React.ChangeEvent) => { - let inputVal = e.target.value; - - switch (e.target.name) { - case 'username': - setUsername(inputVal); - break; + // define error setters to pass to resetErrorValidation function + const errorSetters = { + setInvalidUser, + setInvalidUserMsg, + setInvalidPassword, + setInvalidPasswordMsg + }; + // define handle change setters to pass to handleChange function + const handleChangeSetters = { + setUsername, + setPassword + }; - case 'password': - setPassword(inputVal); - break; - } + /** + * Handles input changes for form fields and updates the state accordingly. + * This function delegates to the `handleChange` function, passing the event + * and the `handleChangeSetters` for updating the specific state tied to the input fields. + * @param {React.ChangeEvent} e - The event object that triggered the change. + */ + const handleInputChange = (e: React.ChangeEvent): void => { + handleChange(e, handleChangeSetters); }; - const handleLogin = (e: React.MouseEvent) => { - e.preventDefault(); - setInvalidUser(false); - setInvalidUserMsg(''); - setInvalidPass(false); - setInvalidPassMsg(''); - sessionIsCreated(username, password, false).then((loginStatus) => { + /** + * Handles the form submission for user login. This function prevents the default form submission behavior, + * resets any previous validation errors, and attempts to create a session with the provided credentials. + * If successful, the user is redirected to the home page. Otherwise, it updates the UI with appropriate error messages + * based on the error type returned from the login attempt. + * @param {React.FormEvent} e - The event object that triggered the form submission, typically used to prevent the default form behavior. + */ + const handleLogin = async (e: React.FormEvent) => { + e.preventDefault(); // Prevent default form submission behavior. + resetErrorValidation(errorSetters); // Reset validation errors before a new login attempt. + const isValid = validateInputs({ + username, + password, + errorSetters + }); // Validate Inputs using Auth helper function + if (!isValid) { + console.log('Validation failed, login attempt not processed.'); + return; + } + // Attempt to create a session using the provided credentials. + try { + const loginStatus = await sessionIsCreated(username, password, false); if (loginStatus === 'Success') { - props.history.push('/'); + console.log('Login successful, redirecting...'); + history.push('/'); } else { - switch (loginStatus) { - case 'No Username Input': - setInvalidUser(true); - setInvalidUserMsg(loginStatus); - break; - - case 'No Password Input': - setInvalidPass(true); - setInvalidPassMsg(loginStatus); - break; - - case 'Invalid Username': - setInvalidUser(true); - setInvalidUserMsg(loginStatus); - break; - - case 'Incorrect Password': - setInvalidPass(true); - setInvalidPassMsg(loginStatus); - break; + if ( + [ + 'No Username Input', + 'No Password Input', + 'Invalid Username', + 'Incorrect Password' + ].includes(loginStatus) + ) { + setInvalidUser(true); + setInvalidUserMsg(loginStatus); + } else { + console.error('Unhandled error during login:', loginStatus); } } - }); + } catch (err) { + console.error('Error during signin in handleLogin:', err); + } }; + /** + * Handles the "Enter" key press to trigger a sign-in button click. + * This function checks if the pressed key is "Enter" and, if so, prevents the default action + * and programmatically clicks the sign-in button. This allows users to submit the form by pressing Enter. + * @param {KeyboardEvent} e - The keyboard event that triggered this handler. + */ const keyBindSignIn = useCallback((e) => { if (e.key === 'Enter') { e.preventDefault(); @@ -144,6 +187,12 @@ const SignIn: React.FC = (props) => { } }, []); + /** + * Sets up and cleans up the keydown event listener for the sign-in form. + * This effect binds the 'Enter' key to trigger a sign-in button click across the component. + * It ensures that the event listener is removed when the component unmounts to prevent memory leaks + * and unintended behavior in other parts of the application. + */ useEffect(() => { document.addEventListener('keydown', keyBindSignIn); return () => { @@ -151,24 +200,6 @@ const SignIn: React.FC = (props) => { }; }, []); - const handleLoginGuest = ( - e: React.MouseEvent - ) => { - e.preventDefault(); - window.localStorage.setItem('ssid', 'guest'); - props.history.push('/'); - }; - - const handleGithubLogin = ( - e: React.MouseEvent - ) => { - e.preventDefault(); - window.location.assign(`${API_BASE_URL}/auth/github`); - }; - - const classBtn = - 'MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-4 MuiButton-fullWidth'; - return ( @@ -192,82 +223,84 @@ const SignIn: React.FC = (props) => { > Log in - - + + + - - + + + + + Log In + + OR handleGithubLogin(e)} + onClick={(e) => { + e.preventDefault(); + window.location.assign(`${API_BASE_URL}/auth/github`); + }} sx={{ marginBottom: '1rem', textTransform: 'none', @@ -295,8 +328,6 @@ const SignIn: React.FC = (props) => { { @@ -328,11 +359,13 @@ const SignIn: React.FC = (props) => { Sign in With Google handleLoginGuest(e)} + onClick={(e) => { + e.preventDefault(); + window.localStorage.setItem('ssid', 'guest'); + history.push('/'); + }} sx={{ marginBottom: '1rem', textTransform: 'none', @@ -363,7 +396,7 @@ const SignIn: React.FC = (props) => { Forgot password? diff --git a/app/src/components/login/SignUp.tsx b/app/src/components/login/SignUp.tsx index 59305c90..1e8825a3 100644 --- a/app/src/components/login/SignUp.tsx +++ b/app/src/components/login/SignUp.tsx @@ -2,18 +2,15 @@ import React, { useState } from 'react'; import { RouteComponentProps, Link as RouteLink, - withRouter + withRouter, + useHistory } from 'react-router-dom'; -import { - SigninDark, - SigninLight -} from '../../../../app/src/public/styles/theme'; +import { SigninDark } from '../../../../app/src/public/styles/theme'; import { StyledEngineProvider, Theme, ThemeProvider } from '@mui/material/styles'; -import { useDispatch, useSelector } from 'react-redux'; import { Box, Avatar, @@ -27,10 +24,15 @@ import { import AssignmentIcon from '@mui/icons-material/Assignment'; import { LoginInt } from '../../interfaces/Interfaces'; -import { RootState } from '../../redux/store'; import makeStyles from '@mui/styles/makeStyles'; -import { newUserIsCreated } from '../../helperFunctions/auth'; +import { + newUserIsCreated, + handleChange, + resetErrorValidation, + validateInputs, + setErrorMessages +} from '../../helperFunctions/auth'; declare module '@mui/styles/defaultTheme' { // eslint-disable-next-line @typescript-eslint/no-empty-interface @@ -66,135 +68,97 @@ const useStyles = makeStyles((theme) => ({ } })); -const SignUp: React.FC = (props) => { +const SignUp: React.FC = () => { const classes = useStyles(); - const dispatch = useDispatch(); + const history = useHistory(); const [email, setEmail] = useState(''); const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [passwordVerify, setPasswordVerify] = useState(''); const [invalidEmailMsg, setInvalidEmailMsg] = useState(''); - const [invalidUsernameMsg, setInvalidUsernameMsg] = useState(''); + const [invalidUsernameMsg, setInvalidUserMsg] = useState(''); const [invalidPasswordMsg, setInvalidPasswordMsg] = useState(''); const [invalidVerifyPasswordMsg, setInvalidVerifyPasswordMsg] = useState(''); const [invalidEmail, setInvalidEmail] = useState(false); - const [invalidUsername, setInvalidUsername] = useState(false); + const [invalidUsername, setInvalidUser] = useState(false); const [invalidPassword, setInvalidPassword] = useState(false); const [invalidVerifyPassword, setInvalidVerifyPassword] = useState(false); - const handleChange = (e: React.ChangeEvent) => { - let inputVal = e.target.value; - switch (e.target.name) { - case 'email': - setEmail(inputVal); - break; - case 'username': - setUsername(inputVal); - break; - case 'password': - setPassword(inputVal); - break; - case 'passwordVerify': - setPasswordVerify(inputVal); - break; - } + // define error setters to pass to resetErrorValidation function + const errorSetters = { + setInvalidEmail, + setInvalidEmailMsg, + setInvalidUser, + setInvalidUserMsg, + setInvalidPassword, + setInvalidPasswordMsg, + setInvalidVerifyPassword, + setInvalidVerifyPasswordMsg + }; + // define handle change setters to pass to handleChange function + const handleChangeSetters = { + setEmail, + setUsername, + setPassword, + setPasswordVerify }; - const handleSignUp = (e: React.MouseEvent) => { - e.preventDefault(); - - // Reset Error Validation - setInvalidEmailMsg(''); - setInvalidUsernameMsg(''); - setInvalidPasswordMsg(''); - setInvalidVerifyPasswordMsg(''); - setInvalidEmail(false); - setInvalidUsername(false); - setInvalidPassword(false); - setInvalidVerifyPassword(false); - - if (email === '') { - setInvalidEmail(true); - setInvalidEmailMsg('No Email Entered'); - return; - } else if (!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(email)) { - setInvalidEmail(true); - setInvalidEmailMsg('Invalid Email Format'); - return; - } else { - setInvalidEmail(false); - } - - if (username === '') { - setInvalidUsername(true); - setInvalidUsernameMsg('No Username Entered'); - return; - } else if (!/^[\w\s-]{4,15}$/i.test(username)) { - setInvalidUsername(true); - setInvalidUsernameMsg('Must Be 4 - 15 Characters Long'); - return; - } else if (!/^[\w-]+$/i.test(username)) { - setInvalidUsername(true); - setInvalidUsernameMsg('Cannot Contain Spaces or Special Characters'); - return; - } else { - setInvalidUsername(false); - } + /** + * Handles input changes for form fields and updates the state accordingly. + * This function delegates to the `handleChange` function, passing the event + * and the `handleChangeSetters` for updating the specific state tied to the input fields. + * @param {React.ChangeEvent} e - The event object that triggered the change. + */ + const handleInputChange = (e: React.ChangeEvent): void => { + handleChange(e, handleChangeSetters); + }; - if (password === '') { - setInvalidPassword(true); - setInvalidPasswordMsg('No Password Entered'); - return; - } else if (password.length < 8) { - setInvalidPassword(true); - setInvalidPasswordMsg('Minimum 8 Characters'); - return; - } else if ( - !/^(?=.*[A-Za-z])(?=.*\d)(?=.*[@$!%*#?&])[A-Za-z\d@$!%*#?&]{8,}$/i.test( - password - ) - ) { - setInvalidPassword(true); - setInvalidPasswordMsg('Minimum 1 Letter, Number, and Special Character'); - return; - } else if (password !== passwordVerify) { - setInvalidPassword(true); - setInvalidVerifyPassword(true); - setInvalidPasswordMsg('Verification Failed'); - setInvalidVerifyPasswordMsg('Verification Failed'); - setPasswordVerify(''); - return; - } else { - setInvalidPassword(false); - } + /** + * Handles the form submission for user registration. Prevents default form behavior, + * validates user inputs, and attempts to register a new user. Redirects to home on success, + * otherwise displays error messages based on the response. + * @param {React.MouseEvent} e - The event object that triggered the submission. + */ + const handleSignUp = async ( + e: React.MouseEvent + ) => { + e.preventDefault(); + resetErrorValidation(errorSetters); // Reset validation errors before a new signup attempt. + const isValid = validateInputs({ + email, + username, + password, + passwordVerify, + errorSetters + }); // Validate Inputs using Auth helper function - if (password !== passwordVerify) { - setInvalidPassword(true); - setInvalidVerifyPassword(true); - setInvalidPasswordMsg('Verification Failed'); - setInvalidVerifyPasswordMsg('Verification Failed'); - setPasswordVerify(''); + if (!isValid) { + console.log('Validation failed, account not created.'); return; - } else { - setInvalidVerifyPassword(false); } - - newUserIsCreated(username, email, password).then((userCreated) => { + try { + const userCreated = await newUserIsCreated(username, email, password); if (userCreated === 'Success') { - props.history.push('/'); + console.log('Account creation successful, redirecting...'); + history.push('/'); } else { switch (userCreated) { case 'Email Taken': - setInvalidEmail(true); - setInvalidEmailMsg('Email Taken'); + setErrorMessages('email', 'Email Taken', errorSetters); break; case 'Username Taken': - setInvalidUsername(true); - setInvalidUsernameMsg('Username Taken'); + setErrorMessages('username', 'Username Taken', errorSetters); break; + default: + console.log( + 'Signup failed: Unknown or unhandled error', + userCreated + ); } } - }); + } catch (error) { + console.error('Error during signup in handleSignUp:', error); + } }; return ( @@ -227,15 +191,15 @@ const SignUp: React.FC = (props) => { className={classes.root} variant="outlined" required - fullWidth id="email" label="Email" name="email" autoComplete="email" value={email} - onChange={handleChange} + onChange={handleInputChange} helperText={invalidEmailMsg} error={invalidEmail} + sx={{ width: '100%' }} /> @@ -243,15 +207,15 @@ const SignUp: React.FC = (props) => { className={classes.root} variant="outlined" required - fullWidth id="username" label="Username" name="username" autoComplete="username" value={username} - onChange={handleChange} + onChange={handleInputChange} helperText={invalidUsernameMsg} error={invalidUsername} + sx={{ width: '100%' }} /> @@ -259,16 +223,16 @@ const SignUp: React.FC = (props) => { className={classes.root} variant="outlined" required - fullWidth name="password" label="Password" type="password" id="password" autoComplete="current-password" value={password} - onChange={handleChange} + onChange={handleInputChange} helperText={invalidPasswordMsg} error={invalidPassword} + sx={{ width: '100%' }} /> @@ -276,16 +240,16 @@ const SignUp: React.FC = (props) => { className={classes.root} variant="outlined" required - fullWidth name="passwordVerify" label="Verify Password" type="password" id="passwordVerify" autoComplete="verify-password" value={passwordVerify} - onChange={handleChange} + onChange={handleInputChange} helperText={invalidVerifyPasswordMsg} error={invalidVerifyPassword} + sx={{ width: '100%' }} /> @@ -315,7 +279,6 @@ const SignUp: React.FC = (props) => {