diff --git a/package-lock.json b/package-lock.json
index 7e75c6e4..d59b58fa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5820,6 +5820,11 @@
}
}
},
+ "fn-name": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/fn-name/-/fn-name-3.0.0.tgz",
+ "integrity": "sha512-eNMNr5exLoavuAMhIUVsOKF79SWd/zG104ef6sxBTSw+cZc6BXdQXDvYcGvp0VbxVVSp1XDUNoz7mg1xMtSznA=="
+ },
"follow-redirects": {
"version": "1.12.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.12.1.tgz",
@@ -7902,6 +7907,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
+ "lodash-es": {
+ "version": "4.17.15",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.15.tgz",
+ "integrity": "sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ=="
+ },
"lodash._reinterpolate": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz",
@@ -10257,6 +10267,11 @@
"react-is": "^16.8.1"
}
},
+ "property-expr": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.2.tgz",
+ "integrity": "sha512-bc/5ggaYZxNkFKj374aLbEDqVADdYaLcFo8XBkishUWbaAdjlphaBFns9TvRA2pUseVL/wMFmui9X3IdNDU37g=="
+ },
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
@@ -12389,6 +12404,11 @@
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
"integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
},
+ "synchronous-promise": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.13.tgz",
+ "integrity": "sha512-R9N6uDkVsghHePKh1TEqbnLddO2IY25OcsksyFp/qBe7XYd0PVbKEWxhcdMhpLzE1I6skj5l4aEZ3CRxcbArlA=="
+ },
"table": {
"version": "5.4.6",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
@@ -12701,6 +12721,11 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
+ "toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha1-riF2gXXRVZ1IvvNUILL0li8JwzA="
+ },
"tough-cookie": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
@@ -14161,6 +14186,20 @@
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
+ },
+ "yup": {
+ "version": "0.29.1",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-0.29.1.tgz",
+ "integrity": "sha512-U7mPIbgfQWI6M3hZCJdGFrr+U0laG28FxMAKIgNvgl7OtyYuUoc4uy9qCWYHZjh49b8T7Ug8NNDdiMIEytcXrQ==",
+ "requires": {
+ "@babel/runtime": "^7.9.6",
+ "fn-name": "~3.0.0",
+ "lodash": "^4.17.15",
+ "lodash-es": "^4.17.11",
+ "property-expr": "^2.0.2",
+ "synchronous-promise": "^2.0.10",
+ "toposort": "^2.0.2"
+ }
}
}
}
diff --git a/package.json b/package.json
index 0242dcb6..049b66ea 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.1",
"redux": "^4.0.5",
- "redux-thunk": "^2.3.0"
+ "redux-thunk": "^2.3.0",
+ "yup": "^0.29.1"
},
"scripts": {
"start": "react-scripts start",
diff --git a/public/index.html b/public/index.html
index aa069f27..f504b4bd 100644
--- a/public/index.html
+++ b/public/index.html
@@ -24,6 +24,9 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
+
React App
diff --git a/src/components/LoginForm.jsx b/src/components/LoginForm.jsx
index af1bed38..e5a37459 100644
--- a/src/components/LoginForm.jsx
+++ b/src/components/LoginForm.jsx
@@ -1,23 +1,48 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
import { login } from "../store/actions";
import { connect } from "react-redux";
+import * as Yup from 'yup'
+import loginFormSchema from "../validation/loginFormSchema";
+import styled from 'styled-components'
const initialLoginFormValues = {
username: "",
password: "",
};
+const initialErrorList = {
+ username: '',
+ password: ''
+}
const LoginForm = (props) => {
- const [loginFormValues, setLoginFormValues] = useState(
- initialLoginFormValues
- );
+ const [loginFormValues, setLoginFormValues] = useState(initialLoginFormValues);
+ const [errorList, setErrorList] = useState(initialErrorList)
+ const [disabled, setDisabled] = useState(true)
const history = useHistory();
const onLoginTextChange = (evt) => {
const { name, value } = evt.target;
+
+ Yup
+ .reach(loginFormSchema, name)
+ .validate(value)
+ .then(() => {
+ setErrorList({
+ ...errorList,
+ [name]: ''
+ })
+ })
+ .catch((err) => {
+ setErrorList({
+ ...errorList,
+ [name]: err.errors[0]
+ })
+ })
+
setLoginFormValues({ ...loginFormValues, [name]: value });
};
+
const onLoginSubmit = (evt) => {
evt.preventDefault();
props.login(loginFormValues);
@@ -25,28 +50,46 @@ const LoginForm = (props) => {
setLoginFormValues(initialLoginFormValues);
};
+ useEffect(() => {
+ loginFormSchema.isValid(loginFormValues).then(valid => {
+ setDisabled(!valid)
+ })
+ }, [loginFormValues])
+
return (
-
+
+
+ Login
+
+
+
+
+
{errorList.username}
+
{errorList.password}
+
{errorList.department}
+
+
+
+
+
);
};
@@ -59,3 +102,46 @@ const mapState = (state) => {
};
export default connect(mapState, { login })(LoginForm);
+
+const FormContainer = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ font-family: 'Roboto Slab', serif;
+`
+const StyledForm = styled.form`
+ width: 70%;
+ /* border: 1px solid black; */
+ border-radius: 20px;
+ box-shadow: 1px 1px 5px black;
+ margin-top: 5%;
+ box-sizing: border-box;
+ padding: 3%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ label {
+ color: #0A2738;
+ }
+ input {
+ border: 1px solid #66889C;
+ border-radius: 2px;
+ }
+
+ * {
+ margin-top: 1.5%;
+ margin-bottom: 1.5%;
+ }
+`
+const StyledHeading = styled.h2`
+ color: #2196F3;
+`
+const StyledSubmit = styled.input`
+ background-color: #2196F3;
+ color: white;
+ border-radius: 10px !important;
+ border: none;
+ padding: .5rem 3rem;
+`
\ No newline at end of file
diff --git a/src/components/RegisterForm.jsx b/src/components/RegisterForm.jsx
index ee730c96..f5bfd940 100644
--- a/src/components/RegisterForm.jsx
+++ b/src/components/RegisterForm.jsx
@@ -1,23 +1,51 @@
-import React, { useState } from "react";
+import React, { useState, useEffect } from "react";
import { axiosWithAuth } from "../utils/axiosWithAuth";
import { useHistory } from "react-router-dom";
+import * as Yup from 'yup'
+import registerFormSchema from '../validation/registerFormSchema'
+import styled from 'styled-components'
const initialRegisterFormValues = {
username: "",
password: "",
+ confirmPassword: "",
department: "",
};
+const initialErrorList = {
+ username: '',
+ password: '',
+ confirmPassword: '',
+ department: ''
+}
const RegisterForm = (props) => {
- const [registerFormValues, setRegisterFormValues] = useState(
- initialRegisterFormValues
- );
+ const [registerFormValues, setRegisterFormValues] = useState(initialRegisterFormValues);
+ const [errorList, setErrorList] = useState(initialErrorList)
+ const [disabled, setDisabled] = useState(true)
const history = useHistory();
const onRegisterTextChange = (evt) => {
const { name, value } = evt.target;
+
+ Yup
+ .reach(registerFormSchema, name)
+ .validate(value)
+ .then(() => {
+ setErrorList({
+ ...errorList,
+ [name]: ''
+ })
+ })
+ .catch((err) => {
+ setErrorList({
+ ...errorList,
+ [name]: err.errors[0]
+ })
+ })
+
setRegisterFormValues({ ...registerFormValues, [name]: value });
};
+
const onRegisterSubmit = (evt) => {
evt.preventDefault();
console.log(registerFormValues);
@@ -31,44 +59,117 @@ const RegisterForm = (props) => {
});
setRegisterFormValues(initialRegisterFormValues);
};
+
+ useEffect(() => {
+ registerFormSchema.isValid(registerFormValues).then(valid => {
+ setDisabled(!valid)
+ })
+ }, [registerFormValues])
+
return (
-
+
+
+ Register
+
+
+
+
+
+
+
{errorList.username}
+
{errorList.password}
+
{errorList.confirmPassword}
+
{errorList.department}
+
+
+
+
+
);
};
export default RegisterForm;
+
+const FormContainer = styled.div`
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ font-family: 'Roboto Slab', serif;
+`
+const StyledForm = styled.form`
+ width: 70%;
+ /* border: 1px solid black; */
+ border-radius: 20px;
+ box-shadow: 1px 1px 5px black;
+ margin-top: 5%;
+ box-sizing: border-box;
+ padding: 3%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+
+ label {
+ color: #0A2738;
+ }
+ input {
+ border: 1px solid #66889C;
+ border-radius: 2px;
+ }
+
+ * {
+ margin-top: 1.5%;
+ margin-bottom: 1.5%;
+ }
+`
+const StyledHeading = styled.h2`
+ color: #2196F3;
+`
+const StyledSubmit = styled.input`
+ background-color: #2196F3;
+ color: white;
+ border-radius: 10px !important;
+ border: none;
+ padding: .5rem 3rem;
+`
\ No newline at end of file
diff --git a/src/index.css b/src/index.css
index ec2585e8..d05ec39b 100644
--- a/src/index.css
+++ b/src/index.css
@@ -1,8 +1,8 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
- 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
- sans-serif;
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
+ sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
diff --git a/src/validation/loginFormSchema.js b/src/validation/loginFormSchema.js
new file mode 100644
index 00000000..4bbc20d5
--- /dev/null
+++ b/src/validation/loginFormSchema.js
@@ -0,0 +1,14 @@
+import * as Yup from 'yup'
+
+const loginFormSchema = Yup.object().shape({
+ username: Yup
+ .string()
+ .trim()
+ .min(3, 'Your username is at least three characters long.')
+ .required('Please enter your username.'),
+ password: Yup
+ .string()
+ .required('Please enter your password.'),
+})
+
+export default loginFormSchema
\ No newline at end of file
diff --git a/src/validation/registerFormSchema.js b/src/validation/registerFormSchema.js
new file mode 100644
index 00000000..0ed13e45
--- /dev/null
+++ b/src/validation/registerFormSchema.js
@@ -0,0 +1,23 @@
+import * as Yup from 'yup'
+
+const registerFormSchema = Yup.object().shape({
+ username: Yup
+ .string()
+ .trim()
+ .min(3, 'Your username must be at least three characters long.')
+ .required('Please enter your desired username.'),
+ password: Yup
+ .string()
+ .min(8, 'Your password must be at least 8 characters long.')
+ .required('Please enter your desired password.'),
+ confirmPassword: Yup
+ .string()
+ .min(8, 'Your password must be at least 8 characters long.')
+ .oneOf([Yup.ref('password'), null])
+ .required('Please confirm your password.'),
+ department: Yup
+ .string()
+ .required('You must select a department.')
+})
+
+export default registerFormSchema
\ No newline at end of file