diff --git a/src/course-home/outline-tab/OutlineTab.jsx b/src/course-home/outline-tab/OutlineTab.jsx index ca26155f82..a48fdf1822 100644 --- a/src/course-home/outline-tab/OutlineTab.jsx +++ b/src/course-home/outline-tab/OutlineTab.jsx @@ -17,7 +17,6 @@ import { fetchOutlineTab } from '../data'; import messages from './messages'; import Section from './Section'; 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'; @@ -39,11 +38,9 @@ const OutlineTab = ({ intl }) => { isSelfPaced, org, title, - userTimezone, } = useModel('courseHomeMeta', courseId); const { - accessExpiration, courseBlocks: { courses, sections, @@ -52,20 +49,12 @@ const OutlineTab = ({ intl }) => { 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 = ({ intl }) => { - - + /> diff --git a/src/course-home/outline-tab/OutlineTab.test.jsx b/src/course-home/outline-tab/OutlineTab.test.jsx index 817612d31e..b658eb84f9 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'; @@ -1176,80 +1176,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/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.jsx b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.jsx index 7e7087ac5b..47d4f707ab 100644 --- a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.jsx +++ b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.jsx @@ -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; @@ -81,24 +74,7 @@ const NotificationsWidget = () => { setNotificationCurrentState: setUpgradeNotificationCurrentState, toggleSidebar: onToggleSidebar, }} - > - - + /> ); }; diff --git a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.test.jsx b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.test.jsx index 11f9daf740..7e1cb16161 100644 --- a/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.test.jsx +++ b/src/courseware/course/new-sidebar/sidebars/discussions-notifications/notifications/NotificationsWidget.test.jsx @@ -18,9 +18,22 @@ import SidebarContext from '../../../SidebarContext'; import NotificationsWidget from './NotificationsWidget'; import setupDiscussionSidebar from '../../../../test-utils'; -initializeMockApp(); jest.mock('@edx/frontend-platform/analytics'); +/* eslint-disable react/prop-types */ +jest.mock('@openedx/frontend-plugin-framework', () => ({ + ...jest.requireActual('@openedx/frontend-plugin-framework'), + Plugin: () => 'Plugin', + PluginSlot: ({ id, pluginProps }) => ( +
+ + PluginSlot_{id} +
+ ), +})); + +initializeMockApp(); + describe('NotificationsWidget', () => { let axiosMock; let store; @@ -93,27 +106,6 @@ describe('NotificationsWidget', () => { expect(screen.getByTestId('notification_widget_slot')).toBeInTheDocument(); }); - it('renders upgrade card', async () => { - 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 }); await fetchAndRender( diff --git a/src/courseware/course/sequence/Unit/UnitSuspense.jsx b/src/courseware/course/sequence/Unit/UnitSuspense.jsx index 09843a7164..299a491b6a 100644 --- a/src/courseware/course/sequence/Unit/UnitSuspense.jsx +++ b/src/courseware/course/sequence/Unit/UnitSuspense.jsx @@ -9,7 +9,6 @@ import PageLoading from '@src/generic/PageLoading'; import messages from '../messages'; import HonorCode from '../honor-code'; -import LockPaywall from '../lock-paywall'; import * as hooks from './hooks'; import { modelKeys } from './constants'; @@ -34,9 +33,7 @@ const UnitSuspense = ({ pluginProps={{ courseId, }} - > - - + /> )} {shouldDisplayHonorCode && ( diff --git a/src/courseware/course/sequence/Unit/UnitSuspense.test.jsx b/src/courseware/course/sequence/Unit/UnitSuspense.test.jsx index f47ee6b241..aa5c7e499d 100644 --- a/src/courseware/course/sequence/Unit/UnitSuspense.test.jsx +++ b/src/courseware/course/sequence/Unit/UnitSuspense.test.jsx @@ -7,7 +7,6 @@ import PageLoading from '@src/generic/PageLoading'; import messages from '../messages'; import HonorCode from '../honor-code'; -import LockPaywall from '../lock-paywall'; import hooks from './hooks'; import { modelKeys } from './constants'; @@ -24,7 +23,6 @@ jest.mock('react', () => ({ })); jest.mock('../honor-code', () => 'HonorCode'); -jest.mock('../lock-paywall', () => 'LockPaywall'); jest.mock('@src/generic/model-store', () => ({ useModel: jest.fn() })); jest.mock('@src/generic/PageLoading', () => 'PageLoading'); @@ -62,31 +60,6 @@ describe('UnitSuspense component', () => { }); }); describe('output', () => { - describe('LockPaywall', () => { - const testNoPaywall = () => { - it('does not display LockPaywall', () => { - el = shallow(); - expect(el.instance.findByType(LockPaywall).length).toEqual(0); - }); - }; - describe('gating not enabled', () => { testNoPaywall(); }); - describe('gating enabled, but no gated content included', () => { - beforeEach(() => { mockModels(true, false); }); - testNoPaywall(); - }); - describe('gating enabled, gated content included', () => { - beforeEach(() => { mockModels(true, true); }); - it('displays LockPaywall in Suspense wrapper with PageLoading fallback', () => { - el = shallow(); - const [component] = el.instance.findByType(LockPaywall); - expect(component.parent.type).toEqual('PluginSlot'); - expect(component.parent.parent.type).toEqual('Suspense'); - expect(component.parent.parent.props.fallback) - .toEqual(); - expect(component.props.courseId).toEqual(props.courseId); - }); - }); - }); describe('HonorCode', () => { it('does not display HonorCode if useShouldDisplayHonorCode => false', () => { hooks.useShouldDisplayHonorCode.mockReturnValueOnce(false); diff --git a/src/courseware/course/sequence/Unit/index.test.jsx b/src/courseware/course/sequence/Unit/index.test.jsx index 7ae0735ba4..3f4ac6fec0 100644 --- a/src/courseware/course/sequence/Unit/index.test.jsx +++ b/src/courseware/course/sequence/Unit/index.test.jsx @@ -28,7 +28,6 @@ jest.mock('../../bookmark/BookmarkButton', () => 'BookmarkButton'); jest.mock('./ContentIFrame', () => 'ContentIFrame'); jest.mock('./UnitSuspense', () => 'UnitSuspense'); jest.mock('../honor-code', () => 'HonorCode'); -jest.mock('../lock-paywall', () => 'LockPaywall'); jest.mock('@src/generic/model-store', () => ({ useModel: jest.fn(), diff --git a/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx b/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx deleted file mode 100644 index c1625c84c2..0000000000 --- a/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx +++ /dev/null @@ -1,149 +0,0 @@ -import React, { useContext } from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { - Alert, Hyperlink, breakpoints, useWindowSize, -} from '@openedx/paragon'; -import { Locked } from '@openedx/paragon/icons'; -import SidebarContext from '../../sidebar/SidebarContext'; -import messages from './messages'; -import certificateLocked from '../../../../generic/assets/edX_locked_certificate.png'; -import { useModel } from '../../../../generic/model-store'; -import { UpgradeButton } from '../../../../generic/upgrade-button'; -import { - VerifiedCertBullet, - UnlockGradedBullet, - FullAccessBullet, - SupportMissionBullet, -} from '../../../../generic/upsell-bullets/UpsellBullets'; - -const LockPaywall = ({ - intl, - courseId, -}) => { - const { notificationTrayVisible } = useContext(SidebarContext); - const course = useModel('coursewareMeta', courseId); - const { - accessExpiration, - marketingUrl, - offer, - } = course; - - const { - org, verifiedMode, - } = useModel('courseHomeMeta', courseId); - - // the following variables are set and used for resposive layout to work with - // whether the NotificationTray is open or not and if there's an offer with longer text - const shouldDisplayBulletPointsBelowCertificate = useWindowSize().width <= breakpoints.large.minWidth; - const shouldDisplayGatedContentOneColumn = useWindowSize().width <= breakpoints.extraLarge.minWidth - && notificationTrayVisible; - const shouldDisplayGatedContentTwoColumns = useWindowSize().width < breakpoints.large.minWidth - && notificationTrayVisible; - const shouldDisplayGatedContentTwoColumnsHalf = useWindowSize().width <= breakpoints.large.minWidth - && !notificationTrayVisible; - const shouldWrapTextOnButton = useWindowSize().width > breakpoints.extraSmall.minWidth; - - const accessExpirationDate = accessExpiration ? new Date(accessExpiration.expirationDate) : null; - const pastExpirationDeadline = accessExpiration ? new Date(Date.now()) > accessExpirationDate : false; - - if (!verifiedMode) { - return null; - } - - const eventProperties = { - org_key: org, - courserun_key: courseId, - }; - - const logClick = () => { - sendTrackEvent('edx.bi.ecommerce.upsell_links_clicked', { - ...eventProperties, - linkCategory: '(none)', - linkName: 'in_course_upgrade', - linkType: 'link', - pageName: 'in_course', - }); - }; - - const logClickPastExpiration = () => { - sendTrackEvent('edx.bi.ecommerce.gated_content.past_expiration.link_clicked', { - ...eventProperties, - linkCategory: 'gated_content', - linkName: 'course_details', - linkType: 'link', - pageName: 'in_course', - }); - }; - - return ( - -
-
-

- {intl.formatMessage(messages['learn.lockPaywall.title'])} -

- {pastExpirationDeadline ? ( -
- {intl.formatMessage(messages['learn.lockPaywall.content.pastExpiration'])} - {intl.formatMessage(messages['learn.lockPaywall.courseDetails'])} -
- ) : ( -
- {intl.formatMessage(messages['learn.lockPaywall.content'])} -
- )} - -
-
- {intl.formatMessage(messages['learn.lockPaywall.example.alt'])} -
- -
-
- {intl.formatMessage(messages['learn.lockPaywall.list.intro'])} -
-
    - - - - -
-
-
-
- - {pastExpirationDeadline - ? null - : ( -
- -
- )} -
-
- ); -}; -LockPaywall.propTypes = { - intl: intlShape.isRequired, - courseId: PropTypes.string.isRequired, -}; -export default injectIntl(LockPaywall); diff --git a/src/courseware/course/sequence/lock-paywall/LockPaywall.scss b/src/courseware/course/sequence/lock-paywall/LockPaywall.scss deleted file mode 100644 index 7bbee59b2c..0000000000 --- a/src/courseware/course/sequence/lock-paywall/LockPaywall.scss +++ /dev/null @@ -1,14 +0,0 @@ -.alert-content.lock-paywall-container { - display: inline-flex; - width: 100%; -} - -.lock-paywall-container svg { - color: $primary-700; -} - -@media only screen and (min-width: 992px) and (max-width: 1100px) { - .list-div { - width: 62%; - } -} diff --git a/src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx b/src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx deleted file mode 100644 index 60ac428642..0000000000 --- a/src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx +++ /dev/null @@ -1,121 +0,0 @@ -import React from 'react'; -import { Factory } from 'rosie'; -import { sendTrackEvent } from '@edx/frontend-platform/analytics'; - -import { - fireEvent, initializeTestStore, render, screen, -} from '../../../../setupTest'; -import LockPaywall from './LockPaywall'; - -jest.mock('@edx/frontend-platform/analytics'); - -describe('Lock Paywall', () => { - let store; - const mockData = { notificationTrayVisible: false }; - - beforeAll(async () => { - store = await initializeTestStore(); - const { courseware } = store.getState(); - Object.assign(mockData, { - courseId: courseware.courseId, - }); - }); - - it('displays unlock link with price', () => { - const { - currencySymbol, - price, - upgradeUrl, - } = store.getState().models.courseHomeMeta[mockData.courseId].verifiedMode; - render(); - - const upgradeLink = screen.getByRole('link', { name: `Upgrade for ${currencySymbol}${price}` }); - expect(upgradeLink).toHaveAttribute('href', `${upgradeUrl}`); - }); - - it('displays discounted price if there is an offer/first time purchase', async () => { - const courseMetadata = Factory.build('courseMetadata', { - offer: { - code: 'EDXWELCOME', - expiration_date: '2070-01-01T12:00:00Z', - original_price: '$100', - discounted_price: '$85', - percentage: 15, - upgrade_url: 'https://example.com/upgrade', - }, - }); - const testStore = await initializeTestStore({ courseMetadata }, false); - render(, { store: testStore }); - - expect(screen.getByText(/Upgrade for/).textContent).toMatch('$85 ($100)'); - }); - - it('sends analytics event onClick of unlock link', () => { - sendTrackEvent.mockClear(); - - const { - currencySymbol, - price, - } = store.getState().models.courseHomeMeta[mockData.courseId].verifiedMode; - render(); - - const upgradeLink = screen.getByRole('link', { name: `Upgrade for ${currencySymbol}${price}` }); - fireEvent.click(upgradeLink); - - expect(sendTrackEvent).toHaveBeenCalledTimes(1); - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.upsell_links_clicked', { - org_key: 'edX', - courserun_key: mockData.courseId, - linkCategory: '(none)', - linkName: 'in_course_upgrade', - linkType: 'link', - pageName: 'in_course', - }); - }); - - it('does not display anything if course does not have verified mode', async () => { - const courseHomeMetadata = Factory.build('courseHomeMetadata', { verified_mode: null }); - const testStore = await initializeTestStore({ courseHomeMetadata, excludeFetchSequence: true }, false); - render(, { store: testStore }); - - expect(screen.queryByTestId('lock-paywall-test-id')).not.toBeInTheDocument(); - }); - - it('displays past expiration message if expiration date has expired', async () => { - const courseMetadata = Factory.build('courseMetadata', { - access_expiration: { - expiration_date: '1995-02-22T05:00:00Z', - }, - marketing_url: 'https://example.com/course-details', - }); - const testStore = await initializeTestStore({ courseMetadata }, false); - render(, { store: testStore }); - expect(screen.getByText('The upgrade deadline for this course passed. To upgrade, enroll in the next available session.')).toBeInTheDocument(); - expect(screen.getByText('View Course Details')) - .toHaveAttribute('href', 'https://example.com/course-details'); - }); - - it('sends analytics event onClick of past expiration course details link', async () => { - sendTrackEvent.mockClear(); - const courseMetadata = Factory.build('courseMetadata', { - access_expiration: { - expiration_date: '1995-02-22T05:00:00Z', - }, - marketing_url: 'https://example.com/course-details', - }); - const testStore = await initializeTestStore({ courseMetadata }, false); - render(, { store: testStore }); - const courseDetailsLink = await screen.getByText('View Course Details'); - fireEvent.click(courseDetailsLink); - - expect(sendTrackEvent).toHaveBeenCalledTimes(1); - expect(sendTrackEvent).toHaveBeenCalledWith('edx.bi.ecommerce.gated_content.past_expiration.link_clicked', { - org_key: 'edX', - courserun_key: mockData.courseId, - linkCategory: 'gated_content', - linkName: 'course_details', - linkType: 'link', - pageName: 'in_course', - }); - }); -}); diff --git a/src/courseware/course/sequence/lock-paywall/index.js b/src/courseware/course/sequence/lock-paywall/index.js deleted file mode 100644 index d609d2794f..0000000000 --- a/src/courseware/course/sequence/lock-paywall/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './LockPaywall'; diff --git a/src/courseware/course/sequence/lock-paywall/messages.js b/src/courseware/course/sequence/lock-paywall/messages.js deleted file mode 100644 index 54f51990b2..0000000000 --- a/src/courseware/course/sequence/lock-paywall/messages.js +++ /dev/null @@ -1,36 +0,0 @@ -import { defineMessages } from '@edx/frontend-platform/i18n'; - -const messages = defineMessages({ - 'learn.lockPaywall.title': { - id: 'learn.lockPaywall.title', - defaultMessage: 'Graded assignments are locked', - description: 'Heading for message shown to indicate that a piece of content is unavailable to audit track users.', - }, - 'learn.lockPaywall.content': { - id: 'learn.lockPaywall.content', - defaultMessage: 'Upgrade to gain access to locked features like this one and get the most out of your course.', - description: 'Message shown to indicate that a piece of content is unavailable to audit track users.', - }, - 'learn.lockPaywall.content.pastExpiration': { - id: 'learn.lockPaywall.content.pastExpiration', - defaultMessage: 'The upgrade deadline for this course passed. To upgrade, enroll in the next available session. ', - description: 'Message shown to indicate that a piece of content is unavailable to audit track users in a course where the expiration deadline has passed.', - }, - 'learn.lockPaywall.courseDetails': { - id: 'learn.lockPaywall.courseDetails', - defaultMessage: 'View Course Details', - description: 'Link to the course details page for this course with a past expiration date.', - }, - 'learn.lockPaywall.example.alt': { - id: 'learn.lockPaywall.example.alt', - defaultMessage: 'Example Certificate', - description: 'Alternate text displayed when the example certificate image cannot be displayed.', - }, - 'learn.lockPaywall.list.intro': { - id: 'learn.lockPaywall.list.intro', - defaultMessage: 'When you upgrade, you:', - description: 'Text displayed to introduce the list of benefits from upgrading.', - }, -}); - -export default messages; diff --git a/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx b/src/courseware/course/sidebar/sidebars/notifications/NotificationTray.jsx index de8ec204cb..f03cc36066 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; @@ -88,23 +81,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 5a744add1a..a8831741f2 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 e9d3b5dde9..0000000000 --- a/src/generic/upgrade-notification/UpgradeNotification.jsx +++ /dev/null @@ -1,564 +0,0 @@ -import React, { useEffect } from 'react'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import { - useIntl, FormattedDate, FormattedMessage, injectIntl, -} from '@edx/frontend-platform/i18n'; -import { sendTrackEvent, sendTrackingLogEvent } from '@edx/frontend-platform/analytics'; -import { Button, Icon, IconButton } from '@openedx/paragon'; -import { Close } 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 injectIntl(UpgradeNotification); diff --git a/src/generic/upgrade-notification/UpgradeNotification.scss b/src/generic/upgrade-notification/UpgradeNotification.scss deleted file mode 100644 index ee70643834..0000000000 --- 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 8d97e4c10b..0000000000 --- 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 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 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 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 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.'); - 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.'); - 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.'); - 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 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 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 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 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.'); - 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/generic/upsell-bullets/UpsellBullets.jsx b/src/generic/upsell-bullets/UpsellBullets.jsx deleted file mode 100644 index e631ebae02..0000000000 --- a/src/generic/upsell-bullets/UpsellBullets.jsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; -import { faCheck } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { getConfig } from '@edx/frontend-platform'; - -const CheckmarkBullet = () => ( - -); - -// Must be child of a
    -export const VerifiedCertBullet = () => { - const verifiedCertLink = ( - - - - ); - return ( -
  • - - -
  • - ); -}; - -// Must be child of a
      -export const UnlockGradedBullet = () => { - const gradedAssignmentsInBoldText = ( - - - - ); - return ( -
    • - - -
    • - ); -}; - -// Must be child of a
        -export const FullAccessBullet = () => { - const fullAccessInBoldText = ( - - - - ); - return ( -
      • - - -
      • - ); -}; - -// Must be child of a
          -export const SupportMissionBullet = () => { - const missionInBoldText = ( - - - - ); - return ( -
        • - - -
        • - ); -}; diff --git a/src/generic/upsell-bullets/UpsellBullets.scss b/src/generic/upsell-bullets/UpsellBullets.scss deleted file mode 100644 index 0cf75905b6..0000000000 --- a/src/generic/upsell-bullets/UpsellBullets.scss +++ /dev/null @@ -1,13 +0,0 @@ -.upsell-bullet > .fa-li { - left: -31px; - padding-right: 22px; -} - -.inline-link-underline { - text-decoration: underline; -} - -.upsell-bullet a { - color: $primary-500; -} - diff --git a/src/generic/upsell-bullets/UpsellBullets.test.jsx b/src/generic/upsell-bullets/UpsellBullets.test.jsx deleted file mode 100644 index daf7494378..0000000000 --- a/src/generic/upsell-bullets/UpsellBullets.test.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import React from 'react'; - -import { - initializeMockApp, - render, - screen, -} from '../../setupTest'; - -import { - VerifiedCertBullet, - UnlockGradedBullet, - FullAccessBullet, - SupportMissionBullet, -} from './UpsellBullets'; - -initializeMockApp(); - -describe('UpsellBullets', () => { - const bullets = ( - <> - - - - - - ); - - it('upsell bullet text properly rendered', async () => { - render(bullets); - expect(screen.getByText(/Earn a.*?of completion to showcase on your resumé/s).textContent).toMatch('Earn a verified certificate 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'); - }); -}); diff --git a/src/index.scss b/src/index.scss index 6a72b32f97..4f445d65b0 100755 --- a/src/index.scss +++ b/src/index.scss @@ -433,13 +433,10 @@ // Import component-specific sass files @import "courseware/course/celebration/CelebrationModal.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";