Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add plugin slots for progress page components
Adds a slot for different components in the progress tab to allow them to be overridden with custom components. # Conflicts: # src/course-home/progress-tab/certificate-status/CertificateStatus.jsx diff --git a/src/course-home/progress-tab/ProgressHeader.jsx b/src/course-home/progress-tab/ProgressHeader.jsx index 4648fd20..1d0fd56f 100644 --- a/src/course-home/progress-tab/ProgressHeader.jsx +++ b/src/course-home/progress-tab/ProgressHeader.jsx @@ -1,9 +1,9 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Button } from '@openedx/paragon'; +import { useSelector } from 'react-redux'; import { useModel } from '../../generic/model-store'; diff --git a/src/course-home/progress-tab/ProgressTab.jsx b/src/course-home/progress-tab/ProgressTab.jsx index 1b829037..a0d86a28 100644 --- a/src/course-home/progress-tab/ProgressTab.jsx +++ b/src/course-home/progress-tab/ProgressTab.jsx @@ -1,27 +1,20 @@ import React from 'react'; -import { useSelector } from 'react-redux'; -import { breakpoints, useWindowSize } from '@openedx/paragon'; +import { useWindowSize } from '@openedx/paragon'; +import { useContextId } from '../../data/hooks'; +import ProgressTabCertificateStatusSidePanelSlot from '../../plugin-slots/ProgressTabCertificateStatusSidePanelSlot'; -import CertificateStatus from './certificate-status/CertificateStatus'; import CourseCompletion from './course-completion/CourseCompletion'; -import CourseGrade from './grades/course-grade/CourseGrade'; -import DetailedGrades from './grades/detailed-grades/DetailedGrades'; -import GradeSummary from './grades/grade-summary/GradeSummary'; import ProgressHeader from './ProgressHeader'; -import RelatedLinks from './related-links/RelatedLinks'; +import ProgressTabCertificateStatusMainBodySlot from '../../plugin-slots/ProgressTabCertificateStatusMainBodySlot'; +import ProgressTabCourseGradeSlot from '../../plugin-slots/ProgressTabCourseGradeSlot'; +import ProgressTabGradeBreakdownSlot from '../../plugin-slots/ProgressTabGradeBreakdownSlot'; +import ProgressTabRelatedLinksSlot from '../../plugin-slots/ProgressTabRelatedLinksSlot'; import { useModel } from '../../generic/model-store'; const ProgressTab = () => { - const { - courseId, - } = useSelector(state => state.courseHome); - - const { - gradesFeatureIsFullyLocked, disableProgressGraph, - } = useModel('progress', courseId); - - const applyLockedOverlay = gradesFeatureIsFullyLocked ? 'locked-overlay' : ''; + const courseId = useContextId(); + const { disableProgressGraph } = useModel('progress', courseId); const windowWidth = useWindowSize().width; if (windowWidth === undefined) { @@ -31,7 +24,6 @@ const ProgressTab = () => { return null; } - const wideScreen = windowWidth >= breakpoints.large.minWidth; return ( <> <ProgressHeader /> @@ -39,18 +31,15 @@ const ProgressTab = () => { {/* Main body */} <div className="col-12 col-md-8 p-0"> {!disableProgressGraph && <CourseCompletion />} - {!wideScreen && <CertificateStatus />} - <CourseGrade /> - <div className={`grades my-4 p-4 rounded raised-card ${applyLockedOverlay}`} aria-hidden={gradesFeatureIsFullyLocked}> - <GradeSummary /> - <DetailedGrades /> - </div> + <ProgressTabCertificateStatusMainBodySlot /> + <ProgressTabCourseGradeSlot /> + <ProgressTabGradeBreakdownSlot /> </div> {/* Side panel */} <div className="col-12 col-md-4 p-0 px-md-4"> - {wideScreen && <CertificateStatus />} - <RelatedLinks /> + <ProgressTabCertificateStatusSidePanelSlot /> + <ProgressTabRelatedLinksSlot /> </div> </div> </> diff --git a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx index 0d157184..a4ac7da7 100644 --- a/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx +++ b/src/course-home/progress-tab/certificate-status/CertificateStatus.jsx @@ -1,11 +1,12 @@ import { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { FormattedDate, FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { Button, Card } from '@openedx/paragon'; import { getConfig } from '@edx/frontend-platform'; +import { useContextId } from '../../../data/hooks'; import { useModel } from '../../../generic/model-store'; import { COURSE_EXIT_MODES, getCourseExitMode } from '../../../courseware/course/course-exit/utils'; import { DashboardLink, IdVerificationSupportLink, ProfileLink } from '../../../shared/links'; @@ -15,9 +16,7 @@ import ProgressCertificateStatusSlot from '../../../plugin-slots/ProgressCertifi const CertificateStatus = () => { const intl = useIntl(); - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { entranceExamData, diff --git a/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx b/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx index 54b6caa9..8c008f0c 100644 --- a/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx +++ b/src/course-home/progress-tab/course-completion/CompletionDonutChart.jsx @@ -1,8 +1,8 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { getLocale, injectIntl, intlShape, isRtl, } from '@edx/frontend-platform/i18n'; +import { useContextId } from '../../../data/hooks'; import { useModel } from '../../../generic/model-store'; import CompleteDonutSegment from './CompleteDonutSegment'; @@ -11,9 +11,7 @@ import LockedDonutSegment from './LockedDonutSegment'; import messages from './messages'; const CompletionDonutChart = ({ intl }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { completionSummary: { diff --git a/src/course-home/progress-tab/credit-information/CreditInformation.jsx b/src/course-home/progress-tab/credit-information/CreditInformation.jsx index f1bbcf6a..27843f9b 100644 --- a/src/course-home/progress-tab/credit-information/CreditInformation.jsx +++ b/src/course-home/progress-tab/credit-information/CreditInformation.jsx @@ -1,9 +1,9 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { getConfig } from '@edx/frontend-platform'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { CheckCircle, WarningFilled, WatchFilled } from '@openedx/paragon/icons'; import { Hyperlink, Icon } from '@openedx/paragon'; +import { useContextId } from '../../../data/hooks'; import { useModel } from '../../../generic/model-store'; import { DashboardLink } from '../../../shared/links'; @@ -11,9 +11,7 @@ import { DashboardLink } from '../../../shared/links'; import messages from './messages'; const CreditInformation = ({ intl }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { creditCourseRequirements, diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx index 6aabdc08..c8dfb7e6 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGrade.jsx @@ -1,6 +1,6 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; @@ -12,9 +12,7 @@ import CreditInformation from '../../credit-information/CreditInformation'; import messages from '../messages'; const CourseGrade = ({ intl }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { creditCourseRequirements, diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx index e662b137..650e3283 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGradeFooter.jsx @@ -1,19 +1,17 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { CheckCircle, WarningFilled } from '@openedx/paragon/icons'; import { breakpoints, Icon, useWindowSize } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import GradeRangeTooltip from './GradeRangeTooltip'; import messages from '../messages'; const CourseGradeFooter = ({ intl, passingGrade }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { courseGrade: { diff --git a/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx b/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx index 4c4cfc7a..6349240e 100644 --- a/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CourseGradeHeader.jsx @@ -1,19 +1,17 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Locked } from '@openedx/paragon/icons'; import { Button, Icon } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; const CourseGradeHeader = ({ intl }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { org, } = useModel('courseHomeMeta', courseId); diff --git a/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx b/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx index b8699370..3ea95785 100644 --- a/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx +++ b/src/course-home/progress-tab/grades/course-grade/CurrentGradeTooltip.jsx @@ -1,20 +1,18 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useSelector } from 'react-redux'; import { getLocale, injectIntl, intlShape, isRtl, } from '@edx/frontend-platform/i18n'; import { OverlayTrigger, Popover } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; const CurrentGradeTooltip = ({ intl, tooltipClassName }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { courseGrade: { diff --git a/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx b/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx index 3cbbe5b1..98ed604e 100644 --- a/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx +++ b/src/course-home/progress-tab/grades/course-grade/GradeBar.jsx @@ -1,10 +1,10 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { getLocale, injectIntl, intlShape, isRtl, } from '@edx/frontend-platform/i18n'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import CurrentGradeTooltip from './CurrentGradeTooltip'; import PassingGradeTooltip from './PassingGradeTooltip'; @@ -12,9 +12,7 @@ import PassingGradeTooltip from './PassingGradeTooltip'; import messages from '../messages'; const GradeBar = ({ intl, passingGrade }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { courseGrade: { diff --git a/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx b/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx index 7489e73a..c049cde7 100644 --- a/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx +++ b/src/course-home/progress-tab/grades/course-grade/GradeRangeTooltip.jsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; @@ -7,14 +6,13 @@ import { InfoOutline } from '@openedx/paragon/icons'; import { Icon, IconButton, OverlayTrigger, Popover, } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; const GradeRangeTooltip = ({ intl, iconButtonClassName, passingGrade }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { gradesFeatureIsFullyLocked, diff --git a/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx b/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx index 529859c5..deb9dde2 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/DetailedGrades.jsx @@ -1,11 +1,11 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Blocked } from '@openedx/paragon/icons'; import { Icon, Hyperlink } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import { showUngradedAssignments } from '../../utils'; @@ -15,9 +15,7 @@ import messages from '../messages'; const DetailedGrades = ({ intl }) => { const { administrator } = getAuthenticatedUser(); - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { org, tabs, diff --git a/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx b/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx index f20bae32..4b55e824 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/DetailedGradesTable.jsx @@ -1,10 +1,10 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { getLocale, injectIntl, intlShape, isRtl, } from '@edx/frontend-platform/i18n'; import { DataTable } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; @@ -12,9 +12,7 @@ import SubsectionTitleCell from './SubsectionTitleCell'; import { showUngradedAssignments } from '../../utils'; const DetailedGradesTable = ({ intl }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { sectionScores, diff --git a/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx b/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx index c3b3cb8b..a1776456 100644 --- a/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx +++ b/src/course-home/progress-tab/grades/detailed-grades/SubsectionTitleCell.jsx @@ -1,5 +1,4 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; @@ -9,15 +8,14 @@ import { Collapsible, Icon, Row } from '@openedx/paragon'; import { ArrowDropDown, ArrowDropUp, Blocked, Info, } from '@openedx/paragon/icons'; +import { useContextId } from '../../../../data/hooks'; import messages from '../messages'; import { useModel } from '../../../../generic/model-store'; import ProblemScoreDrawer from './ProblemScoreDrawer'; const SubsectionTitleCell = ({ intl, subsection }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { org, } = useModel('courseHomeMeta', courseId); diff --git a/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx b/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx index 8de9fced..d0602af9 100644 --- a/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/AssignmentTypeCell.jsx @@ -1,18 +1,16 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Blocked } from '@openedx/paragon/icons'; import { Icon } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; const AssignmentTypeCell = ({ intl, assignmentType, footnoteMarker, footnoteId, locked, }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { gradesFeatureIsFullyLocked, diff --git a/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx b/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx index 14f6b2c3..92b78ebe 100644 --- a/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/DroppableAssignmentFootnote.jsx @@ -1,16 +1,15 @@ import React from 'react'; -import { useSelector } from 'react-redux'; + import PropTypes from 'prop-types'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useContextId } from '../../../../data/hooks'; import messages from '../messages'; import { useModel } from '../../../../generic/model-store'; const DroppableAssignmentFootnote = ({ footnotes, intl }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { gradesFeatureIsFullyLocked, } = useModel('progress', courseId); diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx index e6c6b9ad..ffc5e2c8 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummary.jsx @@ -1,14 +1,13 @@ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; + +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import GradeSummaryHeader from './GradeSummaryHeader'; import GradeSummaryTable from './GradeSummaryTable'; const GradeSummary = () => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { gradingPolicy: { diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx index fc860c10..6a91061f 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryHeader.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useSelector } from 'react-redux'; + import PropTypes from 'prop-types'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; @@ -7,14 +7,13 @@ import { Icon, IconButton, OverlayTrigger, Popover, } from '@openedx/paragon'; import { Blocked, InfoOutline } from '@openedx/paragon/icons'; +import { useContextId } from '../../../../data/hooks'; import messages from '../messages'; import { useModel } from '../../../../generic/model-store'; const GradeSummaryHeader = ({ intl, allOfSomeAssignmentTypeIsLocked }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { gradesFeatureIsFullyLocked, } = useModel('progress', courseId); @@ -28,7 +27,7 @@ const GradeSummaryHeader = ({ intl, allOfSomeAssignmentTypeIsLocked }) => { placement="top" show={showTooltip} overlay={( - <Popover> + <Popover id="grade-summary-tooltip"> <Popover.Content className="small text-dark-700"> {intl.formatMessage(messages.gradeSummaryTooltipBody)} </Popover.Content> diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx index 628a65e2..54e0388e 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTable.jsx @@ -1,11 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { useSelector } from 'react-redux'; import { getLocale, injectIntl, intlShape, isRtl, } from '@edx/frontend-platform/i18n'; import { DataTable } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import AssignmentTypeCell from './AssignmentTypeCell'; @@ -15,9 +15,7 @@ import GradeSummaryTableFooter from './GradeSummaryTableFooter'; import messages from '../messages'; const GradeSummaryTable = ({ intl, setAllOfSomeAssignmentTypeIsLocked }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { gradingPolicy: { diff --git a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx index 2c3235be..18ad54d8 100644 --- a/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx +++ b/src/course-home/progress-tab/grades/grade-summary/GradeSummaryTableFooter.jsx @@ -1,18 +1,16 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { getLocale, injectIntl, intlShape, isRtl, } from '@edx/frontend-platform/i18n'; import { DataTable } from '@openedx/paragon'; +import { useContextId } from '../../../../data/hooks'; import { useModel } from '../../../../generic/model-store'; import messages from '../messages'; const GradeSummaryTableFooter = ({ intl }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { courseGrade: { diff --git a/src/course-home/progress-tab/related-links/RelatedLinks.jsx b/src/course-home/progress-tab/related-links/RelatedLinks.jsx index e7a6adf3..0030f421 100644 --- a/src/course-home/progress-tab/related-links/RelatedLinks.jsx +++ b/src/course-home/progress-tab/related-links/RelatedLinks.jsx @@ -1,18 +1,16 @@ import React from 'react'; -import { useSelector } from 'react-redux'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; import { Hyperlink } from '@openedx/paragon'; +import { useContextId } from '../../../data/hooks'; import messages from './messages'; import { useModel } from '../../../generic/model-store'; const RelatedLinks = ({ intl }) => { - const { - courseId, - } = useSelector(state => state.courseHome); + const courseId = useContextId(); const { org, tabs, diff --git a/src/data/hooks.ts b/src/data/hooks.ts new file mode 100644 index 00000000..f8ad29be --- /dev/null +++ b/src/data/hooks.ts @@ -0,0 +1,5 @@ +import { useSelector } from 'react-redux'; +import { RootState } from '../store'; + +// eslint-disable-next-line import/prefer-default-export +export const useContextId = () => useSelector<RootState>(state => state.courseHome.courseId); diff --git a/src/index.jsx b/src/index.jsx index 6da653de..972d3c1e 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -26,7 +26,7 @@ import { TabContainer } from './tab-page'; import { fetchDatesTab, fetchOutlineTab, fetchProgressTab } from './course-home/data'; import { fetchCourse } from './courseware/data'; -import initializeStore from './store'; +import { store } from './store'; import NoticesProvider from './generic/notices'; import PathFixesProvider from './generic/path-fixes'; import LiveTab from './course-home/live-tab/LiveTab'; @@ -38,7 +38,7 @@ import PageNotFound from './generic/PageNotFound'; subscribe(APP_READY, () => { ReactDOM.render( - <AppProvider store={initializeStore()}> + <AppProvider store={store}> <Helmet> <link rel="shortcut icon" href={getConfig().FAVICON_URL} type="image/x-icon" /> </Helmet> diff --git a/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/README.md b/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/README.md new file mode 100644 index 00000000..f2fe797e --- /dev/null +++ b/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/README.md @@ -0,0 +1,47 @@ +# Progress Tab Certificate Status Slot + +### Slot ID: `progress_tab_certificate_status_main_body_slot` +### Props: + +## Description + +This slot is used to replace or modify the Certificate Status component in the +main body of the Progress Tab. + +## Example + +The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`. + +![Screenshot of Content added after the Certificate Status Container](./images/progress_tab_certificate_status_slot.png) + +```js +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; +import { useContextId } from './src/data/hooks'; + +const config = { + pluginSlots: { + progress_tab_certificate_status_main_body_slot: { + plugins: [ + { + // Insert custom content after certificate status + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_certificate_status_content', + type: DIRECT_PLUGIN, + RenderWidget: () => { + const courseId = useContextId(); + return ( + <div> + <p>📚: {courseId}</p> + </div> + ); + }, + }, + }, + ] + } + }, +} + +export default config; +``` diff --git a/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/images/progress_tab_certificate_status_slot.png b/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/images/progress_tab_certificate_status_slot.png new file mode 100644 index 00000000..4f5858d4 Binary files /dev/null and b/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/images/progress_tab_certificate_status_slot.png differ diff --git a/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/index.jsx b/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/index.jsx new file mode 100644 index 00000000..563217fb --- /dev/null +++ b/src/plugin-slots/ProgressTabCertificateStatusMainBodySlot/index.jsx @@ -0,0 +1,19 @@ +import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import { breakpoints, useWindowSize } from '@openedx/paragon'; +import CertificateStatus from '../../course-home/progress-tab/certificate-status/CertificateStatus'; + +const ProgressTabCertificateStatusMainBodySlot = () => { + const windowWidth = useWindowSize().width; + const wideScreen = windowWidth >= breakpoints.large.minWidth; + return ( + <PluginSlot + id="progress_tab_certificate_status_main_body_slot" + > + {windowWidth && !wideScreen && <CertificateStatus />} + </PluginSlot> + ); +}; + +ProgressTabCertificateStatusMainBodySlot.propTypes = {}; + +export default ProgressTabCertificateStatusMainBodySlot; diff --git a/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/README.md b/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/README.md new file mode 100644 index 00000000..83f73643 --- /dev/null +++ b/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/README.md @@ -0,0 +1,47 @@ +# Progress Tab Certificate Status Slot + +### Slot ID: `progress_tab_certificate_status_side_panel_slot` +### Props: + +## Description + +This slot is used to replace or modify the Certificate Status component in the +side panel of the Progress Tab. + +## Example + +The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`. + +![Screenshot of Content added after the Certificate Status Container](./images/progress_tab_certificate_status_slot.png) + +```js +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; +import { useContextId } from './src/data/hooks'; + +const config = { + pluginSlots: { + progress_tab_certificate_status_side_panel_slot: { + plugins: [ + { + // Insert custom content after certificate status + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_certificate_status_content', + type: DIRECT_PLUGIN, + RenderWidget: () => { + const courseId = useContextId(); + return ( + <div> + <p>📚: {courseId}</p> + </div> + ); + }, + }, + }, + ] + } + }, +} + +export default config; +``` diff --git a/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/images/progress_tab_certificate_status_slot.png b/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/images/progress_tab_certificate_status_slot.png new file mode 100644 index 00000000..4f5858d4 Binary files /dev/null and b/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/images/progress_tab_certificate_status_slot.png differ diff --git a/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/index.jsx b/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/index.jsx new file mode 100644 index 00000000..e8354c9f --- /dev/null +++ b/src/plugin-slots/ProgressTabCertificateStatusSidePanelSlot/index.jsx @@ -0,0 +1,19 @@ +import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import { breakpoints, useWindowSize } from '@openedx/paragon'; +import CertificateStatus from '../../course-home/progress-tab/certificate-status/CertificateStatus'; + +const ProgressTabCertificateStatusSidePanelSlot = () => { + const windowWidth = useWindowSize().width; + const wideScreen = windowWidth >= breakpoints.large.minWidth; + return ( + <PluginSlot + id="progress_tab_certificate_status_side_panel_slot" + > + {windowWidth && wideScreen && <CertificateStatus />} + </PluginSlot> + ); +}; + +ProgressTabCertificateStatusSidePanelSlot.propTypes = {}; + +export default ProgressTabCertificateStatusSidePanelSlot; diff --git a/src/plugin-slots/ProgressTabCourseGradeSlot/README.md b/src/plugin-slots/ProgressTabCourseGradeSlot/README.md new file mode 100644 index 00000000..8c0d7381 --- /dev/null +++ b/src/plugin-slots/ProgressTabCourseGradeSlot/README.md @@ -0,0 +1,46 @@ +# Progress Tab Course Grade Slot + +### Slot ID: `progress_tab_course_grade_slot` +### Props: + +## Description + +This slot is used to replace or modify the Course Grades view in the Progress Tab. + +## Example + +The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`. + +![Screenshot of Content added after the Grades Container](./images/progress_tab_course_grade_slot.png) + +```js +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; +import { useContextId } from './src/data/hooks'; + +const config = { + pluginSlots: { + progress_tab_course_grade_slot: { + plugins: [ + { + // Insert custom content after course grade widget + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_course_grade_content', + type: DIRECT_PLUGIN, + RenderWidget: () => { + const courseId = useContextId(); + return ( + <div> + <p>📚: {courseId}</p> + </div> + ); + }, + }, + }, + ] + } + }, +} + +export default config; +``` diff --git a/src/plugin-slots/ProgressTabCourseGradeSlot/images/progress_tab_course_grade_slot.png b/src/plugin-slots/ProgressTabCourseGradeSlot/images/progress_tab_course_grade_slot.png new file mode 100644 index 00000000..82a15f26 Binary files /dev/null and b/src/plugin-slots/ProgressTabCourseGradeSlot/images/progress_tab_course_grade_slot.png differ diff --git a/src/plugin-slots/ProgressTabCourseGradeSlot/index.jsx b/src/plugin-slots/ProgressTabCourseGradeSlot/index.jsx new file mode 100644 index 00000000..fa4bf956 --- /dev/null +++ b/src/plugin-slots/ProgressTabCourseGradeSlot/index.jsx @@ -0,0 +1,14 @@ +import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import CourseGrade from '../../course-home/progress-tab/grades/course-grade/CourseGrade'; + +const ProgressTabCourseGradeSlot = () => ( + <PluginSlot + id="progress_tab_course_grade_slot" + > + <CourseGrade /> + </PluginSlot> +); + +ProgressTabCourseGradeSlot.propTypes = {}; + +export default ProgressTabCourseGradeSlot; diff --git a/src/plugin-slots/ProgressTabGradeBreakdownSlot/README.md b/src/plugin-slots/ProgressTabGradeBreakdownSlot/README.md new file mode 100644 index 00000000..85465e69 --- /dev/null +++ b/src/plugin-slots/ProgressTabGradeBreakdownSlot/README.md @@ -0,0 +1,46 @@ +# Progress Tab Grade Breakdown Slot + +### Slot ID: `progress_tab_grade_breakdown_slot` +### Props: + +## Description + +This slot is used to replace or modify the Grade Summary and Details Breakdown view in the Progress Tab. + +## Example + +The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`. + +![Screenshot of Content added after the Grade Summary and Details Container](./images/progress_tab_grade_breakdown_slot.png) + +```js +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; +import { useContextId } from './src/data/hooks'; + +const config = { + pluginSlots: { + progress_tab_grade_breakdown_slot: { + plugins: [ + { + // Insert custom content after grade summary widget + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_grade_summary_content', + type: DIRECT_PLUGIN, + RenderWidget: () => { + const courseId = useContextId(); + return ( + <div> + <p>📚: {courseId}</p> + </div> + ); + }, + }, + }, + ] + } + }, +} + +export default config; +``` diff --git a/src/plugin-slots/ProgressTabGradeBreakdownSlot/images/progress_tab_grade_breakdown_slot.png b/src/plugin-slots/ProgressTabGradeBreakdownSlot/images/progress_tab_grade_breakdown_slot.png new file mode 100644 index 00000000..03df7a4a Binary files /dev/null and b/src/plugin-slots/ProgressTabGradeBreakdownSlot/images/progress_tab_grade_breakdown_slot.png differ diff --git a/src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx b/src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx new file mode 100644 index 00000000..f54f1f7c --- /dev/null +++ b/src/plugin-slots/ProgressTabGradeBreakdownSlot/index.jsx @@ -0,0 +1,29 @@ +import { useModel } from '@src/generic/model-store'; +import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import React from 'react'; +import DetailedGrades from '../../course-home/progress-tab/grades/detailed-grades/DetailedGrades'; +import GradeSummary from '../../course-home/progress-tab/grades/grade-summary/GradeSummary'; +import { useContextId } from '../../data/hooks'; + +const ProgressTabGradeBreakdownSlot = () => { + const courseId = useContextId(); + const { gradesFeatureIsFullyLocked } = useModel('progress', courseId); + const applyLockedOverlay = gradesFeatureIsFullyLocked ? 'locked-overlay' : ''; + return ( + <PluginSlot + id="progress_tab_grade_breakdown_slot" + > + <div + className={`grades my-4 p-4 rounded raised-card ${applyLockedOverlay}`} + aria-hidden={gradesFeatureIsFullyLocked} + > + <GradeSummary /> + <DetailedGrades /> + </div> + </PluginSlot> + ); +}; + +ProgressTabGradeBreakdownSlot.propTypes = {}; + +export default ProgressTabGradeBreakdownSlot; diff --git a/src/plugin-slots/ProgressTabRelatedLinksSlot/README.md b/src/plugin-slots/ProgressTabRelatedLinksSlot/README.md new file mode 100644 index 00000000..32ea7610 --- /dev/null +++ b/src/plugin-slots/ProgressTabRelatedLinksSlot/README.md @@ -0,0 +1,46 @@ +# Progress Tab Related Links Slot + +### Slot ID: `progress_tab_related_links_slot` +### Props: + +## Description + +This slot is used to replace or modify the related links view in the Progress Tab. + +## Example + +The following `env.config.jsx` will render the `course_id` of the course as a `<p>` element in a `<div>`. + +![Screenshot of Content added after the Related Links Container](./images/progress_tab_related_links_slot.png) + +```js +import { DIRECT_PLUGIN, PLUGIN_OPERATIONS } from '@openedx/frontend-plugin-framework'; +import { useContextId } from './src/data/hooks'; + +const config = { + pluginSlots: { + progress_tab_related_links_slot: { + plugins: [ + { + // Insert custom content after related links widget + op: PLUGIN_OPERATIONS.Insert, + widget: { + id: 'custom_related_links_content', + type: DIRECT_PLUGIN, + RenderWidget: () => { + const courseId = useContextId(); + return ( + <div> + <p>📚: {courseId}</p> + </div> + ); + }, + }, + }, + ] + } + }, +} + +export default config; +``` diff --git a/src/plugin-slots/ProgressTabRelatedLinksSlot/images/progress_tab_related_links_slot.png b/src/plugin-slots/ProgressTabRelatedLinksSlot/images/progress_tab_related_links_slot.png new file mode 100644 index 00000000..5ad62f91 Binary files /dev/null and b/src/plugin-slots/ProgressTabRelatedLinksSlot/images/progress_tab_related_links_slot.png differ diff --git a/src/plugin-slots/ProgressTabRelatedLinksSlot/index.jsx b/src/plugin-slots/ProgressTabRelatedLinksSlot/index.jsx new file mode 100644 index 00000000..c91dec1a --- /dev/null +++ b/src/plugin-slots/ProgressTabRelatedLinksSlot/index.jsx @@ -0,0 +1,14 @@ +import { PluginSlot } from '@openedx/frontend-plugin-framework'; +import RelatedLinks from '../../course-home/progress-tab/related-links/RelatedLinks'; + +const ProgressTabRelatedLinksSlot = () => ( + <PluginSlot + id="progress_tab_related_links_slot" + > + <RelatedLinks /> + </PluginSlot> +); + +ProgressTabRelatedLinksSlot.propTypes = {}; + +export default ProgressTabRelatedLinksSlot; diff --git a/src/store.js b/src/store.ts similarity index 92% rename from src/store.js rename to src/store.ts index 9343b0d2..32a77cda 100644 --- a/src/store.js +++ b/src/store.ts @@ -29,3 +29,7 @@ export default function initializeStore() { }), }); } + +export const store = initializeStore(); + +export type RootState = ReturnType<typeof store.getState>;
- Loading branch information