diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx index 5caeeefa..35f28e60 100644 --- a/src/course-home/outline-tab/OutlineTab.jsx +++ b/src/course-home/outline-tab/OutlineTab.jsx @@ -16,7 +16,6 @@ import CourseTools from './widgets/CourseTools'; import { fetchOutlineTab } from '../data'; import messages from './messages'; import ShiftDatesAlert from '../suggested-schedule-messaging/ShiftDatesAlert'; -import UpgradeNotification from '../../generic/upgrade-notification/UpgradeNotification'; import UpgradeToShiftDatesAlert from '../suggested-schedule-messaging/UpgradeToShiftDatesAlert'; import useCertificateAvailableAlert from './alerts/certificate-status-alert'; import useCourseEndAlert from './alerts/course-end-alert'; @@ -40,13 +39,11 @@ const OutlineTab = () => { isSelfPaced, org, title, - userTimezone, } = useModel('courseHomeMeta', courseId); const expandButtonRef = useRef(); const { - accessExpiration, courseBlocks: { courses, sections, @@ -55,20 +52,12 @@ const OutlineTab = () => { selectedGoal, weeklyLearningGoalEnabled, } = {}, - datesBannerInfo, datesWidget: { courseDateBlocks, }, enableProctoredExams, - offer, - timeOffsetMillis, - verifiedMode, } = useModel('outline', courseId); - const { - marketingUrl, - } = useModel('coursewareMeta', courseId); - const [expandAll, setExpandAll] = useState(false); const navigate = useNavigate(); @@ -198,21 +187,7 @@ const OutlineTab = () => { courseId, model: 'outline', }} - > - - + /> diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx index 458bc644..8a95e66a 100644 --- a/src/course-home/outline-tab/OutlineTab.test.jsx +++ b/src/course-home/outline-tab/OutlineTab.test.jsx @@ -5,7 +5,7 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { Factory } from 'rosie'; import { getConfig } from '@edx/frontend-platform'; -import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; +import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import MockAdapter from 'axios-mock-adapter'; import Cookies from 'js-cookie'; @@ -1190,80 +1190,6 @@ describe('Outline Tab', () => { }); }); - describe('Upgrade Card', () => { - it('renders title when upgrade is available', async () => { - await fetchAndRender(); - expect(screen.queryByRole('heading', { name: 'Pursue a verified certificate' })).toBeInTheDocument(); - }); - - it('displays link to upgrade', async () => { - await fetchAndRender(); - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - }); - - it('viewing upgrade card sends analytics', async () => { - sendTrackEvent.mockClear(); - sendTrackingLogEvent.mockClear(); - await fetchAndRender(); - - expect(sendTrackEvent).toHaveBeenCalledTimes(1); - expect(sendTrackEvent).toHaveBeenCalledWith('Promotion Viewed', { - org_key: 'edX', - courserun_key: courseId, - creative: 'sidebarupsell', - name: 'In-Course Verification Prompt', - position: 'sidebar-message', - promotion_id: 'courseware_verified_certificate_upsell', - }); - - expect(sendTrackingLogEvent).toHaveBeenCalledTimes(1); - expect(sendTrackingLogEvent).toHaveBeenCalledWith('edx.bi.course.upgrade.sidebarupsell.displayed', { - org_key: 'edX', - courserun_key: courseId, - }); - }); - - it('clicking upgrade link sends analytics', async () => { - await fetchAndRender(); - - // Clearing after render to remove any events sent on view (ex. 'Promotion Viewed') - sendTrackEvent.mockClear(); - sendTrackingLogEvent.mockClear(); - const upgradeButton = screen.getByRole('link', { name: 'Upgrade for $149' }); - - fireEvent.click(upgradeButton); - - expect(sendTrackEvent).toHaveBeenCalledTimes(2); - expect(sendTrackEvent).toHaveBeenNthCalledWith(1, 'Promotion Clicked', { - org_key: 'edX', - courserun_key: courseId, - creative: 'sidebarupsell', - name: 'In-Course Verification Prompt', - position: 'sidebar-message', - promotion_id: 'courseware_verified_certificate_upsell', - }); - expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.bi.ecommerce.upsell_links_clicked', { - org_key: 'edX', - courserun_key: courseId, - linkCategory: 'green_upgrade', - linkName: 'course_home_green', - linkType: 'button', - pageName: 'course_home', - }); - - expect(sendTrackingLogEvent).toHaveBeenCalledTimes(2); - expect(sendTrackingLogEvent).toHaveBeenNthCalledWith(1, 'edx.bi.course.upgrade.sidebarupsell.clicked', { - org_key: 'edX', - courserun_key: courseId, - }); - expect(sendTrackingLogEvent).toHaveBeenNthCalledWith(2, 'edx.course.enrollment.upgrade.clicked', { - org_key: 'edX', - courserun_key: courseId, - location: 'sidebar-message', - }); - }); - }); - describe('Account Activation Alert', () => { beforeEach(() => { const intersectionObserverMock = () => ({ diff --git a/src/courseware/course/Course.test.jsx b/src/courseware/course/Course.test.jsx index c428be0e..d9987c1b 100644 --- a/src/courseware/course/Course.test.jsx +++ b/src/courseware/course/Course.test.jsx @@ -148,18 +148,6 @@ describe('Course', () => { }); }); - it('displays notification trigger and toggles active class on click', async () => { - render(, { wrapWithRouter: true }); - - waitFor(() => { - const notificationTrigger = screen.getByRole('button', { name: /Show notification tray/i }); - expect(notificationTrigger).toBeInTheDocument(); - expect(notificationTrigger.parentNode).not.toHaveClass('sidebar-active', { exact: true }); - fireEvent.click(notificationTrigger); - expect(notificationTrigger.parentNode).toHaveClass('sidebar-active'); - }); - }); - it('handles click to open/close discussions sidebar', async () => { await setupDiscussionSidebar(); diff --git a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.test.tsx b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.test.tsx index 23b437d2..ce89c30a 100644 --- a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.test.tsx +++ b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.test.tsx @@ -9,7 +9,7 @@ import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; import { breakpoints } from '@openedx/paragon'; import { - initializeMockApp, render, screen, within, act, fireEvent, waitFor, + initializeMockApp, render, screen, act, fireEvent, waitFor, } from '../../../../../../setupTest'; import initializeStore from '../../../../../../store'; import { appendBrowserTimezoneToUrl, executeThunk } from '../../../../../../utils'; @@ -93,27 +93,6 @@ describe('NotificationsWidget', () => { expect(screen.getByTestId('notification_widget_slot')).toBeInTheDocument(); }); - it('renders upgrade card', async () => { - const contextData: Partial = { - currentSidebar: ID, - courseId, - hideNotificationbar: false, - isNotificationbarAvailable: true, - }; - await fetchAndRender( - - - , - ); - - // The Upgrade Notification should be inside the PluginSlot. - const UpgradeNotification = document.querySelector('.upgrade-notification'); - expect(UpgradeNotification).toBeInTheDocument(); - - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - expect(screen.queryByText('You have no new notifications at this time.')).not.toBeInTheDocument(); - }); - it('renders no notifications bar if no verified mode', async () => { setMetadata({ verified_mode: null }); const contextData: Partial = { @@ -130,44 +109,6 @@ describe('NotificationsWidget', () => { expect(screen.queryByText('Notifications')).not.toBeInTheDocument(); }); - it.each([ - { - description: 'close the notification widget.', - enabledInContext: true, - testId: 'notification-widget', - }, - { - description: 'close the sidebar when the notification widget is closed, and the discussion widget is unavailable.', - enabledInContext: false, - testId: 'sidebar-DISCUSSIONS_NOTIFICATIONS', - }, - ])('successfully %s', async ({ enabledInContext, testId }) => { - const userVerifiedMode = Factory.build('verifiedMode'); - - await setupDiscussionSidebar({ - verifiedMode: userVerifiedMode, - enabledInContext, - isNewDiscussionSidebarViewEnabled: true, - }); - - const sidebarButton = screen.getByRole('button', { name: /Show sidebar tray/i }); - - await act(async () => { - fireEvent.click(sidebarButton); - }); - - const notificationWidget = await waitFor(() => screen.getByTestId('notification-widget')); - const closeNotificationButton = within(notificationWidget).getByRole('button', { name: /Close/i }); - - await act(async () => { - fireEvent.click(closeNotificationButton); - }); - - await waitFor(() => { - expect(screen.queryByTestId(testId)).not.toBeInTheDocument(); - }); - }); - it('marks notification as seen 3 seconds later', async () => { const onNotificationSeen = jest.fn(); const contextData: Partial = { diff --git a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.tsx b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.tsx index 8b822c82..50644782 100644 --- a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.tsx +++ b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.tsx @@ -3,7 +3,6 @@ import React, { useContext, useEffect, useMemo } from 'react'; import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { PluginSlot } from '@openedx/frontend-plugin-framework'; import { useModel } from '../../../../../../generic/model-store'; -import UpgradeNotification from '../../../../../../generic/upgrade-notification/UpgradeNotification'; import { WIDGETS } from '../../../../../../constants'; import SidebarContext from '../../../SidebarContext'; @@ -21,17 +20,11 @@ const NotificationsWidget = () => { const course = useModel('coursewareMeta', courseId); const { - accessExpiration, - contentTypeGatingEnabled, end, enrollmentEnd, enrollmentMode, enrollmentStart, - marketingUrl, - offer, start, - timeOffsetMillis, - userTimezone, verificationStatus, } = course; @@ -83,23 +76,7 @@ const NotificationsWidget = () => { setNotificationCurrentState: setUpgradeNotificationCurrentState, toggleSidebar: onToggleSidebar, }} - > - - + /> ); }; diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx index 1fbc8301..ef01e13e 100644 --- a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx +++ b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx @@ -5,7 +5,6 @@ import { sendTrackEvent } from '@edx/frontend-platform/analytics'; import { PluginSlot } from '@openedx/frontend-plugin-framework'; import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; import { useModel } from '@src/generic/model-store'; -import UpgradeNotification from '../../../../../generic/upgrade-notification/UpgradeNotification'; import messages from '../../../messages'; import SidebarBase from '../../common/SidebarBase'; @@ -23,17 +22,11 @@ const NotificationTray = ({ intl }) => { const course = useModel('coursewareMeta', courseId); const { - accessExpiration, - contentTypeGatingEnabled, end, enrollmentEnd, enrollmentMode, enrollmentStart, - marketingUrl, - offer, start, - timeOffsetMillis, - userTimezone, verificationStatus, } = course; @@ -90,23 +83,7 @@ const NotificationTray = ({ intl }) => { notificationCurrentState: upgradeNotificationCurrentState, setNotificationCurrentState: setUpgradeNotificationCurrentState, }} - > - - + /> ) : (

{intl.formatMessage(messages.noNotificationsMessage)}

)} diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx index 79a75663..269a35f7 100644 --- a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx +++ b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.test.jsx @@ -94,26 +94,6 @@ describe('NotificationTray', () => { expect(screen.getByTestId('notification_tray_slot')).toBeInTheDocument(); }); - it('renders upgrade card', async () => { - await fetchAndRender( - - - , - ); - - expect(document.querySelector('.upgrade-notification')).toBeInTheDocument(); - - expect(screen.getByRole('link', { name: 'Upgrade for $149' })) - .toBeInTheDocument(); - expect(screen.queryByText('You have no new notifications at this time.')) - .not - .toBeInTheDocument(); - }); - it('renders no notifications message if no verified mode', async () => { setMetadata({ verified_mode: null }); await fetchAndRender( diff --git a/src/generic/upgrade-notification/UpgradeNotification.jsx b/src/generic/upgrade-notification/UpgradeNotification.jsx deleted file mode 100644 index 13e785c8..00000000 --- a/src/generic/upgrade-notification/UpgradeNotification.jsx +++ /dev/null @@ -1,573 +0,0 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { useIntl, FormattedDate, FormattedMessage } from '@edx/frontend-platform/i18n'; -import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; -import { Button, Icon, IconButton } from '@openedx/paragon'; -import { Close, Launch } from '@openedx/paragon/icons'; -import { setLocalStorage } from '../../data/localStorage'; -import { UpgradeButton } from '../upgrade-button'; -import { - VerifiedCertBullet, - UnlockGradedBullet, - FullAccessBullet, - SupportMissionBullet, -} from '../upsell-bullets/UpsellBullets'; -import messages from '../messages'; - -const UpsellNoFBECardContent = () => ( -
    - - -
-); - -const UpsellFBEFarAwayCardContent = () => ( -
    - - - - -
-); - -const UpsellFBESoonCardContent = ({ accessExpirationDate, timezoneFormatArgs }) => { - const includingAnyProgress = ( - - - - ); - - const date = ( - - ); - - const benefitsOfUpgrading = ( - - - - - - - ); - - return ( -
-

- -

-

- -

-
- ); -}; - -UpsellFBESoonCardContent.propTypes = { - accessExpirationDate: PropTypes.PropTypes.instanceOf(Date).isRequired, - timezoneFormatArgs: PropTypes.shape({ - timeZone: PropTypes.string, - }), -}; - -UpsellFBESoonCardContent.defaultProps = { - timezoneFormatArgs: {}, -}; - -const PastExpirationCardContent = () => ( -
-

- -

-
-); - -const ExpirationCountdown = ({ - courseId, hoursToExpiration, setupgradeNotificationCurrentState, type, -}) => { - let expirationText; - if (hoursToExpiration >= 24) { // More than 1 day left - // setupgradeNotificationCurrentState is available in NotificationTray (not course home) - if (setupgradeNotificationCurrentState) { - if (type === 'access') { - setupgradeNotificationCurrentState('accessDaysLeft'); - setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'accessDaysLeft'); - } - if (type === 'offer') { - setupgradeNotificationCurrentState('FPDdaysLeft'); - setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'FPDdaysLeft'); - } - } - expirationText = ( - - ); - } else if (hoursToExpiration >= 1) { // More than 1 hour left - // setupgradeNotificationCurrentState is available in NotificationTray (not course home) - if (setupgradeNotificationCurrentState) { - if (type === 'access') { - setupgradeNotificationCurrentState('accessHoursLeft'); - setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'accessHoursLeft'); - } - if (type === 'offer') { - setupgradeNotificationCurrentState('FPDHoursLeft'); - setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'FPDHoursLeft'); - } - } - expirationText = ( - - ); - } else { // Less than 1 hour - // setupgradeNotificationCurrentState is available in NotificationTray (not course home) - if (setupgradeNotificationCurrentState) { - if (type === 'access') { - setupgradeNotificationCurrentState('accessLastHour'); - setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'accessLastHour'); - } - if (type === 'offer') { - setupgradeNotificationCurrentState('FPDLastHour'); - setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'FPDLastHour'); - } - } - expirationText = ( - - ); - } - return (
{expirationText}
); -}; - -ExpirationCountdown.propTypes = { - courseId: PropTypes.string.isRequired, - hoursToExpiration: PropTypes.number.isRequired, - setupgradeNotificationCurrentState: PropTypes.func, - type: PropTypes.string, -}; -ExpirationCountdown.defaultProps = { - setupgradeNotificationCurrentState: null, - type: null, -}; - -const AccessExpirationDateBanner = ({ - courseId, accessExpirationDate, timezoneFormatArgs, setupgradeNotificationCurrentState, -}) => { - if (setupgradeNotificationCurrentState) { - setupgradeNotificationCurrentState('accessDateView'); - setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'accessDateView'); - } - return ( -
- - ), - }} - /> -
- ); -}; - -AccessExpirationDateBanner.propTypes = { - courseId: PropTypes.string.isRequired, - accessExpirationDate: PropTypes.PropTypes.instanceOf(Date).isRequired, - timezoneFormatArgs: PropTypes.shape({ - timeZone: PropTypes.string, - }), - setupgradeNotificationCurrentState: PropTypes.func, -}; - -AccessExpirationDateBanner.defaultProps = { - timezoneFormatArgs: {}, - setupgradeNotificationCurrentState: null, -}; - -const PastExpirationDateBanner = ({ - courseId, accessExpirationDate, timezoneFormatArgs, setupgradeNotificationCurrentState, -}) => { - if (setupgradeNotificationCurrentState) { - setupgradeNotificationCurrentState('PastExpirationDate'); - setLocalStorage(`upgradeNotificationCurrentState.${courseId}`, 'PastExpirationDate'); - } - return ( -
- - ), - }} - /> -
- ); -}; - -PastExpirationDateBanner.propTypes = { - courseId: PropTypes.string.isRequired, - accessExpirationDate: PropTypes.PropTypes.instanceOf(Date).isRequired, - timezoneFormatArgs: PropTypes.shape({ - timeZone: PropTypes.string, - }), - setupgradeNotificationCurrentState: PropTypes.func, -}; - -PastExpirationDateBanner.defaultProps = { - timezoneFormatArgs: {}, - setupgradeNotificationCurrentState: null, -}; - -const UpgradeNotification = ({ - accessExpiration, - contentTypeGatingEnabled, - marketingUrl, - courseId, - offer, - org, - setupgradeNotificationCurrentState, - shouldDisplayBorder, - timeOffsetMillis, - upsellPageName, - userTimezone, - verifiedMode, - toggleSidebar, -}) => { - const intl = useIntl(); - const dateNow = Date.now(); - const timezoneFormatArgs = userTimezone ? { timeZone: userTimezone } : {}; - const correctedTime = new Date(dateNow + timeOffsetMillis); - const accessExpirationDate = accessExpiration ? new Date(accessExpiration.expirationDate) : null; - const pastExpirationDeadline = accessExpiration ? new Date(dateNow) > accessExpirationDate : false; - - const eventProperties = { - org_key: org, - courserun_key: courseId, - }; - - const promotionEventProperties = { - creative: 'sidebarupsell', - name: 'In-Course Verification Prompt', - position: 'sidebar-message', - promotion_id: 'courseware_verified_certificate_upsell', - ...eventProperties, - }; - - useEffect(() => { - sendTrackingLogEvent('edx.bi.course.upgrade.sidebarupsell.displayed', eventProperties); - sendTrackEvent('Promotion Viewed', promotionEventProperties); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - if (!verifiedMode) { - return null; - } - - const logClick = () => { - sendTrackingLogEvent('edx.bi.course.upgrade.sidebarupsell.clicked', eventProperties); - sendTrackingLogEvent('edx.course.enrollment.upgrade.clicked', { - ...eventProperties, - location: 'sidebar-message', - }); - sendTrackEvent('Promotion Clicked', promotionEventProperties); - sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', { - ...eventProperties, - linkCategory: 'green_upgrade', - linkName: `${upsellPageName}_green`, - linkType: 'button', - pageName: upsellPageName, - }); - }; - - const logClickPastExpiration = () => { - sendTrackEvent('edx.bi.ecommerce.upgrade_notification.past_expiration.button_clicked', { - ...eventProperties, - linkCategory: 'upgrade_notification', - linkName: `${upsellPageName}_course_details`, - linkType: 'button', - pageName: upsellPageName, - }); - }; - - /* - There are 5 parts that change in the upgrade card: - upgradeNotificationHeaderText - expirationBanner - upsellMessage - callToActionButton - offerCode - */ - let upgradeNotificationHeaderText; - let expirationBanner; - let upsellMessage; - let callToActionButton; - let offerCode; - - if (!!accessExpiration && !!contentTypeGatingEnabled) { - const hoursToAccessExpiration = Math.floor((accessExpirationDate - correctedTime) / 1000 / 60 / 60); - - if (hoursToAccessExpiration >= (7 * 24)) { - if (offer) { // countdown to the first purchase discount if there is one - const hoursToDiscountExpiration = Math.floor((new Date(offer.expirationDate) - correctedTime) / 1000 / 60 / 60); - upgradeNotificationHeaderText = ( - - ); - expirationBanner = ( - - ); - } else { - upgradeNotificationHeaderText = ( - - ); - expirationBanner = ( - - ); - } - upsellMessage = ; - } else if (hoursToAccessExpiration < (7 * 24) && hoursToAccessExpiration >= 0) { - // more urgent messaging if there's less than 7 days left to access expiration - upgradeNotificationHeaderText = ( - - ); - expirationBanner = ( - - ); - upsellMessage = ( - - ); - } else { // access expiration deadline has passed - upgradeNotificationHeaderText = ( - - ); - expirationBanner = ( - - ); - upsellMessage = ( - - ); - } - } else { // FBE is turned off - upgradeNotificationHeaderText = ( - - ); - upsellMessage = (); - } - - if (pastExpirationDeadline) { - callToActionButton = ( - - ); - } else { - callToActionButton = ( - - ); - } - - if (offer) { // if there's a first purchase discount, message the code at the bottom - offerCode = ( -
- {offer.code}), - }} - /> -
- ); - } - - return ( -
-
-

- {upgradeNotificationHeaderText} - {!!toggleSidebar && ( -
- -
- )} -

- {expirationBanner} -
- {upsellMessage} -
-
- {callToActionButton} -
- {offerCode} -
-
- ); -}; - -UpgradeNotification.propTypes = { - courseId: PropTypes.string.isRequired, - org: PropTypes.string.isRequired, - accessExpiration: PropTypes.shape({ - expirationDate: PropTypes.string, - }), - contentTypeGatingEnabled: PropTypes.bool, - marketingUrl: PropTypes.string, - offer: PropTypes.shape({ - expirationDate: PropTypes.string, - percentage: PropTypes.number, - code: PropTypes.string, - }), - toggleSidebar: PropTypes.func, - shouldDisplayBorder: PropTypes.bool, - setupgradeNotificationCurrentState: PropTypes.func, - timeOffsetMillis: PropTypes.number, - upsellPageName: PropTypes.string.isRequired, - userTimezone: PropTypes.string, - verifiedMode: PropTypes.shape({ - currencySymbol: PropTypes.string.isRequired, - price: PropTypes.number.isRequired, - upgradeUrl: PropTypes.string.isRequired, - }), -}; - -UpgradeNotification.defaultProps = { - accessExpiration: null, - contentTypeGatingEnabled: false, - marketingUrl: null, - offer: null, - setupgradeNotificationCurrentState: null, - shouldDisplayBorder: null, - timeOffsetMillis: 0, - userTimezone: null, - verifiedMode: null, - toggleSidebar: null, -}; - -export default UpgradeNotification; diff --git a/src/generic/upgrade-notification/UpgradeNotification.scss b/src/generic/upgrade-notification/UpgradeNotification.scss deleted file mode 100644 index ee706438..00000000 --- a/src/generic/upgrade-notification/UpgradeNotification.scss +++ /dev/null @@ -1,46 +0,0 @@ -.upgrade-notification { - border-radius: 0 !important; -} - -.upgrade-notification-header { - margin: 1.25rem; -} - -.upsell-warning { - background-color: $danger-100; -} - -.upsell-warning-light { - background-color: $warning-100; -} - -.upsell-warning, .upsell-warning-light { - padding: 0.5rem 1.25rem; -} - -// .fa-ul added so specificity is higher than Font Awesome's .fa-ul. -// An additional Font Awesome stylesheet is imported by Braze in -// stage/production but not devstack. -.upgrade-notification-ul.fa-ul { - padding: 0.875rem 1.25rem 0; - margin: 0 0 1rem 2.5rem; -} - -.upgrade-notification-text { - padding: 0.875rem 1.25rem 0 1.25rem; -} - -.upgrade-notification-button { - padding: 1.25rem; - padding-top: 0; -} - -.discount-info { - border-top: 1px solid $light-400; - padding-top: .75rem; - padding-bottom: .75rem; -} - -.font-size-18 { - font-size: 18px !important; -} diff --git a/src/generic/upgrade-notification/UpgradeNotification.test.jsx b/src/generic/upgrade-notification/UpgradeNotification.test.jsx deleted file mode 100644 index 17d38f00..00000000 --- a/src/generic/upgrade-notification/UpgradeNotification.test.jsx +++ /dev/null @@ -1,322 +0,0 @@ -import React from 'react'; -import { Factory } from 'rosie'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; - -import { - fireEvent, - initializeMockApp, - render, - screen, - waitFor, -} from '../../setupTest'; -import UpgradeNotification from './UpgradeNotification'; - -initializeMockApp(); -jest.mock('@edx/frontend-platform/analytics'); -const dateNow = new Date('2021-04-13T11:01:58.000Z'); -jest - .spyOn(global.Date, 'now') - .mockImplementation(() => dateNow.valueOf()); - -describe('Upgrade Notification', () => { - function buildAndRender(attributes) { - const upgradeNotificationData = Factory.build('upgradeNotificationData', { ...attributes }); - render(); - } - - it('sends upgrade click info to segment', async () => { - sendTrackEvent.mockClear(); - buildAndRender({ pageName: 'test' }); - - const upgradeButton = await waitFor(() => screen.queryByRole('link', { name: 'Upgrade for $149' })); - fireEvent.click(upgradeButton); - - expect(sendTrackEvent).toHaveBeenCalledTimes(3); - expect(sendTrackEvent).toHaveBeenNthCalledWith(3, 'edx.bi.ecommerce.upsell_links_clicked', { - org_key: 'edX', - courserun_key: 'course-v1:edX+DemoX+Demo_Course', - linkCategory: 'green_upgrade', - linkName: 'test_green', - linkType: 'button', - pageName: 'test', - }); - }); - - it('does not render when there is no verified mode', async () => { - buildAndRender({ verifiedMode: null }); - expect(screen.queryByRole('link', { name: 'Upgrade for $149' })).not.toBeInTheDocument(); - }); - - it('renders non-FBE when there is a verified mode but no FBE', async () => { - buildAndRender(); - expect(screen.getByRole('heading', { name: 'Pursue a verified certificate' })).toBeInTheDocument(); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate (learn more in a new tab) of completion to showcase on your resumé'); - expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our mission at edX'); - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - }); - - it('renders non-FBE when there is a verified mode and access expiration, but no content gating', async () => { - const expirationDate = new Date(dateNow); - expirationDate.setMinutes(expirationDate.getMinutes() + 45); - buildAndRender({ - accessExpiration: { - expirationDate: expirationDate.toString(), - }, - }); - expect(screen.getByRole('heading', { name: 'Pursue a verified certificate' })).toBeInTheDocument(); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate (learn more in a new tab) of completion to showcase on your resumé'); - expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our mission at edX'); - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - }); - - it('renders non-FBE when there is a verified mode and content gating, but no access expiration', async () => { - buildAndRender({ - contentTypeGatingEnabled: true, - accessExpiration: null, - }); - expect(screen.getByRole('heading', { name: 'Pursue a verified certificate' })).toBeInTheDocument(); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate (learn more in a new tab) of completion to showcase on your resumé'); - expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our mission at edX'); - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - }); - - it('renders non-FBE with a discount properly', async () => { - const discountExpirationDate = new Date(dateNow); - discountExpirationDate.setDate(discountExpirationDate.getDate() + 6); - buildAndRender({ - offer: { - expirationDate: discountExpirationDate.toString(), - percentage: 15, - code: 'Welcome15', - discountedPrice: '$126.65', - originalPrice: '$149', - upgradeUrl: 'www.exampleUpgradeUrl.com', - }, - }); - expect(screen.getByRole('heading', { name: 'Pursue a verified certificate' })).toBeInTheDocument(); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate (learn more in a new tab) of completion to showcase on your resumé'); - expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our mission at edX'); - expect(screen.getByText(/Upgrade for/).textContent).toMatch('$126.65 ($149)'); - expect(screen.getByText(/Use code.*?at checkout/s).textContent).toMatch('Use code Welcome15 at checkout'); - }); - - it('renders FBE expiration within an hour properly', async () => { - const expirationDate = new Date(dateNow); - expirationDate.setMinutes(expirationDate.getMinutes() + 45); - buildAndRender({ - accessExpiration: { - expirationDate: expirationDate.toString(), - }, - contentTypeGatingEnabled: true, - }); - expect(screen.getByRole('heading', { name: 'Course Access Expiration' })).toBeInTheDocument(); - expect(screen.getByText('Less than 1 hour left')).toBeInTheDocument(); - expect(screen.getByText(/You will lose all access to this course.*?on/s).textContent).toMatch('You will lose all access to this course, including any progress, on April 13.'); - expect(screen.getByText(/Upgrading your course enables you/s).textContent).toMatch('Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the benefits of upgrading in a new tab.'); - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - }); - - it('renders FBE expiration within 24 hours properly', async () => { - const expirationDate = new Date(dateNow); - expirationDate.setHours(expirationDate.getHours() + 12); - buildAndRender({ - accessExpiration: { - expirationDate: expirationDate.toString(), - }, - contentTypeGatingEnabled: true, - }); - expect(screen.getByRole('heading', { name: 'Course Access Expiration' })).toBeInTheDocument(); - expect(screen.getByText('12 hours left')).toBeInTheDocument(); - expect(screen.getByText(/You will lose all access to this course.*?on/s)).toHaveTextContent('You will lose all access to this course, including any progress, on April 13.'); - expect(screen.getByText(/Upgrading your course enables you/s).textContent).toMatch('Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the benefits of upgrading in a new tab.'); - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - }); - - it('renders FBE expiration within 7 days properly', async () => { - const expirationDate = new Date(dateNow); - expirationDate.setDate(expirationDate.getDate() + 6); - buildAndRender({ - accessExpiration: { - expirationDate: expirationDate.toString(), - }, - contentTypeGatingEnabled: true, - }); - expect(screen.getByRole('heading', { name: 'Course Access Expiration' })).toBeInTheDocument(); - expect(screen.getByText('6 days left')).toBeInTheDocument(); // setting the time to 12 will mean that it's slightly less than 12 - expect(screen.getByText(/You will lose all access to this course.*?on/s).textContent).toMatch('You will lose all access to this course, including any progress, on April 19.'); - expect(screen.getByText(/Upgrading your course enables you/s).textContent).toMatch('Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the benefits of upgrading in a new tab.'); - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - }); - - it('renders FBE expiration greater than 7 days properly', async () => { - const expirationDate = new Date(dateNow); - expirationDate.setDate(expirationDate.getDate() + 14); - buildAndRender({ - accessExpiration: { - expirationDate: expirationDate.toString(), - }, - contentTypeGatingEnabled: true, - }); - expect(screen.getByRole('heading', { name: 'Upgrade your course today' })).toBeInTheDocument(); - expect(screen.getByText(/Course access will expire/s).textContent).toMatch('Course access will expire April 27'); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate (learn more in a new tab) of completion to showcase on your resumé'); - expect(screen.getByText(/Unlock your access/s).textContent).toMatch('Unlock your access to all course activities, including graded assignments'); - expect(screen.getByText(/to course content and materials/s).textContent).toMatch('Full access to course content and materials, even after the course ends'); - expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our mission at edX'); - expect(screen.getByRole('link', { name: 'Upgrade for $149' })).toBeInTheDocument(); - }); - - it('renders discount less than an hour properly', async () => { - const accessExpirationDate = new Date(dateNow); - accessExpirationDate.setDate(accessExpirationDate.getDate() + 21); - const discountExpirationDate = new Date(dateNow); - discountExpirationDate.setMinutes(discountExpirationDate.getMinutes() + 30); - buildAndRender({ - accessExpiration: { - expirationDate: accessExpirationDate.toString(), - }, - contentTypeGatingEnabled: true, - offer: { - expirationDate: discountExpirationDate.toString(), - percentage: 15, - code: 'Welcome15', - discountedPrice: '$126.65', - originalPrice: '$149', - upgradeUrl: 'www.exampleUpgradeUrl.com', - }, - }); - expect(screen.getByRole('heading', { name: '15% First-Time Learner Discount' })).toBeInTheDocument(); - expect(screen.getByText('Less than 1 hour left')).toBeInTheDocument(); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate (learn more in a new tab) of completion to showcase on your resumé'); - expect(screen.getByText(/Unlock your access/s).textContent).toMatch('Unlock your access to all course activities, including graded assignments'); - expect(screen.getByText(/to course content and materials/s).textContent).toMatch('Full access to course content and materials, even after the course ends'); - expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our mission at edX'); - expect(screen.getByText(/Upgrade for/).textContent).toMatch('$126.65 ($149)'); - expect(screen.getByText(/Use code.*?at checkout/s).textContent).toMatch('Use code Welcome15 at checkout'); - }); - - it('renders discount less than a day properly', async () => { - const accessExpirationDate = new Date(dateNow); - accessExpirationDate.setDate(accessExpirationDate.getDate() + 21); - const discountExpirationDate = new Date(dateNow); - discountExpirationDate.setHours(discountExpirationDate.getHours() + 12); - buildAndRender({ - accessExpiration: { - expirationDate: accessExpirationDate.toString(), - }, - contentTypeGatingEnabled: true, - offer: { - expirationDate: discountExpirationDate.toString(), - percentage: 15, - code: 'Welcome15', - discountedPrice: '$126.65', - originalPrice: '$149', - upgradeUrl: 'www.exampleUpgradeUrl.com', - }, - }); - expect(screen.getByRole('heading', { name: '15% First-Time Learner Discount' })).toBeInTheDocument(); - expect(screen.getByText(/hours left/s).textContent).toMatch('12 hours left'); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate (learn more in a new tab) of completion to showcase on your resumé'); - expect(screen.getByText(/Unlock your access/s).textContent).toMatch('Unlock your access to all course activities, including graded assignments'); - expect(screen.getByText(/to course content and materials/s).textContent).toMatch('Full access to course content and materials, even after the course ends'); - expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our mission at edX'); - expect(screen.getByText(/Upgrade for/).textContent).toMatch('$126.65 ($149)'); - expect(screen.getByText(/Use code.*?at checkout/s).textContent).toMatch('Use code Welcome15 at checkout'); - }); - - it('renders discount less a week properly', async () => { - const accessExpirationDate = new Date(dateNow); - accessExpirationDate.setDate(accessExpirationDate.getDate() + 21); - const discountExpirationDate = new Date(dateNow); - discountExpirationDate.setDate(discountExpirationDate.getDate() + 6); - buildAndRender({ - accessExpiration: { - expirationDate: accessExpirationDate.toString(), - }, - contentTypeGatingEnabled: true, - offer: { - expirationDate: discountExpirationDate.toString(), - percentage: 15, - code: 'Welcome15', - discountedPrice: '$126.65', - originalPrice: '$149', - upgradeUrl: 'www.exampleUpgradeUrl.com', - }, - }); - expect(screen.getByRole('heading', { name: '15% First-Time Learner Discount' })).toBeInTheDocument(); - expect(screen.getByText(/days left/s).textContent).toMatch('6 days left'); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate (learn more in a new tab) of completion to showcase on your resumé'); - expect(screen.getByText(/Unlock your access/s).textContent).toMatch('Unlock your access to all course activities, including graded assignments'); - expect(screen.getByText(/to course content and materials/s).textContent).toMatch('Full access to course content and materials, even after the course ends'); - expect(screen.getByText(/Support our.*?at edX/s).textContent).toMatch('Support our mission at edX'); - expect(screen.getByText(/Upgrade for/).textContent).toMatch('$126.65 ($149)'); - expect(screen.getByText(/Use code.*?at checkout/s).textContent).toMatch('Use code Welcome15 at checkout'); - }); - - it('renders discount less a week access expiration less than a week properly', async () => { - const accessExpirationDate = new Date(dateNow); - accessExpirationDate.setDate(accessExpirationDate.getDate() + 5); - const discountExpirationDate = new Date(dateNow); - discountExpirationDate.setDate(discountExpirationDate.getDate() + 6); - buildAndRender({ - accessExpiration: { - expirationDate: accessExpirationDate.toString(), - }, - contentTypeGatingEnabled: true, - offer: { - expirationDate: discountExpirationDate.toString(), - percentage: 15, - code: 'Welcome15', - discountedPrice: '$126.65', - originalPrice: '$149', - upgradeUrl: 'www.exampleUpgradeUrl.com', - }, - }); - expect(screen.getByRole('heading', { name: 'Course Access Expiration' })).toBeInTheDocument(); - expect(screen.getByText('5 days left')).toBeInTheDocument(); // setting the time to 12 will mean that it's slightly less than 12 - expect(screen.getByText(/You will lose all access to this course.*?on/s).textContent).toMatch('You will lose all access to this course, including any progress, on April 18.'); - expect(screen.getByText(/Upgrading your course enables you/s).textContent).toMatch('Upgrading your course enables you to pursue a verified certificate and unlocks numerous features. Learn more about the benefits of upgrading in a new tab.'); - expect(screen.getByText(/Upgrade for/).textContent).toMatch('$126.65 ($149)'); - expect(screen.getByText(/Use code.*?at checkout/s).textContent).toMatch('Use code Welcome15 at checkout'); - }); - - it('renders past access expiration message properly', async () => { - const expirationDate = new Date(dateNow); - expirationDate.setDate(expirationDate.getDate() - 1); - buildAndRender({ - contentTypeGatingEnabled: true, - accessExpiration: { - expirationDate: expirationDate.toString(), - }, - }); - expect(screen.getByRole('heading', { name: 'Course Access Expiration' })).toBeInTheDocument(); - expect(screen.getByText(/The upgrade deadline/s).textContent).toMatch('The upgrade deadline for this course passed'); - expect(screen.getByText(/To upgrade/s).textContent).toMatch('To upgrade, enroll in the next available session'); - expect(screen.getByRole('button', { name: 'View Course Details' })).toBeInTheDocument(); - }); - - it('sends course details click info to segment if past access expiration', async () => { - const expirationDate = new Date(dateNow); - expirationDate.setDate(expirationDate.getDate() - 1); - sendTrackEvent.mockClear(); - buildAndRender({ - pageName: 'test', - contentTypeGatingEnabled: true, - accessExpiration: { - expirationDate: expirationDate.toString(), - }, - }); - - const courseDetailsLink = await waitFor(() => screen.queryByRole('button', { name: 'View Course Details' })); - fireEvent.click(courseDetailsLink); - expect(sendTrackEvent).toHaveBeenCalledTimes(2); - expect(sendTrackEvent).toHaveBeenNthCalledWith(2, 'edx.bi.ecommerce.upgrade_notification.past_expiration.button_clicked', { - org_key: 'edX', - courserun_key: 'course-v1:edX+DemoX+Demo_Course', - linkCategory: 'upgrade_notification', - linkName: 'test_course_details', - linkType: 'button', - pageName: 'test', - }); - }); -}); diff --git a/src/index.scss b/src/index.scss index 205d12a3..f4ae867e 100755 --- a/src/index.scss +++ b/src/index.scss @@ -462,13 +462,10 @@ @import "courseware/course/celebration/CelebrationModal.scss"; @import "courseware/course/sidebar/sidebars/discussions/Discussions.scss"; @import "courseware/course/sidebar/sidebars/notifications/NotificationIcon.scss"; -@import "courseware/course/sequence/lock-paywall/LockPaywall.scss"; @import "shared/streak-celebration/StreakCelebrationModal.scss"; @import "courseware/course/content-tools/calculator/calculator.scss"; @import "courseware/course/content-tools/contentTools.scss"; @import "course-home/dates-tab/timeline/Day.scss"; -@import "generic/upgrade-notification/UpgradeNotification.scss"; -@import "generic/upsell-bullets/UpsellBullets.scss"; @import "course-home/outline-tab/widgets/ProctoringInfoPanel.scss"; @import "course-home/outline-tab/widgets/FlagButton.scss"; @import "course-home/progress-tab/course-completion/CompletionDonutChart.scss";