+
-
+ );
+ } else {
+ form = (
+
+ );
+ }
return (
-
+
+
+ {form}
+
+
);
}
@@ -43,4 +67,7 @@ ConfigFormContainer.propTypes = {
onSubmit: PropTypes.func.isRequired,
// eslint-disable-next-line react/forbid-prop-types
formRef: PropTypes.object.isRequired,
+ intl: intlShape.isRequired,
};
+
+export default injectIntl(ConfigFormContainer);
diff --git a/src/pages-and-resources/discussions/Discussions.jsx b/src/pages-and-resources/discussions/Discussions.jsx
index 471d62492..1da41d2c0 100644
--- a/src/pages-and-resources/discussions/Discussions.jsx
+++ b/src/pages-and-resources/discussions/Discussions.jsx
@@ -14,35 +14,52 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Button, StatefulButton } from '@edx/paragon';
import { Check } from '@edx/paragon/icons';
-import FullScreenModal from '../../generic/full-screen-modal/FullScreenModal';
-import Stepper from '../../generic/stepper/Stepper';
+import FullScreenModal from '../../generic/full-screen-modal';
+import Stepper from '../../generic/stepper';
+import { useModel } from '../../generic/model-store';
-import messages from './messages';
import AppList from './AppList';
import ConfigFormContainer from './ConfigFormContainer';
+import messages from './messages';
import { fetchApps, saveAppConfig } from './data/thunks';
-import StepperFooter from '../../generic/stepper/StepperFooter';
-import StepperHeader from '../../generic/stepper/StepperHeader';
-import StepperBody from '../../generic/stepper/StepperBody';
-import FullScreenModalHeader from '../../generic/full-screen-modal/FullScreenModalHeader';
-import FullScreenModalBody from '../../generic/full-screen-modal/FullScreenModalBody';
function Discussions({ courseId, intl }) {
- const discussionsPath = `/course/${courseId}/pages-and-resources/discussions`;
-
const [selectedAppId, setSelectedAppId] = useState(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const formRef = useRef();
-
const { path } = useRouteMatch();
const { pathname } = useLocation();
const dispatch = useDispatch();
+ const app = useModel('apps', selectedAppId);
+
+ // Route paths
+ // TODO: pagesAndResourcesPath should probably be passed in.
+ const pagesAndResourcesPath = `/course/${courseId}/pages-and-resources`;
+ const discussionsPath = `${pagesAndResourcesPath}/discussions`;
+ const selectedAppConfigPath = `${discussionsPath}/configure/${selectedAppId}`;
+
+ const isFirstStep = pathname === discussionsPath;
+ const submitButtonState = isSubmitting ? 'pending' : 'default';
+
+ const steps = [{
+ label: intl.formatMessage(messages.selectDiscussionTool),
+ iconLabel: isFirstStep ? undefined : (
+
+ ),
+ incomplete: false,
+ },
+ {
+ label: intl.formatMessage(messages.configureApp, {
+ name: app ? app.name : 'discussions',
+ }),
+ incomplete: isFirstStep,
+ }];
useEffect(() => {
dispatch(fetchApps(courseId));
}, [courseId]);
- const onSelectApp = useCallback((appId) => {
+ const handleSelectApp = useCallback((appId) => {
if (selectedAppId === appId) {
setSelectedAppId(null);
} else {
@@ -50,57 +67,48 @@ function Discussions({ courseId, intl }) {
}
}, [selectedAppId]);
- const onClose = useCallback(() => {
- history.push(`/course/${courseId}/pages-and-resources`);
+ const handleClose = useCallback(() => {
+ history.push(pagesAndResourcesPath);
}, [courseId]);
- const onStartConfig = useCallback(() => {
- history.push(`${discussionsPath}/configure/${selectedAppId}`);
+ const handleStartConfig = useCallback(() => {
+ history.push(selectedAppConfigPath);
}, [discussionsPath, selectedAppId]);
// This causes the form to be submitted from a button outside the form.
- const onApply = () => {
+ const handleApply = () => {
setIsSubmitting(true);
formRef.current.requestSubmit();
};
// This is a callback that gets called after the form has been submitted successfully.
- const onSubmit = useCallback((values) => {
+ const handleSubmit = useCallback((values) => {
+ console.log(values);
dispatch(saveAppConfig(courseId, selectedAppId, values)).then(() => {
- history.push(`/course/${courseId}/pages-and-resources`);
+ history.push(pagesAndResourcesPath);
});
}, [courseId, selectedAppId, courseId]);
- const onBack = useCallback(() => {
- history.push(discussionsPath);
- setSelectedAppId(null);
+ const handleBack = useCallback(() => {
+ if (isFirstStep) {
+ history.push(pagesAndResourcesPath);
+ } else {
+ history.push(discussionsPath);
+ setSelectedAppId(null);
+ }
}, [discussionsPath]);
- const isFirstStep = pathname === discussionsPath;
-
- const steps = [{
- label: 'Select discussion tool',
- iconLabel: isFirstStep ? undefined : (
-
- ),
- },
- {
- label: 'Configure discussions',
- }];
-
- const submitButtonState = isSubmitting ? 'pending' : 'default';
-
return (
-
-
-
+
+
+
-
-
+
+
@@ -108,18 +116,18 @@ function Discussions({ courseId, intl }) {
-
-
-
+
-
+
);
}
diff --git a/src/pages-and-resources/discussions/apps/legacy/LegacyConfigForm.jsx b/src/pages-and-resources/discussions/apps/legacy/LegacyConfigForm.jsx
new file mode 100644
index 000000000..4d56bf80f
--- /dev/null
+++ b/src/pages-and-resources/discussions/apps/legacy/LegacyConfigForm.jsx
@@ -0,0 +1,189 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { Form, TransitionReplace } from '@edx/paragon';
+import { useFormik } from 'formik';
+import * as Yup from 'yup';
+
+import FormSwitchGroup from '../../../../generic/FormSwitchGroup';
+import messages from './messages';
+
+function LegacyConfigForm({
+ appConfig, onSubmit, intl, formRef,
+}) {
+ // TODO: This regex doesn't seem to be working yet/validation isn't displaying. Unclear why.
+ const blackoutDateRegex = /(\[\]|\[(\["\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}){0,1}"\])*\])/;
+ const {
+ handleSubmit,
+ handleChange,
+ handleBlur,
+ values,
+ errors,
+ } = useFormik({
+ initialValues: appConfig,
+ validationSchema: Yup.object().shape({
+ blackoutDates: Yup.string().matches(blackoutDateRegex, 'bad regex'),
+
+ }),
+ onSubmit,
+ });
+
+ const divider =
; + + return ( +
+ {intl.formatMessage(messages.blackoutDatesLabel)}
+
+
+ Bad blackout date
+
+
+ {intl.formatMessage(messages.blackoutDatesHelp)}
+
+
+
+ );
+}
+
+LegacyConfigForm.propTypes = {
+ appConfig: PropTypes.shape({
+ divideByCohorts: PropTypes.bool.isRequired,
+ allowDivisionByUnit: PropTypes.bool.isRequired,
+ divideCourseWideTopics: PropTypes.bool.isRequired,
+ divideGeneralTopic: PropTypes.bool.isRequired,
+ divideQuestionsForTAs: PropTypes.bool.isRequired,
+ inContextDiscussion: PropTypes.bool.isRequired,
+ gradedUnitPages: PropTypes.bool.isRequired,
+ groupInContextSubsection: PropTypes.bool.isRequired,
+ allowUnitLevelVisibility: PropTypes.bool.isRequired,
+ allowAnonymousPosts: PropTypes.bool.isRequired,
+ allowAnonymousPostsPeers: PropTypes.bool.isRequired,
+ blackoutDates: PropTypes.string.isRequired,
+ }).isRequired,
+ intl: intlShape.isRequired,
+ onSubmit: PropTypes.func.isRequired,
+ // eslint-disable-next-line react/forbid-prop-types
+ formRef: PropTypes.object.isRequired,
+};
+
+export default injectIntl(LegacyConfigForm);
diff --git a/src/pages-and-resources/discussions/apps/legacy/index.js b/src/pages-and-resources/discussions/apps/legacy/index.js
new file mode 100644
index 000000000..f0b934129
--- /dev/null
+++ b/src/pages-and-resources/discussions/apps/legacy/index.js
@@ -0,0 +1 @@
+export { default } from './LegacyConfigForm';
diff --git a/src/pages-and-resources/discussions/apps/legacy/messages.js b/src/pages-and-resources/discussions/apps/legacy/messages.js
new file mode 100644
index 000000000..802b865f9
--- /dev/null
+++ b/src/pages-and-resources/discussions/apps/legacy/messages.js
@@ -0,0 +1,104 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ divisionByGroup: {
+ id: 'authoring.discussions.builtIn.divisionByGroup',
+ defaultMessage: 'Division by group',
+ },
+ divideByCohortsLabel: {
+ id: 'authoring.discussions.builtIn.divideByCohorts.label',
+ defaultMessage: 'Divide discussions by cohorts',
+ },
+ divideByCohortsHelp: {
+ id: 'authoring.discussions.builtIn.divideByCohorts.help',
+ defaultMessage: 'Learners will only be able to view and respond to discussions posted by members of their cohort.',
+ },
+ allowDivisionByUnitLabel: {
+ id: 'authoring.discussions.builtIn.allowDivisionByUnit.label',
+ defaultMessage: 'Allow cohort division for each course unit',
+ },
+ allowDivisionByUnitHelp: {
+ id: 'authoring.discussions.builtIn.allowDivisionByUnit.help',
+ defaultMessage: 'With this advanced setting enabled, you will be able to override the global visibility, and turn the division of cohorts on or off for each unit from the course outline view.',
+ },
+ divideCourseWideTopicsLabel: {
+ id: 'authoring.discussions.builtIn.divideCourseWideTopics.label',
+ defaultMessage: 'Divide course wide discussion topics',
+ },
+ divideCourseWideTopicsHelp: {
+ id: 'authoring.discussions.builtIn.divideCourseWideTopics.help',
+ defaultMessage: 'Choose which of your general course wide discussion topics you would like to divide.',
+ },
+ visibilityInContext: {
+ id: 'authoring.discussions.builtIn.visibilityInContext',
+ defaultMessage: 'Visibility of in-context discussions',
+ },
+ inContextDiscussionLabel: {
+ id: 'authoring.discussions.builtIn.inContextDiscussion.label',
+ defaultMessage: 'In-context discussion',
+ },
+ inContextDiscussionHelp: {
+ id: 'authoring.discussions.builtIn.inContextDiscussion.help',
+ defaultMessage: 'Learners will eb able to view or hide a discussion side panel to engage with discussion on te course unit page.',
+ },
+ gradedUnitPagesLabel: {
+ id: 'authoring.discussions.builtIn.gradedUnitPages.label',
+ defaultMessage: 'Graded unit pages',
+ },
+ gradedUnitPagesHelp: {
+ id: 'authoring.discussions.builtIn.gradedUnitPages.help',
+ defaultMessage: 'Allow learners to engage with discussion on all graded unit pages except timed exams.',
+ },
+ groupInContextSubsectionLabel: {
+ id: 'authoring.discussions.builtIn.groupInContextSubsection.label',
+ defaultMessage: 'Group in context discussion at the subsection level',
+ },
+ groupInContextSubsectionHelp: {
+ id: 'authoring.discussions.builtIn.groupInContextSubsection.help',
+ defaultMessage: 'Learners will be able to view any post in the sub-section no matter which unit page they are viewing. While this is not recommended, if your course has short learning sequences or low enrollment grouping may increase engagement.',
+ },
+ allowUnitLevelVisibilityLabel: {
+ id: 'authoring.discussions.builtIn.allowUnitLevelVisibility.label',
+ defaultMessage: 'Allow visibility configuration for each course unit',
+ },
+ allowUnitLevelVisibilityHelp: {
+ id: 'authoring.discussions.builtIn.allowUnitLevelVisibility.help',
+ defaultMessage: 'With this advanced setting enabled you will be able to override the global visibility setting and turn discussions on or off for each unit from the course outline view..',
+ },
+ anonymousPosting: {
+ id: 'authoring.discussions.builtIn.anonymousPosting',
+ defaultMessage: 'Anonymous posting',
+ },
+ allowAnonymousPostsLabel: {
+ id: 'authoring.discussions.builtIn.allowAnonymous.label',
+ defaultMessage: 'Allow Anonymous Discussion Posts',
+ },
+ allowAnonymousPostsHelp: {
+ id: 'authoring.discussions.builtIn.allowAnonymous.help',
+ defaultMessage: 'Enter true or false. If true, students can create discussion posts that are anonymous to all users.',
+ },
+ allowAnonymousPostsPeersLabel: {
+ id: 'authoring.discussions.builtIn.allowAnonymousPeers.label',
+ defaultMessage: 'Allow Anonymous Discussion Posts to Peers',
+ },
+ allowAnonymousPostsPeersHelp: {
+ id: 'authoring.discussions.builtIn.allowAnonymousPeers.help',
+ defaultMessage: 'Enter true or false. If true, students can create discussion posts that are anonymous to other students. This setting does not make posts anonymous to course staff.',
+ },
+ blackoutDatesLabel: {
+ id: 'authoring.discussions.builtIn.blackoutDates.label',
+ defaultMessage: 'Discussion Blackout Dates',
+ },
+ blackoutDatesHelp: {
+ id: 'authoring.discussions.builtIn.blackoutDates.help',
+ defaultMessage:
+ `Enter pairs of dates between which students cannot post to discussion forums. Inside the provided
+ brackets, enter an additional set of square brackets surrounding each pair of dates you add.
+ Format each pair of dates as ["YYYY-MM-DD", "YYYY-MM-DD"]. To specify times as well as dates,
+ format each pair as ["YYYY-MM-DDTHH:MM", "YYYY-MM-DDTHH:MM"]. Be sure to include the "T" between
+ 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"]]`,
+ },
+});
+
+export default messages;
diff --git a/src/pages-and-resources/discussions/LtiConfigForm.jsx b/src/pages-and-resources/discussions/apps/lti/LtiConfigForm.jsx
similarity index 95%
rename from src/pages-and-resources/discussions/LtiConfigForm.jsx
rename to src/pages-and-resources/discussions/apps/lti/LtiConfigForm.jsx
index bc7eec278..7fb92aa8e 100644
--- a/src/pages-and-resources/discussions/LtiConfigForm.jsx
+++ b/src/pages-and-resources/discussions/apps/lti/LtiConfigForm.jsx
@@ -1,9 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
-import {
- Form, Hyperlink,
-} from '@edx/paragon';
+import { Form, Hyperlink } from '@edx/paragon';
import { useFormik } from 'formik';
import * as Yup from 'yup';
@@ -29,8 +27,7 @@ function LtiConfigForm({
});
return (
-
{intl.formatMessage(messages.heading)}
+{intl.formatMessage(messages.heading)}
{intl.formatMessage(messages['viewLive.button'])} diff --git a/src/pages-and-resources/discussions/ConfigFormContainer.jsx b/src/pages-and-resources/discussions/ConfigFormContainer.jsx index 628325557..47fce44c3 100644 --- a/src/pages-and-resources/discussions/ConfigFormContainer.jsx +++ b/src/pages-and-resources/discussions/ConfigFormContainer.jsx @@ -1,16 +1,18 @@ import React, { useEffect } from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; - import { useRouteMatch } from 'react-router'; +import { Card, Container } from '@edx/paragon'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + import { useModel } from '../../generic/model-store'; - -import LtiConfigForm from './LtiConfigForm'; import { fetchAppConfig } from './data/thunks'; +import LegacyConfigForm from './apps/legacy'; +import LtiConfigForm from './apps/lti'; +import messages from './messages'; -// eslint-disable-next-line no-unused-vars -export default function ConfigFormContainer({ - courseId, onSubmit, formRef, +function ConfigFormContainer({ + courseId, onSubmit, formRef, intl, }) { const { params: { appId: routeAppId } } = useRouteMatch(); @@ -28,13 +30,35 @@ export default function ConfigFormContainer({ return null; } + let form = null; + + if (app.id === 'edx-discussions') { + form = ( ++ {intl.formatMessage(messages.configureApp, { name: app.name })} +
+; + + return ( +