From 5236e110ddf32f9ad376f13248969fca4e9633fb Mon Sep 17 00:00:00 2001 From: Awais Ansari Date: Thu, 16 Nov 2023 13:13:01 +0500 Subject: [PATCH] feat: updated MFE structure and reduced re-rendering for sidebar --- src/components/FilterBar.jsx | 2 +- .../NavigationBar/CourseTabsNavigation.jsx | 22 +- .../NavigationBar/data/selectors.js | 4 +- src/components/NavigationBar/index.js | 2 - src/components/Search.jsx | 22 +- src/data/constants.js | 2 +- .../withConditionalInContextRendering.jsx | 11 + src/discussions/data/hooks.js | 130 ++++--- .../discussions-home/CourseHeader.jsx | 20 ++ .../discussions-home/DiscussionActionBar.jsx | 24 ++ .../discussions-home/DiscussionFooter.jsx | 9 + .../discussions-home/DiscussionHeader.jsx | 17 + .../discussions-home/DiscussionLayout.jsx | 53 +++ .../discussions-home/DiscussionSidebar.jsx | 99 ++---- .../discussions-home/DiscussionsHome.jsx | 151 ++------ src/discussions/discussions-home/InfoPage.jsx | 37 ++ .../discussions-home/LayoutSwitcher.jsx | 40 +++ .../discussions-home/LegacyBreadcrumb.jsx | 23 ++ .../discussions-home/ResizableSidebar.jsx | 54 +++ .../topic-search/TopicSearchBar.jsx | 9 +- .../navigation-bar/NavigationBar.jsx | 39 ++- src/discussions/posts/AllPostsList.jsx | 14 + src/discussions/posts/CategoryPostsList.jsx | 22 ++ src/discussions/posts/LoadMoreButton.jsx | 90 +++++ src/discussions/posts/PostsList.jsx | 7 +- src/discussions/posts/PostsSearchInfo.jsx | 32 ++ src/discussions/posts/PostsView.jsx | 97 +---- src/discussions/posts/TopicPostsList.jsx | 35 ++ src/discussions/posts/data/hooks.js | 16 +- .../posts/post-actions-bar/AddPostButton.jsx | 46 +++ .../posts/post-actions-bar/PostActionsBar.jsx | 52 +-- .../posts/post-actions-bar/SearchField.jsx | 20 ++ .../posts/post-filter-bar/ActionItem.jsx | 37 ++ .../posts/post-filter-bar/CohortFilters.jsx | 81 +++++ .../post-filter-bar/CollapsibleFilter.jsx | 69 ++++ .../posts/post-filter-bar/PostFilterBar.jsx | 331 +----------------- .../posts/post-filter-bar/PostSortFilters.jsx | 52 +++ .../post-filter-bar/PostStatusFilters.jsx | 79 +++++ .../posts/post-filter-bar/PostTypeFilters.jsx | 52 +++ .../withFilterHandleChange.jsx | 84 +++++ src/discussions/posts/post/PostLink.jsx | 23 +- .../tours/DiscussionsProductTour.jsx | 26 +- 42 files changed, 1277 insertions(+), 758 deletions(-) delete mode 100644 src/components/NavigationBar/index.js create mode 100644 src/discussions/common/withConditionalInContextRendering.jsx create mode 100644 src/discussions/discussions-home/CourseHeader.jsx create mode 100644 src/discussions/discussions-home/DiscussionActionBar.jsx create mode 100644 src/discussions/discussions-home/DiscussionFooter.jsx create mode 100644 src/discussions/discussions-home/DiscussionHeader.jsx create mode 100644 src/discussions/discussions-home/DiscussionLayout.jsx create mode 100644 src/discussions/discussions-home/InfoPage.jsx create mode 100644 src/discussions/discussions-home/LayoutSwitcher.jsx create mode 100644 src/discussions/discussions-home/LegacyBreadcrumb.jsx create mode 100644 src/discussions/discussions-home/ResizableSidebar.jsx create mode 100644 src/discussions/posts/AllPostsList.jsx create mode 100644 src/discussions/posts/CategoryPostsList.jsx create mode 100644 src/discussions/posts/LoadMoreButton.jsx create mode 100644 src/discussions/posts/PostsSearchInfo.jsx create mode 100644 src/discussions/posts/TopicPostsList.jsx create mode 100644 src/discussions/posts/post-actions-bar/AddPostButton.jsx create mode 100644 src/discussions/posts/post-actions-bar/SearchField.jsx create mode 100644 src/discussions/posts/post-filter-bar/ActionItem.jsx create mode 100644 src/discussions/posts/post-filter-bar/CohortFilters.jsx create mode 100644 src/discussions/posts/post-filter-bar/CollapsibleFilter.jsx create mode 100644 src/discussions/posts/post-filter-bar/PostSortFilters.jsx create mode 100644 src/discussions/posts/post-filter-bar/PostStatusFilters.jsx create mode 100644 src/discussions/posts/post-filter-bar/PostTypeFilters.jsx create mode 100644 src/discussions/posts/post-filter-bar/withFilterHandleChange.jsx diff --git a/src/components/FilterBar.jsx b/src/components/FilterBar.jsx index e1cbaa2f..c8bd88a2 100644 --- a/src/components/FilterBar.jsx +++ b/src/components/FilterBar.jsx @@ -17,7 +17,7 @@ import { } from '../data/constants'; import { selectCourseCohorts } from '../discussions/cohorts/data/selectors'; import messages from '../discussions/posts/post-filter-bar/messages'; -import { ActionItem } from '../discussions/posts/post-filter-bar/PostFilterBar'; +import ActionItem from '../discussions/posts/post-filter-bar/PostFilterBar'; const FilterBar = ({ intl, diff --git a/src/components/NavigationBar/CourseTabsNavigation.jsx b/src/components/NavigationBar/CourseTabsNavigation.jsx index f8ae0cce..e76a2cca 100644 --- a/src/components/NavigationBar/CourseTabsNavigation.jsx +++ b/src/components/NavigationBar/CourseTabsNavigation.jsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { memo, useEffect } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; @@ -6,25 +6,30 @@ import { useDispatch, useSelector } from 'react-redux'; import { useIntl } from '@edx/frontend-platform/i18n'; +import withConditionalInContextRendering from '../../discussions/common/withConditionalInContextRendering'; +import { useCourseId } from '../../discussions/data/hooks'; import { fetchTab } from './data/thunks'; import Tabs from './tabs/Tabs'; import messages from './messages'; import './navBar.scss'; -const CourseTabsNavigation = ({ - activeTab, className, courseId, rootSlug, -}) => { +const CourseTabsNavigation = ({ activeTab, className, rootSlug }) => { const dispatch = useDispatch(); const intl = useIntl(); + const courseId = useCourseId(); const tabs = useSelector(state => state.courseTabs.tabs); useEffect(() => { - dispatch(fetchTab(courseId, rootSlug)); + if (courseId) { + dispatch(fetchTab(courseId, rootSlug)); + } }, [courseId]); + console.log('CourseTabsNavigation'); + return ( -
+
{!!tabs.length && ( state.courseTabs; -export const selectCourseTabs = state => state.courseTabs; +export default selectCourseTabs; diff --git a/src/components/NavigationBar/index.js b/src/components/NavigationBar/index.js deleted file mode 100644 index e2236ee7..00000000 --- a/src/components/NavigationBar/index.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable import/prefer-default-export */ -export { default as CourseTabsNavigation } from './CourseTabsNavigation'; diff --git a/src/components/Search.jsx b/src/components/Search.jsx index 5c5bfa2d..5fb1dc73 100644 --- a/src/components/Search.jsx +++ b/src/components/Search.jsx @@ -1,5 +1,5 @@ import React, { - useCallback, useContext, useEffect, useRef, useState, + useCallback, useEffect, useMemo, useRef, useState, } from 'react'; import camelCase from 'lodash/camelCase'; @@ -9,7 +9,7 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { Icon, SearchField } from '@edx/paragon'; import { Search as SearchIcon } from '@edx/paragon/icons'; -import { DiscussionContext } from '../discussions/common/context'; +import { useCurrentPage } from '../discussions/data/hooks'; import { setUsernameSearch } from '../discussions/learners/data'; import { setSearchQuery } from '../discussions/posts/data'; import postsMessages from '../discussions/posts/post-actions-bar/messages'; @@ -18,7 +18,7 @@ import { setFilter as setTopicFilter } from '../discussions/topics/data/slices'; const Search = () => { const intl = useIntl(); const dispatch = useDispatch(); - const { page } = useContext(DiscussionContext); + const page = useCurrentPage(); const postSearch = useSelector(({ threads }) => threads.filters.search); const topicSearch = useSelector(({ topics }) => topics.filter); const learnerSearch = useSelector(({ learners }) => learners.usernameSearch); @@ -26,15 +26,15 @@ const Search = () => { const isTopicSearch = 'topics'.includes(page); const [searchValue, setSearchValue] = useState(''); const previousSearchValueRef = useRef(''); - let currentValue = ''; - if (isPostSearch) { - currentValue = postSearch; - } else if (isTopicSearch) { - currentValue = topicSearch; - } else { - currentValue = learnerSearch; - } + const currentValue = useMemo(() => { + if (isPostSearch) { + return postSearch; + } if (isTopicSearch) { + return topicSearch; + } + return learnerSearch; + }, [postSearch, topicSearch, learnerSearch]); const onClear = useCallback(() => { dispatch(setSearchQuery('')); diff --git a/src/data/constants.js b/src/data/constants.js index d0c7960c..3876c84a 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -137,7 +137,7 @@ export const DiscussionProvider = { OPEN_EDX: 'openedx', }; -const BASE_PATH = `${getConfig().PUBLIC_PATH}:courseId`; +export const BASE_PATH = `${getConfig().PUBLIC_PATH}:courseId`; export const Routes = { DISCUSSIONS: { diff --git a/src/discussions/common/withConditionalInContextRendering.jsx b/src/discussions/common/withConditionalInContextRendering.jsx new file mode 100644 index 00000000..54798383 --- /dev/null +++ b/src/discussions/common/withConditionalInContextRendering.jsx @@ -0,0 +1,11 @@ +import { useEnableInContextSidebar } from '../data/hooks'; + +const withConditionalInContextRendering = (WrappedComponent, condition) => ( + function SidebarConditionalRenderer(props) { + const enableInContextSidebar = useEnableInContextSidebar(); + + return enableInContextSidebar === condition && ; + } +); + +export default withConditionalInContextRendering; diff --git a/src/discussions/data/hooks.js b/src/discussions/data/hooks.js index 466eb140..bb57fb41 100644 --- a/src/discussions/data/hooks.js +++ b/src/discussions/data/hooks.js @@ -11,7 +11,9 @@ import { useIntl } from '@edx/frontend-platform/i18n'; import { AppContext } from '@edx/frontend-platform/react'; import { breakpoints, useWindowSize } from '@edx/paragon'; -import { RequestStatus, Routes } from '../../data/constants'; +import { + ALL_ROUTES, BASE_PATH, RequestStatus, Routes, +} from '../../data/constants'; import { selectTopicsUnderCategory } from '../../data/selectors'; import { fetchCourseBlocks } from '../../data/thunks'; import { DiscussionContext } from '../common/context'; @@ -20,7 +22,7 @@ import { threadsLoadingStatus } from '../posts/data/selectors'; import { selectTopics } from '../topics/data/selectors'; import tourCheckpoints from '../tours/constants'; import { selectTours } from '../tours/data/selectors'; -import { updateTourShowStatus } from '../tours/data/thunks'; +import { fetchDiscussionTours, updateTourShowStatus } from '../tours/data/thunks'; import messages from '../tours/messages'; import { discussionsPath } from '../utils'; import { @@ -69,44 +71,6 @@ export const useSidebarVisible = () => { return !hideSidebar; }; -export function useCourseDiscussionData(courseId) { - const dispatch = useDispatch(); - const { authenticatedUser } = useContext(AppContext); - - useEffect(() => { - async function fetchBaseData() { - await dispatch(fetchCourseConfig(courseId)); - await dispatch(fetchCourseBlocks(courseId, authenticatedUser.username)); - } - - fetchBaseData(); - }, [courseId]); -} - -export function useRedirectToThread(courseId, enableInContextSidebar) { - const dispatch = useDispatch(); - const history = useHistory(); - const location = useLocation(); - - const redirectToThread = useSelector( - (state) => state.threads.redirectToThread, - ); - - useEffect(() => { - // After posting a new thread we'd like to redirect users to it, the topic and post id are temporarily - // stored in redirectToThread - if (redirectToThread) { - dispatch(clearRedirect()); - const newLocation = discussionsPath(Routes.COMMENTS.PAGES[enableInContextSidebar ? 'topics' : 'my-posts'], { - courseId, - postId: redirectToThread.threadId, - topicId: redirectToThread.topicId, - })(location); - history.push(newLocation); - } - }, [redirectToThread]); -} - export function useIsOnDesktop() { const windowSize = useWindowSize(); return windowSize.width >= breakpoints.medium.minWidth; @@ -260,3 +224,89 @@ export const useDebounce = (value, delay) => { ); return debouncedValue; }; + +export const useEnableInContextSidebar = () => { + const location = useLocation(); + + return Boolean(new URLSearchParams(location.search).get('inContextSidebar') !== null); +}; + +export const useCourseId = () => { + const { params: { courseId } } = useRouteMatch(BASE_PATH); + + return courseId; +}; + +export const useCurrentPage = () => { + const { params: { page } } = useRouteMatch(`${Routes.COMMENTS.PAGE}?`); + + return page; +}; + +export const usePostId = () => { + const { params: { postId } } = useRouteMatch(ALL_ROUTES); + + return postId; +}; + +export const useLearnerUsername = () => { + const { params: { learnerUsername } } = useRouteMatch(ALL_ROUTES); + + return learnerUsername; +}; + +export const useTopicId = () => { + const { params: { topicId } } = useRouteMatch(ALL_ROUTES); + + return topicId; +}; + +export const useCategory = () => { + const { params: { category } } = useRouteMatch(ALL_ROUTES); + + return category; +}; + +export function useRedirectToThread() { + const dispatch = useDispatch(); + const history = useHistory(); + const location = useLocation(); + const courseId = useCourseId(); + const enableInContextSidebar = useEnableInContextSidebar(); + + const redirectToThread = useSelector( + (state) => state.threads.redirectToThread, + ); + + useEffect(() => { + // After posting a new thread we'd like to redirect users to it, the topic and post id are temporarily + // stored in redirectToThread + if (redirectToThread) { + dispatch(clearRedirect()); + const newLocation = discussionsPath(Routes.COMMENTS.PAGES[enableInContextSidebar ? 'topics' : 'my-posts'], { + courseId, + postId: redirectToThread.threadId, + topicId: redirectToThread.topicId, + })(location); + history.push(newLocation); + } + }, [redirectToThread]); +} + +export function useCourseDiscussionData() { + const dispatch = useDispatch(); + const courseId = useCourseId(); + const { authenticatedUser } = useContext(AppContext); + + useEffect(() => { + async function fetchBaseData() { + await Promise.all([ + dispatch(fetchCourseConfig(courseId)), + dispatch(fetchCourseBlocks(courseId, authenticatedUser.username)), + dispatch(fetchDiscussionTours()), + ]); + } + + fetchBaseData(); + }, [courseId]); +} diff --git a/src/discussions/discussions-home/CourseHeader.jsx b/src/discussions/discussions-home/CourseHeader.jsx new file mode 100644 index 00000000..e825772f --- /dev/null +++ b/src/discussions/discussions-home/CourseHeader.jsx @@ -0,0 +1,20 @@ +import React, { memo } from 'react'; + +import { useSelector } from 'react-redux'; + +import { LearningHeader } from '@edx/frontend-component-header'; + +import selectCourseTabs from '../../components/NavigationBar/data/selectors'; +import withConditionalInContextRendering from '../common/withConditionalInContextRendering'; + +const CourseHeader = () => { + const { courseNumber, courseTitle, org } = useSelector(selectCourseTabs); + + console.log('CourseHeader', courseNumber, courseTitle, org); + + return (courseNumber || courseTitle || org) && ( + + ); +}; + +export default memo(withConditionalInContextRendering(CourseHeader, false)); diff --git a/src/discussions/discussions-home/DiscussionActionBar.jsx b/src/discussions/discussions-home/DiscussionActionBar.jsx new file mode 100644 index 00000000..c6b902aa --- /dev/null +++ b/src/discussions/discussions-home/DiscussionActionBar.jsx @@ -0,0 +1,24 @@ +import React, { memo } from 'react'; + +import classNames from 'classnames'; + +import { useEnableInContextSidebar } from '../data/hooks'; +import NavigationBar from '../navigation/navigation-bar/NavigationBar'; +import PostActionsBar from '../posts/post-actions-bar/PostActionsBar'; + +const DiscussionActionBar = () => { + const enableInContextSidebar = useEnableInContextSidebar(); + + return ( +
+ + +
+ ); +}; + +export default memo(DiscussionActionBar); diff --git a/src/discussions/discussions-home/DiscussionFooter.jsx b/src/discussions/discussions-home/DiscussionFooter.jsx new file mode 100644 index 00000000..7566a113 --- /dev/null +++ b/src/discussions/discussions-home/DiscussionFooter.jsx @@ -0,0 +1,9 @@ +import React, { memo } from 'react'; + +import Footer from '@edx/frontend-component-footer'; + +import withConditionalInContextRendering from '../common/withConditionalInContextRendering'; + +const DiscussionFooter = () =>