move course upsell

This commit is contained in:
Matthew Piatetsky
2017-12-19 10:42:52 -05:00
parent 792d0db5da
commit 277407f2a6
6 changed files with 81 additions and 311 deletions

View File

@@ -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%;
}

View File

@@ -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">

View File

@@ -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 });
});
}
}

View File

@@ -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' });
});
});
});

View File

@@ -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())}

View File

@@ -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()