diff --git a/src/pages-and-resources/discussions/apps/legacy/LegacyConfigForm.jsx b/src/pages-and-resources/discussions/apps/legacy/LegacyConfigForm.jsx
index 35edd1b8f..6a96c8dc2 100644
--- a/src/pages-and-resources/discussions/apps/legacy/LegacyConfigForm.jsx
+++ b/src/pages-and-resources/discussions/apps/legacy/LegacyConfigForm.jsx
@@ -2,21 +2,32 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Form } from '@edx/paragon';
import { useFormik } from 'formik';
+import * as Yup from 'yup';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import DivisionByGroupFields from '../shared/DivisionByGroupFields';
import AnonymousPostingFields from '../shared/AnonymousPostingFields';
-import BlackoutDatesField from '../shared/BlackoutDatesField';
+import BlackoutDatesField, { blackoutDatesRegex } from '../shared/BlackoutDatesField';
-export default function LegacyConfigForm({
- appConfig, onSubmit, formRef,
+import messages from '../shared/messages';
+
+function LegacyConfigForm({
+ appConfig, onSubmit, formRef, intl,
}) {
const {
handleSubmit,
handleChange,
handleBlur,
values,
+ errors,
} = useFormik({
initialValues: appConfig,
+ validationSchema: Yup.object().shape({
+ blackoutDates: Yup.string().matches(
+ blackoutDatesRegex,
+ intl.formatMessage(messages.blackoutDatesFormattingError),
+ ),
+ }),
onSubmit,
});
@@ -37,6 +48,7 @@ export default function LegacyConfigForm({
values={values}
/>
-
+
{intl.formatMessage(messages.consumerKey)}
-
- {intl.formatMessage(messages.consumerKeyRequired)}
-
+ {errors.consumerKey && (
+
+ {errors.consumerKey}
+
+ )}
-
+
{intl.formatMessage(messages.consumerSecret)}
-
- {intl.formatMessage(messages.consumerSecretRequired)}
-
+ {errors.consumerSecret && (
+
+ {errors.consumerSecret}
+
+ )}
-
+
{intl.formatMessage(messages.launchUrl)}
-
- {intl.formatMessage(messages.launchUrlRequired)}
-
+ {errors.launchUrl && (
+
+ {errors.launchUrl}
+
+ )}
);
diff --git a/src/pages-and-resources/discussions/apps/shared/BlackoutDatesField.jsx b/src/pages-and-resources/discussions/apps/shared/BlackoutDatesField.jsx
index b563302f5..5f54744e5 100644
--- a/src/pages-and-resources/discussions/apps/shared/BlackoutDatesField.jsx
+++ b/src/pages-and-resources/discussions/apps/shared/BlackoutDatesField.jsx
@@ -4,11 +4,76 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Form } from '@edx/paragon';
import messages from './messages';
+/**
+ * Lets break this regex down.
+ *
+ * The goal is to accept arrays of dates like the following:
+ *
+ * [["2015-09-15", "2015-09-21"], ["2015-10-01T11:45", "2015-10-08"]]
+ *
+ * Any date can be YYYY-MM-DDTHH:MM or just YYYY-MM-DD like the above.
+ * The hours and minutes are optional, as illustrated.
+ *
+ * The regex errs on the side of being too loose, so you'll see things that are not perfect. It's
+ * better to be too liberal than to accidentally reject something that should be fine.
+ *
+ * So let multi-line this regex and explain the parts:
+ *
+ * Beginning of the string:
+ * ^
+ * The outer square brackets:
+ * \[
+ * Start of a group for a pair of dates with their square brackets:
+ * (\[
+ * A group for the first date (YYYY-MM-DDTHH:MM) with its opening double quote:
+ * ("
+ * Any four digits for the YYYY year, and a dash:
+ * [0-9]{4}-
+ * MM Months 00 - 12 with either a 0 followed by a digit, 1-9, OR a 1 followed by a digit 0-2
+ * Then a dash:
+ * (0[1-9]|1[0-2])-
+ * Finally, for days, accepts any digit 0-3 followed by any digit 0-9. Not a very exact regex.
+ * [0-3][0-9]
+ * A sub-group for the hours and minutes. T is just part of it:
+ * (T
+ * The hours HH are a 0 or 1 followed by 0-9, OR a 2 followed by 0-3. Captures digits 00-23:
+ * ([0-1][0-9]|2[0-3])
+ * Then a colon!
+ * :
+ * The minutes MM are any digit 0-5 followed by any digit 0-9, capturing 00-59:
+ * ([0-5][0-9])
+ * The THH:MM is optional, so end the group by allowing it to repeat 0 or 1 times
+ * ){0,1}
+ * Now end the first date group with its closing double quote, the group parenthesis, and a comma.
+ * The comma is a necessary separator between the first and second dates:
+ * "),
+ * Now start the second date of the pair:
+ * ("
+ * The second date is identical to the first, so here it is in all its glory:
+ * [0-9]{4}-(0[1-9]|1[0-2])-[0-3][0-9] // YYYY-MM-DD, identical to above
+ * (T([0-1][0-9]|2[0-3]):([0-5][0-9])){0,1} // THH:MM, identical to above
+ * Close out the second date with its closing double quotes:
+ * ")
+ * Close out the pair of dates with its closing square bracket:
+ * \]
+ * An optional comma after the pair of dates, in case there's another pair. If there isn't another
+ * date, there shouldn't be another comma, but this regex errors on the side of looseness.
+ * (,){0,1}
+ * This entire group, ["YYYY-MM-DDTHH:MM", "YYYY-MM-DDTHH:MM"], can be repeated zero or more times.
+ * )*
+ * Close out the last square bracket around all the groups:
+ * \]
+ * End of string:
+ * $
+ */
+export const blackoutDatesRegex = /^\[(\[("[0-9]{4}-(0[1-9]|1[0-2])-[0-3][0-9](T([0-1][0-9]|2[0-3]):([0-5][0-9])){0,1}"),("[0-9]{4}-(0[1-9]|1[0-2])-[0-3][0-9](T([0-1][0-9]|2[0-3]):([0-5][0-9])){0,1}")\](,){0,1})*\]$/;
+
function BlackoutDatesField({
onBlur,
onChange,
intl,
values,
+ errors,
}) {
return (
<>
@@ -22,6 +87,11 @@ function BlackoutDatesField({
onBlur={onBlur}
floatingLabel={intl.formatMessage(messages.blackoutDatesLabel)}
/>
+ {errors.blackoutDates && (
+
+ {errors.blackoutDates}
+
+ )}
{intl.formatMessage(messages.blackoutDatesHelp)}
@@ -37,6 +107,9 @@ BlackoutDatesField.propTypes = {
values: PropTypes.shape({
blackoutDates: PropTypes.string,
}).isRequired,
+ errors: PropTypes.shape({
+ blackoutDates: PropTypes.string,
+ }).isRequired,
};
export default injectIntl(BlackoutDatesField);
diff --git a/src/pages-and-resources/discussions/apps/shared/messages.js b/src/pages-and-resources/discussions/apps/shared/messages.js
index 5074ab39b..060753b1e 100644
--- a/src/pages-and-resources/discussions/apps/shared/messages.js
+++ b/src/pages-and-resources/discussions/apps/shared/messages.js
@@ -111,6 +111,10 @@ const messages = defineMessages({
the date and time. For example, an entry defining two blackout periods looks like this, including
the outer pair of square brackets: [["2015-09-15", "2015-09-21"], ["2015-10-01", "2015-10-08"]]`,
},
+ blackoutDatesFormattingError: {
+ id: 'authoring.discussions.builtIn.blackoutDates.formattingError',
+ defaultMessage: "There's a formatting error in your blackout dates.",
+ },
});
export default messages;