move course upsell
This commit is contained in:
@@ -1,152 +1,39 @@
|
||||
/*
|
||||
NOTE: If you make significant changes to the design, remember to update the Segment event properties and change
|
||||
the creative property. This will allow us to better track individual performance of each style of the message.
|
||||
Search for the courseware_verified_certificate_upsell promotion ID.
|
||||
*/
|
||||
NOTE: If you make significant changes to the design, remember to update the Segment event properties and change
|
||||
the creative property. This will allow us to better track individual performance of each style of the message.
|
||||
Search for the courseware_verified_certificate_upsell promotion ID.
|
||||
*/
|
||||
|
||||
// Expanded upgrade message
|
||||
.vc-message {
|
||||
background: $lms-hero-color;
|
||||
color: $white;
|
||||
padding: $baseline;
|
||||
position: relative;
|
||||
margin: 0 0 $baseline;
|
||||
|
||||
// CSS animation for smooth height transition
|
||||
-webkit-transition: all 0.2s ease-in-out;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
// Message copy
|
||||
.vc-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: $font-weight-light;
|
||||
margin-bottom: 1rem;
|
||||
width: calc(100% - 70px);
|
||||
}
|
||||
|
||||
.vc-selling-points {
|
||||
@include padding-left(0);
|
||||
|
||||
font-size: 0.825rem;
|
||||
margin: 1rem 0;
|
||||
display: table;
|
||||
|
||||
> .vc-selling-point {
|
||||
list-style: none;
|
||||
display: table-row;
|
||||
|
||||
&::before {
|
||||
content: "\2022";
|
||||
display: table-cell;
|
||||
|
||||
@include padding-right($baseline/2);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: "";
|
||||
display: table-row;
|
||||
height: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Upgrade button
|
||||
.btn-upgrade {
|
||||
color: theme-color("inverse");
|
||||
background-color: theme-color("purchase");
|
||||
font-size: $font-size-base;
|
||||
-webkit-transition: $btn-transition;
|
||||
transition: $btn-transition;
|
||||
|
||||
&:hover {
|
||||
background-color: theme-color("success");
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide button
|
||||
.vc-toggle {
|
||||
@include right($baseline/2);
|
||||
|
||||
top: $baseline/2;
|
||||
position: absolute;
|
||||
color: theme-color("inverse");
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
color: theme-color("light");
|
||||
}
|
||||
}
|
||||
|
||||
// Cert image
|
||||
.vc-hero {
|
||||
@include float(right);
|
||||
@include padding-left(1rem);
|
||||
|
||||
clear: both;
|
||||
|
||||
img {
|
||||
@include float(right);
|
||||
|
||||
clear: both;
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
.section.section-upgrade {
|
||||
border-left: solid 1px #d9d9d9;
|
||||
border-bottom: solid 1px #d9d9d9;
|
||||
border-right: solid 1px #d9d9d9;
|
||||
border-top: 5px solid #008100;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
// Collapsed upgrade message
|
||||
.vc-message.polite {
|
||||
@include padding($baseline/2, 0, $baseline/2, $baseline);
|
||||
padding-top: $baseline/2;
|
||||
padding-bottom: $baseline/2;
|
||||
min-height: 46px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.vc-title {
|
||||
@include margin-right(auto);
|
||||
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.vc-cta {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.vc-toggle {
|
||||
@include right(0);
|
||||
|
||||
margin: 0 $baseline/2;
|
||||
order: 99;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
width: $baseline*6;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.vc-fade:not(.vc-polite-only) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
flex-flow: row wrap;
|
||||
|
||||
.vc-title {
|
||||
width: auto;
|
||||
margin-bottom: $baseline/2;
|
||||
}
|
||||
}
|
||||
.section.section-upgrade .btn-link {
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
@media (max-width: $grid-breakpoints-sm) {
|
||||
.vc-message .vc-hero {
|
||||
display: none !important;
|
||||
}
|
||||
.section.section-upgrade .btn-upgrade {
|
||||
background-color: #008100;
|
||||
border-color: #008100;
|
||||
}
|
||||
|
||||
.section-upgrade .upgrade-container {
|
||||
float: right;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.section.section-upgrade p {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.btn-brand {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.section.section-upgrade img {
|
||||
width: 43%;
|
||||
}
|
||||
|
||||
@@ -29,30 +29,6 @@
|
||||
<div class="page-content">
|
||||
<div class="layout layout-1t2t">
|
||||
<main class="layout-col layout-col-b">
|
||||
<div class="section">
|
||||
<div class="vc-message tex2jax_ignore" role="group" aria-labelledby="vc-title" tabindex="-1" style="display: none;">
|
||||
<h3 class="vc-title vc-fade vc-polite-only">Pursue a verified certificate</h3>
|
||||
<button class="vc-toggle vc-fade vc-polite-only btn-link" type="button" aria-controls="moreinfo"
|
||||
aria-expanded="true" aria-label="Show/Hide">Show less
|
||||
</button>
|
||||
|
||||
<div class="vc-hero vc-fade">
|
||||
<img src="img/sample-certificate.png"
|
||||
alt="Sample verified certificate with your name, the course title, the logo of the institution and the signatures of the instructors for this course."/>
|
||||
</div>
|
||||
|
||||
<ul class="vc-selling-points vc-fade">
|
||||
<li class="vc-selling-point">Official proof of completion</li>
|
||||
<li class="vc-selling-point">Easily shareable certificate</li>
|
||||
<li class="vc-selling-point">Proven motivator to complete the course</li>
|
||||
<li class="vc-selling-point">Certificate purchases help us continue to offer free courses</li>
|
||||
</ul>
|
||||
|
||||
<div class="vc-cta vc-fade vc-polite-only">
|
||||
<a class="btn btn-upgrade" data-creative="original_hero" data-position="hero" href="#">Upgrade ($100)</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section section-dates">
|
||||
<div class="welcome-message">
|
||||
<div class="dismiss-message">
|
||||
@@ -110,6 +86,21 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="section section-upgrade">
|
||||
<h3 class="hd hd-6">Pursue a verified certificate</h3>
|
||||
<div class="upgrade-container">
|
||||
<p>
|
||||
<a class="btn-brand btn-upgrade"
|
||||
href="${upgrade_url}"
|
||||
data-creative="sidebarupsell"
|
||||
data-position="sidebar-message">
|
||||
Upgrade $49
|
||||
</a>
|
||||
</p>
|
||||
<p><button class="btn-link btn-small promo-learn-more">Learn More</button></p>
|
||||
</div>
|
||||
<img src="https://courses.edx.org/static/images/edx-verified-mini-cert.png" alt="">
|
||||
</div>
|
||||
<div class="section section-dates">
|
||||
<h3 class="hd hd-6 section-title handouts-header">Important Course Dates</h3>
|
||||
<div class="date-summary-container">
|
||||
|
||||
@@ -144,75 +144,17 @@ export class CourseHome { // eslint-disable-line import/prefer-default-export
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persists the collapsed state of the upgrade message. If the message is collapsed,
|
||||
* this information is persisted to local storage. Expanding the message *removes* the
|
||||
* key from local storage.
|
||||
*/
|
||||
persistUpgradeMessageState(collapsed) {
|
||||
if (window.localStorage) {
|
||||
if (collapsed) {
|
||||
window.localStorage.setItem(this.msgStateStorageKey, true);
|
||||
} else {
|
||||
window.localStorage.removeItem(this.msgStateStorageKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configureUpgradeMessage() {
|
||||
const $vcMessage = $('.vc-message');
|
||||
const $vcDismissToggle = $('.vc-toggle', $vcMessage);
|
||||
const logEventProperties = { courseRunKey: this.courseRunKey };
|
||||
|
||||
Logger.log('edx.bi.course.upgrade.hero.displayed', logEventProperties);
|
||||
|
||||
// Get height of container and button
|
||||
let vcHeight = $vcMessage.outerHeight();
|
||||
|
||||
// Update based on window
|
||||
window.onresize = () => {
|
||||
if (!$vcMessage.hasClass('polite')) {
|
||||
vcHeight = $vcMessage.outerHeight();
|
||||
}
|
||||
};
|
||||
|
||||
function collapseMessage(duration = 400) {
|
||||
$('.vc-fade').fadeOut(duration, () => {
|
||||
$vcDismissToggle.text(gettext('Show more')).attr('aria-expanded', false);
|
||||
$('.vc-polite-only').fadeIn(duration);
|
||||
$vcMessage.height('auto').addClass('polite');
|
||||
});
|
||||
}
|
||||
|
||||
// Use the previously-persisted state to determine the initial display state of the message.
|
||||
if (window.localStorage && window.localStorage.getItem(this.msgStateStorageKey)) {
|
||||
collapseMessage(0);
|
||||
}
|
||||
$vcMessage.show();
|
||||
|
||||
$vcDismissToggle.click(() => {
|
||||
if ($vcMessage.hasClass('polite')) {
|
||||
// Expand message
|
||||
Logger.log('edx.bi.course.upgrade.hero.expanded', logEventProperties);
|
||||
this.persistUpgradeMessageState(false);
|
||||
|
||||
$('.vc-fade').fadeOut(400);
|
||||
$vcMessage.animate({ height: vcHeight }, 400, () => {
|
||||
$vcMessage.height('auto').removeClass('polite');
|
||||
$vcDismissToggle.text(gettext('Show less')).attr('aria-expanded', true);
|
||||
$('.vc-fade').fadeIn(400);
|
||||
});
|
||||
} else {
|
||||
// Collapse message
|
||||
Logger.log('edx.bi.course.upgrade.hero.collapsed', logEventProperties);
|
||||
this.persistUpgradeMessageState(true);
|
||||
collapseMessage();
|
||||
}
|
||||
Logger.log('edx.bi.course.upgrade.sidebarupsell.displayed', logEventProperties);
|
||||
$('.section-upgrade .btn-upgrade').click(() => {
|
||||
Logger.log('edx.bi.course.upgrade.sidebarupsell.clicked', logEventProperties);
|
||||
Logger.log('edx.course.enrollment.upgrade.clicked', { location: 'sidebar-message' });
|
||||
});
|
||||
|
||||
$('.btn-upgrade', $vcMessage).click(() => {
|
||||
Logger.log('edx.bi.course.upgrade.hero.clicked', logEventProperties);
|
||||
Logger.log('edx.course.enrollment.upgrade.clicked', { location: 'hero' });
|
||||
$('.promo-learn-more').click(() => {
|
||||
$('.action-toggle-verification-sock').click();
|
||||
$('.action-toggle-verification-sock')[0].scrollIntoView({ behavior: 'smooth', alignToTop: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,54 +50,21 @@ describe('Course Home factory', () => {
|
||||
describe('Upgrade message events', () => {
|
||||
const segmentEventProperties = {
|
||||
promotion_id: 'courseware_verified_certificate_upsell',
|
||||
creative: 'original_hero',
|
||||
creative: 'sidebarupsell',
|
||||
name: 'In-Course Verification Prompt',
|
||||
position: 'hero',
|
||||
position: 'sidebar-message',
|
||||
};
|
||||
|
||||
it('should send events to Segment and edX on initial load', () => {
|
||||
expect(window.analytics.track).toHaveBeenCalledWith('Promotion Viewed', segmentEventProperties);
|
||||
expect(Logger.log).toHaveBeenCalledWith('edx.bi.course.upgrade.hero.displayed', { courseRunKey: runKey });
|
||||
expect(Logger.log).toHaveBeenCalledWith('edx.bi.course.upgrade.sidebarupsell.displayed', { courseRunKey: runKey });
|
||||
});
|
||||
|
||||
it('should send events to Segment and edX after clicking the upgrade button ', () => {
|
||||
$('.vc-message .btn-upgrade').click();
|
||||
$('.section-upgrade .btn-upgrade').click();
|
||||
expect(window.analytics.track).toHaveBeenCalledWith('Promotion Viewed', segmentEventProperties);
|
||||
expect(Logger.log).toHaveBeenCalledWith('edx.bi.course.upgrade.hero.clicked', { courseRunKey: runKey });
|
||||
expect(Logger.log).toHaveBeenCalledWith('edx.course.enrollment.upgrade.clicked', { location: 'hero' });
|
||||
});
|
||||
});
|
||||
|
||||
describe('upgrade message display toggle', () => {
|
||||
let $message;
|
||||
let $toggle;
|
||||
|
||||
beforeEach(() => {
|
||||
$.fx.off = true;
|
||||
|
||||
$message = $('.vc-message');
|
||||
$toggle = $('.vc-toggle', $message);
|
||||
expect($message.length).toEqual(1);
|
||||
expect($toggle.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('hides/shows the message and writes/removes a key from local storage', () => {
|
||||
// NOTE (CCB): Ideally this should be two tests--one for collapse, another for expansion.
|
||||
// After a couple hours I have been unable to make these two tests pass, probably due to
|
||||
// issues with the initial state of local storage.
|
||||
expect($message.is(':visible')).toBeTruthy();
|
||||
expect($message.hasClass('polite')).toBeFalsy();
|
||||
expect($toggle.text().trim()).toEqual('Show less');
|
||||
|
||||
$toggle.click();
|
||||
expect($message.hasClass('polite')).toBeTruthy();
|
||||
expect($toggle.text().trim()).toEqual('Show more');
|
||||
expect(window.localStorage.getItem(home.msgStateStorageKey)).toEqual('true');
|
||||
|
||||
$toggle.click();
|
||||
expect($message.hasClass('polite')).toBeFalsy();
|
||||
expect($toggle.text().trim()).toEqual('Show less');
|
||||
expect(window.localStorage.getItem(home.msgStateStorageKey)).toBeNull();
|
||||
expect(Logger.log).toHaveBeenCalledWith('edx.bi.course.upgrade.sidebarupsell.clicked', { courseRunKey: runKey });
|
||||
expect(Logger.log).toHaveBeenCalledWith('edx.course.enrollment.upgrade.clicked', { location: 'sidebar-message' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -58,40 +58,6 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
|
||||
</header>
|
||||
<div class="page-content">
|
||||
<main class="page-content-main">
|
||||
|
||||
% if upgrade_url and upgrade_price:
|
||||
<div class="section">
|
||||
<div class="vc-message tex2jax_ignore" role="group" aria-labelledby="vc-title" tabindex="-1" style="display: none;">
|
||||
|
||||
<h3 class="vc-title vc-fade vc-polite-only">Pursue a verified certificate</h3>
|
||||
|
||||
<button class="vc-toggle vc-fade vc-polite-only btn-link" type="button" aria-controls="moreinfo"
|
||||
aria-expanded="true" aria-label="${_("Show/Hide")}">
|
||||
${_("Show less")}
|
||||
</button>
|
||||
|
||||
<div class="vc-hero vc-fade">
|
||||
<img src="${static.url('course_experience/images/verified-cert.png')}"
|
||||
alt="${_("Sample verified certificate with your name, the course title, the logo of the institution and the signatures of the instructors for this course.")}"/>
|
||||
</div>
|
||||
|
||||
<ul class="vc-selling-points vc-fade">
|
||||
<li class="vc-selling-point">${_("Official proof of completion")}</li>
|
||||
<li class="vc-selling-point">${_("Easily shareable certificate")}</li>
|
||||
<li class="vc-selling-point">${_("Proven motivator to complete the course")}</li>
|
||||
<li class="vc-selling-point">${_("Certificate purchases help us continue to offer free courses")}</li>
|
||||
</ul>
|
||||
|
||||
<div class="vc-cta vc-fade vc-polite-only">
|
||||
<a class="btn btn-upgrade"
|
||||
href="${ upgrade_url }"
|
||||
data-creative="hero_matthew_smith"
|
||||
data-position="hero">${_("Upgrade ({price})").format(price=upgrade_price)}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
% endif
|
||||
|
||||
% if course_home_message_fragment:
|
||||
${HTML(course_home_message_fragment.body_html())}
|
||||
% endif
|
||||
@@ -147,6 +113,23 @@ from openedx.features.course_experience import UNIFIED_COURSE_TAB_FLAG, SHOW_REV
|
||||
</ul>
|
||||
</div>
|
||||
% endif
|
||||
% if upgrade_url and upgrade_price:
|
||||
<div class="section section-upgrade">
|
||||
<h3 class="hd hd-6">${_("Pursue a verified certificate")}</h3>
|
||||
<div class="upgrade-container">
|
||||
<p>
|
||||
<a class="btn-brand btn-upgrade"
|
||||
href="${upgrade_url}"
|
||||
data-creative="sidebarupsell"
|
||||
data-position="sidebar-message">
|
||||
${_("Upgrade ({price})").format(price=upgrade_price)}
|
||||
</a>
|
||||
</p>
|
||||
<p><button class="btn-link btn-small promo-learn-more">${_('Learn More')}</button></p>
|
||||
</div>
|
||||
<img src="https://courses.edx.org/static/images/edx-verified-mini-cert.png" alt="">
|
||||
</div>
|
||||
% endif
|
||||
% if dates_fragment:
|
||||
<div class="section section-dates">
|
||||
${HTML(dates_fragment.body_html())}
|
||||
|
||||
@@ -512,15 +512,15 @@ class CourseHomeFragmentViewTests(ModuleStoreTestCase):
|
||||
|
||||
def assert_upgrade_message_not_displayed(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertNotIn('vc-message', response.content)
|
||||
self.assertNotIn('section-upgrade', response.content)
|
||||
|
||||
def assert_upgrade_message_displayed(self):
|
||||
response = self.client.get(self.url)
|
||||
self.assertIn('vc-message', response.content)
|
||||
self.assertIn('section-upgrade', response.content)
|
||||
url = EcommerceService().get_checkout_page_url(self.verified_mode.sku)
|
||||
self.assertIn('<a class="btn btn-upgrade"', response.content)
|
||||
self.assertIn('<a class="btn-brand btn-upgrade"', response.content)
|
||||
self.assertIn(url, response.content)
|
||||
self.assertIn('Upgrade (${price})</a>'.format(price=self.verified_mode.min_price), response.content)
|
||||
self.assertIn('Upgrade (${price})'.format(price=self.verified_mode.min_price), response.content)
|
||||
|
||||
def test_no_upgrade_message_if_logged_out(self):
|
||||
self.client.logout()
|
||||
|
||||
Reference in New Issue
Block a user