aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorilyayudovin <46264063+ilyayudovin@users.noreply.github.com>2020-08-29 23:39:15 +0300
committerGitHub <noreply@github.com>2020-08-29 23:39:15 +0300
commit96f1c8f86fc973ca66c8725a86b6cf277adb9c72 (patch)
tree866380704610aa2807777c21b3971dd71a020617
parent890141100a2e9b942a6ef2de15620fa5a01ba581 (diff)
parent3c4f51295ec4ccbed37d2faf8fd751608fc48843 (diff)
downloadwhich-ui-96f1c8f86fc973ca66c8725a86b6cf277adb9c72.tar.gz
Merge pull request #97 from which-ecosystem/validSignUp
fix input validation errors
-rw-r--r--.eslintrc.json1
-rw-r--r--package-lock.json91
-rw-r--r--package.json5
-rw-r--r--src/containers/Login/Login.tsx99
-rw-r--r--src/containers/Registration/Registration.tsx170
5 files changed, 243 insertions, 123 deletions
diff --git a/.eslintrc.json b/.eslintrc.json
index 75a787c..479e04f 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -21,6 +21,7 @@
"no-cond-assign": 0,
"no-nested-ternary": 0,
"linebreak-style": 0,
+ "object-curly-newline": 0,
"react/prop-types": 0,
"react/no-children-prop": 0,
"react/no-danger": 0,
diff --git a/package-lock.json b/package-lock.json
index 4f71e0b..d80d20f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1771,6 +1771,12 @@
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz",
"integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw=="
},
+ "@types/yup": {
+ "version": "0.29.6",
+ "resolved": "https://registry.npmjs.org/@types/yup/-/yup-0.29.6.tgz",
+ "integrity": "sha512-YPDo5L5uHyxQ4UkyJST+33stD8Z6IT9fvmKyaPAGxkZ6q19foEi6sQGkmqBvzSyRPdstFEeJiS2rKuTn8rfO5g==",
+ "dev": true
+ },
"@typescript-eslint/eslint-plugin": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.1.0.tgz",
@@ -4292,6 +4298,11 @@
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
+ "deepmerge": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
+ },
"default-gateway": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/default-gateway/-/default-gateway-4.2.0.tgz",
@@ -5860,6 +5871,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.11.0",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.11.0.tgz",
@@ -5928,6 +5944,32 @@
"mime-types": "^2.1.12"
}
},
+ "formik": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.1.5.tgz",
+ "integrity": "sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ==",
+ "requires": {
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.14",
+ "lodash-es": "^4.17.14",
+ "react-fast-compare": "^2.0.1",
+ "scheduler": "^0.18.0",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^1.10.0"
+ },
+ "dependencies": {
+ "scheduler": {
+ "version": "0.18.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.18.0.tgz",
+ "integrity": "sha512-agTSHR1Nbfi6ulI0kYNK0203joW2Y5W4po4l+v03tOoiJKpTBbxpNhWDvqc/4IcOw+KLmSiQLTasZ4cab2/UWQ==",
+ "requires": {
+ "loose-envify": "^1.1.0",
+ "object-assign": "^4.1.1"
+ }
+ }
+ }
+ },
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@@ -8089,6 +8131,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",
@@ -10447,6 +10494,11 @@
"react-is": "^16.8.1"
}
},
+ "property-expr": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.4.tgz",
+ "integrity": "sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg=="
+ },
"proxy-addr": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz",
@@ -10906,6 +10958,11 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.7.tgz",
"integrity": "sha512-TAv1KJFh3RhqxNvhzxj6LeT5NWklP6rDr2a0jaTfsZ5wSZWHOGeqQyejUp3xxLfPt2UpyJEcVQB/zyPcmonNFA=="
},
+ "react-fast-compare": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw=="
+ },
"react-icons": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-3.10.0.tgz",
@@ -12935,6 +12992,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",
@@ -13247,6 +13309,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",
@@ -14719,6 +14786,30 @@
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
}
+ },
+ "yup": {
+ "version": "0.29.3",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-0.29.3.tgz",
+ "integrity": "sha512-RNUGiZ/sQ37CkhzKFoedkeMfJM0vNQyaz+wRZJzxdKE7VfDeVKH8bb4rr7XhRLbHJz5hSjoDNwMEIaKhuMZ8gQ==",
+ "requires": {
+ "@babel/runtime": "^7.10.5",
+ "fn-name": "~3.0.0",
+ "lodash": "^4.17.15",
+ "lodash-es": "^4.17.11",
+ "property-expr": "^2.0.2",
+ "synchronous-promise": "^2.0.13",
+ "toposort": "^2.0.2"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.11.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.11.2.tgz",
+ "integrity": "sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ }
+ }
}
}
}
diff --git a/package.json b/package.json
index 22c87ff..2bb2500 100644
--- a/package.json
+++ b/package.json
@@ -9,6 +9,7 @@
"axios": "^0.19.2",
"bluebird": "^3.7.2",
"compressorjs": "^1.0.6",
+ "formik": "^2.1.5",
"lodash": "^4.17.15",
"notistack": "^0.9.17",
"react": "^16.13.1",
@@ -20,7 +21,8 @@
"react-virtualized": "^9.21.2",
"swr": "^0.3.0",
"typeface-roboto": "0.0.75",
- "which-types": "^1.6.1"
+ "which-types": "^1.6.1",
+ "yup": "^0.29.3"
},
"scripts": {
"start": "react-scripts start",
@@ -49,6 +51,7 @@
"@types/react-dom": "^16.9.8",
"@types/react-router-dom": "^5.1.5",
"@types/react-virtualized": "^9.21.10",
+ "@types/yup": "^0.29.6",
"@typescript-eslint/eslint-plugin": "^3.1.0",
"@typescript-eslint/parser": "^3.1.0",
"eslint": "^6.8.0",
diff --git a/src/containers/Login/Login.tsx b/src/containers/Login/Login.tsx
index bec0db5..3d58c63 100644
--- a/src/containers/Login/Login.tsx
+++ b/src/containers/Login/Login.tsx
@@ -1,14 +1,24 @@
-import React, { useState, useRef } from 'react';
+import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
+import { Formik, Form, Field } from 'formik';
import { makeStyles } from '@material-ui/core/styles';
import {
TextField,
Button,
FormControlLabel,
- Switch
+ Switch,
+ InputAdornment,
+ IconButton
} from '@material-ui/core';
+import { Visibility, VisibilityOff } from '@material-ui/icons';
import { useAuth } from '../../hooks/useAuth';
+interface Fields {
+ username: string;
+ password: string;
+ remember: boolean;
+}
+
const useStyles = makeStyles(theme => ({
root: {
'& > *': {
@@ -37,54 +47,71 @@ const useStyles = makeStyles(theme => ({
const Login: React.FC = () => {
const [error, setError] = useState<boolean>(false);
- const [remember, setRemember] = useState<boolean>(true);
+ const [showPassword, setShowPassword] = useState<boolean>(false);
const classes = useStyles();
- const nameRef = useRef<HTMLInputElement>();
- const passwordRef = useRef<HTMLInputElement>();
const { login } = useAuth();
const history = useHistory();
- const handleCheck = () => {
- setRemember(!remember);
- };
-
- const handleSubmit = async () => {
- const name = nameRef.current?.value?.toLowerCase();
- const password = passwordRef.current?.value;
- if (name && password) {
- login(name, password, remember).then(success => {
- if (success) history.push(`/profile/${name}`);
+ const handleSubmit = async ({ username, password, remember }: Fields) => {
+ if (username && password) {
+ login(username, password, remember).then(success => {
+ if (success) history.push(`/profile/${username}`);
else setError(true);
});
- }
+ } else setError(true);
};
-
const handleRegistration = () => {
history.push('/registration');
};
+ const toggleShowPassword = () => {
+ setShowPassword(prevState => !prevState);
+ };
+
return (
<>
<div className={classes.formHeader}>Sign In</div>
- <form className={classes.root} noValidate autoComplete="off">
- <TextField
- inputRef={nameRef}
- error={error}
- label="Login"
- />
- <TextField
- inputRef={passwordRef}
- error={error}
- helperText={error && 'Invalid credentials'}
- label="Password"
- type="password"
- />
- <FormControlLabel
- control={<Switch color="primary" onClick={handleCheck} checked={remember} size="small" />}
- label="Remember me"
- />
- <Button variant="contained" onClick={handleSubmit}>submit</Button>
- </form>
+ <Formik
+ initialValues={{ username: '', password: '', remember: true }}
+ onSubmit={handleSubmit}
+ >
+ {({ values, isSubmitting }) => (
+ <Form className={classes.root} autoComplete="off">
+ <Field
+ name="username"
+ label="Login"
+ value={values.username}
+ error={error}
+ as={TextField}
+ />
+ <Field
+ name="password"
+ label="Password"
+ as={TextField}
+ value={values.password}
+ error={error}
+ helperText={error && 'Invalid credentials'}
+ type={showPassword ? 'text' : 'password'}
+ InputProps={{
+ endAdornment: (
+ <InputAdornment position="end">
+ <IconButton size="small" onClick={toggleShowPassword}>
+ {showPassword ? <Visibility /> : <VisibilityOff />}
+ </IconButton>
+ </InputAdornment>
+ )
+ }}
+ />
+ <Field
+ name="remember"
+ label="Remember me"
+ as={FormControlLabel}
+ control={<Switch color="primary" checked={values.remember} size="small" />}
+ />
+ <Button variant="contained" type="submit" disabled={isSubmitting}>submit</Button>
+ </Form>
+ )}
+ </Formik>
<div className={classes.formTransfer}>
<div>{'Don\'t have an account?'}</div>
<span
diff --git a/src/containers/Registration/Registration.tsx b/src/containers/Registration/Registration.tsx
index 9bcea8e..a3aedb3 100644
--- a/src/containers/Registration/Registration.tsx
+++ b/src/containers/Registration/Registration.tsx
@@ -1,13 +1,35 @@
-import React, { useState, useRef } from 'react';
+import React, { useState } from 'react';
import { useHistory } from 'react-router-dom';
+import { Formik, Form, Field } from 'formik';
+import * as Yup from 'yup';
import { makeStyles } from '@material-ui/core/styles';
-import TextField from '@material-ui/core/TextField';
-import Button from '@material-ui/core/Button';
-import CheckCircleIcon from '@material-ui/icons/CheckCircle';
-import InputAdornment from '@material-ui/core/InputAdornment';
+import {
+ TextField,
+ Button,
+ InputAdornment,
+ IconButton
+} from '@material-ui/core';
+import { Visibility, VisibilityOff } from '@material-ui/icons';
import { post } from '../../requests';
import { useAuth } from '../../hooks/useAuth';
+interface Fields {
+ username: string;
+ email: string;
+ password: string;
+}
+
+const validationSchema = Yup.object({
+ username: Yup.string()
+ .lowercase('Must be lowercase')
+ .required('This field is required'),
+ email: Yup.string()
+ .email('Invalid email address')
+ .required('This field is required'),
+ password: Yup.string()
+ .min(6, 'Should be at least 6 characters')
+ .required('This field is required')
+});
const useStyles = makeStyles(theme => ({
root: {
@@ -35,102 +57,78 @@ const useStyles = makeStyles(theme => ({
}
}));
-const inputStyle = { WebkitBoxShadow: '0 0 0 1000px snow inset' };
-
-
const Registration: React.FC = () => {
- const errorOutputs = {
- usernameError: 'Username is required',
- emailError: 'Invalid email address',
- passwordError: 'Should be at least 6 characters'
- };
- const [errorPassword, setErrorPassword] = useState<boolean>(false);
- const [errorEmail, setErrorEmail] = useState<boolean>(false);
- const [errorUsername, setErrorUsername] = useState<boolean>(false);
-
const classes = useStyles();
- const usernameRef = useRef<HTMLInputElement>();
- const emailRef = useRef<HTMLInputElement>();
- const passwordRef = useRef<HTMLInputElement>();
const { login } = useAuth();
const history = useHistory();
-
- const handleSubmit = () => {
- const username = usernameRef.current?.value?.toLowerCase();
- const password = passwordRef.current?.value;
- const email = emailRef.current?.value;
- if (username && password) {
- post('/users', { username, password, email })
- .then(() => login(username, password))
- .then(() => history.push(`/profile/${username}`));
- }
- };
+ const [showPassword, setShowPassword] = useState<boolean>(false);
const handleLogin = () => {
history.push('/login');
};
- const handleLoginChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- setErrorUsername(e.currentTarget.value.length === 0);
+ const handleSubmit = ({ username, email, password }: Fields) => {
+ post('/users', { username, email, password })
+ .then(() => login(username, password))
+ .then(() => history.push(`/profile/${username}`));
};
- const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- setErrorEmail(!/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(e.currentTarget.value));
- };
- const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- setErrorPassword(e.currentTarget.value.length < 6);
+
+ const toggleShowPassword = () => {
+ setShowPassword(prevState => !prevState);
};
return (
<>
<div className={classes.formHeader}>Sign Up</div>
- <form className={classes.root} noValidate autoComplete="off">
- <TextField
- inputRef={usernameRef}
- label="Username"
- error={errorUsername}
- helperText={errorUsername && errorOutputs.usernameError}
- required
- onChange={handleLoginChange}
- inputProps={{ style: inputStyle }}
- />
- <TextField
- inputRef={emailRef}
- label="Email"
- error={errorEmail}
- helperText={errorEmail && errorOutputs.emailError}
- onChange={handleEmailChange}
- InputProps={errorEmail ? {} : {
- endAdornment: (
- <InputAdornment position="end">
- <CheckCircleIcon color="primary" />
- </InputAdornment>
- ),
- inputProps: {
- style: inputStyle
- }
- }}
- />
- <TextField
- inputRef={passwordRef}
- label="Password"
- type="password"
- required
- error={errorPassword}
- helperText={errorPassword && errorOutputs.passwordError}
- onChange={handlePasswordChange}
- InputProps={errorPassword ? {} : {
- endAdornment: (
- <InputAdornment position="end">
- <CheckCircleIcon color="primary" />
- </InputAdornment>
- ),
- inputProps: {
- style: inputStyle
- }
- }}
- />
- <Button variant="contained" onClick={handleSubmit}>submit</Button>
- </form>
+ <Formik
+ initialValues={{ username: '', email: '', password: '' }}
+ validationSchema={validationSchema}
+ onSubmit={handleSubmit}
+ >
+ {({ values, errors, touched, isSubmitting }) => (
+ <Form className={classes.root} autoComplete="off">
+ <Field
+ id="username"
+ name="username"
+ label="Username"
+ value={values.username.toLowerCase()}
+ error={touched.username && !!errors.username}
+ helperText={touched.username && errors.username}
+ required
+ as={TextField}
+ />
+ <Field
+ name="email"
+ label="Email"
+ value={values.email}
+ error={touched.email && !!errors.email}
+ helperText={touched.email && errors.email}
+ required
+ as={TextField}
+ />
+ <Field
+ name="password"
+ label="Password"
+ value={values.password}
+ error={touched.password && !!errors.password}
+ helperText={touched.password && errors.password}
+ required
+ type={showPassword ? 'text' : 'password'}
+ as={TextField}
+ InputProps={{
+ endAdornment: (
+ <InputAdornment position="end">
+ <IconButton size="small" onClick={toggleShowPassword}>
+ {showPassword ? <Visibility /> : <VisibilityOff />}
+ </IconButton>
+ </InputAdornment>
+ )
+ }}
+ />
+ <Button variant="contained" type="submit" disabled={isSubmitting}>submit</Button>
+ </Form>
+ )}
+ </Formik>
<div className={classes.formTransfer}>
<div>Already have an account?</div>
<span