fix: Remove B2C Subscriptions (#35303)

REV-3697
This commit is contained in:
Juliana Kang
2024-09-04 14:01:45 -04:00
committed by GitHub
parent ef4e03729e
commit 51d538cbe7
50 changed files with 9 additions and 1709 deletions

View File

@@ -1,86 +0,0 @@
import Backbone from 'backbone';
import moment from 'moment';
import DateUtils from 'edx-ui-toolkit/js/utils/date-utils';
import StringUtils from 'edx-ui-toolkit/js/utils/string-utils';
/**
* Model for Program Subscription Data.
*/
class ProgramSubscriptionModel extends Backbone.Model {
constructor({ context }, ...args) {
const {
subscriptionData: [data = {}],
programData: { subscription_prices },
urls = {},
userPreferences = {},
subscriptionsTrialLength: trialLength = 7,
} = context;
const priceInUSD = subscription_prices?.find(({ currency }) => currency === 'USD');
const subscriptionState = data.subscription_state?.toLowerCase() ?? '';
const subscriptionPrice = StringUtils.interpolate(
gettext('${price}/month {currency}'),
{
price: parseFloat(priceInUSD?.price),
currency: priceInUSD?.currency,
}
);
const subscriptionUrl =
subscriptionState === 'active'
? urls.manage_subscription_url
: urls.buy_subscription_url;
const hasActiveTrial = false;
const remainingDays = 0;
const [currentPeriodEnd] = ProgramSubscriptionModel.formatDate(
data.current_period_end,
userPreferences
);
const [trialEndDate, trialEndTime] = ['', ''];
super(
{
hasActiveTrial,
currentPeriodEnd,
remainingDays,
subscriptionPrice,
subscriptionState,
subscriptionUrl,
trialEndDate,
trialEndTime,
trialLength,
},
...args
);
}
static formatDate(date, userPreferences) {
if (!date) {
return ['', ''];
}
const userTimezone = (
userPreferences.time_zone || moment?.tz?.guess?.() || '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 = '';
return [localDate, localTime];
}
}
export default ProgramSubscriptionModel;

View File

@@ -11,58 +11,18 @@ import HeaderView from './views/program_list_header_view';
function ProgramListFactory(options) {
const progressCollection = new ProgressCollection();
const subscriptionCollection = new Backbone.Collection();
if (options.userProgress) {
progressCollection.set(options.userProgress);
options.progressCollection = progressCollection; // eslint-disable-line no-param-reassign
}
if (options.programsSubscriptionData.length) {
subscriptionCollection.set(options.programsSubscriptionData);
options.subscriptionCollection = subscriptionCollection; // eslint-disable-line no-param-reassign
}
if (options.programsData.length) {
if (!options.mobileOnly) {
new HeaderView({
context: options,
}).render();
}
const activeSubscriptions = options.programsSubscriptionData
// eslint-disable-next-line camelcase
.filter(({ subscription_state }) => subscription_state === 'active')
.sort((a, b) => new Date(b.created) - new Date(a.created));
// Sort programs so programs with active subscriptions are at the top
if (activeSubscriptions.length) {
// eslint-disable-next-line no-param-reassign
options.programsData = options.programsData
.map((programsData) => ({
...programsData,
subscriptionIndex: activeSubscriptions.findIndex(
// eslint-disable-next-line camelcase
({ resource_id }) => resource_id === programsData.uuid,
),
}))
.sort(({ subscriptionIndex: indexA }, { subscriptionIndex: indexB }) => {
switch (true) {
case indexA === -1 && indexB === -1:
// Maintain the original order for non-subscription programs
return 0;
case indexA === -1:
// Move non-subscription program to the end
return 1;
case indexB === -1:
// Keep non-subscription program to the end
return -1;
default:
// Sort by subscriptionIndex in ascending order
return indexA - indexB;
}
});
}
}
new CollectionListView({

View File

@@ -1,7 +1,5 @@
/* globals setFixtures */
import Backbone from 'backbone';
import CollectionListView from '../views/collection_list_view';
import ProgramCardView from '../views/program_card_view';
import ProgramCollection from '../collections/program_collection';
@@ -11,7 +9,6 @@ describe('Collection List View', () => {
let view = null;
let programCollection;
let progressCollection;
let subscriptionCollection;
const context = {
programsData: [
{
@@ -101,21 +98,14 @@ describe('Collection List View', () => {
not_started: 3,
},
],
programsSubscriptionData: [{
resource_id: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8',
subscription_state: 'active',
}],
isUserB2CSubscriptionsEnabled: false,
};
beforeEach(() => {
setFixtures('<div class="program-cards-container"></div>');
programCollection = new ProgramCollection(context.programsData);
progressCollection = new ProgressCollection();
subscriptionCollection = new Backbone.Collection(context.programsSubscriptionData);
progressCollection.set(context.userProgress);
context.progressCollection = progressCollection;
context.subscriptionCollection = subscriptionCollection;
view = new CollectionListView({
el: '.program-cards-container',

View File

@@ -17,10 +17,8 @@ describe('Course Card View', () => {
programData,
collectionCourseStatus,
courseData: {},
subscriptionData: [],
urls: {},
userPreferences: {},
isSubscriptionEligible: false,
};
if (typeof collectionCourseStatus === 'undefined') {

View File

@@ -1,58 +0,0 @@
/* globals setFixtures */
import ProgramAlertListView from '../views/program_alert_list_view';
describe('Program Alert List View', () => {
let view = null;
const context = {
enrollmentAlerts: [{ title: 'Test Program' }],
trialEndingAlerts: [{
title: 'Test Program',
hasActiveTrial: true,
currentPeriodEnd: 'May 8, 2023',
remainingDays: 2,
subscriptionPrice: '$100/month USD',
subscriptionState: 'active',
subscriptionUrl: null,
trialEndDate: 'Apr 20, 2023',
trialEndTime: '5:59 am',
trialLength: 7,
}],
pageType: 'programDetails',
};
beforeEach(() => {
setFixtures('<div class="js-program-details-alerts"></div>');
view = new ProgramAlertListView({
el: '.js-program-details-alerts',
context,
});
view.render();
});
afterEach(() => {
view.remove();
});
it('should exist', () => {
expect(view).toBeDefined();
});
it('should render no enrollement alert', () => {
expect(view.$('.alert:first .alert-heading').text().trim()).toEqual(
'Enroll in a Test Program\'s course',
);
expect(view.$('.alert:first .alert-message').text().trim()).toEqual(
'You have an active subscription to the Test Program program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.',
);
});
it('should render subscription trial is expiring alert', () => {
expect(view.$('.alert:last .alert-heading').text().trim()).toEqual(
'Subscription trial expires in 2 days',
);
expect(view.$('.alert:last .alert-message').text().trim()).toEqual(
'Your Test Program trial will expire in 2 days at 5:59 am on Apr 20, 2023 and the card on file will be charged $100/month USD.',
);
});
});

View File

@@ -42,7 +42,6 @@ describe('Program card View', () => {
name: 'Wageningen University & Research',
},
],
subscriptionIndex: 1,
};
const userProgress = [
{
@@ -58,11 +57,6 @@ describe('Program card View', () => {
not_started: 3,
},
];
// eslint-disable-next-line no-undef
const subscriptionCollection = new Backbone.Collection([{
resource_id: 'a87e5eac-3c93-45a1-a8e1-4c79ca8401c8',
subscription_state: 'active',
}]);
const progressCollection = new ProgressCollection();
const cardRenders = ($card) => {
expect($card).toBeDefined();
@@ -80,8 +74,6 @@ describe('Program card View', () => {
model: programModel,
context: {
progressCollection,
subscriptionCollection,
isUserB2CSubscriptionsEnabled: true,
},
});
});
@@ -133,10 +125,6 @@ describe('Program card View', () => {
view.remove();
view = new ProgramCardView({
model: programModel,
context: {
subscriptionCollection,
isUserB2CSubscriptionsEnabled: true,
},
});
cardRenders(view.$el);
expect(view.$('.progress').length).toEqual(0);
@@ -149,10 +137,6 @@ describe('Program card View', () => {
programModel = new ProgramModel(programNoBanner);
view = new ProgramCardView({
model: programModel,
context: {
subscriptionCollection,
isUserB2CSubscriptionsEnabled: true,
},
});
cardRenders(view.$el);
expect(view.$el.find('.banner-image').attr('srcset')).toEqual('');
@@ -167,16 +151,8 @@ describe('Program card View', () => {
programModel = new ProgramModel(programNoBanner);
view = new ProgramCardView({
model: programModel,
context: {
subscriptionCollection,
isUserB2CSubscriptionsEnabled: true,
},
});
cardRenders(view.$el);
expect(view.$el.find('.banner-image').attr('srcset')).toEqual('');
});
it('should render the subscription badge if subscription is active', () => {
expect(view.$('.subscription-badge .badge').html()?.trim()).toEqual('Subscribed');
});
});

View File

@@ -45,16 +45,6 @@ describe('Program Details Header View', () => {
},
],
},
subscriptionData: [
{
trial_end: '1970-01-01T03:25:45Z',
current_period_end: '1970-06-03T07:12:04Z',
price: '100.00',
currency: 'USD',
subscription_state: 'active',
},
],
isSubscriptionEligible: true,
};
beforeEach(() => {
@@ -81,8 +71,4 @@ 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');
});
});

View File

@@ -1,9 +1,7 @@
/* globals setFixtures */
import Backbone from 'backbone';
import moment from 'moment';
import SubscriptionModel from '../models/program_subscription_model';
import ProgramSidebarView from '../views/program_details_sidebar_view';
describe('Program Progress View', () => {
@@ -25,15 +23,13 @@ describe('Program Progress View', () => {
"url": "/certificates/bed3980e67ca40f0b31e309d9dfe9e7e", "type": "course", "title": "Introduction to the Treatment of Urban Sewage"
}
],
urls: {"program_listing_url": "/dashboard/programs/", "commerce_api_url": "/api/commerce/v0/baskets/", "track_selection_url": "/course_modes/choose/", "program_record_url": "/foo/bar", "buy_subscription_url": "/subscriptions", "orders_and_subscriptions_url": "/orders", "subscriptions_learner_help_center_url": "/learner"},
urls: {"program_listing_url": "/dashboard/programs/", "commerce_api_url": "/api/commerce/v0/baskets/", "track_selection_url": "/course_modes/choose/"},
userPreferences: {"pref-lang": "en"}
};
/* eslint-enable */
let programModel;
let courseData;
let subscriptionData;
let certificateCollection;
let isSubscriptionEligible;
const testCircle = (progress) => {
const $circle = view.$('.progress-circle');
@@ -53,55 +49,15 @@ describe('Program Progress View', () => {
expect(parseInt($numbers.find('.total').html(), 10)).toEqual(total);
};
const testSubscriptionState = (state, heading, body) => {
isSubscriptionEligible = true;
subscriptionData.subscription_state = state;
// eslint-disable-next-line no-use-before-define
view = initView();
// eslint-disable-next-line no-param-reassign
body += ' on the <a class="subscription-link" href="/orders">Orders and subscriptions</a> page';
expect(view.$('.js-subscription-info')[0]).toBeInDOM();
expect(
view.$('.js-subscription-info .divider-heading').text().trim(),
).toEqual(heading);
expect(
view.$('.js-subscription-info .subscription-section p:nth-child(1)'),
).toContainHtml(body);
expect(
view.$('.js-subscription-info .subscription-section p:nth-child(2)'),
).toContainText(
/Need help\? Check out the.*Learner Help Center.*to troubleshoot issues or contact support/,
);
expect(
view.$('.js-subscription-info .subscription-section p:nth-child(2) .subscription-link').attr('href'),
).toEqual('/learner');
};
const initView = () => new ProgramSidebarView({
el: '.js-program-sidebar',
model: programModel,
courseModel: courseData,
subscriptionModel: new SubscriptionModel({
context: {
programData: {
subscription_eligible: isSubscriptionEligible,
subscription_prices: [{
price: '100.00',
currency: 'USD',
}],
},
subscriptionData: [subscriptionData],
urls: data.urls,
userPreferences: data.userPreferences,
},
}),
certificateCollection,
industryPathways: data.industryPathways,
creditPathways: data.creditPathways,
programTabViewEnabled: false,
urls: data.urls,
isSubscriptionEligible,
});
beforeEach(() => {
@@ -109,14 +65,6 @@ describe('Program Progress View', () => {
programModel = new Backbone.Model(data.programData);
courseData = new Backbone.Model(data.courseData);
certificateCollection = new Backbone.Collection(data.certificateData);
isSubscriptionEligible = false;
subscriptionData = {
trial_end: '1970-01-01T03:25:45Z',
current_period_end: '1970-06-03T07:12:04Z',
price: '100.00',
currency: 'USD',
subscription_state: 'pre',
};
});
afterEach(() => {
@@ -203,69 +151,14 @@ describe('Program Progress View', () => {
el: '.js-program-sidebar',
model: programModel,
courseModel: courseData,
subscriptionModel: new SubscriptionModel({
context: {
programData: {
subscription_eligible: isSubscriptionEligible,
subscription_prices: [{
price: '100.00',
currency: 'USD',
}],
},
subscriptionData: [subscriptionData],
urls: data.urls,
userPreferences: data.userPreferences,
},
}),
certificateCollection,
industryPathways: [],
creditPathways: [],
programTabViewEnabled: false,
urls: data.urls,
isSubscriptionEligible,
});
expect(emptyView.$('.program-credit-pathways .divider-heading')).toHaveLength(0);
expect(emptyView.$('.program-industry-pathways .divider-heading')).toHaveLength(0);
});
it('should not render subscription info if program is not subscription eligible', () => {
view = initView();
expect(view.$('.js-subscription-info')[0]).not.toBeInDOM();
});
it('should render subscription info if program is subscription eligible', () => {
testSubscriptionState(
'pre',
'Inactive subscription',
'If you had a subscription previously, your payment history is still available',
);
});
it('should render active trial subscription info if subscription is active with trial', () => {
subscriptionData.trial_end = moment().add(3, 'days').utc().format(
'YYYY-MM-DDTHH:mm:ss[Z]',
);
testSubscriptionState(
'active',
'Trial subscription',
'View your receipts or modify your subscription',
);
});
it('should render active subscription info if subscription active', () => {
testSubscriptionState(
'active',
'Active subscription',
'View your receipts or modify your subscription',
);
});
it('should render inactive subscription info if subscription inactive', () => {
testSubscriptionState(
'inactive',
'Inactive subscription',
'Restart your subscription for $100/month USD. Your payment history is still available',
);
});
});

View File

@@ -7,11 +7,6 @@ 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,
@@ -468,24 +463,11 @@ describe('Program Details View', () => {
},
],
},
subscriptionData: [
{
trial_end: '1970-01-01T03:25:45Z',
current_period_end: '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',
orders_and_subscriptions_url: '/orders',
},
userPreferences: {
'pref-lang': 'en',
@@ -513,59 +495,9 @@ describe('Program Details View', () => {
},
],
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]',
);
}
// eslint-disable-next-line no-use-before-define
view = initView({
// eslint-disable-next-line no-undef
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 testSubscriptionSunsetting = (state, heading, body) => {
const subscriptionData = {
...options.subscriptionData[0],
subscription_state: state,
};
// eslint-disable-next-line no-use-before-define
view = initView({
// eslint-disable-next-line no-undef
programData: $.extend({}, options.programData, {
subscription_eligible: false,
}),
isUserB2CSubscriptionsEnabled: true,
subscriptionData: [subscriptionData],
});
view.render();
expect(view.$('.upgrade-subscription')[0]).not.toBeInDOM();
expect(view.$('.upgrade-subscription .upgrade-button')).not
.toContainText(heading);
expect(view.$('.upgrade-subscription .subscription-info-brief')).not
.toContainText(body);
};
const initView = (updates) => {
// eslint-disable-next-line no-undef
const viewOptions = $.extend({}, options, updates);
@@ -730,37 +662,4 @@ describe('Program Details View', () => {
properties,
);
});
it('should not render the get subscription link if program is not active', () => {
testSubscriptionSunsetting(
'pre',
'Start 7-day free trial',
'$100/month USD subscription after trial ends. Cancel anytime.',
);
});
it('should not render appropriate subscription text when subscription is active with trial', () => {
testSubscriptionSunsetting(
'active',
'Manage my subscription',
'Trial ends',
true,
);
});
it('should not render appropriate subscription text when subscription is active', () => {
testSubscriptionSunsetting(
'active',
'Manage my subscription',
'Your next billing date is',
);
});
it('should not render appropriate subscription text when subscription is inactive', () => {
testSubscriptionSunsetting(
'inactive',
'Restart my subscription',
'$100/month USD subscription. Cancel anytime.',
);
});
});

View File

@@ -13,27 +13,14 @@ describe('Program List Header View', () => {
{
uuid: '5b234e3c-3a2e-472e-90db-6f51501dc86c',
title: 'edX Demonstration Program',
subscription_eligible: null,
subscription_prices: [],
detail_url: '/dashboard/programs/5b234e3c-3a2e-472e-90db-6f51501dc86c/',
},
{
uuid: 'b90d70d5-f981-4508-bdeb-5b792d930c03',
title: 'Test Program',
subscription_eligible: true,
subscription_prices: [{ price: '500.00', currency: 'USD' }],
detail_url: '/dashboard/programs/b90d70d5-f981-4508-bdeb-5b792d930c03/',
},
],
programsSubscriptionData: [
{
id: 'eeb25640-9741-4c11-963c-8a27337f217c',
resource_id: 'b90d70d5-f981-4508-bdeb-5b792d930c03',
trial_end: '2022-04-20T05:59:42Z',
current_period_end: '2023-05-08T05:59:42Z',
subscription_state: 'active',
},
],
userProgress: [
{
uuid: '5b234e3c-3a2e-472e-90db-6f51501dc86c',
@@ -50,13 +37,9 @@ describe('Program List Header View', () => {
all_unenrolled: true,
},
],
isUserB2CSubscriptionsEnabled: true,
};
beforeEach(() => {
context.subscriptionCollection = new Backbone.Collection(
context.programsSubscriptionData,
);
context.progressCollection = new ProgressCollection(
context.userProgress,
);
@@ -78,18 +61,4 @@ describe('Program List Header View', () => {
it('should render the program heading', () => {
expect(view.$('h2:first').text().trim()).toEqual('My programs');
});
it('should render a program alert', () => {
expect(
view.$('.js-program-list-alerts .alert .alert-heading').html().trim(),
).toEqual('Enroll in a Test Program\'s course');
expect(
view.$('.js-program-list-alerts .alert .alert-message'),
).toContainHtml(
'According to our records, you are not enrolled in any courses included in your Test Program program subscription. Enroll in a course from the <i>Program Details</i> page.',
);
expect(
view.$('.js-program-list-alerts .alert .view-button').attr('href'),
).toEqual('/dashboard/programs/b90d70d5-f981-4508-bdeb-5b792d930c03/');
});
});

View File

@@ -6,12 +6,6 @@ describe('Sidebar View', () => {
let view = null;
const context = {
marketingUrl: 'https://www.example.org/programs',
subscriptionUpsellData: {
marketing_url: 'https://www.example.org/program-subscriptions',
minimum_price: '$39',
trial_length: 7,
},
isUserB2CSubscriptionsEnabled: true,
};
beforeEach(() => {
@@ -32,10 +26,6 @@ describe('Sidebar View', () => {
expect(view).toBeDefined();
});
it('should not render the subscription upsell section', () => {
expect(view.$('.js-subscription-upsell')[0]).not.toBeInDOM();
});
it('should load the exploration panel given a marketing URL', () => {
expect(view.$('.program-advertise .advertise-message').html().trim())
.toEqual(
@@ -49,10 +39,6 @@ describe('Sidebar View', () => {
view.remove();
view = new SidebarView({
el: '.sidebar',
context: {
isUserB2CSubscriptionsEnabled: true,
subscriptionUpsellData: context.subscriptionUpsellData,
},
});
view.render();
const $ad = view.$el.find('.program-advertise');

View File

@@ -9,8 +9,6 @@ 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 {
@@ -27,9 +25,6 @@ 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 || {};
@@ -93,8 +88,6 @@ class CourseCardView extends Backbone.View {
this.upgradeMessage = new UpgradeMessageView({
$el: $upgradeMessage,
model: this.model,
subscriptionModel: this.subscriptionModel,
isSubscriptionEligible: this.context.isSubscriptionEligible,
});
$certStatus.remove();

View File

@@ -1,89 +0,0 @@
import Backbone from 'backbone';
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
import StringUtils from 'edx-ui-toolkit/js/utils/string-utils';
import warningIcon from '../../../images/warning-icon.svg';
import programAlertTpl from '../../../templates/learner_dashboard/program_alert_list_view.underscore';
class ProgramAlertListView extends Backbone.View {
constructor(options) {
const defaults = {
el: '.js-program-details-alerts',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
initialize({ context }) {
this.tpl = HtmlUtils.template(programAlertTpl);
this.enrollmentAlerts = context.enrollmentAlerts || [];
this.trialEndingAlerts = context.trialEndingAlerts || [];
this.pageType = context.pageType;
this.render();
}
render() {
const data = {
alertList: this.getAlertList(),
warningIcon,
};
HtmlUtils.setHtml(this.$el, this.tpl(data));
}
getAlertList() {
const alertList = this.enrollmentAlerts.map(
({ title: programName, url }) => ({
url,
// eslint-disable-next-line no-undef
urlText: gettext('View program'),
title: StringUtils.interpolate(
// eslint-disable-next-line no-undef
gettext('Enroll in a {programName}\'s course'),
{ programName },
),
message: this.pageType === 'programDetails'
? StringUtils.interpolate(
// eslint-disable-next-line no-undef
gettext('You have an active subscription to the {programName} program but are not enrolled in any courses. Enroll in a remaining course and enjoy verified access.'),
{ programName },
)
: HtmlUtils.interpolateHtml(
// eslint-disable-next-line no-undef
gettext('According to our records, you are not enrolled in any courses included in your {programName} program subscription. Enroll in a course from the {i_start}Program Details{i_end} page.'),
{
programName,
i_start: HtmlUtils.HTML('<i>'),
i_end: HtmlUtils.HTML('</i>'),
},
),
}),
);
return alertList.concat(this.trialEndingAlerts.map(
({ title: programName, remainingDays, ...data }) => ({
title: StringUtils.interpolate(
remainingDays < 1
// eslint-disable-next-line no-undef
? gettext('Subscription trial expires in less than 24 hours')
// eslint-disable-next-line no-undef
: ngettext('Subscription trial expires in {remainingDays} day', 'Subscription trial expires in {remainingDays} days', remainingDays),
{ remainingDays },
),
message: StringUtils.interpolate(
remainingDays < 1
// eslint-disable-next-line no-undef
? gettext('Your {programName} trial will expire at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.')
// eslint-disable-next-line no-undef
: ngettext('Your {programName} trial will expire in {remainingDays} day at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.', 'Your {programName} trial will expire in {remainingDays} days at {trialEndTime} on {trialEndDate} and the card on file will be charged {subscriptionPrice}.', remainingDays),
{
programName,
remainingDays,
...data,
},
),
}),
));
}
}
export default ProgramAlertListView;

View File

@@ -30,10 +30,6 @@ class ProgramCardView extends Backbone.View {
uuid: this.model.get('uuid'),
});
}
this.isSubscribed = (
context.isUserB2CSubscriptionsEnabled &&
this.model.get('subscriptionIndex') > -1
) ?? false;
this.render();
}
@@ -45,7 +41,6 @@ class ProgramCardView extends Backbone.View {
this.getProgramProgress(),
{
orgList: orgList.join(' '),
isSubscribed: this.isSubscribed,
},
);

View File

@@ -30,9 +30,7 @@ class ProgramDetailsSidebarView extends Backbone.View {
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();
}
@@ -42,14 +40,12 @@ class ProgramDetailsSidebarView extends Backbone.View {
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,
},

View File

@@ -10,10 +10,6 @@ import CourseCardView from './course_card_view';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import HeaderView from './program_header_view';
import SidebarView from './program_details_sidebar_view';
import AlertListView from './program_alert_list_view';
// eslint-disable-next-line import/no-named-as-default, import/no-named-as-default-member
import SubscriptionModel from '../models/program_subscription_model';
import launchIcon from '../../../images/launch-icon.svg';
import restartIcon from '../../../images/restart-icon.svg';
@@ -27,7 +23,6 @@ class ProgramDetailsView extends Backbone.View {
el: '.js-program-details-wrapper',
events: {
'click .complete-program': 'trackPurchase',
'click .js-subscription-cta': 'trackSubscriptionCTA',
},
};
// eslint-disable-next-line prefer-object-spread
@@ -46,9 +41,6 @@ class ProgramDetailsView extends Backbone.View {
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,
@@ -61,11 +53,6 @@ class ProgramDetailsView extends Backbone.View {
this.courseData.get('not_started') || [],
this.options.userPreferences,
);
this.subscriptionEventParams = {
label: this.options.programData.title,
program_uuid: this.options.programData.uuid,
};
this.options.isSubscriptionEligible = this.getIsSubscriptionEligible();
this.render();
@@ -76,7 +63,6 @@ class ProgramDetailsView extends Backbone.View {
pageName: 'program_dashboard',
linkCategory: 'green_upgrade',
});
this.trackSubscriptionEligibleProgramView();
}
static getUrl(base, programData) {
@@ -107,7 +93,6 @@ class ProgramDetailsView extends Backbone.View {
creditPathways: this.options.creditPathways,
discussionFragment: this.options.discussionFragment,
live_fragment: this.options.live_fragment,
isSubscriptionEligible: this.options.isSubscriptionEligible,
launchIcon,
restartIcon,
};
@@ -115,7 +100,6 @@ class ProgramDetailsView extends Backbone.View {
data = $.extend(
data,
this.programModel.toJSON(),
this.subscriptionModel.toJSON(),
);
HtmlUtils.setHtml(this.$el, this.tpl(data));
this.postRender();
@@ -126,20 +110,6 @@ class ProgramDetailsView extends Backbone.View {
model: new Backbone.Model(this.options),
});
if (this.options.isSubscriptionEligible) {
const { enrollmentAlerts, trialEndingAlerts } = this.getAlerts();
if (enrollmentAlerts.length || trialEndingAlerts.length) {
this.alertListView = new AlertListView({
context: {
enrollmentAlerts,
trialEndingAlerts,
pageType: 'programDetails',
},
});
}
}
if (this.remainingCourseCollection.length > 0) {
new CollectionListView({
el: '.js-course-list-remaining',
@@ -178,12 +148,10 @@ class ProgramDetailsView extends Backbone.View {
el: '.js-program-sidebar',
model: this.programModel,
courseModel: this.courseData,
subscriptionModel: this.subscriptionModel,
certificateCollection: this.certificateCollection,
industryPathways: this.options.industryPathways,
creditPathways: this.options.creditPathways,
programTabViewEnabled: this.options.programTabViewEnabled,
isSubscriptionEligible: this.options.isSubscriptionEligible,
urls: this.options.urls,
});
let hasIframe = false;
@@ -197,59 +165,6 @@ class ProgramDetailsView extends Backbone.View {
}).bind(this);
}
getIsSubscriptionEligible() {
const courseCollections = [
this.completedCourseCollection,
this.inProgressCourseCollection,
];
const isSomeCoursePurchasable = courseCollections.some((collection) => (
collection.some((course) => (
course.get('upgrade_url')
&& !(course.get('expired') === true)
))
));
const programPurchasedWithoutSubscription = (
this.subscriptionModel.get('subscriptionState') !== 'active'
&& this.subscriptionModel.get('subscriptionState') !== 'inactive'
&& !isSomeCoursePurchasable
&& this.remainingCourseCollection.length === 0
);
const isSubscriptionActiveSunsetting = (
this.subscriptionModel.get('subscriptionState') === 'active'
)
return (
this.options.isUserB2CSubscriptionsEnabled
&& isSubscriptionActiveSunsetting
&& !programPurchasedWithoutSubscription
);
}
getAlerts() {
const alerts = {
enrollmentAlerts: [],
trialEndingAlerts: [],
};
if (this.subscriptionModel.get('subscriptionState') === 'active') {
if (this.courseData.get('all_unenrolled')) {
alerts.enrollmentAlerts.push({
title: this.programModel.get('title'),
});
}
if (
this.subscriptionModel.get('remainingDays') <= 7
&& this.subscriptionModel.get('hasActiveTrial')
) {
alerts.trialEndingAlerts.push({
title: this.programModel.get('title'),
...this.subscriptionModel.toJSON(),
});
}
}
return alerts;
}
trackPurchase() {
const data = this.options.programData;
window.analytics.track('edx.bi.user.dashboard.program.purchase', {
@@ -258,37 +173,6 @@ class ProgramDetailsView extends Backbone.View {
uuid: data.uuid,
});
}
trackSubscriptionCTA() {
const state = this.subscriptionModel.get('subscriptionState');
if (state === 'active') {
window.analytics.track(
'edx.bi.user.subscription.program-detail-page.manage.clicked',
this.subscriptionEventParams,
);
} else {
const isNewSubscription = state !== 'inactive';
window.analytics.track(
'edx.bi.user.subscription.program-detail-page.subscribe.clicked',
{
category: `${this.options.programData.variant} bundle`,
is_new_subscription: isNewSubscription,
is_trial_eligible: isNewSubscription,
...this.subscriptionEventParams,
},
);
}
}
trackSubscriptionEligibleProgramView() {
if (this.options.isSubscriptionEligible) {
window.analytics.track(
'edx.bi.user.subscription.program-detail-page.viewed',
this.subscriptionEventParams,
);
}
}
}
export default ProgramDetailsView;

View File

@@ -42,22 +42,11 @@ 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() {
// eslint-disable-next-line no-undef
const data = $.extend(this.model.toJSON(), {
breakpoints: this.breakpoints,
logo: this.getLogo(),
isSubscribed: this.getIsSubscribed(),
});
if (this.model.get('programData')) {

View File

@@ -2,10 +2,6 @@ import Backbone from 'backbone';
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
import AlertListView from './program_alert_list_view';
import SubscriptionModel from '../models/program_subscription_model';
import programListHeaderTpl from '../../../templates/learner_dashboard/program_list_header_view.underscore';
class ProgramListHeaderView extends Backbone.View {
@@ -19,76 +15,11 @@ class ProgramListHeaderView extends Backbone.View {
initialize({ context }) {
this.context = context;
this.tpl = HtmlUtils.template(programListHeaderTpl);
this.programAndSubscriptionData = context.programsData
.map((programData) => ({
programData,
subscriptionData: context.subscriptionCollection
?.findWhere({
resource_id: programData.uuid,
subscription_state: 'active',
})
?.toJSON(),
}))
.filter(({ subscriptionData }) => !!subscriptionData);
this.render();
}
render() {
HtmlUtils.setHtml(this.$el, this.tpl(this.context));
this.postRender();
}
postRender() {
if (this.context.isUserB2CSubscriptionsEnabled) {
const enrollmentAlerts = this.getEnrollmentAlerts();
const trialEndingAlerts = this.getTrialEndingAlerts();
if (enrollmentAlerts.length || trialEndingAlerts.length) {
this.alertListView = new AlertListView({
el: '.js-program-list-alerts',
context: {
enrollmentAlerts,
trialEndingAlerts,
pageType: 'programList',
},
});
}
}
}
getEnrollmentAlerts() {
return this.programAndSubscriptionData
.map(({ programData, subscriptionData }) =>
this.context.progressCollection?.findWhere({
uuid: programData.uuid,
all_unenrolled: true,
}) ? {
title: programData.title,
url: programData.detail_url,
} : null
)
.filter(Boolean);
}
getTrialEndingAlerts() {
return this.programAndSubscriptionData
.map(({ programData, subscriptionData }) => {
const subscriptionModel = new SubscriptionModel({
context: {
programData,
subscriptionData: [subscriptionData],
userPreferences: this.context?.userPreferences,
},
});
return (
subscriptionModel.get('remainingDays') <= 7 &&
subscriptionModel.get('hasActiveTrial') && {
title: programData.title,
...subscriptionModel.toJSON(),
}
);
})
.filter(Boolean);
}
}

View File

@@ -10,9 +10,6 @@ class SidebarView extends Backbone.View {
constructor(options) {
const defaults = {
el: '.sidebar',
events: {
'click .js-subscription-upsell-cta ': 'trackSubscriptionUpsellCTA',
},
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
@@ -33,12 +30,6 @@ class SidebarView extends Backbone.View {
context: this.context,
});
}
trackSubscriptionUpsellCTA() {
window.analytics.track(
'edx.bi.user.subscription.program-dashboard.upsell.clicked',
);
}
}
export default SidebarView;

View File

@@ -1,30 +0,0 @@
import Backbone from 'backbone';
import HtmlUtils from 'edx-ui-toolkit/js/utils/html-utils';
import subscriptionUpsellTpl from '../../../templates/learner_dashboard/subscription_upsell_view.underscore';
class SubscriptionUpsellView extends Backbone.View {
constructor(options) {
const defaults = {
el: '.js-subscription-upsell',
};
// eslint-disable-next-line prefer-object-spread
super(Object.assign({}, defaults, options));
}
initialize(options) {
this.tpl = HtmlUtils.template(subscriptionUpsellTpl);
this.subscriptionUpsellModel = new Backbone.Model(
options.subscriptionUpsellData,
);
this.render();
}
render() {
const data = this.subscriptionUpsellModel.toJSON();
HtmlUtils.setHtml(this.$el, this.tpl(data));
}
}
export default SubscriptionUpsellView;

View File

@@ -3,18 +3,12 @@ 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) {
if (options.isSubscriptionEligible) {
this.messageTpl = HtmlUtils.template(upgradeMessageSubscriptionTpl);
} else {
this.messageTpl = HtmlUtils.template(upgradeMessageTpl);
}
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');
@@ -30,7 +24,6 @@ class UpgradeMessageView extends Backbone.View {
const data = $.extend(
{},
this.model.toJSON(),
this.subscriptionModel.toJSON(),
);
HtmlUtils.setHtml(this.$el, this.messageTpl(data));
}

View File

@@ -90,21 +90,6 @@ $btn-color-primary: $primary-dark;
}
}
.program-details-alerts {
.page-banner {
margin: 0;
padding: 0 0 48px;
gap: 24px;
}
}
.program-details-tab-alerts {
.page-banner {
margin: 0;
gap: 24px;
}
}
// CSS for April 2017 version of Program Details Page
.program-details {
.window-wrap {
@@ -449,42 +434,6 @@ $btn-color-primary: $primary-dark;
}
}
.upgrade-subscription {
margin: 16px 0 10px;
row-gap: 16px;
column-gap: 24px;
}
.subscription-icon-launch {
width: 22.5px;
height: 22.5px;
margin-inline-start: 8px;
}
.subscription-icon-restart {
width: 22.5px;
height: 22.5px;
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%;
padding: 15px 15px 15px 0px;
@@ -681,24 +630,6 @@ $btn-color-primary: $primary-dark;
.program-sidebar {
padding: 40px 40px 40px 0px;
.program-record,.subscription-info {
text-align: left;
padding-bottom: 2em;
}
.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;

View File

@@ -39,13 +39,6 @@
.program-cards-container {
@include grid-container();
padding-top: 32px;
.subscription-badge {
position: absolute;
top: 8px;
left: 8px;
z-index: 10;
}
}
.sidebar {