From 58cbed25d704373e87b3caf0013d1d77d0252049 Mon Sep 17 00:00:00 2001 From: Zacharis278 Date: Fri, 28 Jun 2024 09:07:47 -0400 Subject: [PATCH] feat: undo remove lockpaywall --- .../course/sequence/Unit/UnitSuspense.jsx | 5 +- .../sequence/lock-paywall/LockPaywall.jsx | 149 ++++++++++++++++++ .../sequence/lock-paywall/LockPaywall.scss | 14 ++ .../lock-paywall/LockPaywall.test.jsx | 121 ++++++++++++++ .../course/sequence/lock-paywall/index.js | 1 + .../course/sequence/lock-paywall/messages.js | 36 +++++ 6 files changed, 325 insertions(+), 1 deletion(-) create mode 100644 src/courseware/course/sequence/lock-paywall/LockPaywall.jsx create mode 100644 src/courseware/course/sequence/lock-paywall/LockPaywall.scss create mode 100644 src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx create mode 100644 src/courseware/course/sequence/lock-paywall/index.js create mode 100644 src/courseware/course/sequence/lock-paywall/messages.js diff --git a/src/courseware/course/sequence/Unit/UnitSuspense.jsx b/src/courseware/course/sequence/Unit/UnitSuspense.jsx index 299a491b..09843a71 100644 --- a/src/courseware/course/sequence/Unit/UnitSuspense.jsx +++ b/src/courseware/course/sequence/Unit/UnitSuspense.jsx @@ -9,6 +9,7 @@ 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'; @@ -33,7 +34,9 @@ const UnitSuspense = ({ pluginProps={{ courseId, }} - /> + > + + )} {shouldDisplayHonorCode && ( diff --git a/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx b/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx new file mode 100644 index 00000000..c1625c84 --- /dev/null +++ b/src/courseware/course/sequence/lock-paywall/LockPaywall.jsx @@ -0,0 +1,149 @@ +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 new file mode 100644 index 00000000..7bbee59b --- /dev/null +++ b/src/courseware/course/sequence/lock-paywall/LockPaywall.scss @@ -0,0 +1,14 @@ +.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 new file mode 100644 index 00000000..60ac4286 --- /dev/null +++ b/src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx @@ -0,0 +1,121 @@ +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 new file mode 100644 index 00000000..d609d279 --- /dev/null +++ b/src/courseware/course/sequence/lock-paywall/index.js @@ -0,0 +1 @@ +export { default } from './LockPaywall'; diff --git a/src/courseware/course/sequence/lock-paywall/messages.js b/src/courseware/course/sequence/lock-paywall/messages.js new file mode 100644 index 00000000..54f51990 --- /dev/null +++ b/src/courseware/course/sequence/lock-paywall/messages.js @@ -0,0 +1,36 @@ +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;