diff options
author | eug-vs <eug-vs@keemail.me> | 2020-12-04 03:19:16 +0300 |
---|---|---|
committer | eug-vs <eug-vs@keemail.me> | 2020-12-04 03:19:16 +0300 |
commit | ef7a52434e5b6fbfb9c31af0172861a9e2bb0351 (patch) | |
tree | efc6659168092e65b5fc1074c1e3b65e36c420ac | |
parent | 43e81090ef8301b326f9721251bad1fe10ab18f8 (diff) | |
download | famcs-kit-ef7a52434e5b6fbfb9c31af0172861a9e2bb0351.tar.gz |
feat: validate form with yup
-rw-r--r-- | package-lock.json | 43 | ||||
-rw-r--r-- | package.json | 4 | ||||
-rw-r--r-- | src/containers/BsuFantomSection/EventForm.tsx | 49 |
3 files changed, 80 insertions, 16 deletions
diff --git a/package-lock.json b/package-lock.json index 141b45d..e6ff325 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2146,7 +2146,8 @@ "@types/lodash": { "version": "4.14.165", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.165.tgz", - "integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==" + "integrity": "sha512-tjSSOTHhI5mCHTy/OOXYIhi2Wt1qcbHmuXD1Ha7q70CgI/I71afO4XtLb/cVexki1oVYchpul/TOuu3Arcdxrg==", + "dev": true }, "@types/minimatch": { "version": "3.0.3", @@ -2315,6 +2316,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.10", + "resolved": "https://registry.npmjs.org/@types/yup/-/yup-0.29.10.tgz", + "integrity": "sha512-kRKRZaWkxxnOK7H5C4oWqhCw9ID1QF3cBZ2oAPoXYsjIncwgpDGigWtXGjZ91t+hsc3cvPdBci9YoJo1A96CYg==", + "dev": true + }, "@typescript-eslint/eslint-plugin": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.5.0.tgz", @@ -4355,6 +4362,11 @@ "sha.js": "^2.4.8" } }, + "cronstrue": { + "version": "1.105.0", + "resolved": "https://registry.npmjs.org/cronstrue/-/cronstrue-1.105.0.tgz", + "integrity": "sha512-Bv8GHi5uJvxtq/9T7lgBwum7UVKMfR+LSPHZXiezP0E5gnODPVRQBAkCwijCIaWEepqmRcxTAxrUFB0UQK2wdw==" + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -6402,6 +6414,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.13.0", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz", @@ -9524,11 +9541,6 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, - "nanoclone": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz", - "integrity": "sha512-wynEP02LmIbLpcYw8uBKpcfF6dmg2vcpKqxeH5UcoKEYdExslsdUA4ugFauuaeYdTB76ez6gJW8XAZ6CgkXYxA==" - }, "nanoid": { "version": "3.1.16", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.16.tgz", @@ -13843,6 +13855,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.15", + "resolved": "https://registry.npmjs.org/synchronous-promise/-/synchronous-promise-2.0.15.tgz", + "integrity": "sha512-k8uzYIkIVwmT+TcglpdN50pS2y1BDcUnBPK9iJeGu0Pl1lOI8pD6wtzgw91Pjpe+RxtTncw32tLxs/R0yNL2Mg==" + }, "table": { "version": "5.4.6", "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", @@ -15813,16 +15830,16 @@ } }, "yup": { - "version": "0.32.0", - "resolved": "https://registry.npmjs.org/yup/-/yup-0.32.0.tgz", - "integrity": "sha512-J6zDSGozP6+1rhYHCfVqHvDfASOmasi4HvyafwLT4pudAuzmYQ8rWowuA5cchxfi/HxOJi6uK1gmdat9M8GNCw==", + "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", - "@types/lodash": "^4.14.165", - "lodash": "^4.17.20", + "fn-name": "~3.0.0", + "lodash": "^4.17.15", "lodash-es": "^4.17.11", - "nanoclone": "^0.2.1", - "property-expr": "^2.0.4", + "property-expr": "^2.0.2", + "synchronous-promise": "^2.0.13", "toposort": "^2.0.2" } } diff --git a/package.json b/package.json index b5e4718..54febe7 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "dependencies": { "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", + "cronstrue": "^1.105.0", "formik": "^2.2.5", "lodash": "^4.17.20", "react": "^17.0.1", @@ -12,13 +13,14 @@ "react-dom": "^17.0.1", "react-scripts": "4.0.0", "swr": "^0.3.8", - "yup": "^0.32.0" + "yup": "^0.29.3" }, "devDependencies": { "@types/lodash": "^4.14.165", "@types/node": "^12.0.0", "@types/react": "^16.9.53", "@types/react-dom": "^16.9.8", + "@types/yup": "^0.29.10", "gh-pages": "^3.1.0", "typescript": "^4.0.3" }, diff --git a/src/containers/BsuFantomSection/EventForm.tsx b/src/containers/BsuFantomSection/EventForm.tsx index 09bb87b..9fc18b5 100644 --- a/src/containers/BsuFantomSection/EventForm.tsx +++ b/src/containers/BsuFantomSection/EventForm.tsx @@ -1,6 +1,9 @@ import React from 'react'; -import { Grid, TextField, Button } from '@material-ui/core'; +import _ from 'lodash'; +import cronstrue from 'cronstrue'; +import * as Yup from 'yup'; import { Formik, Form, Field } from 'formik'; +import { Grid, TextField, Button, Link } from '@material-ui/core'; import { post } from '../../requests'; import { Event } from '../../types'; @@ -15,7 +18,36 @@ interface Fields { conferenceId: string; } +const describeCrontab = (crontab: string) => { + try { + return cronstrue.toString(crontab); + } catch { + return false; + } +}; + +const validationSchema = Yup.object({ + name: Yup.string().required('This field is required'), + conferenceId: Yup.string().required('This field is required'), + attendanceId: Yup.string(), + schedule: Yup.string() + .required('This field is required') + .test('cron', 'Invalid crontab', value => !!describeCrontab(value || '')) +}); + const EventForm: React.FC<PropTypes> = ({ mutate }) => { + + const describeSchedule = (schedule: string) => { + const description = describeCrontab(schedule); + if (description) return `Event will run ${_.lowerFirst(description)}`; + const link = <Link href="https://crontab.guru">crontab.guru</Link>; + return ( + <> + The schedule is invalid. Check out {link} for more help with cron scheduling. + </> + ); + }; + const handleSubmit = (fields: Fields, { resetForm }: any) => { const { name, schedule, attendanceId, conferenceId } = fields; @@ -41,9 +73,10 @@ const EventForm: React.FC<PropTypes> = ({ mutate }) => { return ( <Formik initialValues={{ name: '', schedule: '* * * * *', attendanceId: '', conferenceId: '' }} + validationSchema={validationSchema} onSubmit={handleSubmit} > - {({ values, errors, touched, isSubmitting }) => ( + {({ values, errors, touched, isValid }) => ( <Form autoComplete="off"> <Grid container spacing={2}> <Grid item sm={6} xs={12}> @@ -51,6 +84,8 @@ const EventForm: React.FC<PropTypes> = ({ mutate }) => { name="name" label="Name" value={values.name} + error={touched.name && !!errors.name} + helperText={touched.name && errors.name} variant="outlined" fullWidth required @@ -62,6 +97,10 @@ const EventForm: React.FC<PropTypes> = ({ mutate }) => { name="schedule" label="Schedule" value={values.schedule} + error={touched.schedule && !!errors.schedule} + helperText={ + (touched.schedule && errors.schedule) || "Should be a valid crontab, for example: 30 12 * * Mon" + } variant="outlined" fullWidth required @@ -73,6 +112,8 @@ const EventForm: React.FC<PropTypes> = ({ mutate }) => { name="conferenceId" label="Conference ID" value={values.conferenceId} + error={touched.conferenceId && !!errors.conferenceId} + helperText={touched.conferenceId && errors.conferenceId} variant="outlined" fullWidth required @@ -89,9 +130,13 @@ const EventForm: React.FC<PropTypes> = ({ mutate }) => { as={TextField} /> </Grid> + <Grid item xs={12}> + {describeSchedule(values.schedule)} + </Grid> <Grid item sm={2} xs={12}> <Button type="submit" + disabled={!isValid} variant="contained" size="large" color="primary" |