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 <cduarte@edx.org>
This commit is contained in:
@@ -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,
|
||||
|
||||
65
src/course-home/data/api.js
Normal file
65
src/course-home/data/api.js
Normal file
@@ -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 });
|
||||
}
|
||||
7
src/course-home/data/index.js
Normal file
7
src/course-home/data/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
fetchDatesTab,
|
||||
fetchOutlineTab,
|
||||
resetDeadlines,
|
||||
} from './thunks';
|
||||
|
||||
export { reducer } from './slice';
|
||||
38
src/course-home/data/slice.js
Normal file
38
src/course-home/data/slice.js
Normal file
@@ -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;
|
||||
76
src/course-home/data/thunks.js
Normal file
76
src/course-home/data/thunks.js
Normal file
@@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -21,7 +21,7 @@ function DatesBanner(props) {
|
||||
{intl.formatMessage(messages[`datesBanner.${name}.body`])}
|
||||
</div>
|
||||
{bannerClickHandler && (
|
||||
<button type="button" className="btn rounded align-self-center border border-primary bg-white mr-3" onClick={bannerClickHandler}>
|
||||
<button type="button" className="btn rounded align-self-center border border-primary bg-white mr-3 font-weight-bold" onClick={bannerClickHandler}>
|
||||
{intl.formatMessage(messages[`datesBanner.${name}.button`])}
|
||||
</button>
|
||||
)}
|
||||
@@ -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;
|
||||
3
src/course-home/dates-banner/index.js
Normal file
3
src/course-home/dates-banner/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
import DatesBannerContainer from './DatesBannerContainer';
|
||||
|
||||
export default DatesBannerContainer;
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
@@ -8,10 +8,10 @@ import DatesBannerContainer from '../dates-banner/DatesBannerContainer';
|
||||
function DatesTab({ intl }) {
|
||||
return (
|
||||
<>
|
||||
<DatesBannerContainer model="dates" />
|
||||
<h2 className="mb-4">
|
||||
{intl.formatMessage(messages.title)}
|
||||
</h2>
|
||||
<DatesBannerContainer model="dates" />
|
||||
<Timeline />
|
||||
</>
|
||||
);
|
||||
@@ -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,
|
||||
@@ -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,
|
||||
3
src/course-home/dates-tab/index.jsx
Normal file
3
src/course-home/dates-tab/index.jsx
Normal file
@@ -0,0 +1,3 @@
|
||||
import DatesTab from './DatesTab';
|
||||
|
||||
export default DatesTab;
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './outline-tab/OutlineTab';
|
||||
@@ -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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
1
src/course-home/outline-tab/index.js
Normal file
1
src/course-home/outline-tab/index.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './OutlineTab';
|
||||
@@ -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 {
|
||||
@@ -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(
|
||||
@@ -214,6 +214,7 @@ export default function CoursewareContainer() {
|
||||
activeTabSlug="courseware"
|
||||
courseId={courseId}
|
||||
unitId={routeUnitId}
|
||||
courseStatus={courseStatus}
|
||||
>
|
||||
<Course
|
||||
courseId={courseId}
|
||||
|
||||
@@ -42,48 +42,13 @@ function normalizeMetadata(metadata) {
|
||||
};
|
||||
}
|
||||
|
||||
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 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 });
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
export {
|
||||
fetchCourse,
|
||||
fetchDatesTab,
|
||||
fetchOutlineTab,
|
||||
fetchSequence,
|
||||
} from './thunks';
|
||||
|
||||
export { getResumeBlock } from './api';
|
||||
export { getResumeBlock, normalizeBlocks } from './api';
|
||||
export { reducer } from './slice';
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './DatesTab';
|
||||
@@ -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, () => {
|
||||
<UserMessagesProvider>
|
||||
<Switch>
|
||||
<Route path="/redirect" component={CoursewareRedirect} />
|
||||
<Route path="/course/:courseId/outline">
|
||||
<Route path="/course/:courseId/home">
|
||||
<TabContainer tab="outline" fetch={fetchOutlineTab}>
|
||||
<OutlineTab />
|
||||
</TabContainer>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<TabPage
|
||||
activeTabSlug={tab}
|
||||
courseId={courseId}
|
||||
courseStatus={courseStatus}
|
||||
>
|
||||
{children}
|
||||
</TabPage>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user