Merge pull request #16783 from edx/HarryRein/course-run-selection-program-dashboard
Harry rein/course run selection program dashboard
This commit is contained in:
@@ -213,6 +213,14 @@ class CourseEntitlement(TimeStampedModel):
|
||||
"""
|
||||
return self.policy.is_entitlement_redeemable(self)
|
||||
|
||||
def to_dict(self):
|
||||
""" Convert entitlement to dictionary representation. """
|
||||
return {
|
||||
'uuid': str(self.uuid),
|
||||
'course_uuid': str(self.course_uuid),
|
||||
'expired_at': self.expired_at
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def set_enrollment(cls, entitlement, enrollment):
|
||||
"""
|
||||
|
||||
@@ -42,6 +42,11 @@
|
||||
return desiredCourseRun;
|
||||
},
|
||||
|
||||
isEnrolledInSession: function() {
|
||||
// Returns true if the user is currently enrolled in a session of the course
|
||||
return _.findWhere(this.context.course_runs, {is_enrolled: true}) !== undefined;
|
||||
},
|
||||
|
||||
getUnselectedCourseRun: function(courseRuns) {
|
||||
var unselectedRun = {},
|
||||
courseRun;
|
||||
@@ -143,8 +148,9 @@
|
||||
formatDateString: function(run) {
|
||||
var pacingType = run.pacing_type,
|
||||
dateString,
|
||||
start = this.get('start_date') || run.start_date,
|
||||
end = this.get('end_date') || run.end_date,
|
||||
start = this.valueIsDefined(run.start_date) ? run.advertised_start || run.start_date :
|
||||
this.get('start_date'),
|
||||
end = this.valueIsDefined(run.end_date) ? run.end_date : this.get('end_date'),
|
||||
now = new Date(),
|
||||
startDate = new Date(start),
|
||||
endDate = new Date(end);
|
||||
@@ -178,26 +184,27 @@
|
||||
},
|
||||
|
||||
setActiveCourseRun: function(courseRun, userPreferences) {
|
||||
var startDateString;
|
||||
|
||||
var startDateString,
|
||||
isEnrolled = this.isEnrolledInSession() && courseRun.key;
|
||||
if (courseRun) {
|
||||
if (this.valueIsDefined(courseRun.advertised_start)) {
|
||||
startDateString = courseRun.advertised_start;
|
||||
} else {
|
||||
startDateString = this.formatDate(courseRun.start, userPreferences);
|
||||
}
|
||||
|
||||
this.set({
|
||||
certificate_url: courseRun.certificate_url,
|
||||
course_run_key: courseRun.key,
|
||||
course_run_key: courseRun.key || '',
|
||||
course_url: courseRun.course_url || '',
|
||||
title: this.context.title,
|
||||
end_date: this.formatDate(courseRun.end, userPreferences),
|
||||
enrollable_course_runs: this.getEnrollableCourseRuns(),
|
||||
is_course_ended: courseRun.is_course_ended,
|
||||
is_enrolled: courseRun.is_enrolled,
|
||||
is_enrolled: isEnrolled,
|
||||
is_enrollment_open: courseRun.is_enrollment_open,
|
||||
course_key: this.context.key,
|
||||
user_entitlement: this.context.user_entitlement,
|
||||
is_unfulfilled_entitlement: this.context.user_entitlement && !isEnrolled,
|
||||
marketing_url: courseRun.marketing_url,
|
||||
mode_slug: courseRun.type,
|
||||
start_date: startDateString,
|
||||
@@ -220,6 +227,10 @@
|
||||
updateCourseRun: function(courseRunKey) {
|
||||
var selectedCourseRun = _.findWhere(this.get('course_runs'), {key: courseRunKey});
|
||||
if (selectedCourseRun) {
|
||||
// Update the current context to set the course run to the enrolled state
|
||||
_.each(this.context.course_runs, function(run) {
|
||||
if (run.key === selectedCourseRun.key) run.is_enrolled = true; // eslint-disable-line no-param-reassign, max-len
|
||||
});
|
||||
this.setActiveCourseRun(selectedCourseRun);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Store data for the current
|
||||
* Store data for the current entitlement.
|
||||
*/
|
||||
(function(define) {
|
||||
'use strict';
|
||||
@@ -13,7 +13,6 @@
|
||||
availableSessions: [],
|
||||
entitlementUUID: '',
|
||||
currentSessionId: '',
|
||||
userId: '',
|
||||
courseName: ''
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
'js/learner_dashboard/views/certificate_status_view',
|
||||
'js/learner_dashboard/views/expired_notification_view',
|
||||
'js/learner_dashboard/views/course_enroll_view',
|
||||
'js/learner_dashboard/views/course_entitlement_view',
|
||||
'text!../../../templates/learner_dashboard/course_card.underscore'
|
||||
],
|
||||
function(
|
||||
@@ -24,6 +25,7 @@
|
||||
CertificateStatusView,
|
||||
ExpiredNotificationView,
|
||||
CourseEnrollView,
|
||||
EntitlementView,
|
||||
pageTpl
|
||||
) {
|
||||
return Backbone.View.extend({
|
||||
@@ -41,6 +43,8 @@
|
||||
this.grade = this.context.courseData.grades[this.model.get('course_run_key')];
|
||||
this.grade = this.grade * 100;
|
||||
this.collectionCourseStatus = this.context.collectionCourseStatus || '';
|
||||
this.entitlement = this.model.get('user_entitlement');
|
||||
|
||||
this.render();
|
||||
this.listenTo(this.model, 'change', this.render);
|
||||
},
|
||||
@@ -57,7 +61,9 @@
|
||||
var $upgradeMessage = this.$('.upgrade-message'),
|
||||
$certStatus = this.$('.certificate-status'),
|
||||
$expiredNotification = this.$('.expired-notification'),
|
||||
expired = this.model.get('expired');
|
||||
expired = this.model.get('expired'),
|
||||
courseUUID = this.model.get('uuid'),
|
||||
containerSelector = '#course-' + courseUUID;
|
||||
|
||||
this.enrollView = new CourseEnrollView({
|
||||
$parentEl: this.$('.course-actions'),
|
||||
@@ -68,6 +74,27 @@
|
||||
enrollModel: this.enrollModel
|
||||
});
|
||||
|
||||
if (this.entitlement) {
|
||||
this.sessionSelectionView = new EntitlementView({
|
||||
el: this.$(containerSelector + ' .course-entitlement-selection-container'),
|
||||
$parentEl: this.$el,
|
||||
courseCardModel: this.model,
|
||||
enrollModel: this.enrollModel,
|
||||
triggerOpenBtn: '.course-details .change-session',
|
||||
courseCardMessages: '',
|
||||
courseImageLink: '',
|
||||
courseTitleLink: containerSelector + ' .course-details .course-title',
|
||||
dateDisplayField: containerSelector + ' .course-details .course-text',
|
||||
enterCourseBtn: containerSelector + ' .view-course-button',
|
||||
availableSessions: JSON.stringify(this.model.get('course_runs')),
|
||||
entitlementUUID: this.entitlement.uuid,
|
||||
currentSessionId: this.model.isEnrolledInSession() ?
|
||||
this.model.get('course_run_key') : null,
|
||||
enrollUrl: this.model.get('enroll_url'),
|
||||
courseHomeUrl: this.model.get('course_url')
|
||||
});
|
||||
}
|
||||
|
||||
if (this.model.get('upgrade_url') && !(expired === true)) {
|
||||
this.upgradeMessage = new UpgradeMessageView({
|
||||
$el: $upgradeMessage,
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
// Enrollment click event handled here
|
||||
var courseRunKey = $('.run-select').val() || this.model.get('course_run_key');
|
||||
this.model.updateCourseRun(courseRunKey);
|
||||
if (!this.model.get('is_enrolled')) {
|
||||
if (this.model.get('is_enrolled')) {
|
||||
// Create the enrollment.
|
||||
this.enrollModel.save({
|
||||
course_id: courseRunKey
|
||||
|
||||
@@ -37,12 +37,12 @@
|
||||
|
||||
initialize: function(options) {
|
||||
// Set up models and reload view on change
|
||||
this.courseCardModel = new CourseCardModel();
|
||||
this.courseCardModel = options.courseCardModel || new CourseCardModel();
|
||||
this.enrollModel = options.enrollModel;
|
||||
this.entitlementModel = new EntitlementModel({
|
||||
availableSessions: this.formatDates(JSON.parse(options.availableSessions)),
|
||||
entitlementUUID: options.entitlementUUID,
|
||||
currentSessionId: options.currentSessionId,
|
||||
userId: options.userId,
|
||||
courseName: options.courseName
|
||||
});
|
||||
this.listenTo(this.entitlementModel, 'change', this.render);
|
||||
@@ -51,13 +51,18 @@
|
||||
this.enrollUrl = options.enrollUrl;
|
||||
this.courseHomeUrl = options.courseHomeUrl;
|
||||
|
||||
// Grab elements from the parent card that work with this view and bind associated events
|
||||
this.$triggerOpenBtn = $(options.triggerOpenBtn); // Opens/closes session selection view
|
||||
this.$dateDisplayField = $(options.dateDisplayField); // Displays current session dates
|
||||
// Grab elements from the parent card that work with this view
|
||||
this.$parentEl = options.$parentEl; // Containing course card (must be a backbone view root el)
|
||||
this.$enterCourseBtn = $(options.enterCourseBtn); // Button link to course home page
|
||||
this.$courseCardMessages = $(options.courseCardMessages); // Additional session messages
|
||||
this.$courseTitleLink = $(options.courseTitleLink); // Title link to course home page
|
||||
this.$courseImageLink = $(options.courseImageLink); // Image link to course home page
|
||||
|
||||
// Bind action elements with associated events to objects outside this view
|
||||
this.$dateDisplayField = this.$parentEl ? this.$parentEl.find(options.dateDisplayField) :
|
||||
$(options.dateDisplayField); // Displays current session dates
|
||||
this.$triggerOpenBtn = this.$parentEl ? this.$parentEl.find(options.triggerOpenBtn) :
|
||||
$(options.triggerOpenBtn); // Opens/closes session selection view
|
||||
this.$triggerOpenBtn.on('click', this.toggleSessionSelectionPanel.bind(this));
|
||||
|
||||
this.render(options);
|
||||
@@ -72,15 +77,17 @@
|
||||
},
|
||||
|
||||
postRender: function() {
|
||||
// Close popover on click-away
|
||||
// Close any visible popovers on click-away
|
||||
$(document).on('click', function(e) {
|
||||
if (!($(e.target).closest('.enroll-btn-initial, .popover').length)) {
|
||||
if (this.$('.popover:visible').length &&
|
||||
!($(e.target).closest('.enroll-btn-initial, .popover').length)) {
|
||||
this.hideDialog(this.$('.enroll-btn-initial'));
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this.$('.enroll-btn-initial').click(function(e) {
|
||||
this.showDialog($(e.target));
|
||||
// Initialize focus to cancel button on popover load
|
||||
$(document).on('shown.bs.popover', function() {
|
||||
this.$('.final-confirmation-btn:first').focus();
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
@@ -130,7 +137,13 @@
|
||||
*/
|
||||
var successIconEl = '<span class="fa fa-check" aria-hidden="true"></span>';
|
||||
|
||||
// Update the model with the new session Id;
|
||||
// With a containing backbone view, we can simply re-render the parent card
|
||||
if (this.$parentEl) {
|
||||
this.courseCardModel.updateCourseRun(this.currentSessionSelection);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the model with the new session Id
|
||||
this.entitlementModel.set({currentSessionId: this.currentSessionSelection});
|
||||
|
||||
// Allow user to change session
|
||||
@@ -161,6 +174,11 @@
|
||||
3) Remove the messages associated with the enrolled state.
|
||||
4) Remove the link from the course card image and title.
|
||||
*/
|
||||
// With a containing backbone view, we can simply re-render the parent card
|
||||
if (this.$parentEl) {
|
||||
this.courseCardModel.setUnselected();
|
||||
return;
|
||||
}
|
||||
|
||||
// Update the model with the new session Id;
|
||||
this.entitlementModel.set({currentSessionId: this.currentSessionSelection});
|
||||
@@ -198,13 +216,18 @@
|
||||
},
|
||||
|
||||
enrollError: function() {
|
||||
var errorMsgEl = HtmlUtils.HTML(
|
||||
gettext('There was an error. Please reload the page and try again.')
|
||||
// Display a success indicator
|
||||
var errorMsgEl = HtmlUtils.joinHtml(
|
||||
HtmlUtils.HTML('<span class="enroll-error">'),
|
||||
gettext('There was an error. Please reload the page and try again.'),
|
||||
HtmlUtils.HTML('</spandiv>')
|
||||
).text;
|
||||
|
||||
this.$dateDisplayField
|
||||
.find('.fa.fa-spin')
|
||||
.removeClass('fa-spin fa-spinner')
|
||||
.addClass('fa-close');
|
||||
|
||||
this.$dateDisplayField.append(errorMsgEl);
|
||||
this.hideDialog(this.$('.enroll-btn-initial'));
|
||||
},
|
||||
@@ -237,7 +260,6 @@
|
||||
enrollText = gettext('Leave Current Session');
|
||||
}
|
||||
enrollBtnInitial.text(enrollText);
|
||||
this.removeDialog(enrollBtnInitial);
|
||||
this.initializeVerificationDialog(enrollBtnInitial);
|
||||
},
|
||||
|
||||
@@ -263,7 +285,6 @@
|
||||
*/
|
||||
var confirmationMsgTitle,
|
||||
confirmationMsgBody,
|
||||
popoverDialogHtml,
|
||||
currentSessionId = this.entitlementModel.get('currentSessionId'),
|
||||
newSessionId = this.$('.session-select').find('option:selected').data('session_id');
|
||||
|
||||
@@ -279,38 +300,35 @@
|
||||
confirmationMsgBody = gettext('Any course progress or grades from your current session will be lost.'); // eslint-disable-line max-len
|
||||
}
|
||||
|
||||
// Remove existing popover and re-initialize
|
||||
popoverDialogHtml = this.verificationTpl({
|
||||
confirmationMsgTitle: confirmationMsgTitle,
|
||||
confirmationMsgBody: confirmationMsgBody
|
||||
});
|
||||
|
||||
// Re-initialize the popover
|
||||
invokingElement.popover({
|
||||
placement: 'bottom',
|
||||
container: this.$el,
|
||||
html: true,
|
||||
trigger: 'click',
|
||||
content: popoverDialogHtml.text
|
||||
content: this.verificationTpl({
|
||||
confirmationMsgTitle: confirmationMsgTitle,
|
||||
confirmationMsgBody: confirmationMsgBody
|
||||
}).text
|
||||
});
|
||||
},
|
||||
|
||||
removeDialog: function(invokingElement) {
|
||||
removeDialog: function(el) {
|
||||
/* Removes the Bootstrap v4 dialog modal from the update session enrollment button. */
|
||||
invokingElement.popover('dispose');
|
||||
},
|
||||
|
||||
showDialog: function(invokingElement) {
|
||||
/* Given an element with an associated dialog modal, shows the modal. */
|
||||
invokingElement.popover('show');
|
||||
this.$('.final-confirmation-btn:first').focus();
|
||||
var $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
|
||||
if (this.$('popover').length) {
|
||||
$el.popover('dispose');
|
||||
}
|
||||
},
|
||||
|
||||
hideDialog: function(el, returnFocus) {
|
||||
/* Hides the modal without removing it from the DOM. */
|
||||
/* Hides the modal if it is visible without removing it from the DOM. */
|
||||
var $el = el instanceof jQuery ? el : this.$('.enroll-btn-initial');
|
||||
$el.popover('hide');
|
||||
if (returnFocus) {
|
||||
$el.focus();
|
||||
if (this.$('.popover:visible').length) {
|
||||
$el.popover('hide');
|
||||
if (returnFocus) {
|
||||
$el.focus();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -363,12 +381,13 @@
|
||||
|
||||
return _.map(formattedSessionData, function(session) {
|
||||
var formattedSession = session;
|
||||
startDate = this.formatDate(formattedSession.session_start, dateFormat);
|
||||
endDate = this.formatDate(formattedSession.session_end, dateFormat);
|
||||
startDate = this.formatDate(formattedSession.start, dateFormat);
|
||||
endDate = this.formatDate(formattedSession.end, dateFormat);
|
||||
formattedSession.enrollment_end = this.formatDate(formattedSession.enrollment_end, dateFormat);
|
||||
formattedSession.session_dates = this.courseCardModel.formatDateString({
|
||||
start_date: session.session_start_advertised || startDate,
|
||||
end_date: session.session_start_advertised ? null : endDate,
|
||||
start_date: startDate,
|
||||
advertised_start: session.advertised_start,
|
||||
end_date: endDate,
|
||||
pacing_type: formattedSession.pacing_type
|
||||
});
|
||||
return formattedSession;
|
||||
@@ -376,7 +395,7 @@
|
||||
},
|
||||
|
||||
formatDate: function(date, dateFormat) {
|
||||
return date ? moment((new Date(date))).format(dateFormat) : null;
|
||||
return date ? moment((new Date(date))).format(dateFormat) : '';
|
||||
},
|
||||
|
||||
getAvailableSessionWithId: function(sessionId) {
|
||||
|
||||
@@ -36,7 +36,6 @@
|
||||
|
||||
initialize: function(options) {
|
||||
this.options = options;
|
||||
|
||||
this.programModel = new Backbone.Model(this.options.programData);
|
||||
this.courseData = new Backbone.Model(this.options.courseData);
|
||||
this.certificateCollection = new Backbone.Collection(this.options.certificateData);
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
'backbone.associations': 'xmodule_js/common_static/js/vendor/backbone-associations-min',
|
||||
'backbone.paginator': 'common/js/vendor/backbone.paginator',
|
||||
'backbone-super': 'js/vendor/backbone-super',
|
||||
'popper': 'common/js/vendor/popper',
|
||||
'bootstrap': 'common/js/vendor/bootstrap',
|
||||
'URI': 'xmodule_js/common_static/js/vendor/URI.min',
|
||||
'tinymce': 'xmodule_js/common_static/js/vendor/tinymce/js/tinymce/tinymce.full.min',
|
||||
'jquery.tinymce': 'xmodule_js/common_static/js/vendor/tinymce/js/tinymce/jquery.tinymce',
|
||||
@@ -197,6 +199,10 @@
|
||||
'backbone-super': {
|
||||
deps: ['backbone']
|
||||
},
|
||||
'bootstrap': {
|
||||
deps: ['jquery', 'popper'],
|
||||
exports: 'bootstrap'
|
||||
},
|
||||
'paging-collection': {
|
||||
deps: ['jquery', 'underscore', 'backbone.paginator']
|
||||
},
|
||||
|
||||
@@ -10,5 +10,8 @@
|
||||
@import 'elements/program-card';
|
||||
@import 'elements-v2/icons';
|
||||
@import 'elements/progress-circle';
|
||||
|
||||
// Various View Styling
|
||||
@import 'views/course-entitlements';
|
||||
@import 'views/program-details';
|
||||
@import 'views/program-list';
|
||||
|
||||
@@ -51,7 +51,8 @@
|
||||
@import 'multicourse/survey-page';
|
||||
|
||||
// base - specific views
|
||||
@import "views/account-settings";
|
||||
@import 'views/account-settings';
|
||||
@import 'views/course-entitlements';
|
||||
@import 'views/login-register';
|
||||
@import 'views/verification';
|
||||
@import 'views/decoupled-verification';
|
||||
@@ -59,7 +60,7 @@
|
||||
@import 'views/homepage';
|
||||
@import 'views/support';
|
||||
@import 'views/oauth2';
|
||||
@import "views/financial-assistance";
|
||||
@import 'views/financial-assistance';
|
||||
@import 'course/auto-cert';
|
||||
@import 'views/api-access';
|
||||
|
||||
|
||||
@@ -1045,6 +1045,7 @@
|
||||
.related-programs-preface {
|
||||
@include float(left);
|
||||
|
||||
margin: 0 $baseline/2;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@@ -1095,89 +1096,6 @@
|
||||
@include padding($baseline/2, $baseline, $baseline/2, $baseline/2);
|
||||
}
|
||||
}
|
||||
|
||||
// Course Entitlement Session Selection
|
||||
.course-entitlement-selection-container {
|
||||
background-color: theme-color("inverse");
|
||||
|
||||
.action-header {
|
||||
padding-bottom: $baseline/4;
|
||||
font-weight: $font-weight-bold;
|
||||
color: theme-color("dark");
|
||||
}
|
||||
|
||||
.action-controls {
|
||||
display: flex;
|
||||
|
||||
.session-select {
|
||||
background-color: theme-color("inverse");
|
||||
height: $baseline*1.5;
|
||||
flex-grow: 5;
|
||||
margin-bottom: $baseline*0.4;
|
||||
}
|
||||
|
||||
.enroll-btn-initial {
|
||||
@include margin-left($baseline);
|
||||
|
||||
height: $baseline*1.5;
|
||||
flex-grow: 1;
|
||||
letter-spacing: 0;
|
||||
background: theme-color("inverse");
|
||||
border-color: theme-color("primary");
|
||||
color: theme-color("primary");
|
||||
text-shadow: none;
|
||||
font-size: $font-size-base;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
border-radius: $border-radius-sm;
|
||||
transition: all 0.4s ease-out;
|
||||
|
||||
&:hover {
|
||||
background: theme-color("primary");
|
||||
border-color: theme-color("primary");
|
||||
color: theme-color("inverse");
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
flex-direction: column;
|
||||
|
||||
.enroll-btn-initial {
|
||||
margin: $baseline/4 0 $baseline/4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
.popover-title {
|
||||
margin-bottom: $baseline/2;
|
||||
}
|
||||
|
||||
.action-items {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: $baseline/2;
|
||||
|
||||
.final-confirmation-btn {
|
||||
box-shadow: none;
|
||||
border: 1px solid theme-color("dark");
|
||||
background: none;
|
||||
color: theme-color("dark");
|
||||
text-shadow: none;
|
||||
letter-spacing: 0;
|
||||
flex-grow: 1;
|
||||
margin: 0 $baseline/4;
|
||||
padding: $baseline/10 $baseline;
|
||||
font-size: $font-size-base;
|
||||
|
||||
&:hover {
|
||||
background: theme-color("primary");
|
||||
color: theme-color("inverse");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CASE: empty dashboard
|
||||
|
||||
127
lms/static/sass/views/_course-entitlements.scss
Normal file
127
lms/static/sass/views/_course-entitlements.scss
Normal file
@@ -0,0 +1,127 @@
|
||||
// Shared styling between courses and programs dashboard
|
||||
.course-entitlement-selection-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
|
||||
.action-header {
|
||||
padding-bottom: $baseline/4;
|
||||
font-weight: $font-weight-bold;
|
||||
color: theme-color("dark");
|
||||
}
|
||||
|
||||
.action-controls {
|
||||
display: flex;
|
||||
|
||||
.session-select {
|
||||
background-color: theme-color("inverse");
|
||||
height: $baseline*1.5;
|
||||
flex-grow: 5;
|
||||
margin-bottom: $baseline*0.4;
|
||||
max-width: calc(100% - 200px);
|
||||
}
|
||||
|
||||
.enroll-btn-initial {
|
||||
@include margin-left($baseline);
|
||||
|
||||
height: $baseline*1.5;
|
||||
flex-grow: 1;
|
||||
letter-spacing: 0;
|
||||
background: theme-color("inverse");
|
||||
border-color: theme-color("primary");
|
||||
color: theme-color("primary");
|
||||
text-shadow: none;
|
||||
font-size: $font-size-base;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
border-radius: $border-radius-sm;
|
||||
transition: all 0.4s ease-out;
|
||||
|
||||
&:hover {
|
||||
background: theme-color("primary");
|
||||
border-color: theme-color("primary");
|
||||
color: theme-color("inverse");
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
flex-direction: column;
|
||||
|
||||
.session-select {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.enroll-btn-initial {
|
||||
margin: $baseline/4 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popover {
|
||||
.popover-title {
|
||||
margin-bottom: $baseline/2;
|
||||
}
|
||||
|
||||
.action-items {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: $baseline/2;
|
||||
|
||||
.final-confirmation-btn {
|
||||
box-shadow: none;
|
||||
border: 1px solid theme-color("dark");
|
||||
background: none;
|
||||
color: theme-color("dark");
|
||||
text-shadow: none;
|
||||
letter-spacing: 0;
|
||||
flex-grow: 1;
|
||||
margin: 0 $baseline/4;
|
||||
padding: $baseline/10 $baseline;
|
||||
font-size: $font-size-base;
|
||||
|
||||
&:hover {
|
||||
background: theme-color("primary");
|
||||
color: theme-color("inverse");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Styling overrides specific to the programs dashboard
|
||||
.program-course-card {
|
||||
.course-text {
|
||||
.fa-close {
|
||||
color: theme-color("error");
|
||||
}
|
||||
|
||||
.enroll-error {
|
||||
@include margin-left($baseline/4);
|
||||
|
||||
font-size: $font-size-sm;
|
||||
}
|
||||
|
||||
.change-session {
|
||||
@include margin(0, 0, $baseline/4, $baseline/4);
|
||||
|
||||
padding: 0;
|
||||
font-size: $font-size-sm;
|
||||
letter-spacing: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.course-entitlement-selection-container {
|
||||
padding-top: $baseline/2;
|
||||
|
||||
.action-header,
|
||||
.action-controls .session-select{
|
||||
font-size: $font-size-sm;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,7 +437,6 @@
|
||||
.program-course-card {
|
||||
width: 100%;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
|
||||
@media (min-width: $bp-screen-md) {
|
||||
height: auto;
|
||||
@@ -445,6 +444,7 @@
|
||||
|
||||
.section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
@media (min-width: $bp-screen-md) {
|
||||
@@ -452,6 +452,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
.section:not(:last-child) {
|
||||
margin-bottom: $baseline/2;
|
||||
}
|
||||
|
||||
.section:not(:first-child) {
|
||||
margin-top: 0;
|
||||
}
|
||||
@@ -461,10 +465,14 @@
|
||||
|
||||
.course-title {
|
||||
font-size: 1em;
|
||||
color: palette(primary, base);
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
margin: 0;
|
||||
|
||||
.course-title-link,
|
||||
.course-title-link:visited{
|
||||
color: palette(primary, base);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.run-period {
|
||||
|
||||
@@ -34,6 +34,7 @@ from student.models import CourseEnrollment
|
||||
</script>
|
||||
% endfor
|
||||
% if course_entitlements:
|
||||
<!-- This is a temporary solution before we land a fix to load these through Webpack, tracked by LEARNER-3483 -->
|
||||
<script type="text/javascript" src="${static.url('common/js/vendor/popper.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('common/js/vendor/bootstrap.js')}"></script>
|
||||
% endif
|
||||
@@ -141,9 +142,9 @@ from student.models import CourseEnrollment
|
||||
'session_id': course['key'],
|
||||
'enrollment_end': course['enrollment_end'],
|
||||
'pacing_type': course['pacing_type'],
|
||||
'session_start_advertised': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
|
||||
'session_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
|
||||
'session_end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
|
||||
'advertised_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
|
||||
'start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
|
||||
'end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
|
||||
} for course in course_entitlement_available_sessions[str(entitlement.uuid)]]
|
||||
if is_fulfilled_entitlement:
|
||||
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
|
||||
|
||||
@@ -291,8 +291,7 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_
|
||||
enterCourseBtn: '${ '#course-card-' + str(course_card_index) + ' .enter-course' | n, js_escaped_string }',
|
||||
availableSessions: '${ entitlement_available_sessions | n, dump_js_escaped_json }',
|
||||
entitlementUUID: '${ entitlement.course_uuid | n, js_escaped_string }',
|
||||
currentSessionId: '${ entitlement_session.course_id if entitlement_session else '' | n, js_escaped_string }',
|
||||
userId: '${ user.id | n, js_escaped_string }',
|
||||
currentSessionId: '${ entitlement_session.course_id if entitlement_session else "" | n, js_escaped_string }',
|
||||
enrollUrl: '${ reverse('entitlements_api:v1:enrollments', args=[str(entitlement.uuid)]) | n, js_escaped_string }',
|
||||
courseHomeUrl: '${ course_target | n, js_escaped_string }'
|
||||
});
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<div class="section">
|
||||
<div class="section" id="course-<%-uuid%>">
|
||||
<div class="course-meta-container">
|
||||
<div class="course-content">
|
||||
<div class="course-details">
|
||||
<h5 class="course-title">
|
||||
<% if ( marketing_url || course_url ) { %>
|
||||
<% if ( (marketing_url || course_url) && !is_unfulfilled_entitlement) { %>
|
||||
<a href="<%- marketing_url || course_url %>" class="course-title-link">
|
||||
<%- title %>
|
||||
</a>
|
||||
@@ -12,11 +12,14 @@
|
||||
<% } %>
|
||||
</h5>
|
||||
<div class="course-text">
|
||||
<% if (enrolled) { %>
|
||||
<% if (enrolled && !user_entitlement) { %>
|
||||
<span class="enrolled"><%- enrolled %>: </span>
|
||||
<% } %>
|
||||
<% if (dateString) { %>
|
||||
<% if (dateString && !is_unfulfilled_entitlement) { %>
|
||||
<span class="run-period"><%- dateString %></span>
|
||||
<% if (user_entitlement && !is_unfulfilled_entitlement) { %>
|
||||
<button class="change-session btn-link" aria-controls="change-session-<%-user_entitlement.uuid%>"> <%- gettext('Change Session')%></button>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,6 +27,7 @@
|
||||
</div>
|
||||
<div class="course-certificate certificate-status"></div>
|
||||
</div>
|
||||
<div class="course-entitlement-selection-container<% if (!is_unfulfilled_entitlement && user_entitlement) { %> hidden <% } %>"></div>
|
||||
</div>
|
||||
<div class="section action-msg-view"></div>
|
||||
<div class="section upgrade-message"></div>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<%- gettext('View Course') %>
|
||||
<% } %>
|
||||
</a>
|
||||
<% } else { %>
|
||||
<% } else if (!user_entitlement) { %>
|
||||
<% if (enrollable_course_runs.length > 0) { %>
|
||||
<% if (enrollable_course_runs.length > 1) { %>
|
||||
<div class="run-select-container">
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
<div class="action-controls">
|
||||
<select class="session-select" aria-label="<%- StringUtils.interpolate( gettext('Session Selection Dropdown for {courseName}'), { courseName: courseName }) %>">
|
||||
<% _.each(availableSessions, function(session) { %>
|
||||
<option data-session_id="<%- session.session_id %>">
|
||||
<% if (session.session_id === currentSessionId) { %>
|
||||
<option data-session_id="<%- session.session_id || session.key %>">
|
||||
<% if ((session.session_id || session.key) === currentSessionId) { %>
|
||||
<%- StringUtils.interpolate( gettext('{sessionDates} - Currently Selected'), {sessionDates: session.session_dates}) %>
|
||||
<% } else if (session.enrollment_end){ %>
|
||||
<%- StringUtils.interpolate( gettext('{sessionDates} (Open until {enrollmentEnd})'), {sessionDates: session.session_dates, enrollmentEnd: session.enrollment_end}) %>
|
||||
|
||||
@@ -9,6 +9,10 @@ from openedx.core.djangolib.js_utils import (
|
||||
%>
|
||||
|
||||
<%block name="js_extra">
|
||||
<!-- This is a temporary solution before we land a fix to load these through Webpack, tracked by LEARNER-3483 -->
|
||||
<script type="text/javascript" src="${static.url('common/js/vendor/popper.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('common/js/vendor/bootstrap.js')}"></script>
|
||||
|
||||
<%static:require_module module_name="js/learner_dashboard/program_details_factory" class_name="ProgramDetailsFactory">
|
||||
ProgramDetailsFactory({
|
||||
programData: ${program_data | n, dump_js_escaped_json},
|
||||
|
||||
@@ -222,15 +222,26 @@ class ProgramProgressMeter(object):
|
||||
completed, in_progress, not_started = [], [], []
|
||||
|
||||
for course in program_copy['courses']:
|
||||
try:
|
||||
entitlement = CourseEntitlement.objects.get(user=self.user, course_uuid=course['uuid'])
|
||||
except CourseEntitlement.DoesNotExist:
|
||||
entitlement = None
|
||||
|
||||
if self._is_course_complete(course):
|
||||
completed.append(course)
|
||||
elif self._is_course_enrolled(course):
|
||||
course_in_progress = self._is_course_in_progress(now, course)
|
||||
if course_in_progress:
|
||||
elif self._is_course_enrolled(course) or entitlement:
|
||||
# Show all currently enrolled courses and entitlements as in progress
|
||||
if entitlement:
|
||||
course['user_entitlement'] = entitlement.to_dict()
|
||||
course['enroll_url'] = reverse('entitlements_api:v1:enrollments', args=[str(entitlement.uuid)])
|
||||
in_progress.append(course)
|
||||
else:
|
||||
course['expired'] = not course_in_progress
|
||||
not_started.append(course)
|
||||
course_in_progress = self._is_course_in_progress(now, course)
|
||||
if course_in_progress:
|
||||
in_progress.append(course)
|
||||
else:
|
||||
course['expired'] = not course_in_progress
|
||||
not_started.append(course)
|
||||
else:
|
||||
not_started.append(course)
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ from student.models import CourseEnrollment
|
||||
</script>
|
||||
% endfor
|
||||
% if course_entitlements:
|
||||
<!-- This is a temporary solution before we land a fix to load these through Webpack, tracked by LEARNER-3483 -->
|
||||
<script type="text/javascript" src="${static.url('common/js/vendor/popper.js')}"></script>
|
||||
<script type="text/javascript" src="${static.url('common/js/vendor/bootstrap.js')}"></script>
|
||||
% endif
|
||||
@@ -136,9 +137,9 @@ from student.models import CourseEnrollment
|
||||
'session_id': course['key'],
|
||||
'enrollment_end': course['enrollment_end'],
|
||||
'pacing_type': course['pacing_type'],
|
||||
'session_start_advertised': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
|
||||
'session_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
|
||||
'session_end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
|
||||
'advertised_start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).advertised_start,
|
||||
'start': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).start,
|
||||
'end': CourseOverview.get_from_id(CourseKey.from_string(course['key'])).end,
|
||||
} for course in course_entitlement_available_sessions[str(entitlement.uuid)]]
|
||||
if is_fulfilled_entitlement:
|
||||
# If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object
|
||||
|
||||
@@ -25,6 +25,8 @@ module.exports = {
|
||||
// LMS
|
||||
SingleSupportForm: './lms/static/support/jsx/single_support_form.jsx',
|
||||
AlertStatusBar: './lms/static/js/accessible_components/StatusBarAlert.jsx',
|
||||
Bootstrap: './lms/static/common/js/vendor/bootstrap.js',
|
||||
EntitlementView: './lms/static/js/learner_dashboard/views/course_entitlement_view.js',
|
||||
|
||||
// Features
|
||||
CourseGoals: './openedx/features/course_experience/static/course_experience/js/CourseGoals.js',
|
||||
@@ -63,6 +65,9 @@ module.exports = {
|
||||
jQuery: 'jquery',
|
||||
'window.jQuery': 'jquery'
|
||||
}),
|
||||
new webpack.ProvidePlugin({
|
||||
Popper: 'popper.js'
|
||||
}),
|
||||
|
||||
// Note: Until karma-webpack releases v3, it doesn't play well with
|
||||
// the CommonsChunkPlugin. We have a kludge in karma.common.conf.js
|
||||
@@ -169,6 +174,7 @@ module.exports = {
|
||||
gettext: 'gettext',
|
||||
jquery: 'jQuery',
|
||||
logger: 'Logger',
|
||||
popper: 'Popper',
|
||||
underscore: '_',
|
||||
URI: 'URI'
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user