diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx index 138db7b4..5ea7a373 100644 --- a/src/course-home/outline-tab/OutlineTab.test.jsx +++ b/src/course-home/outline-tab/OutlineTab.test.jsx @@ -763,6 +763,33 @@ describe('Outline Tab', () => { }); }); + describe('Requesting Certificate Alert', () => { + it('appears', async () => { + const now = new Date(); + const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1); + setMetadata({ is_enrolled: true }); + setTabData({ + cert_data: { + cert_status: CERT_STATUS_TYPE.REQUESTING, + cert_web_view_url: null, + certificate_available_date: null, + download_url: null, + }, + }, { + date_blocks: [ + { + date_type: 'course-end-date', + date: yesterday.toISOString(), + title: 'End', + }, + ], + }); + await fetchAndRender(); + expect(screen.queryByText('Congratulations! Your certificate is ready.')).toBeInTheDocument(); + expect(screen.queryByText('Request certificate')).toBeInTheDocument(); + }); + }); + describe('Certificate (pdf) Complete Alert', () => { it('appears', async () => { const now = new Date(); diff --git a/src/course-home/outline-tab/alerts/certificate-status-alert/CertificateStatusAlert.jsx b/src/course-home/outline-tab/alerts/certificate-status-alert/CertificateStatusAlert.jsx index ac375ac7..7e9c069b 100644 --- a/src/course-home/outline-tab/alerts/certificate-status-alert/CertificateStatusAlert.jsx +++ b/src/course-home/outline-tab/alerts/certificate-status-alert/CertificateStatusAlert.jsx @@ -7,23 +7,29 @@ import { intlShape, } from '@edx/frontend-platform/i18n'; import { Alert, Button } from '@edx/paragon'; +import { useDispatch } from 'react-redux'; + import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheckCircle, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons'; import { getConfig } from '@edx/frontend-platform'; import certMessages from './messages'; import certStatusMessages from '../../../progress-tab/certificate-status/messages'; +import { requestCert } from '../../../data/thunks'; export const CERT_STATUS_TYPE = { EARNED_NOT_AVAILABLE: 'earned_but_not_available', DOWNLOADABLE: 'downloadable', + REQUESTING: 'requesting', UNVERIFIED: 'unverified', }; function CertificateStatusAlert({ intl, payload }) { + const dispatch = useDispatch(); const { certificateAvailableDate, - certStatusType, + certStatus, courseEndDate, + courseId, certURL, isWebCert, userTimezone, @@ -38,7 +44,7 @@ function CertificateStatusAlert({ intl, payload }) { icon: faCheckCircle, iconClassName: 'alert-icon text-success-500', }; - if (certStatusType === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE) { + if (certStatus === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE) { const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {}; const certificateAvailableDateFormatted = ; const courseEndDateFormatted = ; @@ -57,7 +63,7 @@ function CertificateStatusAlert({ intl, payload }) { />

); - } else if (certStatusType === CERT_STATUS_TYPE.DOWNLOADABLE) { + } else if (certStatus === CERT_STATUS_TYPE.DOWNLOADABLE) { alertProps.header = intl.formatMessage(certMessages.certStatusDownloadableHeader); if (isWebCert) { alertProps.buttonMessage = intl.formatMessage(certStatusMessages.viewableButton); @@ -66,6 +72,12 @@ function CertificateStatusAlert({ intl, payload }) { } alertProps.buttonVisible = true; alertProps.buttonLink = certURL; + } else if (certStatus === CERT_STATUS_TYPE.REQUESTING) { + alertProps.header = intl.formatMessage(certMessages.certStatusDownloadableHeader); + alertProps.buttonMessage = intl.formatMessage(certStatusMessages.requestableButton); + alertProps.buttonVisible = true; + alertProps.buttonLink = ''; + alertProps.buttonAction = () => { dispatch(requestCert(courseId)); }; } return alertProps; }; @@ -86,9 +98,10 @@ function CertificateStatusAlert({ intl, payload }) { }; let alertProps = {}; - switch (certStatusType) { + switch (certStatus) { case CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE: case CERT_STATUS_TYPE.DOWNLOADABLE: + case CERT_STATUS_TYPE.REQUESTING: alertProps = renderCertAwardedStatus(); break; case CERT_STATUS_TYPE.UNVERIFIED: @@ -106,8 +119,9 @@ function CertificateStatusAlert({ intl, payload }) { iconClassName, icon, header, - buttonLink, body, + buttonAction, + buttonLink, buttonMessage, }) => ( @@ -122,6 +136,9 @@ function CertificateStatusAlert({ intl, payload }) { @@ -139,8 +156,9 @@ CertificateStatusAlert.propTypes = { intl: intlShape.isRequired, payload: PropTypes.shape({ certificateAvailableDate: PropTypes.string, - certStatusType: PropTypes.string, + certStatus: PropTypes.string, courseEndDate: PropTypes.string, + courseId: PropTypes.string, certURL: PropTypes.string, isWebCert: PropTypes.bool, userTimezone: PropTypes.string, diff --git a/src/course-home/outline-tab/alerts/certificate-status-alert/hooks.js b/src/course-home/outline-tab/alerts/certificate-status-alert/hooks.js index 2f2f130f..35ad4ad3 100644 --- a/src/course-home/outline-tab/alerts/certificate-status-alert/hooks.js +++ b/src/course-home/outline-tab/alerts/certificate-status-alert/hooks.js @@ -3,29 +3,28 @@ import React, { useMemo } from 'react'; import { getConfig } from '@edx/frontend-platform'; import { useAlert } from '../../../../generic/user-messages'; import { useModel } from '../../../../generic/model-store'; + import { CERT_STATUS_TYPE } from './CertificateStatusAlert'; const CertificateStatusAlert = React.lazy(() => import('./CertificateStatusAlert')); function verifyCertStatusType(status) { - // This method will only return cert statuses when we want to alert on them. - // It should be modified when we want to alert on a new status type. - if (status === CERT_STATUS_TYPE.DOWNLOADABLE) { - return CERT_STATUS_TYPE.DOWNLOADABLE; + switch (status) { + case CERT_STATUS_TYPE.DOWNLOADABLE: + case CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE: + case CERT_STATUS_TYPE.REQUESTING: + case CERT_STATUS_TYPE.UNVERIFIED: + return true; + default: + return false; } - if (status === CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE) { - return CERT_STATUS_TYPE.EARNED_NOT_AVAILABLE; - } - if (status === CERT_STATUS_TYPE.UNVERIFIED) { - return CERT_STATUS_TYPE.UNVERIFIED; - } - return ''; } function useCertificateStatusAlert(courseId) { const { isEnrolled, } = useModel('courseHomeMeta', courseId); + const { datesWidget: { courseDateBlocks, @@ -41,7 +40,6 @@ function useCertificateStatusAlert(courseId) { downloadUrl, } = certData || {}; const endBlock = courseDateBlocks.find(b => b.dateType === 'course-end-date'); - const certStatusType = verifyCertStatusType(certStatus); const isWebCert = downloadUrl === null; let certURL = ''; @@ -51,14 +49,15 @@ function useCertificateStatusAlert(courseId) { // PDF Certificate certURL = downloadUrl; } - const hasCertStatus = certStatusType !== ''; + const hasAlertingCertStatus = verifyCertStatusType(certStatus); // Only show if there is a known cert status that we want provide status on. - const isVisible = isEnrolled && hasCertStatus; + const isVisible = isEnrolled && hasAlertingCertStatus; const payload = { certificateAvailableDate, certURL, - certStatusType, + certStatus, + courseId, courseEndDate: endBlock && endBlock.date, userTimezone, isWebCert,