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