Merge pull request #14773 from edx/ECOM-7388
ECOM-7388 Program details page redesign
This commit is contained in:
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.4 KiB |
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.9 KiB |
1
lms/static/images/programs/xseries-program-details.svg
Normal file
1
lms/static/images/programs/xseries-program-details.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 5.8 KiB |
@@ -85,6 +85,12 @@
|
||||
_.each(enrollableCourseRuns, (function(courseRun) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
courseRun.start_date = this.formatDate(courseRun.start);
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
courseRun.end_date = this.formatDate(courseRun.end);
|
||||
|
||||
// This is used to render the date when selecting a course run to enroll in
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
courseRun.dateString = this.formatDateString(courseRun);
|
||||
}).bind(this));
|
||||
|
||||
return enrollableCourseRuns;
|
||||
@@ -115,12 +121,65 @@
|
||||
return DateUtils.localize(context);
|
||||
},
|
||||
|
||||
getCertificatePriceString: function(run) {
|
||||
var verifiedSeat, currency;
|
||||
if ('seats' in run && run.seats.length) {
|
||||
// eslint-disable-next-line consistent-return
|
||||
verifiedSeat = _.filter(run.seats, function(seat) {
|
||||
if (['verified', 'professional', 'credit'].indexOf(seat.type) >= 0) {
|
||||
return seat;
|
||||
}
|
||||
})[0];
|
||||
currency = verifiedSeat.currency;
|
||||
if (currency === 'USD') {
|
||||
return '$' + verifiedSeat.price;
|
||||
} else {
|
||||
return verifiedSeat.price + ' ' + currency;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
formatDateString: function(run) {
|
||||
var pacingType = run.pacing_type,
|
||||
dateString = '',
|
||||
start = run.start_date || this.get('start_date'),
|
||||
end = run.end_date || this.get('end_date'),
|
||||
now = new Date(),
|
||||
startDate = new Date(start),
|
||||
endDate = new Date(end);
|
||||
|
||||
if (pacingType === 'self_paced') {
|
||||
dateString = 'Self-paced';
|
||||
if (start && startDate > now) {
|
||||
dateString += ' - Starts ' + start;
|
||||
} else if (end && endDate > now) {
|
||||
dateString += ' - Ends ' + end;
|
||||
} else if (end && endDate < now) {
|
||||
dateString += ' - Ended ' + end;
|
||||
}
|
||||
} else if (pacingType === 'instructor_paced') {
|
||||
if (start && end) {
|
||||
dateString = start + ' - ' + end;
|
||||
} else if (start) {
|
||||
dateString = 'Starts ' + start;
|
||||
} else if (end) {
|
||||
dateString = 'Ends ' + end;
|
||||
}
|
||||
}
|
||||
return dateString;
|
||||
},
|
||||
|
||||
valueIsDefined: function(val) {
|
||||
return !([undefined, 'None', null].indexOf(val) >= 0);
|
||||
},
|
||||
|
||||
setActiveCourseRun: function(courseRun, userPreferences) {
|
||||
var startDateString,
|
||||
courseImageUrl;
|
||||
|
||||
if (courseRun) {
|
||||
if (courseRun.advertised_start !== undefined && courseRun.advertised_start !== 'None') {
|
||||
if (this.valueIsDefined(courseRun.advertised_start)) {
|
||||
startDateString = courseRun.advertised_start;
|
||||
} else {
|
||||
startDateString = this.formatDate(courseRun.start, userPreferences);
|
||||
@@ -132,6 +191,7 @@
|
||||
courseImageUrl = courseRun.course_image_url;
|
||||
}
|
||||
|
||||
|
||||
this.set({
|
||||
certificate_url: courseRun.certificate_url,
|
||||
course_image_url: courseImageUrl || '',
|
||||
@@ -148,8 +208,12 @@
|
||||
mode_slug: courseRun.type,
|
||||
start_date: startDateString,
|
||||
upcoming_course_runs: this.getUpcomingCourseRuns(),
|
||||
upgrade_url: courseRun.upgrade_url
|
||||
upgrade_url: courseRun.upgrade_url,
|
||||
price: this.getCertificatePriceString(courseRun)
|
||||
});
|
||||
|
||||
// This is used to render the date for completed and in progress courses
|
||||
this.set({dateString: this.formatDateString(courseRun)});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/certificate_status_2017.underscore',
|
||||
'text!../../../templates/learner_dashboard/certificate_icon.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
certificateStatusTpl,
|
||||
certificateIconTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
statusTpl: HtmlUtils.template(certificateStatusTpl),
|
||||
iconTpl: HtmlUtils.template(certificateIconTpl),
|
||||
|
||||
initialize: function(options) {
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = this.model.toJSON();
|
||||
|
||||
data = $.extend(data, {certificateSvg: this.iconTpl()});
|
||||
HtmlUtils.setHtml(this.$el, this.statusTpl(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1,85 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/learner_dashboard/models/course_enroll_model',
|
||||
'js/learner_dashboard/views/upgrade_message_view_2017',
|
||||
'js/learner_dashboard/views/certificate_status_view_2017',
|
||||
'js/learner_dashboard/views/course_enroll_view_2017',
|
||||
'text!../../../templates/learner_dashboard/course_card_2017.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
EnrollModel,
|
||||
UpgradeMessageView,
|
||||
CertificateStatusView,
|
||||
CourseEnrollView,
|
||||
pageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
className: 'program-course-card',
|
||||
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
|
||||
initialize: function(options) {
|
||||
this.enrollModel = new EnrollModel();
|
||||
if (options.context) {
|
||||
this.urlModel = new Backbone.Model(options.context.urls);
|
||||
this.enrollModel.urlRoot = this.urlModel.get('commerce_api_url');
|
||||
}
|
||||
this.context = options.context || {};
|
||||
this.render();
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = $.extend(this.model.toJSON(), {
|
||||
enrolled: this.context.enrolled || ''
|
||||
});
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
var $upgradeMessage = this.$('.upgrade-message'),
|
||||
$certStatus = this.$('.certificate-status');
|
||||
|
||||
this.enrollView = new CourseEnrollView({
|
||||
$parentEl: this.$('.course-actions'),
|
||||
model: this.model,
|
||||
urlModel: this.urlModel,
|
||||
enrollModel: this.enrollModel
|
||||
});
|
||||
|
||||
if (this.model.get('upgrade_url')) {
|
||||
this.upgradeMessage = new UpgradeMessageView({
|
||||
$el: $upgradeMessage,
|
||||
model: this.model
|
||||
});
|
||||
|
||||
$certStatus.remove();
|
||||
} else if (this.model.get('certificate_url')) {
|
||||
this.certificateStatus = new CertificateStatusView({
|
||||
$el: $certStatus,
|
||||
model: this.model
|
||||
});
|
||||
|
||||
$upgradeMessage.remove();
|
||||
} else {
|
||||
// Styles are applied to these elements which will be visible if they're empty.
|
||||
$upgradeMessage.remove();
|
||||
$certStatus.remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
104
lms/static/js/learner_dashboard/views/course_enroll_view_2017.js
Normal file
104
lms/static/js/learner_dashboard/views/course_enroll_view_2017.js
Normal file
@@ -0,0 +1,104 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/course_enroll_2017.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
pageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
className: 'course-enroll-view',
|
||||
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
|
||||
events: {
|
||||
'click .enroll-button': 'handleEnroll'
|
||||
},
|
||||
|
||||
initialize: function(options) {
|
||||
this.$parentEl = options.$parentEl;
|
||||
this.enrollModel = options.enrollModel;
|
||||
this.urlModel = options.urlModel;
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var filledTemplate;
|
||||
if (this.$parentEl && this.enrollModel) {
|
||||
filledTemplate = this.tpl(this.model.toJSON());
|
||||
HtmlUtils.setHtml(this.$el, filledTemplate);
|
||||
HtmlUtils.setHtml(this.$parentEl, HtmlUtils.HTML(this.$el));
|
||||
}
|
||||
this.postRender();
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
if (this.urlModel) {
|
||||
this.trackSelectionUrl = this.urlModel.get('track_selection_url');
|
||||
}
|
||||
},
|
||||
|
||||
handleEnroll: function() {
|
||||
// Enrollment click event handled here
|
||||
var courseRunKey = $('.run-select').val();
|
||||
this.model.updateCourseRun(courseRunKey);
|
||||
if (!this.model.get('is_enrolled')) {
|
||||
// Create the enrollment.
|
||||
this.enrollModel.save({
|
||||
course_id: courseRunKey
|
||||
}, {
|
||||
success: _.bind(this.enrollSuccess, this),
|
||||
error: _.bind(this.enrollError, this)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
enrollSuccess: function() {
|
||||
var courseRunKey = this.model.get('course_run_key');
|
||||
window.analytics.track('edx.bi.user.program-details.enrollment');
|
||||
if (this.trackSelectionUrl) {
|
||||
// Go to track selection page
|
||||
this.redirect(this.trackSelectionUrl + courseRunKey);
|
||||
} else {
|
||||
this.model.set({
|
||||
is_enrolled: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
enrollError: function(model, response) {
|
||||
if (response.status === 403 && response.responseJSON.user_message_url) {
|
||||
/**
|
||||
* Check if we've been blocked from the course
|
||||
* because of country access rules.
|
||||
* If so, redirect to a page explaining to the user
|
||||
* why they were blocked.
|
||||
*/
|
||||
this.redirect(response.responseJSON.user_message_url);
|
||||
} else if (this.trackSelectionUrl) {
|
||||
/**
|
||||
* Otherwise, go to the track selection page as usual.
|
||||
* This can occur, for example, when a course does not
|
||||
* have a free enrollment mode, so we can't auto-enroll.
|
||||
*/
|
||||
this.redirect(this.trackSelectionUrl + this.model.get('course_run_key'));
|
||||
}
|
||||
},
|
||||
|
||||
redirect: function(url) {
|
||||
window.location.href = url;
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -1,18 +1,17 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/learner_dashboard/collections/course_card_collection',
|
||||
'js/learner_dashboard/views/program_header_view',
|
||||
'js/learner_dashboard/views/collection_list_view',
|
||||
'js/learner_dashboard/views/course_card_view',
|
||||
'js/learner_dashboard/views/program_details_sidebar_view',
|
||||
'text!../../../templates/learner_dashboard/program_details_view.underscore'
|
||||
],
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'js/learner_dashboard/collections/course_card_collection',
|
||||
'js/learner_dashboard/views/program_header_view_2017',
|
||||
'js/learner_dashboard/views/collection_list_view',
|
||||
'js/learner_dashboard/views/course_card_view_2017',
|
||||
'js/learner_dashboard/views/program_details_sidebar_view',
|
||||
'text!../../../templates/learner_dashboard/program_details_view_2017.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
@@ -34,15 +33,36 @@
|
||||
initialize: function(options) {
|
||||
this.options = options;
|
||||
this.programModel = new Backbone.Model(this.options.programData);
|
||||
this.courseCardCollection = new CourseCardCollection(
|
||||
this.programModel.get('courses'),
|
||||
this.courseData = new Backbone.Model(this.options.courseData);
|
||||
this.completedCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('completed') || [],
|
||||
this.options.userPreferences
|
||||
);
|
||||
);
|
||||
this.inProgressCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('in_progress') || [],
|
||||
this.options.userPreferences
|
||||
);
|
||||
this.remainingCourseCollection = new CourseCardCollection(
|
||||
this.courseData.get('not_started') || [],
|
||||
this.options.userPreferences
|
||||
);
|
||||
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
HtmlUtils.setHtml(this.$el, this.tpl());
|
||||
var completedCount = this.completedCourseCollection.length,
|
||||
inProgressCount = this.inProgressCourseCollection.length,
|
||||
remainingCount = this.remainingCourseCollection.length,
|
||||
totalCount = completedCount + inProgressCount + remainingCount,
|
||||
data = {
|
||||
totalCount: totalCount,
|
||||
inProgressCount: inProgressCount,
|
||||
remainingCount: remainingCount,
|
||||
completedCount: completedCount
|
||||
};
|
||||
data = $.extend(data, this.options.programData);
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
this.postRender();
|
||||
},
|
||||
|
||||
@@ -50,16 +70,34 @@
|
||||
this.headerView = new HeaderView({
|
||||
model: new Backbone.Model(this.options)
|
||||
});
|
||||
new CollectionListView({
|
||||
el: '.js-course-list',
|
||||
childView: CourseCardView,
|
||||
collection: this.courseCardCollection,
|
||||
context: this.options,
|
||||
titleContext: {
|
||||
el: 'h2',
|
||||
title: 'Course List'
|
||||
}
|
||||
}).render();
|
||||
|
||||
if (this.remainingCourseCollection.length > 0) {
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-remaining',
|
||||
childView: CourseCardView,
|
||||
collection: this.remainingCourseCollection,
|
||||
context: this.options
|
||||
}).render();
|
||||
}
|
||||
|
||||
if (this.completedCourseCollection.length > 0) {
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-completed',
|
||||
childView: CourseCardView,
|
||||
collection: this.completedCourseCollection,
|
||||
context: this.options
|
||||
}).render();
|
||||
}
|
||||
|
||||
if (this.inProgressCourseCollection.length > 0) {
|
||||
// This is last because the context is modified below
|
||||
new CollectionListView({
|
||||
el: '.js-course-list-in-progress',
|
||||
childView: CourseCardView,
|
||||
collection: this.inProgressCourseCollection,
|
||||
context: $.extend(this.options, {enrolled: gettext('Enrolled')})
|
||||
}).render();
|
||||
}
|
||||
|
||||
new SidebarView({
|
||||
el: '.sidebar',
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/program_header_view_2017.underscore',
|
||||
'text!/static/images/programs/micromasters-program-details.svg',
|
||||
'text!/static/images/programs/xseries-program-details.svg',
|
||||
'text!/static/images/programs/professional-certificate-program-details.svg'
|
||||
],
|
||||
function(Backbone, $, HtmlUtils, pageTpl, MicroMastersLogo,
|
||||
XSeriesLogo, ProfessionalCertificateLogo) {
|
||||
return Backbone.View.extend({
|
||||
breakpoints: {
|
||||
min: {
|
||||
medium: '768px',
|
||||
large: '1180px'
|
||||
}
|
||||
},
|
||||
|
||||
el: '.js-program-header',
|
||||
|
||||
tpl: HtmlUtils.template(pageTpl),
|
||||
|
||||
initialize: function() {
|
||||
this.render();
|
||||
},
|
||||
|
||||
getLogo: function() {
|
||||
var logo = false,
|
||||
type = this.model.get('programData').type;
|
||||
|
||||
if (type === 'MicroMasters') {
|
||||
logo = MicroMastersLogo;
|
||||
} else if (type === 'XSeries') {
|
||||
logo = XSeriesLogo;
|
||||
} else if (type === 'Professional Certificate') {
|
||||
logo = ProfessionalCertificateLogo;
|
||||
}
|
||||
return logo;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = $.extend(this.model.toJSON(), {
|
||||
breakpoints: this.breakpoints,
|
||||
logo: this.getLogo()
|
||||
});
|
||||
|
||||
if (this.model.get('programData')) {
|
||||
HtmlUtils.setHtml(this.$el, this.tpl(data));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1,34 @@
|
||||
(function(define) {
|
||||
'use strict';
|
||||
define(['backbone',
|
||||
'jquery',
|
||||
'underscore',
|
||||
'gettext',
|
||||
'edx-ui-toolkit/js/utils/html-utils',
|
||||
'text!../../../templates/learner_dashboard/upgrade_message_2017.underscore'
|
||||
],
|
||||
function(
|
||||
Backbone,
|
||||
$,
|
||||
_,
|
||||
gettext,
|
||||
HtmlUtils,
|
||||
upgradeMessageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
messageTpl: HtmlUtils.template(upgradeMessageTpl),
|
||||
|
||||
initialize: function(options) {
|
||||
this.$el = options.$el;
|
||||
this.render();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var data = this.model.toJSON();
|
||||
|
||||
HtmlUtils.setHtml(this.$el, this.messageTpl(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}).call(this, define || RequireJS.define);
|
||||
@@ -0,0 +1,289 @@
|
||||
define([
|
||||
'backbone',
|
||||
'jquery',
|
||||
'js/learner_dashboard/models/course_card_model',
|
||||
'js/learner_dashboard/models/course_enroll_model',
|
||||
'js/learner_dashboard/views/course_enroll_view_2017'
|
||||
], function(Backbone, $, CourseCardModel, CourseEnrollModel, CourseEnrollView) {
|
||||
'use strict';
|
||||
|
||||
describe('Course Enroll View', function() {
|
||||
var view = null,
|
||||
courseCardModel,
|
||||
courseEnrollModel,
|
||||
urlModel,
|
||||
setupView,
|
||||
singleCourseRunList,
|
||||
multiCourseRunList,
|
||||
course = {
|
||||
key: 'WageningenX+FFESx',
|
||||
uuid: '9f8562eb-f99b-45c7-b437-799fd0c15b6a',
|
||||
title: 'Systems thinking and environmental sustainability',
|
||||
owners: [
|
||||
{
|
||||
uuid: '0c6e5fa2-96e8-40b2-9ebe-c8b0df2a3b22',
|
||||
key: 'WageningenX',
|
||||
name: 'Wageningen University & Research'
|
||||
}
|
||||
]
|
||||
},
|
||||
urls = {
|
||||
commerce_api_url: '/commerce',
|
||||
track_selection_url: '/select_track/course/'
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
// Stub analytics tracking
|
||||
window.analytics = jasmine.createSpyObj('analytics', ['track']);
|
||||
|
||||
// NOTE: This data is redefined prior to each test case so that tests
|
||||
// can't break each other by modifying data copied by reference.
|
||||
singleCourseRunList = [{
|
||||
key: 'course-v1:WageningenX+FFESx+1T2017',
|
||||
uuid: '2f2edf03-79e6-4e39-aef0-65436a6ee344',
|
||||
title: 'Food Security and Sustainability: Systems thinking and environmental sustainability',
|
||||
image: {
|
||||
src: 'https://example.com/2f2edf03-79e6-4e39-aef0-65436a6ee344.jpg'
|
||||
},
|
||||
marketing_url: 'https://www.edx.org/course/food-security-sustainability-systems-wageningenx-ffesx',
|
||||
start: '2017-02-28T05:00:00Z',
|
||||
end: '2017-05-30T23:00:00Z',
|
||||
enrollment_start: '2017-01-18T00:00:00Z',
|
||||
enrollment_end: null,
|
||||
type: 'verified',
|
||||
certificate_url: '',
|
||||
course_url: 'https://courses.example.com/courses/course-v1:edX+DemoX+Demo_Course',
|
||||
enrollment_open_date: 'Jan 18, 2016',
|
||||
is_course_ended: false,
|
||||
is_enrolled: false,
|
||||
is_enrollment_open: true,
|
||||
upgrade_url: ''
|
||||
}];
|
||||
|
||||
multiCourseRunList = [{
|
||||
key: 'course-v1:WageningenX+FFESx+2T2016',
|
||||
uuid: '9bbb7844-4848-44ab-8e20-0be6604886e9',
|
||||
title: 'Food Security and Sustainability: Systems thinking and environmental sustainability',
|
||||
image: {
|
||||
src: 'https://example.com/9bbb7844-4848-44ab-8e20-0be6604886e9.jpg'
|
||||
},
|
||||
short_description: 'Learn how to apply systems thinking to improve food production systems.',
|
||||
marketing_url: 'https://www.edx.org/course/food-security-sustainability-systems-wageningenx-stesx',
|
||||
start: '2016-09-08T04:00:00Z',
|
||||
end: '2016-11-11T00:00:00Z',
|
||||
enrollment_start: null,
|
||||
enrollment_end: null,
|
||||
pacing_type: 'instructor_paced',
|
||||
type: 'verified',
|
||||
certificate_url: '',
|
||||
course_url: 'https://courses.example.com/courses/course-v1:WageningenX+FFESx+2T2016',
|
||||
enrollment_open_date: 'Jan 18, 2016',
|
||||
is_course_ended: false,
|
||||
is_enrolled: false,
|
||||
is_enrollment_open: true
|
||||
}, {
|
||||
key: 'course-v1:WageningenX+FFESx+1T2017',
|
||||
uuid: '2f2edf03-79e6-4e39-aef0-65436a6ee344',
|
||||
title: 'Food Security and Sustainability: Systems thinking and environmental sustainability',
|
||||
image: {
|
||||
src: 'https://example.com/2f2edf03-79e6-4e39-aef0-65436a6ee344.jpg'
|
||||
},
|
||||
marketing_url: 'https://www.edx.org/course/food-security-sustainability-systems-wageningenx-ffesx',
|
||||
start: '2017-02-28T05:00:00Z',
|
||||
end: '2017-05-30T23:00:00Z',
|
||||
enrollment_start: '2017-01-18T00:00:00Z',
|
||||
enrollment_end: null,
|
||||
type: 'verified',
|
||||
certificate_url: '',
|
||||
course_url: 'https://courses.example.com/courses/course-v1:WageningenX+FFESx+1T2017',
|
||||
enrollment_open_date: 'Jan 18, 2016',
|
||||
is_course_ended: false,
|
||||
is_enrolled: false,
|
||||
is_enrollment_open: true
|
||||
}];
|
||||
});
|
||||
|
||||
setupView = function(courseRuns, urlMap) {
|
||||
course.course_runs = courseRuns;
|
||||
setFixtures('<div class="course-actions"></div>');
|
||||
courseCardModel = new CourseCardModel(course);
|
||||
courseEnrollModel = new CourseEnrollModel({}, {
|
||||
courseId: courseCardModel.get('course_run_key')
|
||||
});
|
||||
if (urlMap) {
|
||||
urlModel = new Backbone.Model(urlMap);
|
||||
}
|
||||
view = new CourseEnrollView({
|
||||
$parentEl: $('.course-actions'),
|
||||
model: courseCardModel,
|
||||
enrollModel: courseEnrollModel,
|
||||
urlModel: urlModel
|
||||
});
|
||||
};
|
||||
|
||||
afterEach(function() {
|
||||
view.remove();
|
||||
urlModel = null;
|
||||
courseCardModel = null;
|
||||
courseEnrollModel = null;
|
||||
});
|
||||
|
||||
it('should exist', function() {
|
||||
setupView(singleCourseRunList);
|
||||
expect(view).toBeDefined();
|
||||
});
|
||||
|
||||
it('should render the course enroll view when not enrolled', function() {
|
||||
setupView(singleCourseRunList);
|
||||
expect(view.$('.enroll-button').text().trim()).toEqual('Enroll Now');
|
||||
expect(view.$('.run-select').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should render the course enroll view when enrolled', function() {
|
||||
singleCourseRunList[0].is_enrolled = true;
|
||||
|
||||
setupView(singleCourseRunList);
|
||||
expect(view.$el.html().trim()).toEqual('');
|
||||
expect(view.$('.run-select').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should not render anything if course runs are empty', function() {
|
||||
setupView([]);
|
||||
|
||||
expect(view.$('.enrollment-info').length).toBe(0);
|
||||
expect(view.$('.run-select').length).toBe(0);
|
||||
expect(view.$('.enroll-button').length).toBe(0);
|
||||
});
|
||||
|
||||
it('should render run selection dropdown if multiple course runs are available', function() {
|
||||
setupView(multiCourseRunList);
|
||||
|
||||
expect(view.$('.run-select').length).toBe(1);
|
||||
expect(view.$('.run-select').val()).toEqual(multiCourseRunList[0].key);
|
||||
expect(view.$('.run-select option').length).toBe(2);
|
||||
});
|
||||
|
||||
it('should enroll learner when enroll button is clicked with one course run available', function() {
|
||||
setupView(singleCourseRunList);
|
||||
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
|
||||
spyOn(courseEnrollModel, 'save');
|
||||
|
||||
view.$('.enroll-button').click();
|
||||
|
||||
expect(courseEnrollModel.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should enroll learner when enroll button is clicked with multiple course runs available', function() {
|
||||
setupView(multiCourseRunList);
|
||||
|
||||
spyOn(courseEnrollModel, 'save');
|
||||
|
||||
view.$('.run-select').val(multiCourseRunList[1].key);
|
||||
view.$('.run-select').trigger('change');
|
||||
view.$('.enroll-button').click();
|
||||
|
||||
expect(courseEnrollModel.save).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should redirect to track selection when audit enrollment succeeds', function() {
|
||||
singleCourseRunList[0].is_enrolled = false;
|
||||
singleCourseRunList[0].mode_slug = 'audit';
|
||||
|
||||
setupView(singleCourseRunList, urls);
|
||||
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.trackSelectionUrl).toBeDefined();
|
||||
|
||||
spyOn(view, 'redirect');
|
||||
|
||||
view.enrollSuccess();
|
||||
|
||||
expect(view.redirect).toHaveBeenCalledWith(
|
||||
view.trackSelectionUrl + courseCardModel.get('course_run_key'));
|
||||
});
|
||||
|
||||
it('should redirect to track selection when enrollment in an unspecified mode is attempted', function() {
|
||||
singleCourseRunList[0].is_enrolled = false;
|
||||
singleCourseRunList[0].mode_slug = null;
|
||||
|
||||
setupView(singleCourseRunList, urls);
|
||||
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.trackSelectionUrl).toBeDefined();
|
||||
|
||||
spyOn(view, 'redirect');
|
||||
|
||||
view.enrollSuccess();
|
||||
|
||||
expect(view.redirect).toHaveBeenCalledWith(
|
||||
view.trackSelectionUrl + courseCardModel.get('course_run_key')
|
||||
);
|
||||
});
|
||||
|
||||
it('should not redirect when urls are not provided', function() {
|
||||
singleCourseRunList[0].is_enrolled = false;
|
||||
singleCourseRunList[0].mode_slug = 'verified';
|
||||
|
||||
setupView(singleCourseRunList);
|
||||
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.verificationUrl).not.toBeDefined();
|
||||
expect(view.dashboardUrl).not.toBeDefined();
|
||||
expect(view.trackSelectionUrl).not.toBeDefined();
|
||||
|
||||
spyOn(view, 'redirect');
|
||||
|
||||
view.enrollSuccess();
|
||||
|
||||
expect(view.redirect).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should redirect to track selection on error', function() {
|
||||
setupView(singleCourseRunList, urls);
|
||||
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.trackSelectionUrl).toBeDefined();
|
||||
|
||||
spyOn(view, 'redirect');
|
||||
|
||||
view.enrollError(courseEnrollModel, {status: 500});
|
||||
expect(view.redirect).toHaveBeenCalledWith(
|
||||
view.trackSelectionUrl + courseCardModel.get('course_run_key')
|
||||
);
|
||||
});
|
||||
|
||||
it('should redirect to login on 403 error', function() {
|
||||
var response = {
|
||||
status: 403,
|
||||
responseJSON: {
|
||||
user_message_url: 'redirect/to/this'
|
||||
}
|
||||
};
|
||||
|
||||
setupView(singleCourseRunList, urls);
|
||||
|
||||
expect(view.$('.enroll-button').length).toBe(1);
|
||||
expect(view.trackSelectionUrl).toBeDefined();
|
||||
|
||||
spyOn(view, 'redirect');
|
||||
|
||||
view.enrollError(courseEnrollModel, response);
|
||||
|
||||
expect(view.redirect).toHaveBeenCalledWith(
|
||||
response.responseJSON.user_message_url
|
||||
);
|
||||
});
|
||||
|
||||
it('sends analytics event when enrollment succeeds', function() {
|
||||
setupView(singleCourseRunList, urls);
|
||||
spyOn(view, 'redirect');
|
||||
view.enrollSuccess();
|
||||
expect(window.analytics.track).toHaveBeenCalledWith(
|
||||
'edx.bi.user.program-details.enrollment'
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -744,6 +744,7 @@
|
||||
'js/spec/learner_dashboard/program_details_header_spec.js',
|
||||
'js/spec/learner_dashboard/course_card_view_spec.js',
|
||||
'js/spec/learner_dashboard/course_enroll_view_spec.js',
|
||||
'js/spec/learner_dashboard/course_enroll_view_spec_2017.js',
|
||||
'js/spec/markdown_editor_spec.js',
|
||||
'js/spec/dateutil_factory_spec.js',
|
||||
'js/spec/navigation_spec.js',
|
||||
|
||||
@@ -60,6 +60,10 @@
|
||||
margin-bottom: $baseline/4;
|
||||
font-size: font-size(small);
|
||||
visibility: hidden;
|
||||
|
||||
.visible {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
.no-action-message {
|
||||
|
||||
@@ -224,6 +224,13 @@ $success-color: rgb(0, 155, 0) !default;
|
||||
// #COLORS- EDX-SPECIFIC
|
||||
// ----------------------------
|
||||
|
||||
// logo colors
|
||||
$micromasters-color: #005585;
|
||||
$xseries-color: #424242;
|
||||
$professional-certificate-color: #9a1f60;
|
||||
$zebra-stripe-color: rgb(249, 250, 252);
|
||||
$divider-color: rgb(226,231,236);
|
||||
|
||||
// old color variables
|
||||
// DEPRECATED: Do not continue to use these colors, instead use pattern libary and base colors above.
|
||||
$dark-gray1: rgb(74,74,74) !default;
|
||||
@@ -326,6 +333,7 @@ $credit-color-base: rgb(244,195,0) !default; // accessible with black text
|
||||
$staff-color: $uxpl-pink-base !default;
|
||||
|
||||
|
||||
|
||||
// ----------------------------
|
||||
// #TYPOGRAPHY
|
||||
// ----------------------------
|
||||
|
||||
@@ -76,3 +76,425 @@
|
||||
padding: 0 $full-width-banner-margin/4;
|
||||
}
|
||||
}
|
||||
|
||||
// CSS for April 2017 version of Program Details Page
|
||||
|
||||
.program-details {
|
||||
.window-wrap {
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
.program-details-wrapper {
|
||||
|
||||
.program-details-header {
|
||||
background-color: $light-gray4;
|
||||
display: flex;
|
||||
color: black;
|
||||
font-family: 'Open Sans';
|
||||
font-weight: normal;
|
||||
flex-wrap: wrap;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 35px;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
|
||||
@media(min-width: $bp-screen-md) {
|
||||
margin-left: 30px;
|
||||
margin-right: 80px;
|
||||
}
|
||||
|
||||
.hd-1 {
|
||||
font-size: 1.5em;
|
||||
@media(min-width: $bp-screen-md) {
|
||||
font-size: 2.375em;
|
||||
}
|
||||
}
|
||||
|
||||
.program-details-icon {
|
||||
margin-left: 3px;
|
||||
margin-top: 10px;
|
||||
height: auto;
|
||||
|
||||
/* IE11 CSS styles */
|
||||
@media(min-width: $bp-screen-md) and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
height: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.micromasters {
|
||||
fill: $micromasters-color;
|
||||
width: 200px;
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
.xseries {
|
||||
fill: xseries-color;
|
||||
width: 150px;
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
.professional.certificate {
|
||||
fill: $professional-certificate-color;
|
||||
width: 250px;
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.meta-info {
|
||||
margin: 0;
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: 70%;
|
||||
}
|
||||
@media(min-width: $bp-screen-lg) {
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
.program-title {
|
||||
font-weight: normal;
|
||||
font-size: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.authoring-organizations {
|
||||
text-align: center;
|
||||
|
||||
display: flex;
|
||||
@media(min-width: $bp-screen-md) {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-family: "Open Sans";
|
||||
font-weight: bold;
|
||||
color: palette(primary, dark);
|
||||
font-size: 0.9375em;
|
||||
|
||||
margin-top: auto;
|
||||
margin-bottom: auto;
|
||||
@media(min-width: $bp-screen-md) {
|
||||
margin: 10px 0 0 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media(min-width: $bp-screen-md) {
|
||||
margin: initial;
|
||||
width: 30%;
|
||||
|
||||
.orgs .org-logo {
|
||||
width: 46.5%;
|
||||
margin-left: 2.5%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media(min-width: $bp-screen-lg) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.program-details-content {
|
||||
@media(min-width: $bp-screen-md) {
|
||||
margin-left: 30px;
|
||||
}
|
||||
margin-left: 10px;
|
||||
}
|
||||
.course-list-heading {
|
||||
font-family: "Open Sans";
|
||||
font-weight: bold;
|
||||
color: palette(primary, dark);
|
||||
font-size: 0.9375em;
|
||||
line-height: normal;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 0;
|
||||
|
||||
.status {
|
||||
margin-right: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.course-list > div:nth-of-type(even) {
|
||||
background-color: $zebra-stripe-color;
|
||||
}
|
||||
|
||||
.course-list-headings {
|
||||
width: 700px;
|
||||
|
||||
.divider {
|
||||
margin-left: 0;
|
||||
margin-bottom: 20px;
|
||||
background-color: $divider-color;
|
||||
margin-top: 5px;
|
||||
height: 3px;
|
||||
width: 315px;
|
||||
@media(min-width: $bp-screen-sm) {
|
||||
width: 550px;
|
||||
}
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: 700px;
|
||||
}
|
||||
border: none;
|
||||
}
|
||||
|
||||
.motivating-section {
|
||||
font-size: 0.9375em;
|
||||
margin-left: 15px;
|
||||
width: 310px;
|
||||
@media(min-width: $bp-screen-sm) {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.motivating-heading {
|
||||
margin-bottom: 0px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.motivating-message {
|
||||
color: #414141;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.program-heading {
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: 70%;
|
||||
}
|
||||
width: 90%;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
|
||||
.program-heading-title {
|
||||
font-family: "Open Sans";
|
||||
font-weight: 600;
|
||||
font-size: 1.3em;
|
||||
color: palette(grayscale, base);
|
||||
margin-bottom: 5px;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.program-heading-message {
|
||||
font-weight: 300;
|
||||
}
|
||||
}
|
||||
|
||||
.course-enroll-view {
|
||||
.enroll-button {
|
||||
width: 100%;
|
||||
|
||||
@media(min-width: $bp-screen-sm) {
|
||||
width: initial;
|
||||
margin-bottom: 0;
|
||||
margin-top: 17px;
|
||||
}
|
||||
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: initial;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: palette(primary, dark);
|
||||
height: 37px;
|
||||
width: 128px;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
margin-bottom: 5px;
|
||||
margin-top: 7px;
|
||||
font-size: 0.9375em;
|
||||
|
||||
/* IE11 CSS styles */
|
||||
@media(min-width: $bp-screen-md) and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
.select-choice {
|
||||
font-family: "Open Sans";
|
||||
font-weight: bold;
|
||||
font-size: 0.9375em;
|
||||
color: palette(grayscale, base);
|
||||
margin-top: 6px;
|
||||
margin-right: 2px;
|
||||
display: block;
|
||||
|
||||
@media(min-width: $bp-screen-md) {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
}
|
||||
.run-select-container {
|
||||
@media(min-width: $bp-screen-md) {
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
.run-select {
|
||||
width: 95%;
|
||||
@media(min-width: $bp-screen-sm) {
|
||||
width: 300px;
|
||||
}
|
||||
height: 34px;
|
||||
padding: 0;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.program-course-card {
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
margin-bottom: 10px;
|
||||
|
||||
@media(min-width: $bp-screen-md) {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-right: 40px;
|
||||
margin-left: 15px;
|
||||
|
||||
@media(min-width: $bp-screen-md) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
@media(min-width: $bp-screen-md) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.course-details {
|
||||
float: none;
|
||||
|
||||
.course-title {
|
||||
font-size: 1em;
|
||||
color: palette(primary, base);
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.run-period {
|
||||
color: palette(grayscale, base);
|
||||
font-size: 0.9375em;
|
||||
}
|
||||
|
||||
.course-text .enrolled {
|
||||
color: palette(grayscale, base);
|
||||
}
|
||||
}
|
||||
|
||||
.course-meta-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.course-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.course-enroll-view {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.upgrade-message {
|
||||
flex-wrap: wrap;
|
||||
|
||||
.upgrade-button {
|
||||
background: palette(success, text);
|
||||
border-color: palette(success, text);
|
||||
height: 37px;
|
||||
width: 128px;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
font-size: 0.9375em;
|
||||
|
||||
/* IE11 CSS styles */
|
||||
@media(min-width: $bp-screen-md) and (-ms-high-contrast: none), (-ms-high-contrast: active) {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.action {
|
||||
width: 100%;
|
||||
margin: 5px 0;
|
||||
|
||||
@media(min-width: $bp-screen-sm) {
|
||||
width: initial;
|
||||
}
|
||||
@media(min-width: $bp-screen-md) {
|
||||
margin-top: -25px;
|
||||
}
|
||||
}
|
||||
|
||||
.certificate-status{
|
||||
padding-top: 0px;
|
||||
|
||||
width: initial;
|
||||
@media(min-width: $bp-screen-sm) {
|
||||
width: 300px;
|
||||
}
|
||||
@media(min-width: $bp-screen-md) {
|
||||
width: initial;
|
||||
}
|
||||
|
||||
.card-msg {
|
||||
font-family: "Open Sans";
|
||||
font-weight: bold;
|
||||
font-size: 0.9375em;
|
||||
color: palette(grayscale, base);
|
||||
display: block;
|
||||
@media(min-width: $bp-screen-sm) {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
|
||||
.price {
|
||||
color: palette(success, text);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.certificate-status {
|
||||
.fa-check-circle {
|
||||
color: palette(success, text);
|
||||
}
|
||||
|
||||
.card-msg {
|
||||
font-family: "Open Sans";
|
||||
font-weight: bold;
|
||||
font-size: 0.9375em;
|
||||
color: palette(grayscale, base);
|
||||
}
|
||||
|
||||
.certificate-status-msg {
|
||||
color: palette(grayscale, base);
|
||||
font-size: 0.9375em;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
24
lms/templates/discovery/course_card_2017.underscore
Normal file
24
lms/templates/discovery/course_card_2017.underscore
Normal file
@@ -0,0 +1,24 @@
|
||||
<article class="course" role="region" aria-label="<%- content.display_name %>">
|
||||
<a href="/courses/<%- course %>/about">
|
||||
<section class="course-info" aria-hidden="true">
|
||||
<h2 class="course-name">
|
||||
<span class="course-organization"><%- org %></span>
|
||||
<span class="course-code"><%- content.number %></span>
|
||||
<span class="course-title"><%- content.display_name %></span>
|
||||
</h2>
|
||||
<div class="course-date" aria-hidden="true">
|
||||
<%- interpolate(
|
||||
gettext("Starts: %(start_date)s"),
|
||||
{ start_date: start }, true
|
||||
) %>
|
||||
</div>
|
||||
</section>
|
||||
<div class="sr">
|
||||
<ul>
|
||||
<li><%- org %></li>
|
||||
<li><%- content.number %></li>
|
||||
<li><%- gettext("Starts") %><time itemprop="startDate" datetime="<%- start %>"><%- start %></time></li>
|
||||
</ul>
|
||||
</div>
|
||||
</a>
|
||||
</article>
|
||||
@@ -0,0 +1,5 @@
|
||||
<p class="certificate-status col-12 md-col-8">
|
||||
<span class="card-msg"><%- gettext('Certificate Status:') %></span>
|
||||
<span class="fa fa-check-circle" aria-hidden="true"></span>
|
||||
<span class="certificate-status-msg"><%- gettext('Certificate Purchased') %></span>
|
||||
</p>
|
||||
28
lms/templates/learner_dashboard/course_card_2017.underscore
Normal file
28
lms/templates/learner_dashboard/course_card_2017.underscore
Normal file
@@ -0,0 +1,28 @@
|
||||
<div class="section">
|
||||
<div class="course-meta-container col-12 md-col-8 sm-col-12">
|
||||
<div class="course-details">
|
||||
<h5 class="course-title">
|
||||
<% if ( marketing_url || course_url ) { %>
|
||||
<a href="<%- marketing_url || course_url %>" class="course-title-link">
|
||||
<%- title %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<%- title %>
|
||||
<% } %>
|
||||
</h5>
|
||||
<div class="course-text">
|
||||
<% if (enrolled) { %>
|
||||
<span class='enrolled'><%- enrolled %>: </span>
|
||||
<% } %>
|
||||
<% if (dateString) { %>
|
||||
<span class="run-period"><%- dateString %></span>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="course-actions col-12 md-col-4 sm-col-12"></div>
|
||||
<div class="certificate-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section action-msg-view"></div>
|
||||
<div class="section upgrade-message"></div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<% if (!is_enrolled) { %>
|
||||
<% if (enrollable_course_runs.length > 0) { %>
|
||||
<% if (enrollable_course_runs.length > 1) { %>
|
||||
<div class="run-select-container">
|
||||
<label class="select-choice" for="select-<%- course_key %>-run">
|
||||
<%- gettext('Choose a course run:') %>
|
||||
</label>
|
||||
<select id="select-<%- course_key %>-run" class="run-select field-input input-select" autocomplete="off">
|
||||
<% _.each (enrollable_course_runs, function(courseRun) { %>
|
||||
<option
|
||||
value="<%- courseRun.key %>"
|
||||
<% if (key === courseRun.key) { %>
|
||||
selected="selected"
|
||||
<% }%>
|
||||
>
|
||||
<%- courseRun.dateString %>
|
||||
</option>
|
||||
<% }); %>
|
||||
</select>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="enroll-button">
|
||||
<button type="button" class="btn-brand btn cta-primary">
|
||||
<%- gettext('Enroll Now') %>
|
||||
</button>
|
||||
</div>
|
||||
<% } else if (upcoming_course_runs.length > 0) {%>
|
||||
<div class="no-action-message">
|
||||
<%- gettext('Coming Soon') %>
|
||||
</div>
|
||||
<div class="enrollment-opens">
|
||||
<%- gettext('Enrollment Opens on') %>
|
||||
<span class="enrollment-open-date">
|
||||
<%- upcoming_course_runs[0].enrollment_open_date %>
|
||||
</span>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="no-action-message">
|
||||
<%- gettext('Not Currently Available') %>
|
||||
</div>
|
||||
<% } %>
|
||||
<% } %>
|
||||
@@ -27,5 +27,5 @@ ProgramDetailsFactory2017({
|
||||
<%block name="bodyclass">program-details</%block>
|
||||
|
||||
<main id="main" aria-label="Content" tabindex="-1">
|
||||
<div class="js-program-details-wrapper"></div>
|
||||
<div class="js-program-details-wrapper program-details-wrapper"></div>
|
||||
</main>
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<header class="js-program-header program-header full-width-banner"></header>
|
||||
<div class="program-details-content">
|
||||
<div class="js-program-progress-view"></div>
|
||||
<div class="program-heading">
|
||||
<% if (inProgressCount === totalCount) { %>
|
||||
<h3 class="program-heading-title"><%- gettext('Congratulations!') %></h3>
|
||||
<div class="program-heading-message">
|
||||
<div><%- interpolate(gettext(
|
||||
'You have successfully completed all the requirements for the %(title)s %(type)s.'),
|
||||
{ title: title, type: type }, true) %>
|
||||
</div>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<h3 class="program-heading-title"><%- gettext('Your Program Journey') %></h3>
|
||||
<div class="program-heading-message">
|
||||
<div>
|
||||
<%- interpolate(gettext(
|
||||
'Track and plan your progress through the %(count)s courses in this program.'),
|
||||
{ count: totalCount }, true) %>
|
||||
</div>
|
||||
<div><%- gettext('To complete the program, you must earn a verified certificate for each course.') %></div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="course-list-headings">
|
||||
<% if (inProgressCount) { %>
|
||||
<div class="in-progress-group">
|
||||
<h4 class="course-list-heading">
|
||||
<span class="status"><%- gettext('COURSES IN PROGRESS') %></span>
|
||||
<span class="count"><%- inProgressCount %></span>
|
||||
</h4>
|
||||
<div class="divider"></div>
|
||||
<div class="course-list js-course-list-in-progress row"></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% if (remainingCount) { %>
|
||||
<div class="remaining-group">
|
||||
<h4 class="course-list-heading">
|
||||
<span class="status"><%- gettext('REMAINING COURSES') %></span>
|
||||
<span class="count"><%- remainingCount %></span>
|
||||
</h4>
|
||||
<div class="divider"></div>
|
||||
<div class="course-list js-course-list-remaining row"></div>
|
||||
</div>
|
||||
<% } %>
|
||||
<div class="completed-group">
|
||||
<h4 class="course-list-heading">
|
||||
<span class="status"><%- gettext('COMPLETED COURSES') %></span>
|
||||
<span class="count"><%- completedCount %></span>
|
||||
</h4>
|
||||
<div class="divider"></div>
|
||||
<% if (completedCount) { %>
|
||||
<div class="course-list js-course-list-completed row"></div>
|
||||
<% } else { %>
|
||||
<div class="motivating-section">
|
||||
<p class='motivating-heading'><%- gettext("As you complete courses, you will see them listed here.") %></p>
|
||||
<p class='motivating-message'><%- gettext('Complete courses on your schedule to ensure you stand out in your field!') %></p>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="js-course-sidebar"></aside>
|
||||
</div>
|
||||
@@ -0,0 +1,21 @@
|
||||
<div class="program-details-header">
|
||||
<div class="meta-info grid-container">
|
||||
<% if (logo) { %>
|
||||
<span aria-label="<%- gettext(programData.type) %>" class="<%- programData.type.toLowerCase() %> program-details-icon"><%= logo %></span>
|
||||
<% } %>
|
||||
<h2 class="hd-1 program-title"><%- programData.title %></h2>
|
||||
</div>
|
||||
<div class='authoring-organizations'>
|
||||
<h2 class="heading">Institutions</h2>
|
||||
<% if (programData.authoring_organizations.length) { %>
|
||||
<div class="orgs">
|
||||
<% _.each(programData.authoring_organizations, function(org) { %>
|
||||
<img src="<%- org.certificate_logo_image_url || org.logo_image_url %>" class="org-logo" alt="<%- StringUtils.interpolate(
|
||||
gettext('{organization}\'s logo'),
|
||||
{organization: org.name}
|
||||
) %>">
|
||||
<% }) %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
<div class="message certificate-status col-12 md-col-8">
|
||||
<span class="card-msg"><%- gettext('Certificate Status:') %></span>
|
||||
<span><%- gettext('Needs verified certificate ') %></span>
|
||||
<span class="price"> <%- price %></span>
|
||||
</div>
|
||||
<div class="action col-12 md-col-4">
|
||||
<button href="<%- upgrade_url %>" type="button" class="btn-brand btn cta-primary upgrade-button">
|
||||
<%- gettext('Buy Certificate') %>
|
||||
</button>
|
||||
</div>
|
||||
@@ -4,6 +4,7 @@ from collections import defaultdict
|
||||
import datetime
|
||||
from urlparse import urljoin
|
||||
|
||||
from dateutil.parser import parse
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.core.urlresolvers import reverse
|
||||
@@ -337,7 +338,6 @@ class ProgramDataExtender(object):
|
||||
required_mode = CourseMode.mode_for_course(self.course_run_key, required_mode_slug)
|
||||
ecommerce = EcommerceService()
|
||||
sku = getattr(required_mode, 'sku', None)
|
||||
|
||||
if ecommerce.is_enabled(self.user) and sku:
|
||||
run_mode['upgrade_url'] = ecommerce.checkout_page_url(required_mode.sku)
|
||||
else:
|
||||
|
||||
Reference in New Issue
Block a user