aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreug-vs <eug-vs@keemail.me>2020-12-04 03:19:16 +0300
committereug-vs <eug-vs@keemail.me>2020-12-04 03:19:16 +0300
commitef7a52434e5b6fbfb9c31af0172861a9e2bb0351 (patch)
treeefc6659168092e65b5fc1074c1e3b65e36c420ac
parent43e81090ef8301b326f9721251bad1fe10ab18f8 (diff)
downloadfamcs-kit-ef7a52434e5b6fbfb9c31af0172861a9e2bb0351.tar.gz
feat: validate form with yup
-rw-r--r--package-lock.json43
-rw-r--r--package.json4
-rw-r--r--src/containers/BsuFantomSection/EventForm.tsx49
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"