diff --git a/package.json b/package.json index 892accc16..a87bf7160 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@material-ui/core": "^4.11.2", "@material-ui/icons": "^4.11.2", "@material-ui/lab": "^4.0.0-alpha.57", + "formik": "^2.2.6", "i18next": "^19.8.4", "mobx": "^6.0.4", "mobx-react": "^7.0.5", @@ -23,11 +24,13 @@ "react": "^17.0.1", "react-dom": "^17.0.1", "react-i18next": "^11.8.5", - "sass": "^1.32.4" + "sass": "^1.32.4", + "yup": "^0.32.8" }, "devDependencies": { "@types/node": "^14.14.21", "@types/react": "^17.0.0", + "@types/yup": "^0.29.11", "@typescript-eslint/eslint-plugin": "^4.13.0", "@typescript-eslint/parser": "^4.13.0", "eslint": "^7.18.0", diff --git a/public/locales/bg/auth.json b/public/locales/bg/auth.json index 507342f70..b9b555868 100644 --- a/public/locales/bg/auth.json +++ b/public/locales/bg/auth.json @@ -19,5 +19,11 @@ "forgotten-password": { "instructions": "За да смените паролата си, моля въведете вашия email и ще ви изпратим потвърждение с инареукции." } + }, + "validation":{ + "email": "Невалиден email", + "required": "Задължително поле", + "password-min": "Паролата трябва да бъде поне {{count}} символа", + "password-match": "Паролата не съвпада" } } diff --git a/public/locales/en/auth.json b/public/locales/en/auth.json index 68376e8e7..58b6f42a9 100644 --- a/public/locales/en/auth.json +++ b/public/locales/en/auth.json @@ -19,5 +19,11 @@ "forgotten-password": { "instructions": "To reset your password, please type your email address below. We will then send you an email with instructions to follow." } + }, + "validation":{ + "email": "Invalid email", + "required": "Required field", + "password-min": "Password should be at least {{count}} characters", + "password-match": "Password doesn't match" } } diff --git a/src/components/changePassword/ChangePasswordPage.tsx b/src/components/changePassword/ChangePasswordPage.tsx index f586aac16..d9e68400c 100644 --- a/src/components/changePassword/ChangePasswordPage.tsx +++ b/src/components/changePassword/ChangePasswordPage.tsx @@ -1,22 +1,42 @@ -import React, { useState } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import { Container, Grid, TextField, Button } from '@material-ui/core' +import { useFormik } from 'formik' +import * as yup from 'yup' import Layout from 'components/layout/Layout' export default function ChangePasswordPage() { const { t } = useTranslation() - const [password, setPassword] = useState('') - const [confirmPassword, setConfirmPassword] = useState('') - function handleSubmit(event: React.FormEvent) { - event.preventDefault() - } + const ChangePasswordSchema = yup.object().shape({ + password: yup + .string() + .min(6, t('auth:validation.password-min', { count: 6 })) + .required(t('auth:validation.required')), + confirmPassword: yup + .string() + .required(t('auth:validation.required')) + .oneOf([yup.ref('password'), null], t('auth:validation.password-match')), + }) + + const formik = useFormik({ + initialValues: { + password: '', + confirmPassword: '', + }, + validationSchema: ChangePasswordSchema, + validateOnChange: false, + validateOnBlur: false, + onSubmit: (values) => { + return + }, + }) return ( -
+ setPassword(event.target.value)} + error={!!formik.errors && !!formik.errors.password} + helperText={formik.errors && formik.errors.password} + value={formik.values.password} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> @@ -38,8 +61,11 @@ export default function ChangePasswordPage() { name="confirmPassword" size="small" variant="outlined" - value={confirmPassword} - onChange={(event) => setConfirmPassword(event.target.value)} + error={!!formik.errors && !!formik.errors.confirmPassword} + helperText={formik.errors && formik.errors.confirmPassword} + value={formik.values.confirmPassword} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> diff --git a/src/components/forgottenPassword/ForgottenPasswordPage.tsx b/src/components/forgottenPassword/ForgottenPasswordPage.tsx index 8ef40db84..39cdff1bc 100644 --- a/src/components/forgottenPassword/ForgottenPasswordPage.tsx +++ b/src/components/forgottenPassword/ForgottenPasswordPage.tsx @@ -1,21 +1,34 @@ -import React, { useState } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import { Typography, Container, Grid, TextField, Button } from '@material-ui/core' +import { useFormik } from 'formik' +import * as yup from 'yup' import Layout from 'components/layout/Layout' export default function ForgottenPasswordPage() { const { t } = useTranslation() - const [email, setEmail] = useState('') - function handleSubmit(event: React.FormEvent) { - event.preventDefault() - } + const ForgottenPasswordSchema = yup.object().shape({ + email: yup.string().email(t('auth:validation.email')).required(t('auth:validation.required')), + }) + + const formik = useFormik({ + initialValues: { + email: '', + }, + validationSchema: ForgottenPasswordSchema, + validateOnChange: false, + validateOnBlur: false, + onSubmit: (values) => { + return + }, + }) return ( - + {t('auth:pages.forgotten-password.instructions')} @@ -28,8 +41,11 @@ export default function ForgottenPasswordPage() { name="email" size="small" variant="outlined" - value={email} - onChange={(event) => setEmail(event.target.value)} + error={!!formik.errors && !!formik.errors.email} + helperText={formik.errors && formik.errors.email} + value={formik.values.email} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> diff --git a/src/components/login/LoginPage.tsx b/src/components/login/LoginPage.tsx index 036454976..db33f72b4 100644 --- a/src/components/login/LoginPage.tsx +++ b/src/components/login/LoginPage.tsx @@ -1,6 +1,8 @@ -import React, { useState } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import { Container, Grid, TextField, Button, Box } from '@material-ui/core' +import { useFormik } from 'formik' +import * as yup from 'yup' import { routes } from 'common/routes' import Layout from 'components/layout/Layout' @@ -8,21 +10,35 @@ import Link from 'components/shared/Link' import { AlertStore } from 'stores/AlertStore' export default function LoginPage() { - const { t } = useTranslation() - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') + const test = useTranslation() + console.log(test) + const t = test.t + const LoginSchema = yup.object().shape({ + email: yup.string().email(t('auth:validation.email')).required(t('auth:validation.required')), + password: yup + .string() + .min(6, t('auth:validation.password-min', { count: 6 })) + .required(t('auth:validation.required')), + }) - function handleSubmit(event: React.FormEvent) { - event.preventDefault() - } - function showAlert() { - AlertStore.show(t('auth:alerts.invalid-login'), 'error') - } + const formik = useFormik({ + initialValues: { + email: '', + password: '', + }, + validationSchema: LoginSchema, + validateOnChange: false, + validateOnBlur: false, + onSubmit: (values) => { + console.log(values) + AlertStore.show(t('auth:alerts.invalid-login'), 'error') + }, + }) return ( - + setEmail(event.target.value)} + error={!!formik.errors && !!formik.errors.email} + helperText={formik.errors && formik.errors.email} + value={formik.values.email} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> @@ -45,17 +64,15 @@ export default function LoginPage() { name="password" size="small" variant="outlined" - value={password} - onChange={(event) => setPassword(event.target.value)} + error={!!formik.errors && !!formik.errors.password} + helperText={formik.errors && formik.errors.password} + value={formik.values.password} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> - diff --git a/src/components/register/RegisterPage.tsx b/src/components/register/RegisterPage.tsx index 962ab0057..6ae218713 100644 --- a/src/components/register/RegisterPage.tsx +++ b/src/components/register/RegisterPage.tsx @@ -1,24 +1,43 @@ -import React, { useState } from 'react' +import React from 'react' import { useTranslation } from 'react-i18next' import { Container, Grid, TextField, Button } from '@material-ui/core' +import { useFormik } from 'formik' +import * as yup from 'yup' import Layout from 'components/layout/Layout' export default function RegisterPage() { const { t } = useTranslation() - const [firstName, setFirstName] = useState('') - const [lastName, setLastName] = useState('') - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - function handleSubmit(event: React.FormEvent) { - event.preventDefault() - } + const RegisterSchema = yup.object().shape({ + firstName: yup.string().required(t('auth:validation.required')), + lastName: yup.string().required(t('auth:validation.required')), + email: yup.string().email(t('auth:validation.email')).required(t('auth:validation.required')), + password: yup + .string() + .min(6, t('auth:validation.password-min', { count: 6 })) + .required(t('auth:validation.required')), + }) + + const formik = useFormik({ + initialValues: { + firstName: '', + lastName: '', + email: '', + password: '', + }, + validationSchema: RegisterSchema, + validateOnChange: false, + validateOnBlur: false, + onSubmit: (values) => { + return + }, + }) return ( - + setFirstName(event.target.value)} + error={!!formik.errors && !!formik.errors.firstName} + helperText={formik.errors && formik.errors.firstName} + value={formik.values.firstName} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> @@ -41,8 +63,11 @@ export default function RegisterPage() { name="lastName" size="small" variant="outlined" - value={lastName} - onChange={(event) => setLastName(event.target.value)} + error={!!formik.errors && !!formik.errors.lastName} + helperText={formik.errors && formik.errors.lastName} + value={formik.values.lastName} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> @@ -53,8 +78,11 @@ export default function RegisterPage() { name="email" size="small" variant="outlined" - value={email} - onChange={(event) => setEmail(event.target.value)} + error={!!formik.errors && !!formik.errors.email} + helperText={formik.errors && formik.errors.email} + value={formik.values.email} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> @@ -65,8 +93,11 @@ export default function RegisterPage() { name="password" size="small" variant="outlined" - value={password} - onChange={(event) => setPassword(event.target.value)} + error={!!formik.errors && !!formik.errors.password} + helperText={formik.errors && formik.errors.password} + value={formik.values.password} + onBlur={formik.handleBlur} + onChange={formik.handleChange} /> diff --git a/yarn.lock b/yarn.lock index 213aa7c12..e7f839c2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -201,6 +201,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.10.5": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.13.tgz#0a21452352b02542db0ffb928ac2d3ca7cb6d66d" + integrity sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.12.7": version "7.12.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.7.tgz#c817233696018e39fbb6c491d2fb684e05ed43bc" @@ -451,6 +458,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== +"@types/lodash@^4.14.165": + version "4.14.168" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.168.tgz#fe24632e79b7ade3f132891afff86caa5e5ce008" + integrity sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q== + "@types/mdast@^3.0.0": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.3.tgz#2d7d671b1cd1ea3deb306ea75036c2a0407d2deb" @@ -503,6 +515,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@types/yup@^0.29.11": + version "0.29.11" + resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.11.tgz#d654a112973f5e004bf8438122bd7e56a8e5cd7e" + integrity sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g== + "@typescript-eslint/eslint-plugin@^4.13.0": version "4.13.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz#5f580ea520fa46442deb82c038460c3dd3524bb6" @@ -1857,6 +1874,11 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" + integrity sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA== + define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -2616,6 +2638,19 @@ for-in@^1.0.2: resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= +formik@^2.2.6: + version "2.2.6" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.2.6.tgz#378a4bafe4b95caf6acf6db01f81f3fe5147559d" + integrity sha512-Kxk2zQRafy56zhLmrzcbryUpMBvT0tal5IvcifK5+4YNGelKsnrODFJ0sZQRMQboblWNym4lAW3bt+tf2vApSA== + dependencies: + 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" + tiny-warning "^1.0.2" + tslib "^1.10.0" + fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" @@ -2912,7 +2947,7 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -3704,6 +3739,11 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash-es@^4.17.11, lodash-es@^4.17.14: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.20.tgz#29f6332eefc60e849f869c264bc71126ad61e8f7" + integrity sha512-JD1COMZsq8maT6mnuz1UMV0jvYD0E0aUsSOdrr1/nAG3dhqQXwRRgeW0cSqH1U43INKcqxaiVIQNOUDld7gRDA== + lodash.sortby@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" @@ -4053,6 +4093,11 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== +nanoclone@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4" + integrity sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA== + nanoid@^3.1.16: version "3.1.20" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788" @@ -4840,6 +4885,11 @@ prop-types@15.7.2, prop-types@^15.6.2, prop-types@^15.7.2: object-assign "^4.1.1" react-is "^16.8.1" +property-expr@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.4.tgz#37b925478e58965031bb612ec5b3260f8241e910" + integrity sha512-sFPkHQjVKheDNnPvotjQmm3KD3uk1fWKUN7CrpdbwmUx3CrG3QiM8QpTSimvig5vTXmTvjz7+TDvXOI9+4rkcg== + prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" @@ -4956,6 +5006,11 @@ react-dom@^17.0.1: object-assign "^4.1.1" scheduler "^0.20.1" +react-fast-compare@^2.0.1: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + react-i18next@^11.8.5: version "11.8.5" resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.8.5.tgz#a093335822e36252cda6efc0f55facef6253643f" @@ -6123,6 +6178,11 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha1-riF2gXXRVZ1IvvNUILL0li8JwzA= + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -6150,7 +6210,7 @@ ts-pnp@^1.1.6: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== -tslib@^1.8.1, tslib@^1.9.0: +tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -6577,6 +6637,19 @@ yocto-queue@^0.1.0: resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== +yup@^0.32.8: + version "0.32.8" + resolved "https://registry.yarnpkg.com/yup/-/yup-0.32.8.tgz#16e4a949a86a69505abf99fd0941305ac9adfc39" + integrity sha512-SZulv5FIZ9d5H99EN5tRCRPXL0eyoYxWIP1AacCrjC9d4DfP13J1dROdKGfpfRHT3eQB6/ikBl5jG21smAfCkA== + dependencies: + "@babel/runtime" "^7.10.5" + "@types/lodash" "^4.14.165" + lodash "^4.17.20" + lodash-es "^4.17.11" + nanoclone "^0.2.1" + property-expr "^2.0.4" + toposort "^2.0.2" + zwitch@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920"