-
- {intl.formatMessage(messages.studioLink)}
-
+ <>
+
+
+ {/* Main body */}
+
- )}
-
-
- {coursewareSummary.map((chapter) => (
-
- ))}
-
+
+ {/* Side panel */}
+
+
+
+
+
+ >
);
}
-ProgressTab.propTypes = {
- intl: intlShape.isRequired,
-};
-
-export default injectIntl(ProgressTab);
+export default ProgressTab;
diff --git a/src/course-home/progress-tab/Subsection.jsx b/src/course-home/progress-tab/Subsection.jsx
deleted file mode 100644
index d65f507d..00000000
--- a/src/course-home/progress-tab/Subsection.jsx
+++ /dev/null
@@ -1,77 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-
-import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
-import messages from './messages';
-
-import DueDateTime from './DueDateTime';
-import ProblemScores from './ProblemScores';
-
-function Subsection({
- intl,
- subsection,
-}) {
- const scoreName = subsection.graded ? 'problem' : 'practice';
-
- const { earned, possible } = subsection.gradedTotal;
-
- const showTotalScore = ((possible > 0) || (earned > 0)) && subsection.showGrades;
-
- // screen reader information
- const totalScoreSr = intl.formatMessage(messages.pointsEarned, { earned, total: possible });
-
- return (
-
-
-
- {subsection.format &&
{subsection.format}
}
- {subsection.due !== null &&
}
-
- {subsection.problemScores.length > 0 && subsection.showGrades && (
-
- )}
- {subsection.problemScores.length > 0 && !subsection.showGrades && subsection.showCorrectness === 'past_due' && (
- {intl.formatMessage(messages[`${scoreName}HiddenUntil`])}
- )}
- {subsection.problemScores.length > 0 && !subsection.showGrades && !(subsection.showCorrectness === 'past_due')
- && {intl.formatMessage(messages[`${scoreName}Hidden`])}
}
- {(subsection.problemScores.length === 0) && (
- {intl.formatMessage(messages.noScores)}
- )}
-
- );
-}
-
-Subsection.propTypes = {
- intl: intlShape.isRequired,
- subsection: PropTypes.shape({
- graded: PropTypes.bool.isRequired,
- url: PropTypes.string.isRequired,
- showGrades: PropTypes.bool.isRequired,
- gradedTotal: PropTypes.shape({
- possible: PropTypes.number,
- earned: PropTypes.number,
- graded: PropTypes.bool,
- }).isRequired,
- showCorrectness: PropTypes.string.isRequired,
- due: PropTypes.string,
- problemScores: PropTypes.arrayOf(PropTypes.shape({
- possible: PropTypes.number,
- earned: PropTypes.number,
- id: PropTypes.string,
- })).isRequired,
- format: PropTypes.string,
- // override: PropTypes.object,
- displayName: PropTypes.string.isRequired,
- percentGraded: PropTypes.number.isRequired,
- }).isRequired,
-};
-
-export default injectIntl(Subsection);
diff --git a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx
new file mode 100644
index 00000000..e3c5af16
--- /dev/null
+++ b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+
+function CertificateStatus() {
+ return (
+
+ {/* TODO: AA-719 */}
+ Certificate status
+
+ );
+}
+
+export default CertificateStatus;
diff --git a/src/course-home/progress-tab/course-completion/CourseCompletion.jsx b/src/course-home/progress-tab/course-completion/CourseCompletion.jsx
new file mode 100644
index 00000000..2e19a78b
--- /dev/null
+++ b/src/course-home/progress-tab/course-completion/CourseCompletion.jsx
@@ -0,0 +1,35 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { useModel } from '../../../generic/model-store';
+
+function CourseCompletion() {
+ // TODO: AA-720
+ const {
+ courseId,
+ } = useSelector(state => state.courseHome);
+
+ const {
+ completionSummary: {
+ completeCount,
+ incompleteCount,
+ lockedCount,
+ },
+ } = useModel('progress', courseId);
+
+ const total = completeCount + incompleteCount + lockedCount;
+ const completePercentage = ((completeCount / total) * 100).toFixed(0);
+ const incompletePercentage = ((incompleteCount / total) * 100).toFixed(0);
+ const lockedPercentage = ((lockedCount / total) * 100).toFixed(0);
+
+ return (
+
+ Course completion
+ This represents how much course content you have completed.
+ Complete: {completePercentage}%
+ Incomplete: {incompletePercentage}%
+ Locked: {lockedPercentage}%
+
+ );
+}
+
+export default CourseCompletion;
diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx
new file mode 100644
index 00000000..25798c70
--- /dev/null
+++ b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+
+function CourseGrade() {
+ return (
+
+ {/* TODO: AA-721 */}
+ Grades
+ This represents your weighted grade against the grade needed to pass this course.
+
+ );
+}
+
+export default CourseGrade;
diff --git a/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx b/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx
new file mode 100644
index 00000000..8ed819bc
--- /dev/null
+++ b/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx
@@ -0,0 +1,57 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { Link } from 'react-router-dom';
+
+import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { useModel } from '../../../../generic/model-store';
+
+import DetailedGradesTable from './DetailedGradesTable';
+
+import messages from '../messages';
+
+function DetailedGrades({ intl }) {
+ const {
+ courseId,
+ } = useSelector(state => state.courseHome);
+
+ const {
+ sectionScores,
+ } = useModel('progress', courseId);
+
+ const hasSectionScores = sectionScores.length > 0;
+
+ const outlineLink = (
+
+ {intl.formatMessage(messages.courseOutline)}
+
+ );
+
+ return (
+
+ {intl.formatMessage(messages.detailedGrades)}
+ {hasSectionScores && (
+
+ )}
+ {!hasSectionScores && (
+ You currently have no graded problem scores.
+ )}
+
+
+
+
+ );
+}
+
+DetailedGrades.propTypes = {
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(DetailedGrades);
diff --git a/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx b/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx
new file mode 100644
index 00000000..eb747497
--- /dev/null
+++ b/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx
@@ -0,0 +1,78 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { DataTable } from '@edx/paragon';
+
+import messages from '../messages';
+
+function DetailedGradesTable({ intl, sectionScores }) {
+ return (
+ sectionScores.map((chapter) => {
+ const subsectionScores = chapter.subsections.filter(
+ (subsection) => !!(
+ subsection.hasGradedAssignment
+ && subsection.showGrades
+ && (subsection.numPointsPossible > 0 || subsection.numPointsEarned > 0)),
+ );
+
+ if (subsectionScores.length === 0) {
+ return null;
+ }
+
+ const detailedGradesData = subsectionScores.map((subsection) => {
+ const title =
{subsection.displayName};
+ return {
+ subsectionTitle: title,
+ score: `${subsection.numPointsEarned}/${subsection.numPointsPossible}`,
+ };
+ });
+
+ return (
+
+
+
+
+
+ );
+ })
+ );
+}
+
+DetailedGradesTable.propTypes = {
+ intl: intlShape.isRequired,
+ sectionScores: PropTypes.arrayOf(PropTypes.shape({
+ displayName: PropTypes.string.isRequired,
+ subsections: PropTypes.arrayOf(PropTypes.shape({
+ displayName: PropTypes.string.isRequired,
+ numPointsEarned: PropTypes.number.isRequired,
+ numPointsPossible: PropTypes.number.isRequired,
+ url: PropTypes.string.isRequired,
+ })),
+ })).isRequired,
+};
+
+DetailedGradesTable.defaultProps = {
+ sectionScores: {
+ subsections: [],
+ },
+};
+
+export default injectIntl(DetailedGradesTable);
diff --git a/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx b/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx
new file mode 100644
index 00000000..585bf0e5
--- /dev/null
+++ b/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx
@@ -0,0 +1,30 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+function AssignmentTypeCell({ assignmentType, footnoteMarker, footnoteId }) {
+ return (
+
+ {assignmentType}
+ {footnoteId && footnoteMarker && (
+
+
+
+ )}
+
+ );
+}
+
+AssignmentTypeCell.propTypes = {
+ assignmentType: PropTypes.string.isRequired,
+ footnoteId: PropTypes.string,
+ footnoteMarker: PropTypes.string,
+};
+
+AssignmentTypeCell.defaultProps = {
+ footnoteId: '',
+ footnoteMarker: '',
+};
+
+export default AssignmentTypeCell;
diff --git a/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx b/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx
new file mode 100644
index 00000000..7a608ac0
--- /dev/null
+++ b/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx
@@ -0,0 +1,41 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+
+import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+
+import messages from '../messages';
+
+function DroppableAssignmentFootnote({ footnotes, intl }) {
+ return (
+ <>
+
+
+ {footnotes.map((footnote, index) => (
+
+ ))}
+
+ >
+ );
+}
+
+DroppableAssignmentFootnote.propTypes = {
+ footnotes: PropTypes.arrayOf(PropTypes.shape({
+ assignmentType: PropTypes.string.isRequired,
+ id: PropTypes.string.isRequired,
+ numDroppable: PropTypes.number.isRequired,
+ })).isRequired,
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(DroppableAssignmentFootnote);
diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx
new file mode 100644
index 00000000..0ca88f60
--- /dev/null
+++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx
@@ -0,0 +1,49 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { useModel } from '../../../../generic/model-store';
+
+import GradeSummaryHeader from './GradeSummaryHeader';
+import GradeSummaryTable from './GradeSummaryTable';
+
+function GradeSummary() {
+ const {
+ courseId,
+ } = useSelector(state => state.courseHome);
+
+ const {
+ sectionScores,
+ gradingPolicy: {
+ assignmentPolicies,
+ },
+ } = useModel('progress', courseId);
+
+ if (assignmentPolicies.length === 0) {
+ return null;
+ }
+
+ // accumulate grades for individual assignment types
+ const gradeByAssignmentType = {};
+ assignmentPolicies.forEach(assignment => {
+ gradeByAssignmentType[assignment.type] = { numPointsEarned: 0, numPointsPossible: 0 };
+ });
+
+ sectionScores.forEach((chapter) => {
+ chapter.subsections.forEach((subsection) => {
+ if (subsection.hasGradedAssignment) {
+ gradeByAssignmentType[subsection.assignmentType].numPointsEarned += subsection.numPointsEarned;
+ gradeByAssignmentType[subsection.assignmentType].numPointsPossible += subsection.numPointsPossible;
+ }
+ });
+ });
+
+ return (
+
+ );
+}
+
+export default GradeSummary;
diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx
new file mode 100644
index 00000000..dea5108e
--- /dev/null
+++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { Icon, OverlayTrigger, Popover } from '@edx/paragon';
+import { InfoOutline } from '@edx/paragon/icons';
+
+import messages from '../messages';
+
+function GradeSummaryHeader({ intl }) {
+ return (
+
+
{intl.formatMessage(messages.gradeSummary)}
+
+
+ {intl.formatMessage(messages.gradeSummaryTooltip)}
+
+
+ )}
+ >
+
+
+
+ );
+}
+
+GradeSummaryHeader.propTypes = {
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(GradeSummaryHeader);
diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx
new file mode 100644
index 00000000..4d407ffa
--- /dev/null
+++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx
@@ -0,0 +1,123 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import PropTypes from 'prop-types';
+
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { DataTable } from '@edx/paragon';
+import { useModel } from '../../../../generic/model-store';
+
+import AssignmentTypeCell from './AssignmentTypeCell';
+import DroppableAssignmentFootnote from './DroppableAssignmentFootnote';
+import GradeSummaryTableFooter from './GradeSummaryTableFooter';
+
+import messages from '../messages';
+
+function GradeSummaryTable({
+ gradeByAssignmentType, intl,
+}) {
+ const {
+ courseId,
+ } = useSelector(state => state.courseHome);
+
+ const {
+ gradingPolicy: {
+ assignmentPolicies,
+ },
+ } = useModel('progress', courseId);
+
+ const footnotes = [];
+
+ const calculateWeightedGrade = (numPointsEarned, numPointsPossible, assignmentWeight) => (
+ numPointsPossible > 0 ? ((numPointsEarned * assignmentWeight * 100) / numPointsPossible).toFixed(0) : 0
+ );
+
+ const getFootnoteId = (assignment) => {
+ const footnoteId = assignment.shortLabel ? assignment.shortLabel : assignment.type;
+ return footnoteId.replace(/[^A-Za-z0-9.-_]+/g, '-');
+ };
+
+ const gradeSummaryData = assignmentPolicies.map((assignment) => {
+ let footnoteId = '';
+ let footnoteMarker = '';
+
+ if (assignment.numDroppable > 0) {
+ footnoteId = getFootnoteId(assignment);
+ footnotes.push({
+ id: footnoteId,
+ numDroppable: assignment.numDroppable,
+ assignmentType: assignment.type,
+ });
+
+ footnoteMarker = footnotes.length;
+ }
+
+ const weightedGrade = calculateWeightedGrade(
+ gradeByAssignmentType[assignment.type].numPointsEarned,
+ gradeByAssignmentType[assignment.type].numPointsPossible,
+ assignment.weight,
+ );
+
+ return {
+ type: { footnoteId, footnoteMarker, type: assignment.type },
+ weight: `${assignment.weight * 100}%`,
+ score: `${gradeByAssignmentType[assignment.type].numPointsEarned}/${gradeByAssignmentType[assignment.type].numPointsPossible}`,
+ weightedGrade: `${weightedGrade}%`,
+ };
+ });
+
+ return (
+ <>
+
(
+
+ ),
+ headerClassName: 'h5 mb-0',
+ },
+ {
+ Header: `${intl.formatMessage(messages.weight)}`,
+ accessor: 'weight',
+ headerClassName: 'justify-content-end h5 mb-0',
+ cellClassName: 'float-right small',
+ },
+ {
+ Header: `${intl.formatMessage(messages.score)}`,
+ accessor: 'score',
+ headerClassName: 'justify-content-end h5 mb-0',
+ cellClassName: 'float-right small',
+ },
+ {
+ Header: `${intl.formatMessage(messages.weightedGrade)}`,
+ accessor: 'weightedGrade',
+ headerClassName: 'justify-content-end h5 mb-0 text-right',
+ cellClassName: 'float-right font-weight-bold small',
+ },
+ ]}
+ >
+
+
+
+
+ {footnotes && (
+
+ )}
+ >
+ );
+}
+
+GradeSummaryTable.propTypes = {
+ gradeByAssignmentType: PropTypes.shape({}).isRequired,
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(GradeSummaryTable);
diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx
new file mode 100644
index 00000000..a8cf041f
--- /dev/null
+++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { DataTable } from '@edx/paragon';
+import { useModel } from '../../../../generic/model-store';
+
+import messages from '../messages';
+
+function GradeSummaryTableFooter({ intl }) {
+ const {
+ courseId,
+ } = useSelector(state => state.courseHome);
+
+ const {
+ courseGrade: {
+ isPassing,
+ percent,
+ },
+ } = useModel('progress', courseId);
+
+ const bgColor = isPassing ? 'bg-success-100' : 'bg-warning-100';
+ const totalGrade = percent * 100;
+
+ return (
+
+
+
{intl.formatMessage(messages.weightedGradeSummary)}
+
{totalGrade}%
+
+
+ );
+}
+
+GradeSummaryTableFooter.propTypes = {
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(GradeSummaryTableFooter);
diff --git a/src/course-home/progress-tab/grades/messages.js b/src/course-home/progress-tab/grades/messages.js
new file mode 100644
index 00000000..9881ccaf
--- /dev/null
+++ b/src/course-home/progress-tab/grades/messages.js
@@ -0,0 +1,52 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ assignmentType: {
+ id: 'progress.assignmentType',
+ defaultMessage: 'Assignment type',
+ },
+ backToContent: {
+ id: 'progress.footnotes.backToContent',
+ defaultMessage: 'Back to content',
+ },
+ courseOutline: {
+ id: 'progress.courseOutline',
+ defaultMessage: 'Course Outline',
+ },
+ detailedGrades: {
+ id: 'progress.detailedGrades',
+ defaultMessage: 'Detailed grades',
+ },
+ footnotesTitle: {
+ id: 'progress.footnotes.title',
+ defaultMessage: 'Grade summary footnotes',
+ },
+ gradeSummary: {
+ id: 'progress.gradeSummary',
+ defaultMessage: 'Grade summary',
+ },
+ gradeSummaryTooltip: {
+ id: 'progress.gradeSummary.tooltip',
+ defaultMessage: "Your course assignment's weight is determined by your instructor. "
+ + 'By multiplying your score by the weight for that assignment type, your weighted grade is calculated. '
+ + "Your weighted grade is what's used to determine if you pass the course.",
+ },
+ score: {
+ id: 'progress.score',
+ defaultMessage: 'Score',
+ },
+ weight: {
+ id: 'progress.weight',
+ defaultMessage: 'Weight',
+ },
+ weightedGrade: {
+ id: 'progress.weightedGrade',
+ defaultMessage: 'Weighted grade',
+ },
+ weightedGradeSummary: {
+ id: 'progress.weightedGradeSummary',
+ defaultMessage: 'Your current weighted grade summary',
+ },
+});
+
+export default messages;
diff --git a/src/course-home/progress-tab/messages.js b/src/course-home/progress-tab/messages.js
index 87bae7fb..3cd3e38c 100644
--- a/src/course-home/progress-tab/messages.js
+++ b/src/course-home/progress-tab/messages.js
@@ -1,123 +1,14 @@
import { defineMessages } from '@edx/frontend-platform/i18n';
const messages = defineMessages({
- problem: {
- id: 'learning.progress.badge.problem',
- defaultMessage: 'Problem Scores: ',
- },
- practice: {
- id: 'learning.progress.badge.practice',
- defaultMessage: 'Practice Scores: ',
- },
- problemHiddenUntil: {
- id: 'learning.progress.badge.problemHiddenUntil',
- defaultMessage: 'Problem scores are hidden until the due date.',
- },
- practiceHiddenUntil: {
- id: 'learning.progress.badge.practiceHiddenUntil',
- defaultMessage: 'Practice scores are hidden until the due date.',
- },
- problemHidden: {
- id: 'learning.progress.badge.probHidden',
- defaultMessage: 'problemlem scores are hidden.',
- },
- practiceHidden: {
- id: 'learning.progress.badge.practiceHidden',
- defaultMessage: 'Practice scores are hidden.',
- },
- noScores: {
- id: 'learning.progress.badge.noScores',
- defaultMessage: 'No problem scores in this section.',
- },
- pointsEarned: {
- id: 'learning.progress.badge.scoreEarned',
- defaultMessage: '{earned} of {total} possible points',
- },
- viewCert: {
- id: 'learning.progress.badge.viewCert',
- defaultMessage: 'View Certificate',
- },
- downloadCert: {
- id: 'learning.progress.badge.downloadCert',
- defaultMessage: 'Download Your Certificate',
- },
- requestCert: {
- id: 'learning.progress.badge.requestCert',
- defaultMessage: 'Request Certificate',
- },
- opensNewWindow: {
- id: 'learning.progress.badge.opensNewWindow',
- defaultMessage: 'Opens in a new browser window',
- },
- certAlt: {
- id: 'learning.progress.badge.certAlt',
- defaultMessage: 'Example Certificate',
- description: 'Alternate text displayed when the example certificate image cannot be displayed.',
+ progressHeader: {
+ id: 'progress.header',
+ defaultMessage: 'Your progress',
},
studioLink: {
- id: 'learning.progress.badge.studioLink',
+ id: 'progress.link.studio',
defaultMessage: 'View grading in Studio',
},
- courseCreditHeader: {
- id: 'learning.progress.courseCreditHeader',
- defaultMessage: 'Course Credit Eligibility',
- },
- creditNotEligible: {
- id: 'learning.progress.creditNotEligible',
- defaultMessage: 'You are not eligible for course credit because you have not met the requirements for credit.',
- },
- creditEligible: {
- id: 'learning.progress.creditEligible',
- defaultMessage: 'You have met the requirements for credit in this course.',
- },
- creditPartialEligible: {
- id: 'learning.progress.creditPartialEligible',
- defaultMessage: 'You have not met the minimum requirements for credit.',
- },
- start: {
- id: 'learning.progress.startVerification',
- defaultMessage: 'Start now',
- },
- tryAgain: {
- id: 'learning.progress.start',
- defaultMessage: 'Try again',
- },
- notStarted: {
- id: 'learning.progress.notStarted',
- defaultMessage: 'Not started',
- },
- failed: {
- id: 'learning.progress.failed',
- defaultMessage: 'Incomplete',
- },
- notMet: {
- id: 'learning.progress.notMet',
- defaultMessage: 'Not met',
- },
- pending: {
- id: 'learning.progress.pending',
- defaultMessage: 'Pending',
- },
- rejected: {
- id: 'learning.progress.rejected',
- defaultMessage: 'Rejected',
- },
- completed: {
- id: 'learning.progress.completed',
- defaultMessage: 'Completed',
- },
- submitted: {
- id: 'learning.progress.submitted',
- defaultMessage: 'Submitted',
- },
- learnMoreCredit: {
- id: 'learning.progress.learnMoreCredit',
- defaultMessage: 'Learn more about course credit',
- },
- purchaseCredit: {
- id: 'learning.progress.purchaseCredit',
- defaultMessage: 'Purchase course credit',
- },
});
export default messages;
diff --git a/src/course-home/progress-tab/related-links/RelatedLinks.jsx b/src/course-home/progress-tab/related-links/RelatedLinks.jsx
new file mode 100644
index 00000000..8f1a07a2
--- /dev/null
+++ b/src/course-home/progress-tab/related-links/RelatedLinks.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import { Link } from 'react-router-dom';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+
+import messages from './messages';
+
+function RelatedLinks({ intl }) {
+ const {
+ courseId,
+ } = useSelector(state => state.courseHome);
+
+ return (
+
+ {intl.formatMessage(messages.relatedLinks)}
+
+
+ );
+}
+
+RelatedLinks.propTypes = {
+ intl: intlShape.isRequired,
+};
+
+export default injectIntl(RelatedLinks);
diff --git a/src/course-home/progress-tab/related-links/messages.js b/src/course-home/progress-tab/related-links/messages.js
new file mode 100644
index 00000000..b696f887
--- /dev/null
+++ b/src/course-home/progress-tab/related-links/messages.js
@@ -0,0 +1,26 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ datesCardDescription: {
+ id: 'progress.relatedLinks.datesCard.description',
+ defaultMessage: 'A schedule view of your course due dates and upcoming assignments.',
+ },
+ datesCardLink: {
+ id: 'progress.relatedLinks.datesCard.link',
+ defaultMessage: 'Dates',
+ },
+ outlineCardDescription: {
+ id: 'progress.relatedLinks.outlineCard.description',
+ defaultMessage: 'A birds-eye view of your course content.',
+ },
+ outlineCardLink: {
+ id: 'progress.relatedLinks.outlineCard.link',
+ defaultMessage: 'Course Outline',
+ },
+ relatedLinks: {
+ id: 'progress.relatedLinks',
+ defaultMessage: 'Related links',
+ },
+});
+
+export default messages;