feat: Add Value Prop past expiration messaging for gated content (#836)
REV-2500
This commit is contained in:
@@ -3,7 +3,9 @@ import PropTypes from 'prop-types';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
import { sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||||
import { Alert, breakpoints, useWindowSize } from '@edx/paragon';
|
import {
|
||||||
|
Alert, Hyperlink, breakpoints, useWindowSize,
|
||||||
|
} from '@edx/paragon';
|
||||||
import { Locked } from '@edx/paragon/icons';
|
import { Locked } from '@edx/paragon/icons';
|
||||||
import SidebarContext from '../../sidebar/SidebarContext';
|
import SidebarContext from '../../sidebar/SidebarContext';
|
||||||
import messages from './messages';
|
import messages from './messages';
|
||||||
@@ -24,6 +26,8 @@ function LockPaywall({
|
|||||||
const { notificationTrayVisible } = useContext(SidebarContext);
|
const { notificationTrayVisible } = useContext(SidebarContext);
|
||||||
const course = useModel('coursewareMeta', courseId);
|
const course = useModel('coursewareMeta', courseId);
|
||||||
const {
|
const {
|
||||||
|
accessExpiration,
|
||||||
|
marketingUrl,
|
||||||
offer,
|
offer,
|
||||||
org,
|
org,
|
||||||
verifiedMode,
|
verifiedMode,
|
||||||
@@ -40,6 +44,9 @@ function LockPaywall({
|
|||||||
&& !notificationTrayVisible;
|
&& !notificationTrayVisible;
|
||||||
const shouldWrapTextOnButton = useWindowSize().width > breakpoints.extraSmall.minWidth;
|
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) {
|
if (!verifiedMode) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -59,6 +66,16 @@ function LockPaywall({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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 (
|
return (
|
||||||
<Alert variant="light" aria-live="off" icon={Locked} className="lock-paywall-container">
|
<Alert variant="light" aria-live="off" icon={Locked} className="lock-paywall-container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
@@ -66,12 +83,18 @@ function LockPaywall({
|
|||||||
<h4 aria-level="3">
|
<h4 aria-level="3">
|
||||||
<span>{intl.formatMessage(messages['learn.lockPaywall.title'])}</span>
|
<span>{intl.formatMessage(messages['learn.lockPaywall.title'])}</span>
|
||||||
</h4>
|
</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="mb-2 upgrade-intro">
|
<div className={classNames('d-inline-flex flex-row', { 'flex-wrap': notificationTrayVisible || shouldDisplayBulletPointsBelowCertificate })}>
|
||||||
{intl.formatMessage(messages['learn.lockPaywall.content'])}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={classNames('d-flex flex-row', { 'flex-wrap': notificationTrayVisible || shouldDisplayBulletPointsBelowCertificate })}>
|
|
||||||
<div style={{ float: 'left' }} className="mr-3 mb-2">
|
<div style={{ float: 'left' }} className="mr-3 mb-2">
|
||||||
<img
|
<img
|
||||||
alt={intl.formatMessage(messages['learn.lockPaywall.example.alt'])}
|
alt={intl.formatMessage(messages['learn.lockPaywall.example.alt'])}
|
||||||
@@ -95,20 +118,24 @@ function LockPaywall({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
{pastExpirationDeadline
|
||||||
className={
|
? null
|
||||||
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,
|
<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}
|
<UpgradeButton
|
||||||
style={{ whiteSpace: shouldWrapTextOnButton ? 'nowrap' : null }}
|
offer={offer}
|
||||||
/>
|
onClick={logClick}
|
||||||
</div>
|
verifiedMode={verifiedMode}
|
||||||
|
style={{ whiteSpace: shouldWrapTextOnButton ? 'nowrap' : null }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
.alert-content.lock-paywall-container {
|
.alert-content.lock-paywall-container {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lock-paywall-container svg {
|
.lock-paywall-container svg {
|
||||||
|
|||||||
@@ -80,4 +80,42 @@ describe('Lock Paywall', () => {
|
|||||||
|
|
||||||
expect(container).toBeEmptyDOMElement();
|
expect(container).toBeEmptyDOMElement();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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',
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,6 +11,16 @@ const messages = defineMessages({
|
|||||||
defaultMessage: 'Upgrade to gain access to locked features like this one and get the most out of your course.',
|
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.',
|
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': {
|
'learn.lockPaywall.example.alt': {
|
||||||
id: 'learn.lockPaywall.example.alt',
|
id: 'learn.lockPaywall.example.alt',
|
||||||
defaultMessage: 'Example Certificate',
|
defaultMessage: 'Example Certificate',
|
||||||
|
|||||||
@@ -6,6 +6,9 @@ Factory.define('courseMetadata')
|
|||||||
.extend(courseMetadataBase)
|
.extend(courseMetadataBase)
|
||||||
.option('host', '')
|
.option('host', '')
|
||||||
.attrs({
|
.attrs({
|
||||||
|
access_expiration: {
|
||||||
|
expiration_date: '2032-02-22T05:00:00Z',
|
||||||
|
},
|
||||||
content_type_gating_enabled: false,
|
content_type_gating_enabled: false,
|
||||||
course_expired_message: null,
|
course_expired_message: null,
|
||||||
course_goals: {
|
course_goals: {
|
||||||
|
|||||||
Reference in New Issue
Block a user