feat: change blocked icon to lock icon (#1619)

* feat: replace blocked icon with locked

* refactor: replace injectIntl with useIntl

* revert: temporary test change

* fix: failing test

* fix: wording of message description

* fix: lingering lint error

* fix: missing message variable
This commit is contained in:
Kristin Aoki
2025-03-12 15:43:27 -04:00
committed by GitHub
parent 679caa61f3
commit a56fd7d0e1
25 changed files with 210 additions and 249 deletions

View File

@@ -1,7 +1,5 @@
import React from 'react';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Button } from '@openedx/paragon';
import { useSelector } from 'react-redux';
@@ -9,7 +7,8 @@ import { useModel } from '../../generic/model-store';
import messages from './messages';
const ProgressHeader = ({ intl }) => {
const ProgressHeader = () => {
const intl = useIntl();
const {
courseId,
targetUserId,
@@ -37,8 +36,4 @@ const ProgressHeader = ({ intl }) => {
);
};
ProgressHeader.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(ProgressHeader);
export default ProgressHeader;

View File

@@ -471,9 +471,12 @@ describe('Progress Tab', () => {
await fetchAndRender();
expect(screen.getByText('limited feature')).toBeInTheDocument();
expect(screen.getByText('Unlock to work towards a certificate.')).toBeInTheDocument();
expect(screen.queryAllByText('You have limited access to graded assignments as part of the audit track in this course.')).toHaveLength(2);
expect(screen.queryAllByText(
'You have limited access to graded assignments as part of the audit track in this course.',
{ exact: false },
)).toHaveLength(2);
expect(screen.queryAllByTestId('blocked-icon')).toHaveLength(4);
expect(screen.queryAllByTestId('locked-icon')).toHaveLength(4);
});
it('does not render subsections for which showGrades is false', async () => {

View File

@@ -1,12 +1,13 @@
import React, { useState } from 'react';
import { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import messages from './messages';
const CompleteDonutSegment = ({ completePercentage, intl, lockedPercentage }) => {
const CompleteDonutSegment = ({ completePercentage, lockedPercentage }) => {
const intl = useIntl();
const [showCompletePopover, setShowCompletePopover] = useState(false);
if (!completePercentage) {
@@ -82,8 +83,7 @@ const CompleteDonutSegment = ({ completePercentage, intl, lockedPercentage }) =>
CompleteDonutSegment.propTypes = {
completePercentage: PropTypes.number.isRequired,
intl: intlShape.isRequired,
lockedPercentage: PropTypes.number.isRequired,
};
export default injectIntl(CompleteDonutSegment);
export default CompleteDonutSegment;

View File

@@ -1,7 +1,4 @@
import React from 'react';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n';
import { useContextId } from '../../../data/hooks';
import { useModel } from '../../../generic/model-store';
@@ -10,7 +7,8 @@ import IncompleteDonutSegment from './IncompleteDonutSegment';
import LockedDonutSegment from './LockedDonutSegment';
import messages from './messages';
const CompletionDonutChart = ({ intl }) => {
const CompletionDonutChart = () => {
const intl = useIntl();
const courseId = useContextId();
const {
@@ -60,8 +58,4 @@ const CompletionDonutChart = ({ intl }) => {
);
};
CompletionDonutChart.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(CompletionDonutChart);
export default CompletionDonutChart;

View File

@@ -1,27 +1,26 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import CompletionDonutChart from './CompletionDonutChart';
import messages from './messages';
const CourseCompletion = ({ intl }) => (
<section className="text-dark-700 mb-4 rounded raised-card p-4">
<div className="row w-100 m-0">
<div className="col-12 col-sm-6 col-md-7 p-0">
<h2>{intl.formatMessage(messages.courseCompletion)}</h2>
<p className="small">
{intl.formatMessage(messages.completionBody)}
</p>
</div>
<div className="col-12 col-sm-6 col-md-5 mt-sm-n3 p-0 text-center">
<CompletionDonutChart />
</div>
</div>
</section>
);
const CourseCompletion = () => {
const intl = useIntl();
CourseCompletion.propTypes = {
intl: intlShape.isRequired,
return (
<section className="text-dark-700 mb-4 rounded raised-card p-4">
<div className="row w-100 m-0">
<div className="col-12 col-sm-6 col-md-7 p-0">
<h2>{intl.formatMessage(messages.courseCompletion)}</h2>
<p className="small">
{intl.formatMessage(messages.completionBody)}
</p>
</div>
<div className="col-12 col-sm-6 col-md-5 mt-sm-n3 p-0 text-center">
<CompletionDonutChart />
</div>
</div>
</section>
);
};
export default injectIntl(CourseCompletion);
export default CourseCompletion;

View File

@@ -1,12 +1,13 @@
import React, { useState } from 'react';
import { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import messages from './messages';
const IncompleteDonutSegment = ({ incompletePercentage, intl }) => {
const IncompleteDonutSegment = ({ incompletePercentage }) => {
const intl = useIntl();
const [showIncompletePopover, setShowIncompletePopover] = useState(false);
if (!incompletePercentage) {
@@ -53,7 +54,6 @@ const IncompleteDonutSegment = ({ incompletePercentage, intl }) => {
IncompleteDonutSegment.propTypes = {
incompletePercentage: PropTypes.number.isRequired,
intl: intlShape.isRequired,
};
export default injectIntl(IncompleteDonutSegment);
export default IncompleteDonutSegment;

View File

@@ -1,12 +1,13 @@
import React, { useState } from 'react';
import { useState } from 'react';
import PropTypes from 'prop-types';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import messages from './messages';
const LockedDonutSegment = ({ intl, lockedPercentage }) => {
const LockedDonutSegment = ({ lockedPercentage }) => {
const intl = useIntl();
const [showLockedPopover, setShowLockedPopover] = useState(false);
if (!lockedPercentage) {
@@ -65,8 +66,7 @@ const LockedDonutSegment = ({ intl, lockedPercentage }) => {
};
LockedDonutSegment.propTypes = {
intl: intlShape.isRequired,
lockedPercentage: PropTypes.number.isRequired,
};
export default injectIntl(LockedDonutSegment);
export default LockedDonutSegment;

View File

@@ -1,6 +1,5 @@
import React from 'react';
import { getConfig } from '@edx/frontend-platform';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { CheckCircle, WarningFilled, WatchFilled } from '@openedx/paragon/icons';
import { Hyperlink, Icon } from '@openedx/paragon';
import { useContextId } from '../../../data/hooks';
@@ -10,7 +9,8 @@ import { DashboardLink } from '../../../shared/links';
import messages from './messages';
const CreditInformation = ({ intl }) => {
const CreditInformation = () => {
const intl = useIntl();
const courseId = useContextId();
const {
@@ -34,36 +34,13 @@ const CreditInformation = ({ intl }) => {
switch (creditCourseRequirements.eligibilityStatus) {
case 'not_eligible':
eligibilityStatus = (
<FormattedMessage
id="progress.creditInformation.creditNotEligible"
defaultMessage="You are no longer eligible for credit in this course. Learn more about {creditLink}."
description="Message to learner who are not eligible for course credit, it can because the a requirement deadline have passed"
values={{ creditLink }}
/>
);
eligibilityStatus = intl.formatMessage(messages.creditNotEligibleStatus, { creditLink });
break;
case 'eligible':
eligibilityStatus = (
<FormattedMessage
id="progress.creditInformation.creditEligible"
defaultMessage="
You have met the requirements for credit in this course. Go to your
{dashboardLink} to purchase course credit. Or learn more about {creditLink}."
description="After the credit requirements are met, leaners can then do the last step which purchasing the credit. Note that is only doable for leaners after they met all the requirements"
values={{ dashboardLink, creditLink }}
/>
);
eligibilityStatus = intl.formatMessage(messages.creditEligibleStatus, { dashboardLink, creditLink });
break;
case 'partial_eligible':
eligibilityStatus = (
<FormattedMessage
id="progress.creditInformation.creditPartialEligible"
defaultMessage="You have not yet met the requirements for credit. Learn more about {creditLink}."
description="This means that one or more requirements is not satisfied yet"
values={{ creditLink }}
/>
);
eligibilityStatus = intl.formatMessage(messages.creditPartialEligibleStatus, { creditLink });
break;
default:
break;
@@ -106,8 +83,4 @@ const CreditInformation = ({ intl }) => {
);
};
CreditInformation.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(CreditInformation);
export default CreditInformation;

View File

@@ -35,6 +35,22 @@ const messages = defineMessages({
defaultMessage: 'Verification submitted',
description: 'It indicate that the learner submitted a requirement but is not graded or reviewed yet',
},
creditNotEligibleStatus: {
id: 'progress.creditInformation.creditNotEligible',
defaultMessage: 'You are no longer eligible for credit in this course. Learn more about {creditLink}.',
description: 'Message to learner who are not eligible for course credit, it can be that a requirement deadline has passed',
},
creditEligibleStatus: {
id: 'progress.creditInformation.creditEligible',
defaultMessage: `You have met the requirements for credit in this course. Go to your
{dashboardLink} to purchase course credit. Or learn more about {creditLink}.`,
description: 'After the credit requirements are met, leaners can then do the last step which purchasing the credit. Note that is only doable for leaners after they met all the requirements',
},
creditPartialEligibleStatus: {
id: 'progress.creditInformation.creditPartialEligible',
defaultMessage: 'You have not yet met the requirements for credit. Learn more about {creditLink}.',
description: 'This means that one or more requirements is not satisfied yet',
},
});
export default messages;

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { useContextId } from '../../../../data/hooks';
import { useModel } from '../../../../generic/model-store';
@@ -11,7 +10,8 @@ import CreditInformation from '../../credit-information/CreditInformation';
import messages from '../messages';
const CourseGrade = ({ intl }) => {
const CourseGrade = () => {
const intl = useIntl();
const courseId = useContextId();
const {
@@ -52,8 +52,4 @@ const CourseGrade = ({ intl }) => {
);
};
CourseGrade.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(CourseGrade);
export default CourseGrade;

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { CheckCircle, WarningFilled } from '@openedx/paragon/icons';
import { breakpoints, Icon, useWindowSize } from '@openedx/paragon';
import { useContextId } from '../../../../data/hooks';
@@ -10,7 +9,8 @@ import { useModel } from '../../../../generic/model-store';
import GradeRangeTooltip from './GradeRangeTooltip';
import messages from '../messages';
const CourseGradeFooter = ({ intl, passingGrade }) => {
const CourseGradeFooter = ({ passingGrade }) => {
const intl = useIntl();
const courseId = useContextId();
const {
@@ -84,8 +84,7 @@ const CourseGradeFooter = ({ intl, passingGrade }) => {
};
CourseGradeFooter.propTypes = {
intl: intlShape.isRequired,
passingGrade: PropTypes.number.isRequired,
};
export default injectIntl(CourseGradeFooter);
export default CourseGradeFooter;

View File

@@ -1,8 +1,6 @@
import React from 'react';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Locked } from '@openedx/paragon/icons';
import { Button, Icon } from '@openedx/paragon';
import { useContextId } from '../../../../data/hooks';
@@ -10,7 +8,8 @@ import { useContextId } from '../../../../data/hooks';
import { useModel } from '../../../../generic/model-store';
import messages from '../messages';
const CourseGradeHeader = ({ intl }) => {
const CourseGradeHeader = () => {
const intl = useIntl();
const courseId = useContextId();
const {
org,
@@ -81,8 +80,4 @@ const CourseGradeHeader = ({ intl }) => {
);
};
CourseGradeHeader.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(CourseGradeHeader);
export default CourseGradeHeader;

View File

@@ -1,9 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import { useContextId } from '../../../../data/hooks';
@@ -11,7 +8,8 @@ import { useModel } from '../../../../generic/model-store';
import messages from '../messages';
const CurrentGradeTooltip = ({ intl, tooltipClassName }) => {
const CurrentGradeTooltip = ({ tooltipClassName }) => {
const intl = useIntl();
const courseId = useContextId();
const {
@@ -67,8 +65,7 @@ CurrentGradeTooltip.defaultProps = {
};
CurrentGradeTooltip.propTypes = {
intl: intlShape.isRequired,
tooltipClassName: PropTypes.string,
};
export default injectIntl(CurrentGradeTooltip);
export default CurrentGradeTooltip;

View File

@@ -1,9 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n';
import { useContextId } from '../../../../data/hooks';
import { useModel } from '../../../../generic/model-store';
import CurrentGradeTooltip from './CurrentGradeTooltip';
@@ -11,7 +8,8 @@ import PassingGradeTooltip from './PassingGradeTooltip';
import messages from '../messages';
const GradeBar = ({ intl, passingGrade }) => {
const GradeBar = ({ passingGrade }) => {
const intl = useIntl();
const courseId = useContextId();
const {
@@ -50,8 +48,7 @@ const GradeBar = ({ intl, passingGrade }) => {
};
GradeBar.propTypes = {
intl: intlShape.isRequired,
passingGrade: PropTypes.number.isRequired,
};
export default injectIntl(GradeBar);
export default GradeBar;

View File

@@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { InfoOutline } from '@openedx/paragon/icons';
import {
Icon, IconButton, OverlayTrigger, Popover,
@@ -11,7 +11,8 @@ import { useModel } from '../../../../generic/model-store';
import messages from '../messages';
const GradeRangeTooltip = ({ intl, iconButtonClassName, passingGrade }) => {
const GradeRangeTooltip = ({ iconButtonClassName, passingGrade }) => {
const intl = useIntl();
const courseId = useContextId();
const {
@@ -78,8 +79,7 @@ GradeRangeTooltip.defaultProps = {
GradeRangeTooltip.propTypes = {
iconButtonClassName: PropTypes.string,
intl: intlShape.isRequired,
passingGrade: PropTypes.number.isRequired,
};
export default injectIntl(GradeRangeTooltip);
export default GradeRangeTooltip;

View File

@@ -1,14 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n';
import { OverlayTrigger, Popover } from '@openedx/paragon';
import messages from '../messages';
const PassingGradeTooltip = ({ intl, passingGrade, tooltipClassName }) => {
const PassingGradeTooltip = ({ passingGrade, tooltipClassName }) => {
const intl = useIntl();
const isLocaleRtl = isRtl(getLocale());
let passingGradeDirection = passingGrade < 50 ? '' : '-';
@@ -54,9 +52,8 @@ PassingGradeTooltip.defaultProps = {
};
PassingGradeTooltip.propTypes = {
intl: intlShape.isRequired,
passingGrade: PropTypes.number.isRequired,
tooltipClassName: PropTypes.string,
};
export default injectIntl(PassingGradeTooltip);
export default PassingGradeTooltip;

View File

@@ -1,9 +1,7 @@
import React from 'react';
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 { useIntl } from '@edx/frontend-platform/i18n';
import { Locked } from '@openedx/paragon/icons';
import { Icon, Hyperlink } from '@openedx/paragon';
import { useContextId } from '../../../../data/hooks';
import { useModel } from '../../../../generic/model-store';
@@ -13,7 +11,8 @@ import DetailedGradesTable from './DetailedGradesTable';
import messages from '../messages';
const DetailedGrades = ({ intl }) => {
const DetailedGrades = () => {
const intl = useIntl();
const { administrator } = getAuthenticatedUser();
const courseId = useContextId();
const {
@@ -59,25 +58,17 @@ const DetailedGrades = ({ intl }) => {
<ul className="micro mb-3 pl-3 text-gray-700">
<li>
<b>{intl.formatMessage(messages.practiceScoreLabel)} </b>
<FormattedMessage
id="progress.detailedGrades.practice-label.info.text"
defaultMessage="Scores from non-graded activities meant for practice and self-assessment."
description="Information text about non-graded practice score label"
/>
{intl.formatMessage(messages.practiceScoreInfoText)}
</li>
<li>
<b>{intl.formatMessage(messages.gradedScoreLabel)} </b>
<FormattedMessage
id="progress.detailedGrades.problem-label.info.text"
defaultMessage="Scores from activities that contribute to your final grade."
description="Information text about graded problem score label"
/>
{intl.formatMessage(messages.gradedScoreInfoText)}
</li>
</ul>
{gradesFeatureIsPartiallyLocked && (
<div className="mb-3 small ml-0 d-inline">
<Icon className="mr-1 mt-1 d-inline-flex" style={{ height: '1rem', width: '1rem' }} src={Blocked} data-testid="blocked-icon" />
{intl.formatMessage(messages.gradeSummaryLimitedAccessExplanation)}
<Icon className="mr-1 mt-1 d-inline-flex" style={{ height: '1rem', width: '1rem' }} src={Locked} data-testid="locked-icon" />
{intl.formatMessage(messages.gradeSummaryLimitedAccessExplanation, { upgradeLink: '' })}
</div>
)}
{hasSectionScores && (
@@ -88,20 +79,11 @@ const DetailedGrades = ({ intl }) => {
)}
{overviewTabUrl && !showUngradedAssignments() && (
<p className="x-small m-0">
<FormattedMessage
id="progress.ungradedAlert"
defaultMessage="For progress on ungraded aspects of the course, view your {outlineLink}."
description="Text that precede link that redirect to course outline page"
values={{ outlineLink }}
/>
{intl.formatMessage(messages.ungradedAlert, { outlineLink })}
</p>
)}
</section>
);
};
DetailedGrades.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(DetailedGrades);
export default DetailedGrades;

View File

@@ -1,8 +1,4 @@
import React from 'react';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n';
import { DataTable } from '@openedx/paragon';
import { useContextId } from '../../../../data/hooks';
@@ -11,7 +7,8 @@ import messages from '../messages';
import SubsectionTitleCell from './SubsectionTitleCell';
import { showUngradedAssignments } from '../../utils';
const DetailedGradesTable = ({ intl }) => {
const DetailedGradesTable = () => {
const intl = useIntl();
const courseId = useContextId();
const {
@@ -66,8 +63,4 @@ const DetailedGradesTable = ({ intl }) => {
);
};
DetailedGradesTable.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(DetailedGradesTable);
export default DetailedGradesTable;

View File

@@ -1,14 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import {
getLocale, injectIntl, intlShape, isRtl,
} from '@edx/frontend-platform/i18n';
import { getLocale, isRtl, useIntl } from '@edx/frontend-platform/i18n';
import messages from '../messages';
const ProblemScoreDrawer = ({ intl, problemScores, subsection }) => {
const ProblemScoreDrawer = ({ problemScores, subsection }) => {
const intl = useIntl();
const isLocaleRtl = isRtl(getLocale());
const scoreLabel = subsection.hasGradedAssignment ? messages.gradedScoreLabel : messages.practiceScoreLabel;
@@ -29,7 +27,6 @@ const ProblemScoreDrawer = ({ intl, problemScores, subsection }) => {
};
ProblemScoreDrawer.propTypes = {
intl: intlShape.isRequired,
problemScores: PropTypes.arrayOf(PropTypes.shape({
earned: PropTypes.number.isRequired,
possible: PropTypes.number.isRequired,
@@ -40,4 +37,4 @@ ProblemScoreDrawer.propTypes = {
}).isRequired,
};
export default injectIntl(ProblemScoreDrawer);
export default ProblemScoreDrawer;

View File

@@ -1,12 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Collapsible, Icon, Row } from '@openedx/paragon';
import {
ArrowDropDown, ArrowDropUp, Blocked, Info,
ArrowDropDown,
ArrowDropUp,
Info,
Locked,
} from '@openedx/paragon/icons';
import { useContextId } from '../../../../data/hooks';
@@ -14,7 +16,8 @@ import messages from '../messages';
import { useModel } from '../../../../generic/model-store';
import ProblemScoreDrawer from './ProblemScoreDrawer';
const SubsectionTitleCell = ({ intl, subsection }) => {
const SubsectionTitleCell = ({ subsection }) => {
const intl = useIntl();
const courseId = useContextId();
const {
org,
@@ -59,8 +62,8 @@ const SubsectionTitleCell = ({ intl, subsection }) => {
aria-label={intl.formatMessage(messages.noAccessToSubsection, { displayName })}
className="mr-1 mt-1 d-inline-flex"
style={{ height: '1rem', width: '1rem' }}
src={Blocked}
data-testid="blocked-icon"
src={Locked}
data-testid="locked-icon"
/>
)}
{url ? (
@@ -100,7 +103,6 @@ const SubsectionTitleCell = ({ intl, subsection }) => {
};
SubsectionTitleCell.propTypes = {
intl: intlShape.isRequired,
subsection: PropTypes.shape({
blockKey: PropTypes.string.isRequired,
displayName: PropTypes.string.isRequired,
@@ -117,4 +119,4 @@ SubsectionTitleCell.propTypes = {
}).isRequired,
};
export default injectIntl(SubsectionTitleCell);
export default SubsectionTitleCell;

View File

@@ -1,22 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { Blocked } from '@openedx/paragon/icons';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Locked } 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,
assignmentType, footnoteMarker, footnoteId, locked,
}) => {
const intl = useIntl();
const courseId = useContextId();
const {
gradesFeatureIsFullyLocked,
} = useModel('progress', courseId);
const lockedIcon = locked ? <Icon id={`assignmentTypeBlockedIcon${assignmentType}`} aria-label={intl.formatMessage(messages.noAccessToAssignmentType, { assignmentType })} className="mr-1 mt-1 d-inline-flex" style={{ height: '1rem', width: '1rem' }} src={Blocked} data-testid="blocked-icon" /> : '';
const lockedIcon = locked ? <Icon id={`assignmentTypeBlockedIcon${assignmentType}`} aria-label={intl.formatMessage(messages.noAccessToAssignmentType, { assignmentType })} className="mr-1 mt-1 d-inline-flex" style={{ height: '1rem', width: '1rem' }} src={Locked} data-testid="locked-icon" /> : '';
return (
<div className="d-flex small">
@@ -43,7 +43,6 @@ const AssignmentTypeCell = ({
};
AssignmentTypeCell.propTypes = {
intl: intlShape.isRequired,
assignmentType: PropTypes.string.isRequired,
footnoteId: PropTypes.string,
footnoteMarker: PropTypes.number,
@@ -56,4 +55,4 @@ AssignmentTypeCell.defaultProps = {
locked: false,
};
export default injectIntl(AssignmentTypeCell);
export default AssignmentTypeCell;

View File

@@ -1,14 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } 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 DroppableAssignmentFootnote = ({ footnotes }) => {
const intl = useIntl();
const courseId = useContextId();
const {
gradesFeatureIsFullyLocked,
@@ -20,14 +19,10 @@ const DroppableAssignmentFootnote = ({ footnotes, intl }) => {
{footnotes.map((footnote, index) => (
<li id={`${footnote.id}-footnote`} key={footnote.id} className="x-small mt-1">
<sup>{index + 1}</sup>
<FormattedMessage
id="progress.footnotes.droppableAssignments"
defaultMessage="The lowest {numDroppable, plural, one{# {assignmentType} score is} other{# {assignmentType} scores are}} dropped."
values={{
numDroppable: footnote.numDroppable,
assignmentType: footnote.assignmentType,
}}
/>
{intl.formatMessage(messages.droppableAssignmentsText, {
numDroppable: footnote.numDroppable,
assignmentType: footnote.assignmentType,
})}
<a className="sr-only" href={`#${footnote.id}-ref`} tabIndex={gradesFeatureIsFullyLocked ? '-1' : '0'}>
{intl.formatMessage(messages.backToContent)}
</a>
@@ -44,7 +39,6 @@ DroppableAssignmentFootnote.propTypes = {
id: PropTypes.string.isRequired,
numDroppable: PropTypes.number.isRequired,
})).isRequired,
intl: intlShape.isRequired,
};
export default injectIntl(DroppableAssignmentFootnote);
export default DroppableAssignmentFootnote;

View File

@@ -1,8 +1,13 @@
import PropTypes from 'prop-types';
import { useIntl } from '@edx/frontend-platform/i18n';
import { Icon, OverlayTrigger, Tooltip } from '@openedx/paragon';
import { Blocked, InfoOutline } from '@openedx/paragon/icons';
import {
Hyperlink,
Icon,
OverlayTrigger,
Stack,
Tooltip,
} from '@openedx/paragon';
import { InfoOutline, Locked } from '@openedx/paragon/icons';
import { useContextId } from '../../../../data/hooks';
import messages from '../messages';
@@ -12,35 +17,48 @@ const GradeSummaryHeader = ({ allOfSomeAssignmentTypeIsLocked }) => {
const intl = useIntl();
const courseId = useContextId();
const {
verifiedMode,
gradesFeatureIsFullyLocked,
} = useModel('progress', courseId);
return (
<div className="row w-100 m-0 align-items-center">
<h3 className="h4 mb-3 mr-1">{intl.formatMessage(messages.gradeSummary)}</h3>
<OverlayTrigger
trigger="hover"
placement="top"
overlay={(
<Tooltip>
{intl.formatMessage(messages.gradeSummaryTooltipBody)}
</Tooltip>
)}
>
<Icon
alt={intl.formatMessage(messages.gradeSummaryTooltipAlt)}
src={InfoOutline}
className="mb-3"
size="sm"
/>
</OverlayTrigger>
<Stack gap={2} className="mb-3">
<Stack direction="horizontal" gap={2}>
<h3 className="h4 m-0">{intl.formatMessage(messages.gradeSummary)}</h3>
<OverlayTrigger
trigger="hover"
placement="top"
overlay={(
<Tooltip>
{intl.formatMessage(messages.gradeSummaryTooltipBody)}
</Tooltip>
)}
>
<Icon
alt={intl.formatMessage(messages.gradeSummaryTooltipAlt)}
src={InfoOutline}
size="sm"
/>
</OverlayTrigger>
</Stack>
{!gradesFeatureIsFullyLocked && allOfSomeAssignmentTypeIsLocked && (
<div className="mb-3 small ml-0 d-inline">
<Icon className="mr-1 mt-1 d-inline-flex" style={{ height: '1rem', width: '1rem' }} src={Blocked} data-testid="blocked-icon" />
{intl.formatMessage(messages.gradeSummaryLimitedAccessExplanation)}
</div>
<Stack direction="horizontal" className="small" gap={2}>
<Icon size="sm" src={Locked} data-testid="locked-icon" />
<span>
{intl.formatMessage(
messages.gradeSummaryLimitedAccessExplanation,
{
upgradeLink: verifiedMode && (
<Hyperlink destination={verifiedMode.upgradeUrl}>
{intl.formatMessage(messages.courseGradePreviewUpgradeButton)}.
</Hyperlink>
),
},
)}
</span>
</Stack>
)}
</div>
</Stack>
);
};

View File

@@ -133,7 +133,7 @@ const messages = defineMessages({
},
gradeSummaryLimitedAccessExplanation: {
id: 'progress.gradeSummary.limitedAccessExplanation',
defaultMessage: 'You have limited access to graded assignments as part of the audit track in this course.',
defaultMessage: 'You have limited access to graded assignments as part of the audit track in this course. {upgradeLink}',
description: 'Text shown when learner has limited access to grade feature',
},
gradeSummaryTooltipAlt: {
@@ -208,6 +208,26 @@ const messages = defineMessages({
defaultMessage: 'Your raw weighted grade summary is {rawGrade} and rounds to {roundedGrade}.',
description: 'Tooltip content that explains the rounding of the summary versus individual assignments',
},
practiceScoreInfoText: {
id: 'progress.detailedGrades.practice-label.info.text',
defaultMessage: 'Scores from non-graded activities meant for practice and self-assessment.',
description: 'Information text about non-graded practice score label',
},
gradedScoreInfoText: {
id: 'progress.detailedGrades.problem-label.info.text',
defaultMessage: 'Scores from activities that contribute to your final grade.',
description: 'Information text about graded problem score label',
},
ungradedAlert: {
id: 'progress.ungradedAlert',
defaultMessage: 'For progress on ungraded aspects of the course, view your {outlineLink}.',
description: 'Text that precede link that redirect to course outline page',
},
droppableAssignmentsText: {
id: 'progress.footnotes.droppableAssignments',
defaultMessage: 'The lowest {numDroppable, plural, one{# {assignmentType} score is} other{# {assignmentType} scores are}} dropped.',
description: 'Footnote text stating how many assignments are dropped',
},
});
export default messages;

View File

@@ -1,15 +1,14 @@
import React from 'react';
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
import { getAuthenticatedUser } from '@edx/frontend-platform/auth';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { useIntl } 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 RelatedLinks = () => {
const intl = useIntl();
const courseId = useContextId();
const {
org,
@@ -56,8 +55,4 @@ const RelatedLinks = ({ intl }) => {
);
};
RelatedLinks.propTypes = {
intl: intlShape.isRequired,
};
export default injectIntl(RelatedLinks);
export default RelatedLinks;