feat: subscription changes for program details page (#32060)
* feat: subscription ui changes on program detail page (#31846) * test: update tests for subscription changes on program details (#32020) * feat: subscription api changes on program details (#32059)
This commit is contained in:
4
lms/static/images/arrow-upright-icon.svg
Normal file
4
lms/static/images/arrow-upright-icon.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M5 0V2H11.59L0 13.59L1.41 15L13 3.41V10H15V0H5Z" fill="#454545
|
||||
"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 179 B |
3
lms/static/images/restart-icon.svg
Normal file
3
lms/static/images/restart-icon.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19 8L15 12H18C18 15.31 15.31 18 12 18C10.99 18 10.03 17.75 9.2 17.3L7.74 18.76C8.97 19.54 10.43 20 12 20C16.42 20 20 16.42 20 12H23L19 8ZM6 12C6 8.69 8.69 6 12 6C13.01 6 13.97 6.25 14.8 6.7L16.26 5.24C15.03 4.46 13.57 4 12 4C7.58 4 4 7.58 4 12H1L5 16L9 12H6Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 388 B |
@@ -0,0 +1,88 @@
|
||||
import Backbone from 'backbone';
|
||||
import moment from 'moment';
|
||||
|
||||
import DateUtils from 'edx-ui-toolkit/js/utils/date-utils';
|
||||
|
||||
/**
|
||||
* Model for Program Subscription Data.
|
||||
*/
|
||||
class ProgramSubscriptionModel extends Backbone.Model {
|
||||
constructor({ context }, ...args) {
|
||||
const {
|
||||
subscriptionData: [data = {}],
|
||||
programData: { subscription_prices },
|
||||
urls,
|
||||
userPreferences,
|
||||
} = context;
|
||||
|
||||
const priceInUSD = subscription_prices?.find(({ currency }) => currency === 'USD')?.price;
|
||||
const trialMoment = moment(
|
||||
DateUtils.localizeTime(
|
||||
DateUtils.stringToMoment(data.trial_end),
|
||||
'UTC'
|
||||
)
|
||||
);
|
||||
|
||||
const subscriptionState = data.subscription_state?.toLowerCase() ?? '';
|
||||
const subscriptionPrice = '$' + parseFloat(priceInUSD);
|
||||
const subscriptionUrl =
|
||||
subscriptionState === 'active'
|
||||
? urls.manage_subscription_url
|
||||
: urls.buy_subscription_url;
|
||||
|
||||
const hasActiveTrial =
|
||||
subscriptionState === 'active' && data.trial_end
|
||||
? trialMoment.isAfter(moment.utc())
|
||||
: false;
|
||||
|
||||
const [nextPaymentDate] = ProgramSubscriptionModel.formatDate(
|
||||
data.next_payment_date,
|
||||
userPreferences
|
||||
);
|
||||
const [trialEndDate, trialEndTime] = ProgramSubscriptionModel.formatDate(
|
||||
data.trial_end,
|
||||
userPreferences
|
||||
);
|
||||
|
||||
const trialLength = 7;
|
||||
|
||||
super(
|
||||
{
|
||||
hasActiveTrial,
|
||||
nextPaymentDate,
|
||||
subscriptionPrice,
|
||||
subscriptionState,
|
||||
subscriptionUrl,
|
||||
trialEndDate,
|
||||
trialEndTime,
|
||||
trialLength,
|
||||
},
|
||||
...args
|
||||
);
|
||||
}
|
||||
|
||||
static formatDate(date, userPreferences) {
|
||||
if (!date) {
|
||||
return ['', ''];
|
||||
}
|
||||
|
||||
const userTimezone = userPreferences?.time_zone || 'UTC';
|
||||
const userLanguage = userPreferences?.['pref-lang'] || 'en';
|
||||
const context = {
|
||||
datetime: date,
|
||||
timezone: userTimezone,
|
||||
language: userLanguage,
|
||||
format: DateUtils.dateFormatEnum.shortDate,
|
||||
};
|
||||
|
||||
const localDate = DateUtils.localize(context);
|
||||
const localTime = DateUtils.localizeTime(
|
||||
DateUtils.stringToMoment(date),
|
||||
userTimezone
|
||||
).format('HH:mm');
|
||||
|
||||
return [localDate, localTime];
|
||||
}
|
||||
}
|
||||
|
||||
export default ProgramSubscriptionModel;
|
||||
@@ -13,8 +13,13 @@ describe('Course Card View', () => {
|
||||
const setupView = (data, isEnrolled, collectionCourseStatus) => {
|
||||
const programData = $.extend({}, data);
|
||||
const context = {
|
||||
courseData: {},
|
||||
programData,
|
||||
collectionCourseStatus,
|
||||
courseData: {},
|
||||
subscriptionData: [],
|
||||
urls: {},
|
||||
userPreferences: {},
|
||||
isSubscriptionEligible: false,
|
||||
};
|
||||
|
||||
if (typeof collectionCourseStatus === 'undefined') {
|
||||
@@ -31,7 +36,7 @@ describe('Course Card View', () => {
|
||||
};
|
||||
|
||||
const validateCourseInfoDisplay = () => {
|
||||
// DRY validation for course card in enrolled state
|
||||
// DRY validation for course card in enrolled state
|
||||
expect(view.$('.course-details .course-title-link').text().trim()).toEqual(course.title);
|
||||
expect(view.$('.course-details .course-title-link').attr('href')).toEqual(
|
||||
course.course_runs[0].marketing_url,
|
||||
@@ -42,8 +47,8 @@ describe('Course Card View', () => {
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// NOTE: This data is redefined prior to each test case so that tests
|
||||
// can't break each other by modifying data copied by reference.
|
||||
// NOTE: This data is redefined prior to each test case so that tests
|
||||
// can't break each other by modifying data copied by reference.
|
||||
course = {
|
||||
key: 'WageningenX+FFESx',
|
||||
uuid: '9f8562eb-f99b-45c7-b437-799fd0c15b6a',
|
||||
|
||||
@@ -45,6 +45,16 @@ describe('Program Details Header View', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
subscriptionData: [
|
||||
{
|
||||
trial_end: '1970-01-01T03:25:45Z',
|
||||
next_payment_date: '1970-06-03T07:12:04Z',
|
||||
price: '100.00',
|
||||
currency: 'USD',
|
||||
subscription_state: 'active',
|
||||
},
|
||||
],
|
||||
isSubscriptionEligible: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -71,4 +81,8 @@ describe('Program Details Header View', () => {
|
||||
expect(view.$('.org-logo').attr('alt'))
|
||||
.toEqual(`${context.programData.authoring_organizations[0].name}'s logo`);
|
||||
});
|
||||
|
||||
it('should render the subscription badge if subscription is active', () => {
|
||||
expect(view.$('.meta-info .badge').html().trim()).toEqual('Subscribed');
|
||||
});
|
||||
});
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,11 +1,17 @@
|
||||
/* globals setFixtures */
|
||||
import moment from 'moment';
|
||||
|
||||
import ProgramDetailsView from '../views/program_details_view';
|
||||
|
||||
describe('Program Details Header View', () => {
|
||||
describe('Program Details View', () => {
|
||||
let view = null;
|
||||
const options = {
|
||||
programData: {
|
||||
subscription_eligible: false,
|
||||
subscription_prices: [{
|
||||
price: '100.00',
|
||||
currency: 'USD',
|
||||
}],
|
||||
subtitle: '',
|
||||
overview: '',
|
||||
weeks_to_complete: null,
|
||||
@@ -462,11 +468,23 @@ describe('Program Details Header View', () => {
|
||||
},
|
||||
],
|
||||
},
|
||||
subscriptionData: [
|
||||
{
|
||||
trial_end: '1970-01-01T03:25:45Z',
|
||||
next_payment_date: '1970-06-03T07:12:04Z',
|
||||
price: '100.00',
|
||||
currency: 'USD',
|
||||
subscription_state: 'pre',
|
||||
},
|
||||
],
|
||||
urls: {
|
||||
program_listing_url: '/dashboard/programs/',
|
||||
commerce_api_url: '/api/commerce/v0/baskets/',
|
||||
track_selection_url: '/course_modes/choose/',
|
||||
program_record_url: 'http://credentials.example.com/records/programs/UUID',
|
||||
buy_subscription_url: '/subscriptions',
|
||||
manage_subscription_url: '/orders',
|
||||
subscriptions_learner_help_center_url: '/learner',
|
||||
},
|
||||
userPreferences: {
|
||||
'pref-lang': 'en',
|
||||
@@ -493,10 +511,36 @@ describe('Program Details Header View', () => {
|
||||
destination_url: 'industry.com',
|
||||
},
|
||||
],
|
||||
programTabViewEnabled: false
|
||||
programTabViewEnabled: false,
|
||||
isUserB2CSubscriptionsEnabled: false,
|
||||
};
|
||||
const data = options.programData;
|
||||
|
||||
const testSubscriptionState = (state, heading, body, trial = false) => {
|
||||
const subscriptionData = {
|
||||
...options.subscriptionData[0],
|
||||
subscription_state: state,
|
||||
};
|
||||
if (trial) {
|
||||
subscriptionData.trial_end = moment().add(3, 'days').utc().format(
|
||||
'YYYY-MM-DDTHH:mm:ss[Z]'
|
||||
);
|
||||
}
|
||||
view = initView({
|
||||
programData: $.extend({}, options.programData, {
|
||||
subscription_eligible: true,
|
||||
}),
|
||||
isUserB2CSubscriptionsEnabled: true,
|
||||
subscriptionData: [subscriptionData],
|
||||
});
|
||||
view.render();
|
||||
expect(view.$('.upgrade-subscription')[0]).toBeInDOM();
|
||||
expect(view.$('.upgrade-subscription .upgrade-button'))
|
||||
.toContainText(heading);
|
||||
expect(view.$('.upgrade-subscription .subscription-info-brief'))
|
||||
.toContainText(body);
|
||||
};
|
||||
|
||||
const initView = (updates) => {
|
||||
const viewOptions = $.extend({}, options, updates);
|
||||
|
||||
@@ -535,9 +579,9 @@ describe('Program Details Header View', () => {
|
||||
expect(view.$('.program-heading-title').text()).toEqual('Your Program Journey');
|
||||
expect(view.$('.program-heading-message').text().trim()
|
||||
.replace(/\s+/g, ' ')).toEqual(
|
||||
'Track and plan your progress through the 3 courses in this program. '
|
||||
+ 'To complete the program, you must earn a verified certificate for each course.',
|
||||
);
|
||||
'Track and plan your progress through the 3 courses in this program. '
|
||||
+ 'To complete the program, you must earn a verified certificate for each course.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the program heading congratulations message if all courses completed', () => {
|
||||
@@ -553,8 +597,8 @@ describe('Program Details Header View', () => {
|
||||
expect(view.$('.program-heading-title').text()).toEqual('Congratulations!');
|
||||
expect(view.$('.program-heading-message').text().trim()
|
||||
.replace(/\s+/g, ' ')).toEqual(
|
||||
'You have successfully completed all the requirements for the Test Course Title Test.',
|
||||
);
|
||||
'You have successfully completed all the requirements for the Test Course Title Test.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the course list headings', () => {
|
||||
@@ -645,4 +689,37 @@ describe('Program Details Header View', () => {
|
||||
properties,
|
||||
);
|
||||
});
|
||||
|
||||
it('should render the get subscription link if program is subscription eligible', () => {
|
||||
testSubscriptionState(
|
||||
'pre',
|
||||
'Start 7-Day free trial',
|
||||
'$100/month subscription after trial ends. Cancel anytime.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render appropriate subscription text when subscription is active with trial', () => {
|
||||
testSubscriptionState(
|
||||
'active',
|
||||
'Manage my subscription',
|
||||
'Active trial ends',
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('should render appropriate subscription text when subscription is active', () => {
|
||||
testSubscriptionState(
|
||||
'active',
|
||||
'Manage my subscription',
|
||||
'Your next billing date is'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render appropriate subscription text when subscription is inactive', () => {
|
||||
testSubscriptionState(
|
||||
'inactive',
|
||||
'Restart my subscription',
|
||||
'Unlock verified access to all courses for $100/month. Cancel anytime.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,8 @@ import ExpiredNotificationView from './expired_notification_view';
|
||||
import CourseEnrollView from './course_enroll_view';
|
||||
import EntitlementView from './course_entitlement_view';
|
||||
|
||||
import SubscriptionModel from '../models/program_subscription_model';
|
||||
|
||||
import pageTpl from '../../../templates/learner_dashboard/course_card.underscore';
|
||||
|
||||
class CourseCardView extends Backbone.View {
|
||||
@@ -24,6 +26,9 @@ class CourseCardView extends Backbone.View {
|
||||
this.enrollModel = new EnrollModel();
|
||||
if (options.context) {
|
||||
this.urlModel = new Backbone.Model(options.context.urls);
|
||||
this.subscriptionModel = new SubscriptionModel({
|
||||
context: options.context,
|
||||
});
|
||||
this.enrollModel.urlRoot = this.urlModel.get('commerce_api_url');
|
||||
}
|
||||
this.context = options.context || {};
|
||||
@@ -86,6 +91,8 @@ class CourseCardView extends Backbone.View {
|
||||
this.upgradeMessage = new UpgradeMessageView({
|
||||
$el: $upgradeMessage,
|
||||
model: this.model,
|
||||
subscriptionModel: this.subscriptionModel,
|
||||
isSubscriptionEligible: this.context.isSubscriptionEligible,
|
||||
});
|
||||
|
||||
$certStatus.remove();
|
||||
|
||||
@@ -8,6 +8,7 @@ import StringUtils from 'edx-ui-toolkit/js/utils/string-utils';
|
||||
import CertificateView from './certificate_list_view';
|
||||
import ProgramProgressView from './progress_circle_view';
|
||||
|
||||
import arrowUprightIcon from '../../../images/arrow-upright-icon.svg';
|
||||
import sidebarTpl from '../../../templates/learner_dashboard/program_details_sidebar.underscore';
|
||||
|
||||
class ProgramDetailsSidebarView extends Backbone.View {
|
||||
@@ -25,23 +26,32 @@ class ProgramDetailsSidebarView extends Backbone.View {
|
||||
this.courseModel = options.courseModel || {};
|
||||
this.certificateCollection = options.certificateCollection || [];
|
||||
this.programCertificate = this.getProgramCertificate();
|
||||
this.programRecordUrl = options.programRecordUrl;
|
||||
this.industryPathways = options.industryPathways;
|
||||
this.creditPathways = options.creditPathways;
|
||||
this.programModel = options.model;
|
||||
this.subscriptionModel = options.subscriptionModel;
|
||||
this.programTabViewEnabled = options.programTabViewEnabled;
|
||||
this.isSubscriptionEligible = options.isSubscriptionEligible;
|
||||
this.urls = options.urls;
|
||||
this.render();
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = $.extend({}, this.model.toJSON(), {
|
||||
programCertificate: this.programCertificate
|
||||
? this.programCertificate.toJSON() : {},
|
||||
programRecordUrl: this.programRecordUrl,
|
||||
industryPathways: this.industryPathways,
|
||||
creditPathways: this.creditPathways,
|
||||
programTabViewEnabled: this.programTabViewEnabled
|
||||
});
|
||||
const data = $.extend(
|
||||
{},
|
||||
this.model.toJSON(),
|
||||
this.subscriptionModel.toJSON(),
|
||||
{
|
||||
programCertificate: this.programCertificate
|
||||
? this.programCertificate.toJSON() : {},
|
||||
industryPathways: this.industryPathways,
|
||||
creditPathways: this.creditPathways,
|
||||
programTabViewEnabled: this.programTabViewEnabled,
|
||||
isSubscriptionEligible: this.isSubscriptionEligible,
|
||||
arrowUprightIcon,
|
||||
...this.urls,
|
||||
},
|
||||
);
|
||||
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
|
||||
@@ -10,6 +10,9 @@ import CourseCardView from './course_card_view';
|
||||
import HeaderView from './program_header_view';
|
||||
import SidebarView from './program_details_sidebar_view';
|
||||
|
||||
import SubscriptionModel from '../models/program_subscription_model';
|
||||
|
||||
import restartIcon from '../../../images/restart-icon.svg';
|
||||
import pageTpl from '../../../templates/learner_dashboard/program_details_view.underscore';
|
||||
import tabPageTpl from '../../../templates/learner_dashboard/program_details_tab_view.underscore';
|
||||
import trackECommerceEvents from '../../commerce/track_ecommerce_events';
|
||||
@@ -32,9 +35,18 @@ class ProgramDetailsView extends Backbone.View {
|
||||
} else {
|
||||
this.tpl = HtmlUtils.template(pageTpl);
|
||||
}
|
||||
this.options.isSubscriptionEligible = (
|
||||
this.options.isUserB2CSubscriptionsEnabled
|
||||
&& this.options.programData.subscription_eligible
|
||||
);
|
||||
this.programModel = new Backbone.Model(this.options.programData);
|
||||
this.courseData = new Backbone.Model(this.options.courseData);
|
||||
this.certificateCollection = new Backbone.Collection(this.options.certificateData);
|
||||
this.certificateCollection = new Backbone.Collection(
|
||||
this.options.certificateData
|
||||
);
|
||||
this.subscriptionModel = new SubscriptionModel({
|
||||
context: this.options,
|
||||
});
|
||||
this.completedCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('completed') || [],
|
||||
this.options.userPreferences,
|
||||
@@ -74,6 +86,7 @@ class ProgramDetailsView extends Backbone.View {
|
||||
this.options.urls.buy_button_url,
|
||||
this.options.programData
|
||||
);
|
||||
|
||||
let data = {
|
||||
totalCount,
|
||||
inProgressCount,
|
||||
@@ -85,9 +98,14 @@ class ProgramDetailsView extends Backbone.View {
|
||||
creditPathways: this.options.creditPathways,
|
||||
discussionFragment: this.options.discussionFragment,
|
||||
live_fragment: this.options.live_fragment,
|
||||
|
||||
isSubscriptionEligible: this.options.isSubscriptionEligible,
|
||||
restartIcon,
|
||||
};
|
||||
data = $.extend(data, this.programModel.toJSON());
|
||||
data = $.extend(
|
||||
data,
|
||||
this.programModel.toJSON(),
|
||||
this.subscriptionModel.toJSON(),
|
||||
);
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
}
|
||||
@@ -132,11 +150,13 @@ class ProgramDetailsView extends Backbone.View {
|
||||
el: '.js-program-sidebar',
|
||||
model: this.programModel,
|
||||
courseModel: this.courseData,
|
||||
subscriptionModel: this.subscriptionModel,
|
||||
certificateCollection: this.certificateCollection,
|
||||
programRecordUrl: this.options.urls.program_record_url,
|
||||
industryPathways: this.options.industryPathways,
|
||||
creditPathways: this.options.creditPathways,
|
||||
programTabViewEnabled: this.options.programTabViewEnabled,
|
||||
isSubscriptionEligible: this.options.isSubscriptionEligible,
|
||||
urls: this.options.urls,
|
||||
});
|
||||
let hasIframe = false;
|
||||
$('#live-tab').click(() => {
|
||||
|
||||
@@ -40,10 +40,21 @@ class ProgramHeaderView extends Backbone.View {
|
||||
return logo;
|
||||
}
|
||||
|
||||
getIsSubscribed() {
|
||||
const isSubscriptionEligible = this.model.get('isSubscriptionEligible');
|
||||
const subscriptionData = this.model.get('subscriptionData')?.[0];
|
||||
|
||||
return (
|
||||
isSubscriptionEligible &&
|
||||
subscriptionData?.subscription_state === 'active'
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = $.extend(this.model.toJSON(), {
|
||||
breakpoints: this.breakpoints,
|
||||
logo: this.getLogo(),
|
||||
isSubscribed: this.getIsSubscribed(),
|
||||
});
|
||||
|
||||
if (this.model.get('programData')) {
|
||||
|
||||
@@ -3,12 +3,18 @@ import Backbone from 'backbone';
|
||||
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
|
||||
|
||||
import upgradeMessageTpl from '../../../templates/learner_dashboard/upgrade_message.underscore';
|
||||
import upgradeMessageSubscriptionTpl from '../../../templates/learner_dashboard/upgrade_message_subscription.underscore';
|
||||
import trackECommerceEvents from '../../commerce/track_ecommerce_events';
|
||||
|
||||
class UpgradeMessageView extends Backbone.View {
|
||||
initialize(options) {
|
||||
this.messageTpl = HtmlUtils.template(upgradeMessageTpl);
|
||||
if (options.isSubscriptionEligible) {
|
||||
this.messageTpl = HtmlUtils.template(upgradeMessageSubscriptionTpl);
|
||||
} else {
|
||||
this.messageTpl = HtmlUtils.template(upgradeMessageTpl);
|
||||
}
|
||||
this.$el = options.$el;
|
||||
this.subscriptionModel = options.subscriptionModel;
|
||||
this.render();
|
||||
|
||||
const courseUpsellButtons = this.$el.find('.program_dashboard_course_upsell_button');
|
||||
@@ -20,7 +26,11 @@ class UpgradeMessageView extends Backbone.View {
|
||||
}
|
||||
|
||||
render() {
|
||||
const data = this.model.toJSON();
|
||||
const data = $.extend(
|
||||
{},
|
||||
this.model.toJSON(),
|
||||
this.subscriptionModel.toJSON(),
|
||||
);
|
||||
HtmlUtils.setHtml(this.$el, this.messageTpl(data));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,6 +434,34 @@ $btn-color-primary: $primary-dark;
|
||||
}
|
||||
}
|
||||
|
||||
.upgrade-subscription {
|
||||
margin: 10px 15px 10px 5px;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.subscription-icon-restart {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-inline-end: 8px;
|
||||
}
|
||||
|
||||
.subscription-icon-arrow-upright {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-inline-start: 8px;
|
||||
}
|
||||
|
||||
.subscription-info-brief {
|
||||
font-size: 0.9375em;
|
||||
color: $gray-500;
|
||||
}
|
||||
|
||||
.subscription-info-upsell {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.8125em;
|
||||
}
|
||||
|
||||
.program-course-card {
|
||||
width: 100%;
|
||||
@@ -631,12 +659,25 @@ $btn-color-primary: $primary-dark;
|
||||
.program-sidebar {
|
||||
padding: 40px 40px 40px 0px;
|
||||
|
||||
.program-record {
|
||||
.program-record,.subscription-info {
|
||||
text-align: left;
|
||||
padding-bottom: 2em;
|
||||
}
|
||||
|
||||
.motivating-section {
|
||||
.subscription-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
color: #414141;
|
||||
|
||||
.subscription-link {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid currentColor;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-section {
|
||||
font-size: 0.9375em;
|
||||
width: auto;
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ from openedx.core.djangolib.js_utils import (
|
||||
<%static:webpack entry="ProgramDetailsFactory">
|
||||
ProgramDetailsFactory({
|
||||
programData: ${program_data | n, dump_js_escaped_json},
|
||||
subscriptionData: ${program_subscription_data | n, dump_js_escaped_json},
|
||||
courseData: ${course_data | n, dump_js_escaped_json},
|
||||
certificateData: ${certificate_data | n, dump_js_escaped_json},
|
||||
urls: ${urls | n, dump_js_escaped_json},
|
||||
@@ -21,6 +22,7 @@ ProgramDetailsFactory({
|
||||
industryPathways: ${industry_pathways | n, dump_js_escaped_json},
|
||||
creditPathways: ${credit_pathways | n, dump_js_escaped_json},
|
||||
programTabViewEnabled: ${program_tab_view_enabled | n, dump_js_escaped_json},
|
||||
isUserB2CSubscriptionsEnabled: ${is_user_b2c_subscriptions_enabled | n, dump_js_escaped_json},
|
||||
discussionFragment: ${discussion_fragment, | n, dump_js_escaped_json},
|
||||
live_fragment: ${live_fragment, | n, dump_js_escaped_json}
|
||||
});
|
||||
|
||||
@@ -8,14 +8,61 @@
|
||||
<% } %>
|
||||
</aside>
|
||||
<aside class="aside js-course-certificates"></aside>
|
||||
<% if (isSubscriptionEligible) { %>
|
||||
<aside class="aside js-subscription-info subscription-info">
|
||||
<h2 class="divider-heading">
|
||||
<%- gettext(
|
||||
hasActiveTrial
|
||||
? 'Trial subscription'
|
||||
: subscriptionState === 'active'
|
||||
? 'Active subscription'
|
||||
: 'Inactive subscription'
|
||||
) %>
|
||||
</h2>
|
||||
<div class="sidebar-section">
|
||||
<div class="subscription-section">
|
||||
<p class="my-0">
|
||||
<%= HtmlUtils.interpolateHtml(
|
||||
gettext(
|
||||
subscriptionState === 'active'
|
||||
? 'View your receipts or modify your subscription on the {a_start}Orders and subscriptions{a_end} page'
|
||||
: subscriptionState === 'inactive'
|
||||
? 'Restart your subscription for {subscriptionPrice}/month. Your payment history is still available on the {a_start}Orders and subscriptions{a_end} page'
|
||||
: 'If you had a subscription previously, your payment history is still available on the {a_start}Orders and subscriptions{a_end} page'
|
||||
),
|
||||
{
|
||||
subscriptionPrice,
|
||||
a_start: HtmlUtils.HTML(`<a class="subscription-link" href="${manage_subscription_url}">`),
|
||||
a_end: HtmlUtils.HTML('</a>'),
|
||||
}
|
||||
) %>
|
||||
</p>
|
||||
<p class="my-0">
|
||||
<%= HtmlUtils.interpolateHtml(
|
||||
gettext(
|
||||
'Need help? Check out the {a_start}Learner Help Center{span_start}{icon}{span_end}{a_end} to troubleshoot issues or contact support '
|
||||
),
|
||||
{
|
||||
a_start: HtmlUtils.HTML(`<a class="subscription-link" href="${subscriptions_learner_help_center_url}" target="_blank" rel="noopener noreferrer">`),
|
||||
a_end: HtmlUtils.HTML('</a>'),
|
||||
span_start: HtmlUtils.HTML('<span class="subscription-icon-arrow-upright">'),
|
||||
icon: HtmlUtils.HTML(arrowUprightIcon),
|
||||
span_end: HtmlUtils.HTML('</span>'),
|
||||
}
|
||||
) %>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
<% } %>
|
||||
<aside class="aside js-program-record program-record">
|
||||
<h2 class="divider-heading"><%- gettext('Program Record') %></h2>
|
||||
<div class="motivating-section">
|
||||
<div class="sidebar-section">
|
||||
<p class="motivating-message"><%- gettext('Once you complete one of the program requirements you have a program record. This record is marked complete once you meet all program requirements. A program record can be used to continue your learning journey and demonstrate your learning to others.') %></p>
|
||||
</div>
|
||||
<% if (programRecordUrl) { %>
|
||||
<% if (program_record_url) { %>
|
||||
<div class="sidebar-button-wrapper">
|
||||
<a href="<%- programRecordUrl %>" class="program-record-link">
|
||||
<a href="<%- program_record_url %>" class="program-record-link">
|
||||
<button class="btn sidebar-button"><%- gettext('View Program Record') %></button>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,47 @@
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (is_learner_eligible_for_one_click_purchase && (typeof is_mobile_only === 'undefined' || is_mobile_only === false)) { %>
|
||||
<% if (isSubscriptionEligible) { %>
|
||||
<div class="d-flex flex-column align-items-start flex-sm-row align-items-sm-center upgrade-subscription">
|
||||
<a href="<%- subscriptionUrl %>" class="btn-brand btn cta-primary upgrade-button">
|
||||
<% if (subscriptionState === 'inactive') { %>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subscription-icon-restart">
|
||||
<% // xss-lint: disable=underscore-not-escaped %>
|
||||
<%= restartIcon %>
|
||||
</div>
|
||||
<span><%- gettext('Restart my subscription') %></span>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<%- StringUtils.interpolate(gettext(
|
||||
subscriptionState === 'active'
|
||||
? 'Manage my subscription'
|
||||
: 'Start {trialLength}-Day free trial'
|
||||
),
|
||||
{ trialLength } ) %>
|
||||
<% } %>
|
||||
</a>
|
||||
<span class="subscription-info-brief">
|
||||
<%- StringUtils.interpolate(
|
||||
gettext(
|
||||
hasActiveTrial
|
||||
? 'Active trial ends {trialEndDate} at {trialEndTime}'
|
||||
: subscriptionState === 'active'
|
||||
? 'Your next billing date is {nextPaymentDate}'
|
||||
: subscriptionState === 'inactive'
|
||||
? 'Unlock verified access to all courses for {subscriptionPrice}/month. Cancel anytime.'
|
||||
: '{subscriptionPrice}/month subscription after trial ends. Cancel anytime.'
|
||||
),
|
||||
{
|
||||
subscriptionPrice,
|
||||
nextPaymentDate,
|
||||
trialEndDate,
|
||||
trialEndTime,
|
||||
}
|
||||
) %>
|
||||
</span>
|
||||
</div>
|
||||
<% } else if (is_learner_eligible_for_one_click_purchase && (typeof is_mobile_only === 'undefined' || is_mobile_only === false)) { %>
|
||||
<a href="<%- completeProgramURL %>" class="btn-brand btn cta-primary upgrade-button complete-program" id="program_dashboard_course_upsell_all_button">
|
||||
<%- gettext('Upgrade All Remaining Courses (')%>
|
||||
<% if (discount_data.is_discounted) { %>
|
||||
|
||||
@@ -22,7 +22,47 @@
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (is_learner_eligible_for_one_click_purchase && (typeof is_mobile_only === 'undefined' || is_mobile_only === false)) { %>
|
||||
<% if (isSubscriptionEligible) { %>
|
||||
<div class="d-flex flex-column align-items-start flex-sm-row align-items-sm-center upgrade-subscription">
|
||||
<a href="<%- subscriptionUrl %>" class="btn-brand btn cta-primary upgrade-button">
|
||||
<% if (subscriptionState === 'inactive') { %>
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="subscription-icon-restart">
|
||||
<% // xss-lint: disable=underscore-not-escaped %>
|
||||
<%= restartIcon %>
|
||||
</div>
|
||||
<span><%- gettext('Restart my subscription') %></span>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<%- StringUtils.interpolate(gettext(
|
||||
subscriptionState === 'active'
|
||||
? 'Manage my subscription'
|
||||
: 'Start {trialLength}-Day free trial'
|
||||
),
|
||||
{ trialLength } ) %>
|
||||
<% } %>
|
||||
</a>
|
||||
<span class="subscription-info-brief">
|
||||
<%- StringUtils.interpolate(
|
||||
gettext(
|
||||
hasActiveTrial
|
||||
? 'Active trial ends {trialEndDate} at {trialEndTime}'
|
||||
: subscriptionState === 'active'
|
||||
? 'Your next billing date is {nextPaymentDate}'
|
||||
: subscriptionState === 'inactive'
|
||||
? 'Unlock verified access to all courses for {subscriptionPrice}/month. Cancel anytime.'
|
||||
: '{subscriptionPrice}/month subscription after trial ends. Cancel anytime.'
|
||||
),
|
||||
{
|
||||
subscriptionPrice,
|
||||
nextPaymentDate,
|
||||
trialEndDate,
|
||||
trialEndTime,
|
||||
}
|
||||
) %>
|
||||
</span>
|
||||
</div>
|
||||
<% } else if (is_learner_eligible_for_one_click_purchase && (typeof is_mobile_only === 'undefined' || is_mobile_only === false)) { %>
|
||||
<a href="<%- completeProgramURL %>" class="btn-brand btn cta-primary upgrade-button complete-program" id="program_dashboard_course_upsell_all_button">
|
||||
<%- gettext('Upgrade All Remaining Courses (')%>
|
||||
<% if (discount_data.is_discounted) { %>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<div class="program-details-header">
|
||||
<div class="meta-info grid-container">
|
||||
<% if (isSubscribed) { %>
|
||||
<div class="mb-3">
|
||||
<span class="badge badge-light"><%- gettext('Subscribed') %></span>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (logo) { %>
|
||||
<% // xss-lint: disable=underscore-not-escaped %>
|
||||
<span aria-label="<%- gettext(programData.type) %>" class="<%- programData.type.toLowerCase() %> program-details-icon"><%= logo %></span>
|
||||
|
||||
@@ -7,6 +7,6 @@
|
||||
<div class="action col-12 md-col-4">
|
||||
<a href="<%- upgrade_url %>" class="btn-brand btn cta-primary upgrade-button single-course-run program_dashboard_course_upsell_button">
|
||||
<%- gettext('Upgrade to Verified') %>
|
||||
<a>
|
||||
</a>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="message certificate-status col-12 md-col-8">
|
||||
<span class="card-msg"><%- gettext('Certificate Status:') %></span>
|
||||
<span><%- gettext('Needs verified certificate ') %></span>
|
||||
</div>
|
||||
<% if ( subscriptionState !== 'active' ) { %>
|
||||
<div class="action d-flex flex-column align-items-start align-items-md-end">
|
||||
<a href="<%- subscriptionUrl %>" class="btn-brand btn cta-primary upgrade-button single-course-run program_dashboard_course_upsell_button">
|
||||
<%- gettext('Upgrade with a subscription') %>
|
||||
</a>
|
||||
<span class="subscription-info-upsell">
|
||||
<%- StringUtils.interpolate(
|
||||
gettext(
|
||||
subscriptionState === 'inactive'
|
||||
? 'Pay {subscriptionPrice}/month for all courses in this program'
|
||||
: 'Pay {subscriptionPrice}/month after {trialLength}-day free trial'
|
||||
),
|
||||
{ subscriptionPrice, trialLength },
|
||||
) %>
|
||||
</span>
|
||||
</div>
|
||||
<% } %>
|
||||
Reference in New Issue
Block a user