diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/BaseCourseCard.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/BaseCourseCard.jsx index 89f17aec1..ca88af3d0 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/BaseCourseCard.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/BaseCourseCard.jsx @@ -14,6 +14,7 @@ import { MoreVert } from '@edx/paragon/icons'; import { EmailSettingsModal } from './email-settings'; import { UnenrollModal } from './unenroll'; import { COURSE_STATUSES, COURSE_PACING, EXECUTIVE_EDUCATION_COURSE_MODES } from '../../../../../constants'; +import { EXEC_ED_COURSE_TYPE, PRODUCT_SOURCE_2U } from '../data/constants'; const BADGE_PROPS_BY_COURSE_STATUS = { [COURSE_STATUSES.inProgress]: { @@ -50,12 +51,12 @@ class BaseCourseCard extends Component { getDropdownMenuItems = () => { const { - hasEmailsEnabled, title, dropdownMenuItems, canUnenroll, + hasEmailsEnabled, title, dropdownMenuItems, canUnenroll, courseType, productSource, } = this.props; const firstMenuItems = []; const lastMenuItems = []; - if (hasEmailsEnabled !== null) { + if (hasEmailsEnabled !== null && (productSource !== PRODUCT_SOURCE_2U && courseType !== EXEC_ED_COURSE_TYPE)) { firstMenuItems.push({ key: 'email-settings', type: 'button', @@ -124,7 +125,7 @@ class BaseCourseCard extends Component { if (pacing) { message += 'This course '; message += isCourseEnded ? 'was ' : 'is '; - message += `${pacing}-paced. `; + message += `${pacing}-led. `; } if (dateMessage) { message += dateMessage; @@ -199,7 +200,7 @@ class BaseCourseCard extends Component { renderUnenrollModal = () => { const { - canUnenroll, courseRunId, type, + canUnenroll, courseRunId, type, courseType, productSource, } = this.props; const { modals } = this.state; @@ -210,6 +211,8 @@ class BaseCourseCard extends Component { return ( { - const { title } = this.props; + const { title, courseType, productSource } = this.props; + const execEdClass = courseType === EXEC_ED_COURSE_TYPE && productSource === PRODUCT_SOURCE_2U ? 'text-light-100' : ''; if (menuItems && menuItems.length > 0) { return (
@@ -252,6 +256,7 @@ class BaseCourseCard extends Component { iconAs={Icon} alt={`course settings for ${title}`} id="course-enrollment-card-settings-dropdown-toggle" + iconClassNames={execEdClass} /> {menuItems.map(menuItem => ( @@ -320,12 +325,24 @@ class BaseCourseCard extends Component { return null; }; + renderIsCourseStarted = () => { + const { startDate, courseType, productSource } = this.props; + const formattedStartDate = startDate ? moment(startDate).format('MMMM Do, YYYY') : null; + + if (courseType === EXEC_ED_COURSE_TYPE && productSource === PRODUCT_SOURCE_2U) { + return <>• Start date: {formattedStartDate}; + } + + return null; + }; + renderOrganizationName = () => { const { orgName, mode } = this.props; const isExecutiveEducation2UCourse = EXECUTIVE_EDUCATION_COURSE_MODES.includes(mode); + const execEdClass = isExecutiveEducation2UCourse ? 'text-light-300' : ''; if (orgName) { - return

{orgName} {isExecutiveEducation2UCourse && <>• Executive Education}

; + return

{orgName} {isExecutiveEducation2UCourse && <>• Executive Education} {this.renderIsCourseStarted()}

; } return null; }; @@ -403,7 +420,7 @@ class BaseCourseCard extends Component { {this.renderMicroMastersTitle()}

- {title} + {title}

{ BADGE_PROPS_BY_COURSE_STATUS[type] && ( @@ -421,7 +438,7 @@ class BaseCourseCard extends Component { {this.renderButtons()} {this.renderChildren()}
-
+
{this.getCourseMiscText()} @@ -448,6 +465,8 @@ BaseCourseCard.propTypes = { buttons: PropTypes.element, children: PropTypes.node, startDate: PropTypes.string, + courseType: PropTypes.string, + productSource: PropTypes.string, endDate: PropTypes.string, hasEmailsEnabled: PropTypes.bool, canUnenroll: PropTypes.bool, @@ -469,6 +488,8 @@ BaseCourseCard.contextType = AppContext; BaseCourseCard.defaultProps = { children: null, startDate: null, + courseType: null, + productSource: null, endDate: null, hasEmailsEnabled: null, canUnenroll: null, diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/CompletedCourseCard.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/CompletedCourseCard.jsx index 0ede7c7a4..415665520 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/CompletedCourseCard.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/CompletedCourseCard.jsx @@ -16,7 +16,10 @@ const CompletedCourseCard = (props) => { title, linkToCourse, courseRunId, + startDate, endDate, + courseType, + productSource, } = props; const config = getConfig(); @@ -30,6 +33,9 @@ const CompletedCourseCard = (props) => { linkToCourse={linkToCourse} title={title} courseRunId={courseRunId} + courseType={courseType} + productSource={productSource} + startDate={startDate} /> ); }; @@ -64,6 +70,9 @@ const CompletedCourseCard = (props) => { buttons={renderButtons()} type="completed" hasViewCertificateLink={false} + courseType={courseType} + productSource={productSource} + startDate={startDate} {...props} > {renderCertificateInfo()} @@ -78,11 +87,17 @@ CompletedCourseCard.propTypes = { linkToCertificate: PropTypes.string, courseRunStatus: PropTypes.string.isRequired, endDate: PropTypes.string, + startDate: PropTypes.string, + courseType: PropTypes.string, + productSource: PropTypes.string, }; CompletedCourseCard.defaultProps = { linkToCertificate: null, endDate: null, + startDate: null, + courseType: null, + productSource: null, }; export default CompletedCourseCard; diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/ContinueLearningButton.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/ContinueLearningButton.jsx index e9c28922a..7debb9d41 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/ContinueLearningButton.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/ContinueLearningButton.jsx @@ -4,6 +4,8 @@ import { AppContext } from '@edx/frontend-platform/react'; import classNames from 'classnames'; import { sendEnterpriseTrackEvent } from '@edx/frontend-enterprise-utils'; +import moment from 'moment'; +import { EXEC_ED_COURSE_TYPE, PRODUCT_SOURCE_2U } from '../data/constants'; /** * A 'Continue Learning' button with parameters. @@ -20,6 +22,9 @@ const ContinueLearningButton = ({ linkToCourse, title, courseRunId, + courseType, + productSource, + startDate, }) => { const { enterpriseConfig } = useContext(AppContext); @@ -32,13 +37,26 @@ const ContinueLearningButton = ({ }, ); }; + + const isCourseStarted = () => moment(startDate) <= moment(); + + const execClassName = (courseType === EXEC_ED_COURSE_TYPE && productSource === PRODUCT_SOURCE_2U) && (!isCourseStarted()) ? ' disabled btn-outline-secondary' : undefined; + + const renderContent = () => { + if ((courseType === EXEC_ED_COURSE_TYPE && productSource === PRODUCT_SOURCE_2U) && !isCourseStarted()) { + const formattedStartDate = moment(startDate).format('MMM D, YYYY'); + return `Available on ${formattedStartDate}`; + } + return 'Resume'; + }; + return ( - Resume + {renderContent()} for {title} ); @@ -46,6 +64,9 @@ const ContinueLearningButton = ({ ContinueLearningButton.defaultProps = { className: 'btn-outline-primary', + startDate: null, + courseType: null, + productSource: null, }; ContinueLearningButton.propTypes = { @@ -53,6 +74,9 @@ ContinueLearningButton.propTypes = { linkToCourse: PropTypes.string.isRequired, title: PropTypes.string.isRequired, courseRunId: PropTypes.string.isRequired, + startDate: PropTypes.string, + courseType: PropTypes.string, + productSource: PropTypes.string, }; export default ContinueLearningButton; diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/InProgressCourseCard.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/InProgressCourseCard.jsx index 1ed35d203..e98aba98c 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/InProgressCourseCard.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/InProgressCourseCard.jsx @@ -20,6 +20,9 @@ export const InProgressCourseCard = ({ title, notifications, courseRunStatus, + startDate, + courseType, + productSource, ...rest }) => { const { @@ -45,6 +48,9 @@ export const InProgressCourseCard = ({ linkToCourse={licenseUpgradeUrl ?? linkToCourse} title={title} courseRunId={courseRunId} + courseType={courseType} + productSource={productSource} + startDate={startDate} /> {shouldShowUpgradeButton && } @@ -152,6 +158,9 @@ export const InProgressCourseCard = ({ linkToCourse={licenseUpgradeUrl ?? linkToCourse} courseRunId={courseRunId} isLoading={isLoadingUpgradeUrl} + courseType={courseType} + productSource={productSource} + startDate={startDate} {...rest} > {renderNotifications()} @@ -177,6 +186,15 @@ InProgressCourseCard.propTypes = { })).isRequired, title: PropTypes.string.isRequired, courseRunStatus: PropTypes.string.isRequired, + startDate: PropTypes.string, + courseType: PropTypes.string, + productSource: PropTypes.string, +}; + +InProgressCourseCard.defaultProps = { + startDate: null, + courseType: null, + productSource: null, }; export default InProgressCourseCard; diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/RequestedCourseCard.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/RequestedCourseCard.jsx index becab65b8..a1f39f65c 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/RequestedCourseCard.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/RequestedCourseCard.jsx @@ -21,12 +21,18 @@ RequestedCourseCard.propTypes = { isRevoked: PropTypes.bool, courseRunStatus: PropTypes.string.isRequired, endDate: PropTypes.string, + startDate: PropTypes.string, + courseType: PropTypes.string, + productSource: PropTypes.string, }; RequestedCourseCard.defaultProps = { linkToCertificate: null, endDate: null, isRevoked: false, + startDate: null, + courseType: null, + productSource: null, }; export default RequestedCourseCard; diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/SavedForLaterCourseCard.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/SavedForLaterCourseCard.jsx index f5fcbdcbb..c7fc32f2c 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/SavedForLaterCourseCard.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/SavedForLaterCourseCard.jsx @@ -19,6 +19,9 @@ const SavedForLaterCourseCard = (props) => { courseRunStatus, endDate, isRevoked, + startDate, + courseType, + productSource, } = props; const { updateCourseEnrollmentStatus, @@ -100,6 +103,9 @@ const SavedForLaterCourseCard = (props) => { linkToCourse={linkToCourse} title={title} courseRunId={courseRunId} + courseType={courseType} + productSource={productSource} + startDate={startDate} /> ); }; @@ -110,6 +116,9 @@ const SavedForLaterCourseCard = (props) => { dropdownMenuItems={getDropdownMenuItems()} type={COURSE_STATUSES.savedForLater} hasViewCertificateLink={false} + courseType={courseType} + productSource={productSource} + startDate={startDate} {...props} > ', () => { expect(wrapper.find(Skeleton)).toBeTruthy(); }); + + describe('Executive-education BaseCard', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders with different startDate values', () => { + const today = new Date().toISOString(); + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const tomorrow = new Date(); + tomorrow.setDate(tomorrow.getDate() + 1); + + [today, yesterday, tomorrow].forEach(startDate => { + wrapper = mount(( + + + + )); + + const hasCourseStarted = wrapper.instance().renderIsCourseStarted(); + expect(hasCourseStarted).toBeTruthy(); + }); + }); + }); }); diff --git a/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.jsx b/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.jsx index b4bcee256..d49400fc0 100644 --- a/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.jsx +++ b/src/components/dashboard/main-content/course-enrollments/course-cards/unenroll/UnenrollModal.jsx @@ -8,6 +8,7 @@ import { logError } from '@edx/frontend-platform/logging'; import { CourseEnrollmentsContext } from '../../CourseEnrollmentsContextProvider'; import { ToastsContext } from '../../../../../Toasts'; import { unenrollFromCourse } from './data'; +import { EXEC_ED_COURSE_TYPE, GETSMARTER_BASE_URL, PRODUCT_SOURCE_2U } from '../../data/constants'; const btnLabels = { default: 'Unenroll', @@ -16,6 +17,8 @@ const btnLabels = { const UnenrollModal = ({ courseRunId, + courseType, + productSource, enrollmentType, isOpen, onClose, @@ -34,14 +37,18 @@ const UnenrollModal = ({ }; const handleUnenrollButtonClick = async () => { - setBtnState('pending'); try { - await unenrollFromCourse({ - courseId: courseRunId, - }); - removeCourseEnrollment({ courseRunId, enrollmentType }); - addToast('You have been unenrolled from the course.'); - onSuccess(); + if (courseType === EXEC_ED_COURSE_TYPE && productSource === PRODUCT_SOURCE_2U) { + window.location.href = `${GETSMARTER_BASE_URL}/cancel-defer`; + } else { + setBtnState('pending'); + await unenrollFromCourse({ + courseId: courseRunId, + }); + removeCourseEnrollment({ courseRunId, enrollmentType }); + addToast('You have been unenrolled from the course.'); + onSuccess(); + } } catch (err) { logError(err); setError(err); @@ -96,6 +103,13 @@ UnenrollModal.propTypes = { isOpen: PropTypes.bool.isRequired, onClose: PropTypes.func.isRequired, onSuccess: PropTypes.func.isRequired, + courseType: PropTypes.string, + productSource: PropTypes.string, +}; + +UnenrollModal.defaultProps = { + courseType: null, + productSource: null, }; export default UnenrollModal; diff --git a/src/components/dashboard/main-content/course-enrollments/data/constants.js b/src/components/dashboard/main-content/course-enrollments/data/constants.js index 20ecef806..b789b055b 100644 --- a/src/components/dashboard/main-content/course-enrollments/data/constants.js +++ b/src/components/dashboard/main-content/course-enrollments/data/constants.js @@ -14,3 +14,7 @@ export const COURSE_STATUSES = { // Not a real course status, represents a subsidy request. requested: 'requested', }; + +export const EXEC_ED_COURSE_TYPE = 'executive-education-2u'; +export const PRODUCT_SOURCE_2U = '2u'; +export const GETSMARTER_BASE_URL = 'https://www.getsmarter.com';