diff --git a/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigForm.jsx b/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigForm.jsx index 983c82a91..ab89ffe66 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigForm.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigForm.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import PropTypes from 'prop-types'; import { Card, Form } from '@edx/paragon'; import { Formik } from 'formik'; @@ -9,6 +9,7 @@ import DivisionByGroupFields from '../shared/DivisionByGroupFields'; import AnonymousPostingFields from '../shared/AnonymousPostingFields'; import DiscussionTopics from '../shared/discussion-topics/DiscussionTopics'; import BlackoutDatesField, { blackoutDatesRegex } from '../shared/BlackoutDatesField'; +import LegacyConfigFormProvider from './LegacyConfigFormProvider'; import messages from '../shared/messages'; import AppConfigFormDivider from '../shared/AppConfigFormDivider'; @@ -36,6 +37,7 @@ Yup.addMethod(Yup.object, 'uniqueProperty', function (propertyName, message) { function LegacyConfigForm({ appConfig, onSubmit, formRef, intl, title, }) { + const [validDiscussionTopics, setValidDiscussionTopics] = useState(appConfig.discussionTopics); const legacyFormValidationSchema = Yup.object().shape({ blackoutDates: Yup.string().matches( blackoutDatesRegex, @@ -72,30 +74,37 @@ function LegacyConfigForm({ && errors.discussionTopics && errors?.discussionTopics[index]?.name, )); + const contextValue = { + validDiscussionTopics, + setValidDiscussionTopics, + discussionTopicErrors, + }; return ( - -
-

{title}

- - - - - - - - - -
+ + +
+

{title}

+ + + + + + + + + +
+
); }} diff --git a/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigFormProvider.jsx b/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigFormProvider.jsx new file mode 100644 index 000000000..72ac50f82 --- /dev/null +++ b/src/pages-and-resources/discussions/app-config-form/apps/legacy/LegacyConfigFormProvider.jsx @@ -0,0 +1,26 @@ +import React, { createContext } from 'react'; +import PropTypes from 'prop-types'; + +export const LegacyConfigFormContext = createContext({}); + +export default function LegacyConfigFormProvider({ children, value }) { + return ( + + {children} + + ); +} + +LegacyConfigFormProvider.propTypes = { + children: PropTypes.node.isRequired, + value: PropTypes.shape({ + discussionTopicErrors: PropTypes.arrayOf(PropTypes.bool), + validDiscussionTopics: PropTypes.arrayOf(PropTypes.shape({ + validTopics: PropTypes.arrayOf(PropTypes.shape({ + name: PropTypes.string, + id: PropTypes.string, + })), + setValidDiscussionTopics: PropTypes.func, + })), + }).isRequired, +}; diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/DivisionByGroupFields.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/DivisionByGroupFields.jsx index a213740c1..21aac43f0 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/DivisionByGroupFields.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/DivisionByGroupFields.jsx @@ -1,13 +1,14 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; +import React, { useEffect, useContext } from 'react'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Form, TransitionReplace } from '@edx/paragon'; import { FieldArray, useFormikContext } from 'formik'; import FormSwitchGroup from '../../../../../generic/FormSwitchGroup'; import messages from './messages'; import AppConfigFormDivider from './AppConfigFormDivider'; +import { LegacyConfigFormContext } from '../legacy/LegacyConfigFormProvider'; -const DivisionByGroupFields = ({ intl, discussionTopicErrors }) => { +const DivisionByGroupFields = ({ intl }) => { + const { validDiscussionTopics } = useContext(LegacyConfigFormContext); const { handleChange, handleBlur, @@ -104,8 +105,8 @@ const DivisionByGroupFields = ({ intl, discussionTopicErrors }) => { onBlur={handleBlur} defaultValue={divideDiscussionIds} > - {discussionTopics.map((topic, index) => ( - topic.name && !discussionTopicErrors[index] ? ( + {validDiscussionTopics.map((topic) => ( + topic.name ? ( { DivisionByGroupFields.propTypes = { intl: intlShape.isRequired, - discussionTopicErrors: PropTypes.arrayOf(PropTypes.bool).isRequired, }; export default injectIntl(DivisionByGroupFields); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.jsx index 973fd34e0..ef9cc6a15 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.jsx @@ -1,24 +1,28 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useContext } from 'react'; import { useDispatch } from 'react-redux'; import { Add } from '@edx/paragon/icons'; import { Button } from '@edx/paragon'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { FieldArray, useFormikContext } from 'formik'; import { v4 as uuid } from 'uuid'; -import PropTypes from 'prop-types'; import messages from '../messages'; import TopicItem from './TopicItem'; import { updateValidationStatus } from '../../../../data/slice'; +import { LegacyConfigFormContext } from '../../legacy/LegacyConfigFormProvider'; -const DiscussionTopics = ({ intl, discussionTopicErrors }) => { +const DiscussionTopics = ({ intl }) => { const { values: appConfig, - setFieldValue, validateForm, } = useFormikContext(); - const { discussionTopics, divideDiscussionIds } = appConfig; + const { discussionTopics } = appConfig; const dispatch = useDispatch(); + const { + discussionTopicErrors, + validDiscussionTopics, + setValidDiscussionTopics, + } = useContext(LegacyConfigFormContext); const isFormInvalid = discussionTopicErrors.some((error) => error === true); useEffect(() => { @@ -28,16 +32,13 @@ const DiscussionTopics = ({ intl, discussionTopicErrors }) => { const handleTopicDelete = async (topicIndex, topicId, remove) => { await remove(topicIndex); validateForm(); - const updatedDividedDiscussionsIds = divideDiscussionIds.filter( - (id) => id !== topicId, - ); - setFieldValue('divideDiscussionIds', updatedDividedDiscussionsIds); + const validTopics = validDiscussionTopics.filter(topic => topic.id !== topicId); + setValidDiscussionTopics(validTopics); }; const addNewTopic = (push) => { const payload = { name: '', id: uuid() }; push(payload); - setFieldValue('divideDiscussionIds', [...divideDiscussionIds, payload.id]); }; return ( @@ -85,7 +86,6 @@ const DiscussionTopics = ({ intl, discussionTopicErrors }) => { DiscussionTopics.propTypes = { intl: intlShape.isRequired, - discussionTopicErrors: PropTypes.arrayOf(PropTypes.bool).isRequired, }; export default injectIntl(DiscussionTopics); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.test.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.test.jsx index 752d223cd..22368938e 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.test.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/DiscussionTopics.test.jsx @@ -20,6 +20,7 @@ import { getAppsUrl } from '../../../../data/api'; import { fetchApps } from '../../../../data/thunks'; import executeThunk from '../../../../../../utils'; import { legacyApiResponse } from '../../../../factories/mockApiResponses'; +import LegacyConfigFormProvider from '../../legacy/LegacyConfigFormProvider'; const appConfig = { id: 'legacy', @@ -36,7 +37,15 @@ const appConfig = { blackoutDates: '[]', }; -const discussionTopicErrors = [false, false]; +const contextValue = { + validDiscussionTopics: [ + { name: 'General', id: 'course' }, + { name: 'Edx', id: '13f106c6-6735-4e84-b097-0456cff55960' }, + ], + setValidDiscussionTopics: jest.fn(), + discussionTopicErrors: [false, false], +}; + const courseId = 'course-v1:edX+TestX+Test_Course'; describe('DiscussionTopics', () => { @@ -66,9 +75,11 @@ describe('DiscussionTopics', () => { const wrapper = render( - - - + + + + + , ); diff --git a/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/TopicItem.jsx b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/TopicItem.jsx index 8269a3d64..24830b7af 100644 --- a/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/TopicItem.jsx +++ b/src/pages-and-resources/discussions/app-config-form/apps/shared/discussion-topics/TopicItem.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useContext, useEffect } from 'react'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Button, @@ -13,15 +13,46 @@ import { Delete, ExpandLess, ExpandMore } from '@edx/paragon/icons'; import { useFormikContext } from 'formik'; import PropTypes from 'prop-types'; import messages from '../messages'; +import { LegacyConfigFormContext } from '../../legacy/LegacyConfigFormProvider'; +import uniqueItems from '../../../utils'; const TopicItem = ({ intl, index, name, onDelete, id, hasError, }) => { - const { handleChange, handleBlur, errors } = useFormikContext(); + const { + handleChange, handleBlur, errors, values: appConfig, setFieldValue, + } = useFormikContext(); const [inFocus, setInFocus] = useState(false); const [showDeletePopup, setShowDeletePopup] = useState(false); const [collapseIsOpen, setCollapseOpen] = useState(); const isGeneralTopic = id === 'course'; + const { + validDiscussionTopics, + setValidDiscussionTopics, + } = useContext(LegacyConfigFormContext); + const { discussionTopics, divideDiscussionIds } = appConfig; + + /** + * Update valid discussion topics & divided discussion topics. + * Removes a specific topic from valid discussion topics & divided discussion topics + * if it is invalid. + * Adds a specific topic to valid discussion topics & divided discussion topics + * if it is invalid. + */ + useEffect(() => { + if (hasError) { + const validTopicsIds = validDiscussionTopics.filter(topic => topic.id !== id); + setValidDiscussionTopics(validTopicsIds); + setFieldValue('divideDiscussionIds', divideDiscussionIds.filter(topic => topic.id !== id)); + } else { + const validTopicsIds = uniqueItems(validDiscussionTopics.map(topic => topic.id), [id]); + const validTopics = discussionTopics.filter( + topic => validTopicsIds.includes(topic.id), + ); + setValidDiscussionTopics(validTopics); + setFieldValue('divideDiscussionIds', uniqueItems(divideDiscussionIds, [id])); + } + }, [hasError, inFocus]); const getHeading = (isOpen = false) => { let heading; diff --git a/src/pages-and-resources/discussions/app-config-form/utils.js b/src/pages-and-resources/discussions/app-config-form/utils.js new file mode 100644 index 000000000..ceeee2f63 --- /dev/null +++ b/src/pages-and-resources/discussions/app-config-form/utils.js @@ -0,0 +1,6 @@ +/** Append items and makes sure the items are unique. */ +const uniqueItems = (sourceArray, items = []) => ( + [...new Set([...sourceArray, ...items])] +); + +export default uniqueItems;