handleKeyDown(e)}>
@@ -107,4 +34,4 @@ const PostsView = () => {
);
};
-export default PostsView;
+export default memo(PostsView);
diff --git a/src/discussions/posts/TopicPostsList.jsx b/src/discussions/posts/TopicPostsList.jsx
new file mode 100644
index 00000000..823db518
--- /dev/null
+++ b/src/discussions/posts/TopicPostsList.jsx
@@ -0,0 +1,35 @@
+import React, { memo, useEffect } from 'react';
+
+import isEmpty from 'lodash/isEmpty';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { useCourseId, useEnableInContextSidebar, useTopicId } from '../data/hooks';
+import { selectEnableInContext } from '../data/selectors';
+import { selectTopics as selectInContextTopics } from '../in-context-topics/data/selectors';
+import { fetchCourseTopicsV3 } from '../in-context-topics/data/thunks';
+import { selectTopics } from '../topics/data/selectors';
+import { fetchCourseTopics } from '../topics/data/thunks';
+import { selectTopicThreadsIds } from './data/selectors';
+import PostsList from './PostsList';
+
+const TopicPostsList = () => {
+ const dispatch = useDispatch();
+ const topicId = useTopicId();
+ const courseId = useCourseId();
+ const enableInContextSidebar = useEnableInContextSidebar();
+ const enableInContext = useSelector(selectEnableInContext);
+ const postsIds = useSelector(selectTopicThreadsIds([topicId]));
+ const topics = useSelector(enableInContext ? selectInContextTopics : selectTopics);
+
+ useEffect(() => {
+ if (isEmpty(topics)) {
+ dispatch((enableInContext || enableInContextSidebar)
+ ? fetchCourseTopicsV3(courseId)
+ : fetchCourseTopics(courseId));
+ }
+ }, [courseId, topics]);
+
+ return
;
+};
+
+export default memo(TopicPostsList);
diff --git a/src/discussions/posts/data/hooks.js b/src/discussions/posts/data/hooks.js
index 8837f9a0..84c95c3d 100644
--- a/src/discussions/posts/data/hooks.js
+++ b/src/discussions/posts/data/hooks.js
@@ -7,20 +7,10 @@ import { selectThreadsByIds } from './selectors';
export const usePostList = (ids) => {
const posts = useSelector(selectThreadsByIds(ids));
- const pinnedPostsIds = [];
- const unpinnedPostsIds = [];
- const sortedIds = useMemo(() => {
- posts.forEach((post) => {
- if (post.pinned) {
- pinnedPostsIds.push(post.id);
- } else {
- unpinnedPostsIds.push(post.id);
- }
- });
-
- return [...pinnedPostsIds, ...unpinnedPostsIds];
- }, [posts]);
+ const sortedIds = useMemo(() => (
+ [...posts].sort((a, b) => (b.pinned - a.pinned)).map((post) => post.id)
+ ), [posts]);
return sortedIds;
};
diff --git a/src/discussions/posts/post-actions-bar/AddPostButton.jsx b/src/discussions/posts/post-actions-bar/AddPostButton.jsx
new file mode 100644
index 00000000..535426f2
--- /dev/null
+++ b/src/discussions/posts/post-actions-bar/AddPostButton.jsx
@@ -0,0 +1,46 @@
+import React, { useCallback } from 'react';
+
+import classNames from 'classnames';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+import { Button } from '@edx/paragon';
+
+import { RequestStatus } from '../../../data/constants';
+import { useEnableInContextSidebar, useUserPostingEnabled } from '../../data/hooks';
+import { selectConfigLoadingStatus } from '../../data/selectors';
+import { showPostEditor } from '../data';
+import messages from './messages';
+
+const AddPostButton = () => {
+ const intl = useIntl();
+ const dispatch = useDispatch();
+ const loadingStatus = useSelector(selectConfigLoadingStatus);
+ const enableInContextSidebar = useEnableInContextSidebar();
+ const isUserPrivilegedInPostingRestriction = useUserPostingEnabled();
+
+ const handleAddPost = useCallback(() => {
+ dispatch(showPostEditor());
+ }, []);
+
+ return (
+ loadingStatus === RequestStatus.SUCCESSFUL && isUserPrivilegedInPostingRestriction && (
+ <>
+ {!enableInContextSidebar &&
+ >
+ )
+ );
+};
+
+export default AddPostButton;
diff --git a/src/discussions/posts/post-actions-bar/PostActionsBar.jsx b/src/discussions/posts/post-actions-bar/PostActionsBar.jsx
index 04e3a7f2..ded02a1f 100644
--- a/src/discussions/posts/post-actions-bar/PostActionsBar.jsx
+++ b/src/discussions/posts/post-actions-bar/PostActionsBar.jsx
@@ -1,70 +1,36 @@
-import React, { useCallback, useContext } from 'react';
+import React, { memo, useCallback } from 'react';
import classNames from 'classnames';
-import { useDispatch, useSelector } from 'react-redux';
import { useIntl } from '@edx/frontend-platform/i18n';
-import {
- Button, Icon, IconButton,
-} from '@edx/paragon';
+import { Icon, IconButton } from '@edx/paragon';
import { Close } from '@edx/paragon/icons';
-import Search from '../../../components/Search';
-import { RequestStatus } from '../../../data/constants';
-import { DiscussionContext } from '../../common/context';
-import { useUserPostingEnabled } from '../../data/hooks';
-import { selectConfigLoadingStatus, selectEnableInContext } from '../../data/selectors';
-import { TopicSearchBar as IncontextSearch } from '../../in-context-topics/topic-search';
+import { useEnableInContextSidebar } from '../../data/hooks';
import { postMessageToParent } from '../../utils';
-import { showPostEditor } from '../data';
+import AddPostButton from './AddPostButton';
import messages from './messages';
+import SearchField from './SearchField';
import './actionBar.scss';
const PostActionsBar = () => {
const intl = useIntl();
- const dispatch = useDispatch();
- const loadingStatus = useSelector(selectConfigLoadingStatus);
- const enableInContext = useSelector(selectEnableInContext);
- const isUserPrivilegedInPostingRestriction = useUserPostingEnabled();
- const { enableInContextSidebar, page } = useContext(DiscussionContext);
+ const enableInContextSidebar = useEnableInContextSidebar();
const handleCloseInContext = useCallback(() => {
postMessageToParent('learning.events.sidebar.close');
}, []);
- const handleAddPost = useCallback(() => {
- dispatch(showPostEditor());
- }, []);
-
return (
- {!enableInContextSidebar && (
- (enableInContext && ['topics', 'category'].includes(page))
- ?
- :
- )}
+
{enableInContextSidebar && (
{intl.formatMessage(messages.title)}
)}
- {loadingStatus === RequestStatus.SUCCESSFUL && isUserPrivilegedInPostingRestriction && (
- <>
- {!enableInContextSidebar &&
}
-
- >
- )}
+
{enableInContextSidebar && (
<>
@@ -84,4 +50,4 @@ const PostActionsBar = () => {
);
};
-export default PostActionsBar;
+export default memo(PostActionsBar);
diff --git a/src/discussions/posts/post-actions-bar/SearchField.jsx b/src/discussions/posts/post-actions-bar/SearchField.jsx
new file mode 100644
index 00000000..50b4965b
--- /dev/null
+++ b/src/discussions/posts/post-actions-bar/SearchField.jsx
@@ -0,0 +1,20 @@
+import React, { memo } from 'react';
+
+import { useSelector } from 'react-redux';
+
+import Search from '../../../components/Search';
+import withConditionalInContextRendering from '../../common/withConditionalInContextRendering';
+import { useCurrentPage } from '../../data/hooks';
+import { selectEnableInContext } from '../../data/selectors';
+import { TopicSearchBar as IncontextSearch } from '../../in-context-topics/topic-search';
+
+const SearchField = () => {
+ const enableInContext = useSelector(selectEnableInContext);
+ const page = useCurrentPage();
+
+ return (
+ enableInContext && ['topics', 'category'].includes(page) ?
:
+ );
+};
+
+export default memo(withConditionalInContextRendering(SearchField, false));
diff --git a/src/discussions/posts/post-filter-bar/ActionItem.jsx b/src/discussions/posts/post-filter-bar/ActionItem.jsx
new file mode 100644
index 00000000..16dd60a8
--- /dev/null
+++ b/src/discussions/posts/post-filter-bar/ActionItem.jsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import classNames from 'classnames';
+
+import { Form, Icon } from '@edx/paragon';
+import { Check } from '@edx/paragon/icons';
+
+const ActionItem = ({
+ id, label, value, selected,
+}) => (
+
+);
+
+ActionItem.propTypes = {
+ id: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+ selected: PropTypes.string.isRequired,
+};
+
+export default React.memo(ActionItem);
diff --git a/src/discussions/posts/post-filter-bar/CohortFilters.jsx b/src/discussions/posts/post-filter-bar/CohortFilters.jsx
new file mode 100644
index 00000000..5e7ba172
--- /dev/null
+++ b/src/discussions/posts/post-filter-bar/CohortFilters.jsx
@@ -0,0 +1,81 @@
+import React, { useEffect, useMemo } from 'react';
+import PropTypes from 'prop-types';
+
+import { capitalize, isEmpty, toString } from 'lodash';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { Form, Spinner } from '@edx/paragon';
+
+import { RequestStatus } from '../../../data/constants';
+import { selectCourseCohorts } from '../../cohorts/data/selectors';
+import { fetchCourseCohorts } from '../../cohorts/data/thunks';
+import { useCourseId } from '../../data/hooks';
+import { selectUserHasModerationPrivileges } from '../../data/selectors';
+import { selectThreadFilters } from '../data/selectors';
+import ActionItem from './ActionItem';
+import withFilterHandleChange from './withFilterHandleChange';
+
+const CohortFilters = ({ handleSortFilterChange }) => {
+ const dispatch = useDispatch();
+ const courseId = useCourseId();
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const currentFilters = useSelector(selectThreadFilters());
+ const { status } = useSelector(state => state.cohorts);
+ const cohorts = useSelector(selectCourseCohorts);
+
+ useEffect(() => {
+ if (userHasModerationPrivileges && isEmpty(cohorts)) {
+ dispatch(fetchCourseCohorts(courseId));
+ }
+ }, [userHasModerationPrivileges]);
+
+ const cohortsMenu = useMemo(() => (
+ <>
+
+ {cohorts.map(cohort => (
+
+ ))}
+ >
+ ), [cohorts, currentFilters.cohort]);
+
+ return (
+ userHasModerationPrivileges && (
+ <>
+
+ {status === RequestStatus.IN_PROGRESS ? (
+
+
+
+ ) : (
+
+
+ {cohortsMenu}
+
+
+ )}
+ >
+ )
+ );
+};
+
+CohortFilters.propTypes = {
+ handleSortFilterChange: PropTypes.func.isRequired,
+};
+
+export default React.memo(withFilterHandleChange(CohortFilters));
diff --git a/src/discussions/posts/post-filter-bar/CollapsibleFilter.jsx b/src/discussions/posts/post-filter-bar/CollapsibleFilter.jsx
new file mode 100644
index 00000000..bd127444
--- /dev/null
+++ b/src/discussions/posts/post-filter-bar/CollapsibleFilter.jsx
@@ -0,0 +1,69 @@
+import React, { useCallback, useMemo, useState } from 'react';
+import PropTypes from 'prop-types';
+
+import { capitalize, toString } from 'lodash';
+import { useSelector } from 'react-redux';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+import { Collapsible, Icon } from '@edx/paragon';
+import { Tune } from '@edx/paragon/icons';
+
+import { selectCourseCohorts } from '../../cohorts/data/selectors';
+import { selectThreadFilters, selectThreadSorting } from '../data/selectors';
+import messages from './messages';
+
+const CollapsibleFilter = ({ children }) => {
+ const intl = useIntl();
+ const currentSorting = useSelector(selectThreadSorting());
+ const currentFilters = useSelector(selectThreadFilters());
+ const cohorts = useSelector(selectCourseCohorts);
+ const [isOpen, setOpen] = useState(false);
+
+ const selectedCohort = useMemo(() => (
+ cohorts.find(cohort => (
+ toString(cohort.id) === currentFilters.cohort
+ ))
+ ), [cohorts, currentFilters.cohort]);
+
+ const handleToggle = useCallback(() => {
+ setOpen(!isOpen);
+ }, [isOpen]);
+
+ return (
+
+
+
+ {intl.formatMessage(messages.sortFilterStatus, {
+ own: false,
+ type: currentFilters.postType,
+ sort: currentSorting,
+ status: currentFilters.status,
+ cohortType: selectedCohort?.name ? 'group' : 'all',
+ cohort: capitalize(selectedCohort?.name),
+ })}
+
+
+
+
+
+
+
+
+
+
+
+ {children}
+
+
+ );
+};
+
+CollapsibleFilter.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+export default React.memo(CollapsibleFilter);
diff --git a/src/discussions/posts/post-filter-bar/PostFilterBar.jsx b/src/discussions/posts/post-filter-bar/PostFilterBar.jsx
index 3633096e..e644a8f1 100644
--- a/src/discussions/posts/post-filter-bar/PostFilterBar.jsx
+++ b/src/discussions/posts/post-filter-bar/PostFilterBar.jsx
@@ -1,317 +1,24 @@
-import React, {
- useCallback, useContext, useEffect, useMemo, useState,
-} from 'react';
-import PropTypes from 'prop-types';
+import React from 'react';
-import classNames from 'classnames';
-import { capitalize, isEmpty, toString } from 'lodash';
-import { useDispatch, useSelector } from 'react-redux';
-import { useParams } from 'react-router-dom';
+import { Form } from '@edx/paragon';
-import { sendTrackEvent } from '@edx/frontend-platform/analytics';
-import { useIntl } from '@edx/frontend-platform/i18n';
-import {
- Collapsible, Form, Icon, Spinner,
-} from '@edx/paragon';
-import { Check, Tune } from '@edx/paragon/icons';
+import CohortFilters from './CohortFilters';
+import CollapsibleFilter from './CollapsibleFilter';
+import PostSortFilters from './PostSortFilters';
+import PostStatusFilters from './PostStatusFilters';
+import PostTypeFilters from './PostTypeFilters';
-import {
- PostsStatusFilter, RequestStatus,
- ThreadOrdering, ThreadType,
-} from '../../../data/constants';
-import { selectCourseCohorts } from '../../cohorts/data/selectors';
-import { fetchCourseCohorts } from '../../cohorts/data/thunks';
-import { DiscussionContext } from '../../common/context';
-import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
-import {
- setCohortFilter, setPostsTypeFilter, setSortedBy, setStatusFilter,
-} from '../data';
-import { selectThreadFilters, selectThreadSorting } from '../data/selectors';
-import messages from './messages';
-
-export const ActionItem = React.memo(({
- id,
- label,
- value,
- selected,
-}) => (
-
-));
-
-ActionItem.propTypes = {
- id: PropTypes.string.isRequired,
- label: PropTypes.string.isRequired,
- value: PropTypes.string.isRequired,
- selected: PropTypes.string.isRequired,
-};
-
-const PostFilterBar = () => {
- const intl = useIntl();
- const dispatch = useDispatch();
- const { courseId } = useParams();
- const { page } = useContext(DiscussionContext);
- const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
- const userIsGroupTa = useSelector(selectUserIsGroupTa);
- const currentSorting = useSelector(selectThreadSorting());
- const currentFilters = useSelector(selectThreadFilters());
- const { status } = useSelector(state => state.cohorts);
- const cohorts = useSelector(selectCourseCohorts);
- const [isOpen, setOpen] = useState(false);
-
- const selectedCohort = useMemo(() => (
- cohorts.find(cohort => (
- toString(cohort.id) === currentFilters.cohort
- ))
- ), [cohorts, currentFilters.cohort]);
-
- const handleSortFilterChange = useCallback((event) => {
- const currentType = currentFilters.postType;
- const currentStatus = currentFilters.status;
- const {
- name,
- value,
- } = event.currentTarget;
- const filterContentEventProperties = {
- statusFilter: currentStatus,
- threadTypeFilter: currentType,
- sortFilter: currentSorting,
- cohortFilter: selectedCohort,
- triggeredBy: name,
- };
-
- if (name === 'type') {
- dispatch(setPostsTypeFilter(value));
- if (
- value === ThreadType.DISCUSSION && currentStatus === PostsStatusFilter.UNANSWERED
- ) {
- // You can't filter discussions by unanswered
- dispatch(setStatusFilter(PostsStatusFilter.ALL));
- }
- filterContentEventProperties.threadTypeFilter = value;
- }
-
- if (name === 'status') {
- dispatch(setStatusFilter(value));
- if (value === PostsStatusFilter.UNANSWERED && currentType !== ThreadType.QUESTION) {
- // You can't filter discussions by unanswered so switch type to questions
- dispatch(setPostsTypeFilter(ThreadType.QUESTION));
- }
- if (value === PostsStatusFilter.UNRESPONDED && currentType !== ThreadType.DISCUSSION) {
- // You can't filter questions by not responded so switch type to discussion
- dispatch(setPostsTypeFilter(ThreadType.DISCUSSION));
- }
- filterContentEventProperties.statusFilter = value;
- }
-
- if (name === 'sort') {
- dispatch(setSortedBy(value));
- filterContentEventProperties.sortFilter = value;
- }
-
- if (name === 'cohort') {
- dispatch(setCohortFilter(value));
- filterContentEventProperties.cohortFilter = value;
- }
-
- sendTrackEvent('edx.forum.filter.content', filterContentEventProperties);
- }, [currentFilters, currentSorting, dispatch, selectedCohort]);
-
- const handleToggle = useCallback(() => {
- setOpen(!isOpen);
- }, [isOpen]);
-
- useEffect(() => {
- if (userHasModerationPrivileges && isEmpty(cohorts)) {
- dispatch(fetchCourseCohorts(courseId));
- }
- }, [courseId, userHasModerationPrivileges]);
-
- const renderCohortFilter = useMemo(() => (
- userHasModerationPrivileges && (
- <>
-
- {status === RequestStatus.IN_PROGRESS ? (
-
-
-
- ) : (
-
-
-
- {cohorts.map(cohort => (
-
- ))}
-
-
- )}
- >
- )
- ), [cohorts, currentFilters.cohort, handleSortFilterChange, status, userHasModerationPrivileges]);
-
- return (
-
-
-
- {intl.formatMessage(messages.sortFilterStatus, {
- own: false,
- type: currentFilters.postType,
- sort: currentSorting,
- status: currentFilters.status,
- cohortType: selectedCohort?.name ? 'group' : 'all',
- cohort: capitalize(selectedCohort?.name),
- })}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
+const PostFilterBar = () => (
+
+
+
+);
export default React.memo(PostFilterBar);
diff --git a/src/discussions/posts/post-filter-bar/PostSortFilters.jsx b/src/discussions/posts/post-filter-bar/PostSortFilters.jsx
new file mode 100644
index 00000000..3b8a0feb
--- /dev/null
+++ b/src/discussions/posts/post-filter-bar/PostSortFilters.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { useSelector } from 'react-redux';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+import { Form } from '@edx/paragon';
+
+import { ThreadOrdering } from '../../../data/constants';
+import { selectThreadSorting } from '../data/selectors';
+import ActionItem from './ActionItem';
+import messages from './messages';
+import withFilterHandleChange from './withFilterHandleChange';
+
+const PostSortFilters = ({ handleSortFilterChange }) => {
+ const intl = useIntl();
+ const currentSorting = useSelector(selectThreadSorting());
+
+ return (
+
+
+
+
+
+ );
+};
+
+PostSortFilters.propTypes = {
+ handleSortFilterChange: PropTypes.func.isRequired,
+};
+
+export default React.memo(withFilterHandleChange(PostSortFilters));
diff --git a/src/discussions/posts/post-filter-bar/PostStatusFilters.jsx b/src/discussions/posts/post-filter-bar/PostStatusFilters.jsx
new file mode 100644
index 00000000..156fd771
--- /dev/null
+++ b/src/discussions/posts/post-filter-bar/PostStatusFilters.jsx
@@ -0,0 +1,79 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { useSelector } from 'react-redux';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+import { Form } from '@edx/paragon';
+
+import { PostsStatusFilter } from '../../../data/constants';
+import { useCurrentPage } from '../../data/hooks';
+import { selectUserHasModerationPrivileges, selectUserIsGroupTa } from '../../data/selectors';
+import { selectThreadFilters } from '../data/selectors';
+import ActionItem from './ActionItem';
+import messages from './messages';
+import withFilterHandleChange from './withFilterHandleChange';
+
+const PostStatusFilters = ({ handleSortFilterChange }) => {
+ const intl = useIntl();
+ const page = useCurrentPage();
+ const userHasModerationPrivileges = useSelector(selectUserHasModerationPrivileges);
+ const userIsGroupTa = useSelector(selectUserIsGroupTa);
+ const { status } = useSelector(selectThreadFilters());
+
+ return (
+
+
+
+ {page !== 'my-posts' && (
+
+ )}
+ {(userHasModerationPrivileges || userIsGroupTa) && (
+
+ )}
+
+
+
+ );
+};
+
+PostStatusFilters.propTypes = {
+ handleSortFilterChange: PropTypes.func.isRequired,
+};
+
+export default React.memo(withFilterHandleChange(PostStatusFilters));
diff --git a/src/discussions/posts/post-filter-bar/PostTypeFilters.jsx b/src/discussions/posts/post-filter-bar/PostTypeFilters.jsx
new file mode 100644
index 00000000..f833bdd1
--- /dev/null
+++ b/src/discussions/posts/post-filter-bar/PostTypeFilters.jsx
@@ -0,0 +1,52 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { useSelector } from 'react-redux';
+
+import { useIntl } from '@edx/frontend-platform/i18n';
+import { Form } from '@edx/paragon';
+
+import { ThreadType } from '../../../data/constants';
+import { selectThreadFilters } from '../data/selectors';
+import ActionItem from './ActionItem';
+import messages from './messages';
+import withFilterHandleChange from './withFilterHandleChange';
+
+const PostTypeFilters = ({ handleSortFilterChange }) => {
+ const intl = useIntl();
+ const currentFilters = useSelector(selectThreadFilters());
+
+ return (
+
+
+
+
+
+ );
+};
+
+PostTypeFilters.propTypes = {
+ handleSortFilterChange: PropTypes.func.isRequired,
+};
+
+export default React.memo(withFilterHandleChange(PostTypeFilters));
diff --git a/src/discussions/posts/post-filter-bar/withFilterHandleChange.jsx b/src/discussions/posts/post-filter-bar/withFilterHandleChange.jsx
new file mode 100644
index 00000000..8fccae6d
--- /dev/null
+++ b/src/discussions/posts/post-filter-bar/withFilterHandleChange.jsx
@@ -0,0 +1,84 @@
+import React, { useCallback, useMemo } from 'react';
+
+import { toString } from 'lodash';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { sendTrackEvent } from '@edx/frontend-platform/analytics';
+
+import { PostsStatusFilter, ThreadType } from '../../../data/constants';
+import { selectCourseCohorts } from '../../cohorts/data/selectors';
+import {
+ setCohortFilter, setPostsTypeFilter, setSortedBy, setStatusFilter,
+} from '../data';
+import { selectThreadFilters, selectThreadSorting } from '../data/selectors';
+
+const withFilterHandleChange = WrappedComponent => (
+ function FilterHandleChange(props) {
+ const dispatch = useDispatch();
+ const currentSorting = useSelector(selectThreadSorting());
+ const currentFilters = useSelector(selectThreadFilters());
+ const cohorts = useSelector(selectCourseCohorts);
+
+ const selectedCohort = useMemo(() => (
+ cohorts?.find(cohort => (
+ toString(cohort.id) === currentFilters.cohort
+ ))
+ ), [cohorts, currentFilters.cohort]);
+
+ const handleSortFilterChange = useCallback((event) => {
+ const currentType = currentFilters.postType;
+ const currentStatus = currentFilters.status;
+ const {
+ name,
+ value,
+ } = event.currentTarget;
+ const filterContentEventProperties = {
+ statusFilter: currentStatus,
+ threadTypeFilter: currentType,
+ sortFilter: currentSorting,
+ cohortFilter: selectedCohort,
+ triggeredBy: name,
+ };
+
+ if (name === 'type') {
+ dispatch(setPostsTypeFilter(value));
+ if (
+ value === ThreadType.DISCUSSION && currentStatus === PostsStatusFilter.UNANSWERED
+ ) {
+ // You can't filter discussions by unanswered
+ dispatch(setStatusFilter(PostsStatusFilter.ALL));
+ }
+ filterContentEventProperties.threadTypeFilter = value;
+ }
+
+ if (name === 'status') {
+ dispatch(setStatusFilter(value));
+ if (value === PostsStatusFilter.UNANSWERED && currentType !== ThreadType.QUESTION) {
+ // You can't filter discussions by unanswered so switch type to questions
+ dispatch(setPostsTypeFilter(ThreadType.QUESTION));
+ }
+ if (value === PostsStatusFilter.UNRESPONDED && currentType !== ThreadType.DISCUSSION) {
+ // You can't filter questions by not responded so switch type to discussion
+ dispatch(setPostsTypeFilter(ThreadType.DISCUSSION));
+ }
+ filterContentEventProperties.statusFilter = value;
+ }
+
+ if (name === 'sort') {
+ dispatch(setSortedBy(value));
+ filterContentEventProperties.sortFilter = value;
+ }
+
+ if (name === 'cohort') {
+ dispatch(setCohortFilter(value));
+ filterContentEventProperties.cohortFilter = value;
+ }
+
+ sendTrackEvent('edx.forum.filter.content', filterContentEventProperties);
+ }, [currentFilters, currentSorting, selectedCohort]);
+
+ return
;
+ }
+);
+
+export default withFilterHandleChange;
diff --git a/src/discussions/posts/post/PostLink.jsx b/src/discussions/posts/post/PostLink.jsx
index 5db61bf9..c7569062 100644
--- a/src/discussions/posts/post/PostLink.jsx
+++ b/src/discussions/posts/post/PostLink.jsx
@@ -1,4 +1,4 @@
-import React, { useContext, useMemo } from 'react';
+import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
@@ -12,7 +12,9 @@ import { CheckCircle } from '@edx/paragon/icons';
import { PushPin } from '../../../components/icons';
import { AvatarOutlineAndLabelColors, Routes, ThreadType } from '../../../data/constants';
import AuthorLabel from '../../common/AuthorLabel';
-import { DiscussionContext } from '../../common/context';
+import {
+ useCategory, useCourseId, useCurrentPage, useEnableInContextSidebar, useLearnerUsername, usePostId,
+} from '../../data/hooks';
import { discussionsPath, isPostPreviewAvailable } from '../../utils';
import { selectThread } from '../data/selectors';
import messages from './messages';
@@ -25,18 +27,18 @@ const PostLink = ({
showDivider,
}) => {
const intl = useIntl();
- const {
- courseId,
- postId: selectedPostId,
- page,
- enableInContextSidebar,
- category,
- learnerUsername,
- } = useContext(DiscussionContext);
+ const courseId = useCourseId();
+ const selectedPostId = usePostId();
+ const page = useCurrentPage();
+ const enableInContextSidebar = useEnableInContextSidebar();
+ const category = useCategory();
+ const learnerUsername = useLearnerUsername();
+
const {
topicId, hasEndorsed, type, author, authorLabel, abuseFlagged, abuseFlaggedCount, read, commentCount,
unreadCommentCount, id, pinned, previewBody, title, voted, voteCount, following, groupId, groupName, createdAt,
} = useSelector(selectThread(postId));
+
const linkUrl = discussionsPath(Routes.COMMENTS.PAGES[page], {
0: enableInContextSidebar ? 'in-context' : undefined,
courseId,
@@ -45,6 +47,7 @@ const PostLink = ({
category,
learnerUsername,
});
+
const showAnsweredBadge = hasEndorsed && type === ThreadType.QUESTION;
const authorLabelColor = AvatarOutlineAndLabelColors[authorLabel];
const canSeeReportedBadge = abuseFlagged || abuseFlaggedCount;
diff --git a/src/discussions/tours/DiscussionsProductTour.jsx b/src/discussions/tours/DiscussionsProductTour.jsx
index 50475af5..105e2e50 100644
--- a/src/discussions/tours/DiscussionsProductTour.jsx
+++ b/src/discussions/tours/DiscussionsProductTour.jsx
@@ -1,31 +1,23 @@
-import React, { useEffect } from 'react';
+import React, { memo } from 'react';
import isEmpty from 'lodash/isEmpty';
-import { useDispatch } from 'react-redux';
import { ProductTour } from '@edx/paragon';
+import withConditionalInContextRendering from '../common/withConditionalInContextRendering';
import { useTourConfiguration } from '../data/hooks';
-import { fetchDiscussionTours } from './data/thunks';
const DiscussionsProductTour = () => {
- const dispatch = useDispatch();
const config = useTourConfiguration();
-
- useEffect(() => {
- dispatch(fetchDiscussionTours());
- }, []);
+ console.log('DiscussionsProductTour');
return (
- // eslint-disable-next-line react/jsx-no-useless-fragment
- <>
- {!isEmpty(config) && (
-
- )}
- >
+ !isEmpty(config) && (
+
+ )
);
};
-export default DiscussionsProductTour;
+export default memo(withConditionalInContextRendering(DiscussionsProductTour, false));