diff --git a/src/course-home/Section.jsx b/src/course-home/Section.jsx index 43d7f3fe..b8626239 100644 --- a/src/course-home/Section.jsx +++ b/src/course-home/Section.jsx @@ -6,9 +6,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import SequenceLink from './SequenceLink'; import { useModel } from '../model-store'; -export default function Section({ id, courseId }) { - const section = useModel('sections', id); - const { title, sequenceIds } = section; +export default function Section({ courseId, title, sequenceIds }) { + const { + courseBlocks: { + sequences, + }, + } = useModel('outline', courseId); + return ( @@ -31,6 +35,7 @@ export default function Section({ id, courseId }) { key={sequenceId} id={sequenceId} courseId={courseId} + title={sequences[sequenceId].title} /> ))} @@ -39,6 +44,7 @@ export default function Section({ id, courseId }) { } Section.propTypes = { - id: PropTypes.string.isRequired, courseId: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + sequenceIds: PropTypes.arrayOf(PropTypes.string).isRequired, }; diff --git a/src/course-home/SequenceLink.jsx b/src/course-home/SequenceLink.jsx index b8a5fdb8..73e72da6 100644 --- a/src/course-home/SequenceLink.jsx +++ b/src/course-home/SequenceLink.jsx @@ -1,13 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Link } from 'react-router-dom'; -import { useModel } from '../model-store'; -export default function SequenceLink({ id, courseId }) { - const sequence = useModel('sequences', id); +export default function SequenceLink({ id, courseId, title }) { return (
- {sequence.title} + {title}
); } @@ -15,4 +13,5 @@ export default function SequenceLink({ id, courseId }) { SequenceLink.propTypes = { id: PropTypes.string.isRequired, courseId: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, }; diff --git a/src/course-home/index.js b/src/course-home/index.js index a5b1f5af..7893520d 100644 --- a/src/course-home/index.js +++ b/src/course-home/index.js @@ -1 +1 @@ -export { default } from './CourseHome'; +export { default } from './outline-tab/OutlineTab'; diff --git a/src/course-home/CourseHome.jsx b/src/course-home/outline-tab/OutlineTab.jsx similarity index 72% rename from src/course-home/CourseHome.jsx rename to src/course-home/outline-tab/OutlineTab.jsx index ce981c13..2be2a3d6 100644 --- a/src/course-home/CourseHome.jsx +++ b/src/course-home/outline-tab/OutlineTab.jsx @@ -2,21 +2,21 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { Button } from '@edx/paragon'; -import { AlertList } from '../user-messages'; +import { AlertList } from '../../user-messages'; -import CourseDates from './CourseDates'; -import CourseTools from './CourseTools'; -import Section from './Section'; -import { useModel } from '../model-store'; +import CourseDates from '../CourseDates'; +import CourseTools from '../CourseTools'; +import Section from '../Section'; +import { useModel } from '../../model-store'; // Note that we import from the component files themselves in the enrollment-alert package. // This is because React.lazy() requires that we import() from a file with a Component as its // default export. // See React.lazy docs here: https://reactjs.org/docs/code-splitting.html#reactlazy -const { EnrollmentAlert, StaffEnrollmentAlert } = React.lazy(() => import('../alerts/enrollment-alert')); -const LogistrationAlert = React.lazy(() => import('../alerts/logistration-alert')); +const { EnrollmentAlert, StaffEnrollmentAlert } = React.lazy(() => import('../../alerts/enrollment-alert')); +const LogistrationAlert = React.lazy(() => import('../../alerts/logistration-alert')); -export default function CourseHome() { +export default function OutlineTab() { const { courseId, } = useSelector(state => state.courseware); @@ -29,9 +29,18 @@ export default function CourseHome() { enrollmentEnd, enrollmentMode, isEnrolled, - sectionIds, } = useModel('courses', courseId); + const { + courseBlocks: { + courses, + sections, + }, + } = useModel('outline', courseId); + + const rootCourseId = Object.keys(courses)[0]; + const { sectionIds } = courses[rootCourseId]; + return ( <> (
))} diff --git a/src/data/api.js b/src/data/api.js index 9653cafa..8fb6cf5b 100644 --- a/src/data/api.js +++ b/src/data/api.js @@ -46,8 +46,8 @@ export async function getCourseMetadata(courseId) { return normalizeMetadata(data); } -export async function getTabData(courseId, tab, version) { - const url = `${getConfig().LMS_BASE_URL}/api/course_home/${version}/${tab}/${courseId}`; +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); @@ -63,17 +63,6 @@ export async function getTabData(courseId, tab, version) { } } -function normalizeOutlineTabData(courseId, courseToolData) { - const courseTools = camelCaseObject(courseToolData); - return { id: courseId, courseTools }; -} - -export async function getOutlineTabData(courseId) { - const url = `${getConfig().LMS_BASE_URL}/api/course_home/v1/outline/${courseId}`; - const { data } = await getAuthenticatedHttpClient().get(url, {}); - return normalizeOutlineTabData(courseId, data.course_tools); -} - function normalizeBlocks(courseId, blocks) { const models = { courses: {}, @@ -161,6 +150,26 @@ 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}/home`); + } + } + + const { + data, + } = tabData; + const courseBlocks = normalizeBlocks(courseId, data.course_blocks.blocks); + const courseTools = camelCaseObject(data.course_tools); + + return { courseTools, courseBlocks }; +} function normalizeSequenceMetadata(sequence) { return { diff --git a/src/data/index.js b/src/data/index.js index 9317a00b..b3cb86f3 100644 --- a/src/data/index.js +++ b/src/data/index.js @@ -1,6 +1,7 @@ export { fetchCourse, fetchDatesTab, + fetchOutlineTab, fetchSequence, } from './thunks'; diff --git a/src/data/thunks.js b/src/data/thunks.js index c2aa3e2c..51396474 100644 --- a/src/data/thunks.js +++ b/src/data/thunks.js @@ -3,7 +3,7 @@ import { getCourseMetadata, getCourseBlocks, getSequenceMetadata, - getTabData, + getDatesTabData, getOutlineTabData, } from './api'; import { @@ -28,8 +28,7 @@ export function fetchCourse(courseId) { Promise.allSettled([ getCourseMetadata(courseId), getCourseBlocks(courseId), - getOutlineTabData(courseId), - ]).then(([courseMetadataResult, courseBlocksResult, outlineTabResult]) => { + ]).then(([courseMetadataResult, courseBlocksResult]) => { if (courseMetadataResult.status === 'fulfilled') { dispatch(addModel({ modelType: 'courses', @@ -62,16 +61,8 @@ export function fetchCourse(courseId) { })); } - if (outlineTabResult.status === 'fulfilled') { - dispatch(addModel({ - modelType: 'outline', - model: outlineTabResult.value, - })); - } - const fetchedMetadata = courseMetadataResult.status === 'fulfilled'; const fetchedBlocks = courseBlocksResult.status === 'fulfilled'; - const fetchedOutline = outlineTabResult.status === 'fulfilled'; // Log errors for each request if needed. Course block failures may occur // even if the course metadata request is successful @@ -81,18 +72,15 @@ export function fetchCourse(courseId) { if (!fetchedMetadata) { logError(courseMetadataResult.reason); } - if (!fetchedOutline) { - logError(outlineTabResult.reason); - } if (fetchedMetadata) { - if (courseMetadataResult.value.canLoadCourseware.hasAccess && fetchedBlocks && fetchedOutline) { + if (courseMetadataResult.value.canLoadCourseware.hasAccess && fetchedBlocks) { // User has access dispatch(fetchCourseSuccess({ courseId })); return; } // User either doesn't have access or only has partial access - // (can't access course blocks or course outline) + // (can't access course blocks) dispatch(fetchCourseDenied({ courseId })); return; } @@ -103,12 +91,12 @@ export function fetchCourse(courseId) { }; } -export function fetchTab(courseId, tab, version) { +export function fetchTab(courseId, tab, version, getTabData) { return async (dispatch) => { dispatch(fetchTabRequest({ courseId })); Promise.allSettled([ getCourseMetadata(courseId), - getTabData(courseId, tab, version), + getTabData(courseId, version), ]).then(([courseMetadataResult, tabDataResult]) => { const fetchedMetadata = courseMetadataResult.status === 'fulfilled'; const fetchedTabData = tabDataResult.status === 'fulfilled'; @@ -149,7 +137,11 @@ export function fetchTab(courseId, tab, version) { } export function fetchDatesTab(courseId) { - return fetchTab(courseId, 'dates', 'v1'); + return fetchTab(courseId, 'dates', 'v1', getDatesTabData); +} + +export function fetchOutlineTab(courseId) { + return fetchTab(courseId, 'outline', 'v1', getOutlineTabData); } export function fetchSequence(sequenceId) { diff --git a/src/dates-tab/fakeData.js b/src/dates-tab/fakeData.js index d8854c32..305d351e 100644 --- a/src/dates-tab/fakeData.js +++ b/src/dates-tab/fakeData.js @@ -1,10 +1,10 @@ // Sample data helpful when developing, to see a variety of configurations. // This set of data is not realistic (mix of having access and not), but it // is intended to demonstrate many UI results. -// To use, have getTabData in api.js return the result of this call instead: +// To use, have getDatesTabData in api.js return the result of this call instead: /* import fakeDatesData from '../dates-tab/fakeData'; -export async function getTabData(courseId, tab, version) { +export async function getDatesTabData(courseId, version) { if (tab === 'dates') { return camelCaseObject(fakeDatesData()); } ... } diff --git a/src/index.jsx b/src/index.jsx index 3e738741..1eb6233d 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 CourseHome from './course-home'; +import OutlineTab from './course-home'; import CoursewareContainer from './courseware'; import CoursewareRedirect from './CoursewareRedirect'; import DatesTab from './dates-tab'; import { TabContainer } from './tab-page'; import store from './store'; -import { fetchCourse, fetchDatesTab } from './data'; +import { fetchDatesTab, fetchOutlineTab } from './data'; subscribe(APP_READY, () => { ReactDOM.render( @@ -33,9 +33,9 @@ subscribe(APP_READY, () => { - - - + + +