Skip to content

Commit

Permalink
feat: undo remove lockpaywall
Browse files Browse the repository at this point in the history
  • Loading branch information
Zacharis278 committed Jun 28, 2024
1 parent c846a43 commit db114cb
Show file tree
Hide file tree
Showing 6 changed files with 325 additions and 1 deletion.
5 changes: 4 additions & 1 deletion src/courseware/course/sequence/Unit/UnitSuspense.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -33,7 +34,9 @@ const UnitSuspense = ({
pluginProps={{
courseId,
}}
/>
>
<LockPaywall courseId={courseId} />
</PluginSlot>
</Suspense>
)}
{shouldDisplayHonorCode && (
Expand Down
149 changes: 149 additions & 0 deletions src/courseware/course/sequence/lock-paywall/LockPaywall.jsx
Original file line number Diff line number Diff line change
@@ -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';

Check failure on line 20 in src/courseware/course/sequence/lock-paywall/LockPaywall.jsx

View workflow job for this annotation

GitHub Actions / tests

Unable to resolve path to module '../../../../generic/upsell-bullets/UpsellBullets'

Check failure on line 20 in src/courseware/course/sequence/lock-paywall/LockPaywall.jsx

View workflow job for this annotation

GitHub Actions / tests

Missing file extension for "../../../../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 (
<Alert variant="light" aria-live="off" icon={Locked} className="lock-paywall-container" data-testId="lock-paywall-test-id">
<div className="row">
<div className="col">
<h4 aria-level="3">
<span>{intl.formatMessage(messages['learn.lockPaywall.title'])}</span>
</h4>
{pastExpirationDeadline ? (
<div className="mb-2 upgrade-intro">
{intl.formatMessage(messages['learn.lockPaywall.content.pastExpiration'])}
<Hyperlink destination={marketingUrl} onClick={logClickPastExpiration} target="_blank">{intl.formatMessage(messages['learn.lockPaywall.courseDetails'])}</Hyperlink>
</div>
) : (
<div className="mb-2 upgrade-intro">
{intl.formatMessage(messages['learn.lockPaywall.content'])}
</div>
)}

<div className={classNames('d-inline-flex flex-row', { 'flex-wrap': notificationTrayVisible || shouldDisplayBulletPointsBelowCertificate })}>
<div style={{ float: 'left' }} className="mr-3 mb-2">
<img
alt={intl.formatMessage(messages['learn.lockPaywall.example.alt'])}
src={certificateLocked}
className="border-0 certificate-image-banner"
style={{ height: '128px', width: '175px' }}
/>
</div>

<div className="mw-xs list-div">
<div className="mb-2">
{intl.formatMessage(messages['learn.lockPaywall.list.intro'])}
</div>
<ul className="fa-ul ml-4 pl-2">
<VerifiedCertBullet />
<UnlockGradedBullet />
<FullAccessBullet />
<SupportMissionBullet />
</ul>
</div>
</div>
</div>

{pastExpirationDeadline
? null
: (
<div
className={
classNames('d-md-flex align-items-md-center text-right', {
'col-md-5 mx-md-0': notificationTrayVisible, 'col-md-4 mx-md-3 justify-content-center': !notificationTrayVisible && !shouldDisplayGatedContentTwoColumnsHalf, 'col-md-11 justify-content-end': shouldDisplayGatedContentOneColumn && !shouldDisplayGatedContentTwoColumns, 'col-md-6 justify-content-center': shouldDisplayGatedContentTwoColumnsHalf,
})
}
>
<UpgradeButton
offer={offer}
onClick={logClick}
verifiedMode={verifiedMode}
style={{ whiteSpace: shouldWrapTextOnButton ? 'nowrap' : null }}
/>
</div>
)}
</div>
</Alert>
);
};
LockPaywall.propTypes = {
intl: intlShape.isRequired,
courseId: PropTypes.string.isRequired,
};
export default injectIntl(LockPaywall);
14 changes: 14 additions & 0 deletions src/courseware/course/sequence/lock-paywall/LockPaywall.scss
Original file line number Diff line number Diff line change
@@ -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%;
}
}
121 changes: 121 additions & 0 deletions src/courseware/course/sequence/lock-paywall/LockPaywall.test.jsx
Original file line number Diff line number Diff line change
@@ -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(<LockPaywall {...mockData} />);

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(<LockPaywall {...mockData} courseId={courseMetadata.id} />, { 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(<LockPaywall {...mockData} />);

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(<LockPaywall {...mockData} courseId={courseHomeMetadata.id} />, { 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(<LockPaywall {...mockData} courseId={courseMetadata.id} />, { 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(<LockPaywall {...mockData} courseId={courseMetadata.id} />, { 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',
});
});
});
1 change: 1 addition & 0 deletions src/courseware/course/sequence/lock-paywall/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './LockPaywall';
36 changes: 36 additions & 0 deletions src/courseware/course/sequence/lock-paywall/messages.js
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit db114cb

Please sign in to comment.