AA-120: Course Tools Widget (#73)
Co-authored-by: Carla Duarte <cduarte@edx.org>
This commit is contained in:
@@ -5,11 +5,12 @@ import { Button } from '@edx/paragon';
|
||||
import { AlertList } from '../user-messages';
|
||||
|
||||
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 Reacy.lazy() requires that we import() from a file with a Component as it's
|
||||
// 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('../enrollment-alert'));
|
||||
@@ -57,6 +58,9 @@ export default function CourseHome() {
|
||||
))}
|
||||
</div>
|
||||
<div className="col col-4">
|
||||
<CourseTools
|
||||
courseId={courseId}
|
||||
/>
|
||||
<CourseDates
|
||||
start={start}
|
||||
end={end}
|
||||
|
||||
58
src/course-home/CourseTools.jsx
Normal file
58
src/course-home/CourseTools.jsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
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';
|
||||
|
||||
|
||||
export default function CourseTools(
|
||||
{ courseId },
|
||||
) {
|
||||
const {
|
||||
courseTools,
|
||||
} = useModel('outline', courseId);
|
||||
|
||||
const renderIcon = (iconClasses) => {
|
||||
switch (iconClasses) {
|
||||
case 'edx.bookmarks':
|
||||
return faBookmark;
|
||||
case 'edx.tool.verified_upgrade':
|
||||
return faCertificate;
|
||||
case 'edx.tool.financial_assistance':
|
||||
return faInfo;
|
||||
case 'edx.calendar-sync':
|
||||
return faCalendar;
|
||||
case 'edx.updates':
|
||||
return faNewspaper;
|
||||
case 'edx.reviews':
|
||||
return faStar;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="mb-3">
|
||||
<h4>Course Tools</h4>
|
||||
{courseTools.map((courseTool) => (
|
||||
<div key={courseTool.analyticsId}>
|
||||
<a data-analytics-id={courseTool.analyticsId} href={courseTool.url}>
|
||||
<FontAwesomeIcon icon={renderIcon(courseTool.analyticsId)} className="mr-2" style={{ width: '20px' }} />
|
||||
{courseTool.title}
|
||||
</a>
|
||||
</div>
|
||||
))}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
CourseTools.propTypes = {
|
||||
courseId: PropTypes.string,
|
||||
};
|
||||
|
||||
CourseTools.defaultProps = {
|
||||
courseId: null,
|
||||
};
|
||||
@@ -63,6 +63,17 @@ 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: {},
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
getCourseBlocks,
|
||||
getSequenceMetadata,
|
||||
getTabData,
|
||||
getOutlineTabData,
|
||||
} from './api';
|
||||
import {
|
||||
addModelsMap, updateModel, updateModels, updateModelsMap, addModel,
|
||||
@@ -27,7 +28,8 @@ export function fetchCourse(courseId) {
|
||||
Promise.allSettled([
|
||||
getCourseMetadata(courseId),
|
||||
getCourseBlocks(courseId),
|
||||
]).then(([courseMetadataResult, courseBlocksResult]) => {
|
||||
getOutlineTabData(courseId),
|
||||
]).then(([courseMetadataResult, courseBlocksResult, outlineTabResult]) => {
|
||||
if (courseMetadataResult.status === 'fulfilled') {
|
||||
dispatch(addModel({
|
||||
modelType: 'courses',
|
||||
@@ -70,8 +72,16 @@ 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,15 +91,18 @@ export function fetchCourse(courseId) {
|
||||
if (!fetchedMetadata) {
|
||||
logError(courseMetadataResult.reason);
|
||||
}
|
||||
if (!fetchedOutline) {
|
||||
logError(outlineTabResult.reason);
|
||||
}
|
||||
|
||||
if (fetchedMetadata) {
|
||||
if (courseMetadataResult.value.canLoadCourseware.hasAccess && fetchedBlocks) {
|
||||
if (courseMetadataResult.value.canLoadCourseware.hasAccess && fetchedBlocks && fetchedOutline) {
|
||||
// User has access
|
||||
dispatch(fetchCourseSuccess({ courseId }));
|
||||
return;
|
||||
}
|
||||
// User either doesn't have access or only has partial access
|
||||
// (can't access course blocks)
|
||||
// (can't access course blocks or course outline)
|
||||
dispatch(fetchCourseDenied({ courseId }));
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user