From a6edc9132f648890eb5693e644998174894fbe41 Mon Sep 17 00:00:00 2001 From: Carla Duarte Date: Thu, 25 Jun 2020 10:26:47 -0400 Subject: [PATCH] AA-186: Refactoring to separate Course Home logic from Courseware (#93) - Pulled Course Home specific components into `course-home` - Created a courseHome reducer (and all necessary data files - api, thunks, slice) - Removed Course Home logic from Courseware's data files (api, thunks, slice, etc.) - Renamed Outline Tab URL to end in `/home` rather than `/outline` again (per Product) Co-authored-by: Carla Duarte --- src/course-header/CourseTabsNavigation.jsx | 1 - src/course-home/data/api.js | 65 +++++++++++++++ src/course-home/data/index.js | 7 ++ src/course-home/data/slice.js | 38 +++++++++ src/course-home/data/thunks.js | 76 +++++++++++++++++ .../dates-banner/DatesBanner.jsx | 2 +- .../dates-banner/DatesBannerContainer.jsx | 6 +- src/course-home/dates-banner/index.js | 3 + .../dates-banner/messages.js | 2 +- src/{ => course-home}/dates-tab/Badge.jsx | 0 src/{ => course-home}/dates-tab/Badge.scss | 0 src/{ => course-home}/dates-tab/DatesTab.jsx | 2 +- src/{ => course-home}/dates-tab/Day.jsx | 4 +- src/{ => course-home}/dates-tab/Day.scss | 0 src/{ => course-home}/dates-tab/Timeline.jsx | 4 +- src/{ => course-home}/dates-tab/badgelist.jsx | 0 src/{ => course-home}/dates-tab/fakeData.js | 0 src/course-home/dates-tab/index.jsx | 3 + src/{ => course-home}/dates-tab/messages.js | 0 src/{ => course-home}/dates-tab/utils.jsx | 0 src/course-home/index.js | 1 - src/course-home/outline-tab/DateSummary.jsx | 2 +- src/course-home/outline-tab/OutlineTab.jsx | 6 +- src/course-home/outline-tab/index.js | 1 + .../outline-tab/{ => widgets}/CourseDates.jsx | 4 +- .../outline-tab/{ => widgets}/CourseTools.jsx | 2 +- src/courseware/CoursewareContainer.jsx | 1 + src/data/api.js | 65 +-------------- src/data/index.js | 4 +- src/data/slice.js | 15 ---- src/data/thunks.js | 82 ------------------- src/dates-tab/index.jsx | 1 - src/index.jsx | 8 +- src/index.scss | 4 +- src/store.js | 2 + src/tab-page/TabContainer.jsx | 4 +- src/tab-page/TabPage.jsx | 6 +- 37 files changed, 227 insertions(+), 194 deletions(-) create mode 100644 src/course-home/data/api.js create mode 100644 src/course-home/data/index.js create mode 100644 src/course-home/data/slice.js create mode 100644 src/course-home/data/thunks.js rename src/{ => course-home}/dates-banner/DatesBanner.jsx (92%) rename src/{ => course-home}/dates-banner/DatesBannerContainer.jsx (93%) create mode 100644 src/course-home/dates-banner/index.js rename src/{ => course-home}/dates-banner/messages.js (98%) rename src/{ => course-home}/dates-tab/Badge.jsx (100%) rename src/{ => course-home}/dates-tab/Badge.scss (100%) rename src/{ => course-home}/dates-tab/DatesTab.jsx (100%) rename src/{ => course-home}/dates-tab/Day.jsx (96%) rename src/{ => course-home}/dates-tab/Day.scss (100%) rename src/{ => course-home}/dates-tab/Timeline.jsx (94%) rename src/{ => course-home}/dates-tab/badgelist.jsx (100%) rename src/{ => course-home}/dates-tab/fakeData.js (100%) create mode 100644 src/course-home/dates-tab/index.jsx rename src/{ => course-home}/dates-tab/messages.js (100%) rename src/{ => course-home}/dates-tab/utils.jsx (100%) delete mode 100644 src/course-home/index.js create mode 100644 src/course-home/outline-tab/index.js rename src/course-home/outline-tab/{ => widgets}/CourseDates.jsx (88%) rename src/course-home/outline-tab/{ => widgets}/CourseTools.jsx (96%) delete mode 100644 src/dates-tab/index.jsx diff --git a/src/course-header/CourseTabsNavigation.jsx b/src/course-header/CourseTabsNavigation.jsx index a24c30e5..3efdb39f 100644 --- a/src/course-header/CourseTabsNavigation.jsx +++ b/src/course-header/CourseTabsNavigation.jsx @@ -36,7 +36,6 @@ CourseTabsNavigation.propTypes = { className: PropTypes.string, tabs: PropTypes.arrayOf(PropTypes.shape({ title: PropTypes.string.isRequired, - priority: PropTypes.number.isRequired, slug: PropTypes.string.isRequired, url: PropTypes.string.isRequired, })).isRequired, diff --git a/src/course-home/data/api.js b/src/course-home/data/api.js new file mode 100644 index 00000000..5e2784fc --- /dev/null +++ b/src/course-home/data/api.js @@ -0,0 +1,65 @@ +import { camelCaseObject, getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { normalizeBlocks } from '../../data'; + +function normalizeCourseHomeCourseMetadata(metadata) { + const data = camelCaseObject(metadata); + return { + ...data, + tabs: data.tabs.map(tab => ({ + slug: tab.tabId, + title: tab.title, + url: tab.url, + })), + }; +} + +export async function getCourseHomeCourseMetadata(courseId) { + const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/course_metadata/${courseId}`; + const { data } = await getAuthenticatedHttpClient().get(url); + return normalizeCourseHomeCourseMetadata(data); +} + +export async function getDatesTabData(courseId) { + const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/dates/${courseId}`; + try { + const { data } = await getAuthenticatedHttpClient().get(url); + return camelCaseObject(data); + } catch (error) { + const { httpErrorStatus } = error && error.customAttributes; + if (httpErrorStatus === 404) { + global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/dates`); + return {}; + } + throw error; + } +} + +export async function getOutlineTabData(courseId) { + const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/outline/${courseId}`; + let { tabData } = {}; + try { + tabData = await getAuthenticatedHttpClient().get(url); + } catch (error) { + const { httpErrorStatus } = error && error.customAttributes; + if (httpErrorStatus === 404) { + global.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/home`); + return {}; + } + throw error; + } + + const { + data, + } = tabData; + const courseBlocks = normalizeBlocks(courseId, data.course_blocks.blocks); + const courseTools = camelCaseObject(data.course_tools); + const datesWidget = camelCaseObject(data.dates_widget); + + return { courseTools, courseBlocks, datesWidget }; +} + +export async function updateCourseDeadlines(courseId) { + const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_experience/v1/reset_course_deadlines`); + await getAuthenticatedHttpClient().post(url.href, { course_key: courseId }); +} diff --git a/src/course-home/data/index.js b/src/course-home/data/index.js new file mode 100644 index 00000000..69e7820b --- /dev/null +++ b/src/course-home/data/index.js @@ -0,0 +1,7 @@ +export { + fetchDatesTab, + fetchOutlineTab, + resetDeadlines, +} from './thunks'; + +export { reducer } from './slice'; diff --git a/src/course-home/data/slice.js b/src/course-home/data/slice.js new file mode 100644 index 00000000..2e3572ac --- /dev/null +++ b/src/course-home/data/slice.js @@ -0,0 +1,38 @@ +/* eslint-disable no-param-reassign */ +import { createSlice } from '@reduxjs/toolkit'; + +export const LOADING = 'loading'; +export const LOADED = 'loaded'; +export const FAILED = 'failed'; + +const slice = createSlice({ + name: 'course-home', + initialState: { + courseStatus: 'loading', + courseId: null, + }, + reducers: { + fetchTabRequest: (state, { payload }) => { + state.courseId = payload.courseId; + state.courseStatus = LOADING; + }, + fetchTabSuccess: (state, { payload }) => { + state.courseId = payload.courseId; + state.courseStatus = LOADED; + }, + fetchTabFailure: (state, { payload }) => { + state.courseId = payload.courseId; + state.courseStatus = FAILED; + }, + }, +}); + +export const { + fetchTabRequest, + fetchTabSuccess, + fetchTabFailure, +} = slice.actions; + +export const { + reducer, +} = slice; diff --git a/src/course-home/data/thunks.js b/src/course-home/data/thunks.js new file mode 100644 index 00000000..a7dd7d7f --- /dev/null +++ b/src/course-home/data/thunks.js @@ -0,0 +1,76 @@ +import { logError } from '@edx/frontend-platform/logging'; +import { + getCourseHomeCourseMetadata, + getDatesTabData, + getOutlineTabData, + updateCourseDeadlines, +} from './api'; + +import { + addModel, +} from '../../model-store'; + +import { + fetchTabFailure, + fetchTabRequest, + fetchTabSuccess, +} from './slice'; + +export function fetchTab(courseId, tab, getTabData) { + return async (dispatch) => { + dispatch(fetchTabRequest({ courseId })); + Promise.allSettled([ + getCourseHomeCourseMetadata(courseId), + getTabData(courseId), + ]).then(([courseHomeCourseMetadataResult, tabDataResult]) => { + const fetchedCourseHomeCourseMetadata = courseHomeCourseMetadataResult.status === 'fulfilled'; + const fetchedTabData = tabDataResult.status === 'fulfilled'; + + if (fetchedCourseHomeCourseMetadata) { + dispatch(addModel({ + modelType: 'courses', + model: { + id: courseId, + ...courseHomeCourseMetadataResult.value, + }, + })); + } else { + logError(courseHomeCourseMetadataResult.reason); + } + + if (fetchedTabData) { + dispatch(addModel({ + modelType: tab, + model: { + id: courseId, + ...tabDataResult.value, + }, + })); + } else { + logError(tabDataResult.reason); + } + + if (fetchedCourseHomeCourseMetadata && fetchedTabData) { + dispatch(fetchTabSuccess({ courseId })); + } else { + dispatch(fetchTabFailure({ courseId })); + } + }); + }; +} + +export function fetchDatesTab(courseId) { + return fetchTab(courseId, 'dates', getDatesTabData); +} + +export function fetchOutlineTab(courseId) { + return fetchTab(courseId, 'outline', getOutlineTabData); +} + +export function resetDeadlines(courseId, getTabData) { + return async (dispatch) => { + updateCourseDeadlines(courseId).then(() => { + dispatch(getTabData(courseId)); + }); + }; +} diff --git a/src/dates-banner/DatesBanner.jsx b/src/course-home/dates-banner/DatesBanner.jsx similarity index 92% rename from src/dates-banner/DatesBanner.jsx rename to src/course-home/dates-banner/DatesBanner.jsx index 6c8d4a0d..7dd0f0fa 100644 --- a/src/dates-banner/DatesBanner.jsx +++ b/src/course-home/dates-banner/DatesBanner.jsx @@ -21,7 +21,7 @@ function DatesBanner(props) { {intl.formatMessage(messages[`datesBanner.${name}.body`])} {bannerClickHandler && ( - )} diff --git a/src/dates-banner/DatesBannerContainer.jsx b/src/course-home/dates-banner/DatesBannerContainer.jsx similarity index 93% rename from src/dates-banner/DatesBannerContainer.jsx rename to src/course-home/dates-banner/DatesBannerContainer.jsx index a95fbb2a..f901b055 100644 --- a/src/dates-banner/DatesBannerContainer.jsx +++ b/src/course-home/dates-banner/DatesBannerContainer.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { useDispatch, useSelector } from 'react-redux'; -import { useModel } from '../model-store'; +import { useModel } from '../../model-store'; import DatesBanner from './DatesBanner'; import { fetchDatesTab, resetDeadlines } from '../data/thunks'; @@ -14,7 +14,7 @@ function DatesBannerContainer(props) { const { courseId, - } = useSelector(state => state.courseware); + } = useSelector(state => state.courseHome); const { datesBannerInfo, @@ -29,7 +29,7 @@ function DatesBannerContainer(props) { const { isSelfPaced, - } = useModel('courseHomeMetadata', courseId); + } = useModel('courses', courseId); const dispatch = useDispatch(); const upgradeToCompleteGraded = model === 'dates' && contentTypeGatingEnabled && !missedDeadlines; diff --git a/src/course-home/dates-banner/index.js b/src/course-home/dates-banner/index.js new file mode 100644 index 00000000..6f197ebb --- /dev/null +++ b/src/course-home/dates-banner/index.js @@ -0,0 +1,3 @@ +import DatesBannerContainer from './DatesBannerContainer'; + +export default DatesBannerContainer; diff --git a/src/dates-banner/messages.js b/src/course-home/dates-banner/messages.js similarity index 98% rename from src/dates-banner/messages.js rename to src/course-home/dates-banner/messages.js index cae3f829..5ad06e48 100644 --- a/src/dates-banner/messages.js +++ b/src/course-home/dates-banner/messages.js @@ -58,7 +58,7 @@ const messages = defineMessages({ }, 'datesBanner.resetDatesBanner.button': { id: 'datesBanner.resetDatesBanner.button', - defaultMessage: 'Reset my deadlines', + defaultMessage: 'Shift due dates', description: 'Button in Reset Dates Banner', }, }); diff --git a/src/dates-tab/Badge.jsx b/src/course-home/dates-tab/Badge.jsx similarity index 100% rename from src/dates-tab/Badge.jsx rename to src/course-home/dates-tab/Badge.jsx diff --git a/src/dates-tab/Badge.scss b/src/course-home/dates-tab/Badge.scss similarity index 100% rename from src/dates-tab/Badge.scss rename to src/course-home/dates-tab/Badge.scss diff --git a/src/dates-tab/DatesTab.jsx b/src/course-home/dates-tab/DatesTab.jsx similarity index 100% rename from src/dates-tab/DatesTab.jsx rename to src/course-home/dates-tab/DatesTab.jsx index 54403e9f..aa5d6b16 100644 --- a/src/dates-tab/DatesTab.jsx +++ b/src/course-home/dates-tab/DatesTab.jsx @@ -8,10 +8,10 @@ import DatesBannerContainer from '../dates-banner/DatesBannerContainer'; function DatesTab({ intl }) { return ( <> -

{intl.formatMessage(messages.title)}

+ ); diff --git a/src/dates-tab/Day.jsx b/src/course-home/dates-tab/Day.jsx similarity index 96% rename from src/dates-tab/Day.jsx rename to src/course-home/dates-tab/Day.jsx index 82afb77d..3d1ff70f 100644 --- a/src/dates-tab/Day.jsx +++ b/src/course-home/dates-tab/Day.jsx @@ -4,7 +4,7 @@ import classNames from 'classnames'; import { useSelector } from 'react-redux'; import { FormattedDate, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { useModel } from '../model-store'; +import { useModel } from '../../model-store'; import { getBadgeListAndColor } from './badgelist'; import { isLearnerAssignment } from './utils'; @@ -14,7 +14,7 @@ function Day({ }) { const { courseId, - } = useSelector(state => state.courseware); + } = useSelector(state => state.courseHome); const { userTimezone, diff --git a/src/dates-tab/Day.scss b/src/course-home/dates-tab/Day.scss similarity index 100% rename from src/dates-tab/Day.scss rename to src/course-home/dates-tab/Day.scss diff --git a/src/dates-tab/Timeline.jsx b/src/course-home/dates-tab/Timeline.jsx similarity index 94% rename from src/dates-tab/Timeline.jsx rename to src/course-home/dates-tab/Timeline.jsx index 3ab288ef..4e82dded 100644 --- a/src/dates-tab/Timeline.jsx +++ b/src/course-home/dates-tab/Timeline.jsx @@ -1,7 +1,7 @@ import React from 'react'; import { useSelector } from 'react-redux'; -import { useModel } from '../model-store'; +import { useModel } from '../../model-store'; import Day from './Day'; import { daycmp, isLearnerAssignment } from './utils'; @@ -9,7 +9,7 @@ import { daycmp, isLearnerAssignment } from './utils'; export default function Timeline() { const { courseId, - } = useSelector(state => state.courseware); + } = useSelector(state => state.courseHome); const { courseDateBlocks, diff --git a/src/dates-tab/badgelist.jsx b/src/course-home/dates-tab/badgelist.jsx similarity index 100% rename from src/dates-tab/badgelist.jsx rename to src/course-home/dates-tab/badgelist.jsx diff --git a/src/dates-tab/fakeData.js b/src/course-home/dates-tab/fakeData.js similarity index 100% rename from src/dates-tab/fakeData.js rename to src/course-home/dates-tab/fakeData.js diff --git a/src/course-home/dates-tab/index.jsx b/src/course-home/dates-tab/index.jsx new file mode 100644 index 00000000..f17f68b8 --- /dev/null +++ b/src/course-home/dates-tab/index.jsx @@ -0,0 +1,3 @@ +import DatesTab from './DatesTab'; + +export default DatesTab; diff --git a/src/dates-tab/messages.js b/src/course-home/dates-tab/messages.js similarity index 100% rename from src/dates-tab/messages.js rename to src/course-home/dates-tab/messages.js diff --git a/src/dates-tab/utils.jsx b/src/course-home/dates-tab/utils.jsx similarity index 100% rename from src/dates-tab/utils.jsx rename to src/course-home/dates-tab/utils.jsx diff --git a/src/course-home/index.js b/src/course-home/index.js deleted file mode 100644 index 7893520d..00000000 --- a/src/course-home/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './outline-tab/OutlineTab'; diff --git a/src/course-home/outline-tab/DateSummary.jsx b/src/course-home/outline-tab/DateSummary.jsx index f1c5b8e1..a09516d4 100644 --- a/src/course-home/outline-tab/DateSummary.jsx +++ b/src/course-home/outline-tab/DateSummary.jsx @@ -3,7 +3,7 @@ import { faCalendarAlt } from '@fortawesome/free-regular-svg-icons'; import { FormattedDate } from '@edx/frontend-platform/i18n'; import React from 'react'; import PropTypes from 'prop-types'; -import { isLearnerAssignment } from '../../dates-tab/utils'; +import { isLearnerAssignment } from '../dates-tab/utils'; import './DateSummary.scss'; export default function DateSummary({ diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx index 17dd536a..ac278475 100644 --- a/src/course-home/outline-tab/OutlineTab.jsx +++ b/src/course-home/outline-tab/OutlineTab.jsx @@ -4,8 +4,8 @@ import { Button } from '@edx/paragon'; import { AlertList } from '../../user-messages'; -import CourseDates from './CourseDates'; -import CourseTools from './CourseTools'; +import CourseDates from './widgets/CourseDates'; +import CourseTools from './widgets/CourseTools'; import Section from './Section'; import { useModel } from '../../model-store'; @@ -19,7 +19,7 @@ const LogistrationAlert = React.lazy(() => import('../../alerts/logistration-ale export default function OutlineTab() { const { courseId, - } = useSelector(state => state.courseware); + } = useSelector(state => state.courseHome); const { title, diff --git a/src/course-home/outline-tab/index.js b/src/course-home/outline-tab/index.js new file mode 100644 index 00000000..09bb23c8 --- /dev/null +++ b/src/course-home/outline-tab/index.js @@ -0,0 +1 @@ +export { default } from './OutlineTab'; diff --git a/src/course-home/outline-tab/CourseDates.jsx b/src/course-home/outline-tab/widgets/CourseDates.jsx similarity index 88% rename from src/course-home/outline-tab/CourseDates.jsx rename to src/course-home/outline-tab/widgets/CourseDates.jsx index b7da2ab0..62904dd4 100644 --- a/src/course-home/outline-tab/CourseDates.jsx +++ b/src/course-home/outline-tab/widgets/CourseDates.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useModel } from '../../model-store'; -import DateSummary from './DateSummary'; +import { useModel } from '../../../model-store'; +import DateSummary from '../DateSummary'; export default function CourseDates({ courseId }) { const { diff --git a/src/course-home/outline-tab/CourseTools.jsx b/src/course-home/outline-tab/widgets/CourseTools.jsx similarity index 96% rename from src/course-home/outline-tab/CourseTools.jsx rename to src/course-home/outline-tab/widgets/CourseTools.jsx index d870f155..4c9c8806 100644 --- a/src/course-home/outline-tab/CourseTools.jsx +++ b/src/course-home/outline-tab/widgets/CourseTools.jsx @@ -5,7 +5,7 @@ import { faBookmark, faCertificate, faInfo, faCalendar, faStar, } from '@fortawesome/free-solid-svg-icons'; import { faNewspaper } from '@fortawesome/free-regular-svg-icons'; -import { useModel } from '../../model-store'; +import { useModel } from '../../../model-store'; export default function CourseTools( diff --git a/src/courseware/CoursewareContainer.jsx b/src/courseware/CoursewareContainer.jsx index 2eba7e29..c3850ded 100644 --- a/src/courseware/CoursewareContainer.jsx +++ b/src/courseware/CoursewareContainer.jsx @@ -214,6 +214,7 @@ export default function CoursewareContainer() { activeTabSlug="courseware" courseId={courseId} unitId={routeUnitId} + courseStatus={courseStatus} > ({ - slug: tab.tabId, - title: tab.title, - url: tab.url, - })), - }; -} - export async function getCourseMetadata(courseId) { const url = `${getConfig().LMS_BASE_URL}/api/courseware/course/${courseId}`; const { data } = await getAuthenticatedHttpClient().get(url); return normalizeMetadata(data); } -export async function getCourseHomeCourseMetadata(courseId) { - const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/course_metadata/${courseId}`; - const { data } = await getAuthenticatedHttpClient().get(url); - return normalizeCourseHomeCourseMetadata(data); -} - -export async function getDatesTabData(courseId, version) { - const url = `${getConfig().LMS_BASE_URL}/api/course_home/${version}/dates/${courseId}`; - try { - const { data } = await getAuthenticatedHttpClient().get(url); - return camelCaseObject(data); - } catch (error) { - const { httpErrorStatus } = error && error.customAttributes; - if (httpErrorStatus === 404) { - return window.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/dates`); - } - // async functions expect return values. to satisfy that requirement - // we return true here which in turn continues with the normal flow of displaying - // the "unexpected error try again" screen to the user. - return true; - } -} - -function normalizeBlocks(courseId, blocks) { +export function normalizeBlocks(courseId, blocks) { const models = { courses: {}, sections: {}, @@ -170,29 +135,6 @@ export async function getCourseBlocks(courseId) { return normalizeBlocks(courseId, data.blocks); } -export async function getOutlineTabData(courseId, version) { - const url = `${getConfig().LMS_BASE_URL}/api/course_home/${version}/outline/${courseId}`; - let { tabData } = {}; - try { - tabData = await getAuthenticatedHttpClient().get(url); - } catch (error) { - const { httpErrorStatus } = error && error.customAttributes; - if (httpErrorStatus === 404) { - return window.location.replace(`${getConfig().LMS_BASE_URL}/courses/${courseId}/course`); - } - } - - const { - data, - } = tabData; - const courseBlocks = normalizeBlocks(courseId, data.course_blocks.blocks); - const courseTools = camelCaseObject(data.course_tools); - const datesWidget = camelCaseObject(data.dates_widget); - return { - courseTools, courseBlocks, datesWidget, - }; -} - function normalizeSequenceMetadata(sequence) { return { sequence: { @@ -250,8 +192,3 @@ export function setFirstSectionCelebrationComplete(courseId) { first_section: false, }); } - -export async function updateCourseDeadlines(courseId) { - const url = new URL(`${getConfig().LMS_BASE_URL}/api/course_experience/v1/reset_course_deadlines`); - await getAuthenticatedHttpClient().post(url.href, { course_key: courseId }); -} diff --git a/src/data/index.js b/src/data/index.js index b3cb86f3..4b3afc08 100644 --- a/src/data/index.js +++ b/src/data/index.js @@ -1,9 +1,7 @@ export { fetchCourse, - fetchDatesTab, - fetchOutlineTab, fetchSequence, } from './thunks'; -export { getResumeBlock } from './api'; +export { getResumeBlock, normalizeBlocks } from './api'; export { reducer } from './slice'; diff --git a/src/data/slice.js b/src/data/slice.js index 0a40228d..088e9519 100644 --- a/src/data/slice.js +++ b/src/data/slice.js @@ -43,18 +43,6 @@ const slice = createSlice({ state.sequenceId = payload.sequenceId; state.sequenceStatus = FAILED; }, - fetchTabRequest: (state, { payload }) => { - state.courseId = payload.courseId; - state.courseStatus = LOADING; - }, - fetchTabSuccess: (state, { payload }) => { - state.courseId = payload.courseId; - state.courseStatus = LOADED; - }, - fetchTabFailure: (state, { payload }) => { - state.courseId = payload.courseId; - state.courseStatus = FAILED; - }, }, }); @@ -66,9 +54,6 @@ export const { fetchSequenceRequest, fetchSequenceSuccess, fetchSequenceFailure, - fetchTabRequest, - fetchTabSuccess, - fetchTabFailure, } = slice.actions; export const { diff --git a/src/data/thunks.js b/src/data/thunks.js index e10c3e2b..e5ed5e69 100644 --- a/src/data/thunks.js +++ b/src/data/thunks.js @@ -3,10 +3,6 @@ import { getCourseMetadata, getCourseBlocks, getSequenceMetadata, - getDatesTabData, - getOutlineTabData, - getCourseHomeCourseMetadata, - updateCourseDeadlines, } from './api'; import { addModelsMap, updateModel, updateModels, updateModelsMap, addModel, @@ -19,9 +15,6 @@ import { fetchSequenceRequest, fetchSequenceSuccess, fetchSequenceFailure, - fetchTabRequest, - fetchTabSuccess, - fetchTabFailure, } from './slice'; export function fetchCourse(courseId) { @@ -93,73 +86,6 @@ export function fetchCourse(courseId) { }; } -export function fetchTab(courseId, tab, version, getTabData) { - return async (dispatch) => { - dispatch(fetchTabRequest({ courseId })); - Promise.allSettled([ - getCourseMetadata(courseId), - getCourseHomeCourseMetadata(courseId), - getTabData(courseId, version), - ]).then(([courseMetadataResult, courseHomeCourseMetadataResult, tabDataResult]) => { - const fetchedMetadata = courseMetadataResult.status === 'fulfilled'; - const fetchedCourseHomeCourseMetadata = courseHomeCourseMetadataResult.status === 'fulfilled'; - const fetchedTabData = tabDataResult.status === 'fulfilled'; - - if (fetchedMetadata) { - /* - * NOTE: The "courses" models created by this thunk do not include an array of sectionIds. - * If that data is required for some use case, then fetchTab will need to call - * getCourseBlocks as well. See fetchCourse above. - */ - dispatch(addModel({ - modelType: 'courses', - model: courseMetadataResult.value, - })); - } else { - logError(courseMetadataResult.reason); - } - - if (fetchedCourseHomeCourseMetadata) { - dispatch(addModel({ - modelType: 'courseHomeMetadata', - model: { - id: courseId, - ...courseHomeCourseMetadataResult.value, - }, - })); - } else { - logError(courseHomeCourseMetadataResult.reason); - } - - if (fetchedTabData) { - dispatch(addModel({ - modelType: tab, - model: { - id: courseId, - ...tabDataResult.value, - }, - })); - } else { - logError(tabDataResult.reason); - } - - if (fetchedMetadata && fetchedCourseHomeCourseMetadata && fetchedTabData) { - dispatch(fetchTabSuccess({ courseId })); - } else { - dispatch(fetchTabFailure({ courseId })); - } - }); - }; -} - -export function fetchDatesTab(courseId) { - return fetchTab(courseId, 'dates', 'v1', getDatesTabData); -} - -export function fetchOutlineTab(courseId) { - return fetchTab(courseId, 'outline', 'v1', getOutlineTabData); -} - export function fetchSequence(sequenceId) { return async (dispatch) => { dispatch(fetchSequenceRequest({ sequenceId })); @@ -180,11 +106,3 @@ export function fetchSequence(sequenceId) { } }; } - -export function resetDeadlines(courseId, getTabData) { - return async (dispatch) => { - updateCourseDeadlines(courseId).then(() => { - dispatch(getTabData(courseId)); - }); - }; -} diff --git a/src/dates-tab/index.jsx b/src/dates-tab/index.jsx deleted file mode 100644 index 5f007a2f..00000000 --- a/src/dates-tab/index.jsx +++ /dev/null @@ -1 +0,0 @@ -export { default } from './DatesTab'; diff --git a/src/index.jsx b/src/index.jsx index 7d6d68f2..368116ed 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -18,14 +18,14 @@ import { UserMessagesProvider } from './user-messages'; import './index.scss'; import './assets/favicon.ico'; -import OutlineTab from './course-home'; +import OutlineTab from './course-home/outline-tab'; import CoursewareContainer from './courseware'; import CoursewareRedirect from './CoursewareRedirect'; -import DatesTab from './dates-tab'; +import DatesTab from './course-home/dates-tab'; import { TabContainer } from './tab-page'; import store from './store'; -import { fetchDatesTab, fetchOutlineTab } from './data'; +import { fetchDatesTab, fetchOutlineTab } from './course-home/data'; subscribe(APP_READY, () => { ReactDOM.render( @@ -33,7 +33,7 @@ subscribe(APP_READY, () => { - + diff --git a/src/index.scss b/src/index.scss index 128ee4b6..0fa2c27f 100755 --- a/src/index.scss +++ b/src/index.scss @@ -311,5 +311,5 @@ $primary: #1176B2; @import 'courseware/course/celebration/CelebrationModal.scss'; @import 'courseware/course/content-tools/calculator/calculator.scss'; @import 'courseware/course/content-tools/contentTools.scss'; -@import 'dates-tab/Badge.scss'; -@import 'dates-tab/Day.scss'; +@import 'course-home/dates-tab/Badge.scss'; +@import 'course-home/dates-tab/Day.scss'; diff --git a/src/store.js b/src/store.js index d95a1f28..846152e3 100644 --- a/src/store.js +++ b/src/store.js @@ -1,11 +1,13 @@ import { configureStore } from '@reduxjs/toolkit'; import { reducer as coursewareReducer } from './data'; import { reducer as modelsReducer } from './model-store'; +import { reducer as courseHomeReducer } from './course-home/data'; const store = configureStore({ reducer: { models: modelsReducer, courseware: coursewareReducer, + courseHome: courseHomeReducer, }, }); diff --git a/src/tab-page/TabContainer.jsx b/src/tab-page/TabContainer.jsx index dd8e784a..3094d816 100644 --- a/src/tab-page/TabContainer.jsx +++ b/src/tab-page/TabContainer.jsx @@ -23,12 +23,14 @@ export default function TabContainer(props) { // we don't want the application to adjust to it until it has actually loaded the new data. const { courseId, - } = useSelector(state => state.courseware); + courseStatus, + } = useSelector(state => state.courseHome); return ( {children} diff --git a/src/tab-page/TabPage.jsx b/src/tab-page/TabPage.jsx index 1989697c..afd025c7 100644 --- a/src/tab-page/TabPage.jsx +++ b/src/tab-page/TabPage.jsx @@ -1,6 +1,6 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { useSelector } from 'react-redux'; import { Header } from '../course-header'; import { useLogistrationAlert } from '../alerts/logistration-alert'; @@ -11,12 +11,11 @@ import LoadedTabPage from './LoadedTabPage'; function TabPage({ intl, + courseStatus, ...passthroughProps }) { useLogistrationAlert(); - const courseStatus = useSelector(state => state.courseware.courseStatus); - if (courseStatus === 'loading') { return ( <> @@ -47,6 +46,7 @@ function TabPage({ TabPage.propTypes = { intl: intlShape.isRequired, + courseStatus: PropTypes.string.isRequired, }; export default injectIntl(TabPage);