diff --git a/common/static/js/src/accessibility_tools.js b/common/static/js/src/accessibility_tools.js index ea6a4fcd31..3c98f56759 100644 --- a/common/static/js/src/accessibility_tools.js +++ b/common/static/js/src/accessibility_tools.js @@ -98,7 +98,7 @@ var accessible_modal = function(trigger, closeButtonId, modalId, mainPageId) { }); // get modal to exit on escape key - $('.modal').on('keydown', function(e) { + $(modalId).on('keydown', function(e) { var keyCode = e.keyCode || e.which; // 27 is the javascript keycode for the ESC key if (keyCode === 27) { diff --git a/lms/static/js/dashboard/dropdown.js b/lms/static/js/dashboard/dropdown.js index 96b5e22328..c7099bee64 100644 --- a/lms/static/js/dashboard/dropdown.js +++ b/lms/static/js/dashboard/dropdown.js @@ -8,10 +8,10 @@ var edx = edx || {}; // Generate the properties object to be passed along with business intelligence events. edx.dashboard.dropdown.toggleCourseActionsDropdownMenu = function(event) { - // define variables for code legibility - var dashboardIndex = $(event.currentTarget).data().dashboardIndex, - $dropdown = $('#actions-dropdown-' + dashboardIndex), - $dropdownButton = $('#actions-dropdown-link-' + dashboardIndex), + var $target = $(event.currentTarget), + dashboardIndex = $target.data().dashboardIndex, + $dropdown = $($target.data('dropdownSelector') || '#actions-dropdown-' + dashboardIndex), + $dropdownButton = $($target.data('dropdownButtonSelector') || '#actions-dropdown-link-' + dashboardIndex), ariaExpandedState = ($dropdownButton.attr('aria-expanded') === 'true'), menuItems = $dropdown.find('a'); @@ -76,14 +76,15 @@ var edx = edx || {}; }); }; - edx.dashboard.dropdown.bindToggleButtons = function() { - $('.action-more').bind( + edx.dashboard.dropdown.bindToggleButtons = function(selector) { + $(selector).bind( 'click', edx.dashboard.dropdown.toggleCourseActionsDropdownMenu ); }; $(document).ready(function() { - edx.dashboard.dropdown.bindToggleButtons(); + edx.dashboard.dropdown.bindToggleButtons('.action-more'); + edx.dashboard.dropdown.bindToggleButtons('.js-entitlement-action-more'); }); }(jQuery)); diff --git a/lms/static/js/learner_dashboard/entitlement_unenrollment_factory.js b/lms/static/js/learner_dashboard/entitlement_unenrollment_factory.js new file mode 100644 index 0000000000..92828b90cf --- /dev/null +++ b/lms/static/js/learner_dashboard/entitlement_unenrollment_factory.js @@ -0,0 +1,12 @@ +(function(define) { + 'use strict'; + + define([ + 'js/learner_dashboard/views/entitlement_unenrollment_view' + ], + function(EntitlementUnenrollmentView) { + return function(options) { + return new EntitlementUnenrollmentView(options); + }; + }); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/learner_dashboard/views/entitlement_unenrollment_view.js b/lms/static/js/learner_dashboard/views/entitlement_unenrollment_view.js new file mode 100644 index 0000000000..00387be204 --- /dev/null +++ b/lms/static/js/learner_dashboard/views/entitlement_unenrollment_view.js @@ -0,0 +1,134 @@ +(function(define) { + 'use strict'; + define(['backbone', + 'jquery', + 'gettext', + 'edx-ui-toolkit/js/utils/html-utils' + ], + function(Backbone, $, gettext, HtmlUtils) { + return Backbone.View.extend({ + el: '.js-entitlement-unenrollment-modal', + closeButtonSelector: '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-close-btn', + headerTextSelector: '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-header-text', + errorTextSelector: '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-error-text', + submitButtonSelector: '.js-entitlement-unenrollment-modal .js-entitlement-unenrollment-modal-submit', + triggerSelector: '.js-entitlement-action-unenroll', + mainPageSelector: '#dashboard-main', + genericErrorMsg: gettext('Your unenrollment request could not be processed. Please try again later.'), + + initialize: function(options) { + var view = this; + this.dashboardPath = options.dashboardPath; + this.signInPath = options.signInPath; + + this.$submitButton = $(this.submitButtonSelector); + this.$headerText = $(this.headerTextSelector); + this.$errorText = $(this.errorTextSelector); + + this.$submitButton.on('click', this.handleSubmit.bind(this)); + + $(this.triggerSelector).each(function() { + var $trigger = $(this); + + $trigger.on('click', view.handleTrigger.bind(view)); + + if (window.accessible_modal) { + window.accessible_modal( + '#' + $trigger.attr('id'), + view.closeButtonSelector, + '#' + view.$el.attr('id'), + view.mainPageSelector + ); + } + }); + }, + + handleTrigger: function(event) { + var $trigger = $(event.target), + courseName = $trigger.data('courseName'), + courseNumber = $trigger.data('courseNumber'), + apiEndpoint = $trigger.data('entitlementApiEndpoint'); + + this.resetModal(); + this.setHeaderText(courseName, courseNumber); + this.setSubmitData(apiEndpoint); + + if (window.edx && window.edx.dashboard && window.edx.dashboard.dropdown) { + window.edx.dashboard.dropdown.toggleCourseActionsDropdownMenu(event); + this.$el.css('position', 'fixed'); + } + }, + + handleSubmit: function() { + var apiEndpoint = this.$submitButton.data('entitlementApiEndpoint'); + + if (apiEndpoint === undefined) { + this.setError(this.genericErrorMsg); + return; + } + + this.$submitButton.prop('disabled', true); + $.ajax({ + url: apiEndpoint, + method: 'DELETE', + complete: this.onComplete.bind(this) + }); + }, + + resetModal: function() { + this.$submitButton.removeData(); + this.$submitButton.prop('disabled', false); + this.$headerText.empty(); + this.$errorText.removeClass('entitlement-unenrollment-modal-error-text-visible'); + this.$errorText.empty(); + }, + + setError: function(message) { + this.$submitButton.prop('disabled', true); + this.$errorText.empty(); + HtmlUtils.setHtml( + this.$errorText, + message + ); + this.$errorText.addClass('entitlement-unenrollment-modal-error-text-visible'); + }, + + setHeaderText: function(courseName, courseNumber) { + this.$headerText.empty(); + HtmlUtils.setHtml( + this.$headerText, + HtmlUtils.interpolateHtml( + gettext('Are you sure you want to unenroll from {courseName} ({courseNumber})? You will be refunded the amount you paid.'), // eslint-disable-line max-len + { + courseName: courseName, + courseNumber: courseNumber + } + ) + ); + }, + + setSubmitData: function(apiEndpoint) { + this.$submitButton.removeData(); + this.$submitButton.data('entitlementApiEndpoint', apiEndpoint); + }, + + onComplete: function(xhr) { + var status = xhr.status, + message = xhr.responseJSON && xhr.responseJSON.detail; + + if (status === 204) { + this.redirectTo(this.dashboardPath); + } else if (status === 401 && message === 'Authentication credentials were not provided.') { + this.redirectTo(this.signInPath + '?next=' + encodeURIComponent(this.dashboardPath)); + } else { + this.setError(this.genericErrorMsg); + } + }, + + redirectTo: function(path) { + window.location.href = path; + } + }); + } + ); +}).call(this, define || RequireJS.define); diff --git a/lms/static/js/spec/dashboard/dropdown_spec.js b/lms/static/js/spec/dashboard/dropdown_spec.js index 1eeaf388b4..96f7418db3 100644 --- a/lms/static/js/spec/dashboard/dropdown_spec.js +++ b/lms/static/js/spec/dashboard/dropdown_spec.js @@ -31,7 +31,7 @@ define(['js/dashboard/dropdown', 'jquery.simulate'], describe('edx.dashboard.dropdown.toggleCourseActionsDropdownMenu', function() { beforeEach(function() { loadFixtures('js/fixtures/dashboard/dashboard.html'); - window.edx.dashboard.dropdown.bindToggleButtons(); + window.edx.dashboard.dropdown.bindToggleButtons('.action-more'); }); it('Clicking the .action-more button toggles the menu', function() { diff --git a/lms/static/js/spec/learner_dashboard/entitlement_unenrollment_view_spec.js b/lms/static/js/spec/learner_dashboard/entitlement_unenrollment_view_spec.js new file mode 100644 index 0000000000..672d2e543b --- /dev/null +++ b/lms/static/js/spec/learner_dashboard/entitlement_unenrollment_view_spec.js @@ -0,0 +1,193 @@ +define([ + 'backbone', + 'jquery', + 'js/learner_dashboard/views/entitlement_unenrollment_view' +], function(Backbone, $, EntitlementUnenrollmentView) { + 'use strict'; + + describe('EntitlementUnenrollmentView', function() { + var view = null, + options = { + dashboardPath: '/dashboard', + signInPath: '/login' + }, + + initView = function() { + return new EntitlementUnenrollmentView(options); + }, + + modalHtml = 'Unenroll ' + + 'Unenroll ' + + '
' + + ' ' + + ' ' + + ' ' + + '
'; + + beforeEach(function() { + setFixtures(modalHtml); + view = initView(); + }); + + afterEach(function() { + view.remove(); + }); + + describe('when an unenroll link is clicked', function() { + it('should reset the modal and set the correct values for header/submit', function() { + var $link1 = $('#link1'), + $link2 = $('#link2'), + $headerTxt = $('.js-entitlement-unenrollment-modal-header-text'), + $errorTxt = $('.js-entitlement-unenrollment-modal-error-text'), + $submitBtn = $('.js-entitlement-unenrollment-modal-submit'); + + $link1.trigger('click'); + expect($headerTxt.html().startsWith('Are you sure you want to unenroll from Test Course 1')).toBe(true); + expect($submitBtn.data()).toEqual({entitlementApiEndpoint: '/test/api/endpoint/1'}); + expect($submitBtn.prop('disabled')).toBe(false); + expect($errorTxt.html()).toEqual(''); + expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(false); + + // Set an error so that we can see that the modal is reset properly when clicked again + view.setError('This is an error'); + expect($errorTxt.html()).toEqual('This is an error'); + expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(true); + expect($submitBtn.prop('disabled')).toBe(true); + + $link2.trigger('click'); + expect($headerTxt.html().startsWith('Are you sure you want to unenroll from Test Course 2')).toBe(true); + expect($submitBtn.data()).toEqual({entitlementApiEndpoint: '/test/api/endpoint/2'}); + expect($submitBtn.prop('disabled')).toBe(false); + expect($errorTxt.html()).toEqual(''); + expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(false); + }); + }); + + describe('when the unenroll submit button is clicked', function() { + it('should send a DELETE request to the configured apiEndpoint', function() { + var $submitBtn = $('.js-entitlement-unenrollment-modal-submit'), + apiEndpoint = '/test/api/endpoint/1'; + + view.setSubmitData(apiEndpoint); + + spyOn($, 'ajax').and.callFake(function(opts) { + expect(opts.url).toEqual(apiEndpoint); + expect(opts.method).toEqual('DELETE'); + expect(opts.complete).toBeTruthy(); + }); + + $submitBtn.trigger('click'); + expect($.ajax).toHaveBeenCalled(); + }); + + it('should set an error and disable submit if the apiEndpoint has not been properly set', function() { + var $errorTxt = $('.js-entitlement-unenrollment-modal-error-text'), + $submitBtn = $('.js-entitlement-unenrollment-modal-submit'); + + expect($submitBtn.data()).toEqual({}); + expect($submitBtn.prop('disabled')).toBe(false); + expect($errorTxt.html()).toEqual(''); + expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(false); + + spyOn($, 'ajax'); + $submitBtn.trigger('click'); + expect($.ajax).not.toHaveBeenCalled(); + + expect($submitBtn.data()).toEqual({}); + expect($submitBtn.prop('disabled')).toBe(true); + expect($errorTxt.html()).toEqual(view.genericErrorMsg); + expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(true); + }); + + describe('when the unenroll request is complete', function() { + it('should redirect to the dashboard if the request was successful', function() { + var $submitBtn = $('.js-entitlement-unenrollment-modal-submit'), + apiEndpoint = '/test/api/endpoint/1'; + + view.setSubmitData(apiEndpoint); + + spyOn($, 'ajax').and.callFake(function(opts) { + expect(opts.url).toEqual(apiEndpoint); + expect(opts.method).toEqual('DELETE'); + expect(opts.complete).toBeTruthy(); + + opts.complete({ + status: 204, + responseJSON: {detail: 'success'} + }); + }); + spyOn(view, 'redirectTo'); + + $submitBtn.trigger('click'); + expect($.ajax).toHaveBeenCalled(); + expect(view.redirectTo).toHaveBeenCalledWith(view.dashboardPath); + }); + + it('should redirect to the login page if the request failed with an auth error', function() { + var $submitBtn = $('.js-entitlement-unenrollment-modal-submit'), + apiEndpoint = '/test/api/endpoint/1'; + + view.setSubmitData(apiEndpoint); + + spyOn($, 'ajax').and.callFake(function(opts) { + expect(opts.url).toEqual(apiEndpoint); + expect(opts.method).toEqual('DELETE'); + expect(opts.complete).toBeTruthy(); + + opts.complete({ + status: 401, + responseJSON: {detail: 'Authentication credentials were not provided.'} + }); + }); + spyOn(view, 'redirectTo'); + + $submitBtn.trigger('click'); + expect($.ajax).toHaveBeenCalled(); + expect(view.redirectTo).toHaveBeenCalledWith( + view.signInPath + '?next=' + encodeURIComponent(view.dashboardPath) + ); + }); + + it('should set an error and disable submit if a non-auth error occurs', function() { + var $errorTxt = $('.js-entitlement-unenrollment-modal-error-text'), + $submitBtn = $('.js-entitlement-unenrollment-modal-submit'), + apiEndpoint = '/test/api/endpoint/1'; + + view.setSubmitData(apiEndpoint); + + spyOn($, 'ajax').and.callFake(function(opts) { + expect(opts.url).toEqual(apiEndpoint); + expect(opts.method).toEqual('DELETE'); + expect(opts.complete).toBeTruthy(); + + opts.complete({ + status: 400, + responseJSON: {detail: 'Bad request.'} + }); + }); + spyOn(view, 'redirectTo'); + + expect($submitBtn.prop('disabled')).toBe(false); + expect($errorTxt.html()).toEqual(''); + expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(false); + + $submitBtn.trigger('click'); + + expect($submitBtn.prop('disabled')).toBe(true); + expect($errorTxt.html()).toEqual(view.genericErrorMsg); + expect($errorTxt.hasClass('entitlement-unenrollment-modal-error-text-visible')).toBe(true); + + expect($.ajax).toHaveBeenCalled(); + expect(view.redirectTo).not.toHaveBeenCalled(); + }); + }); + }); + }); +} +); diff --git a/lms/static/js/toggle_login_modal.js b/lms/static/js/toggle_login_modal.js index f9dbf5d9a7..bb38c52639 100644 --- a/lms/static/js/toggle_login_modal.js +++ b/lms/static/js/toggle_login_modal.js @@ -29,7 +29,7 @@ var o = options; $(this).click(function(e) { - $('.modal').hide(); + $('.modal, .js-modal').hide(); var modal_id = $(this).attr('href'); @@ -113,8 +113,12 @@ $(document).ready(function($) { $('a[rel*=leanModal]').each(function() { - $(this).leanModal({top: 120, overlay: 1, closeButton: '.close-modal', position: 'absolute'}); - embed = $($(this).attr('href')).find('iframe'); + var $link = $(this), + closeButton = $link.data('modalCloseButtonSelector') || '.close-modal', + embed; + + $link.leanModal({top: 120, overlay: 1, closeButton: closeButton, position: 'absolute'}); + embed = $($link.attr('href')).find('iframe'); if (embed.length > 0 && embed.attr('src')) { var sep = (embed.attr('src').indexOf('?') > 0) ? '&' : '?'; embed.data('src', embed.attr('src') + sep + 'autoplay=1&rel=0'); diff --git a/lms/static/lms/js/build.js b/lms/static/lms/js/build.js index 21eb317bd2..b994a38848 100644 --- a/lms/static/lms/js/build.js +++ b/lms/static/lms/js/build.js @@ -34,6 +34,7 @@ 'js/header_factory', 'js/learner_dashboard/course_entitlement_factory', 'js/learner_dashboard/unenrollment_factory', + 'js/learner_dashboard/entitlement_unenrollment_factory', 'js/learner_dashboard/program_details_factory', 'js/learner_dashboard/program_list_factory', 'js/student_account/logistration_factory', diff --git a/lms/static/lms/js/spec/main.js b/lms/static/lms/js/spec/main.js index 278531ee91..d81b26dae4 100644 --- a/lms/static/lms/js/spec/main.js +++ b/lms/static/lms/js/spec/main.js @@ -764,6 +764,7 @@ 'js/spec/learner_dashboard/program_details_view_spec.js', 'js/spec/learner_dashboard/program_details_sidebar_view_spec.js', 'js/spec/learner_dashboard/unenroll_view_spec.js', + 'js/spec/learner_dashboard/entitlement_unenrollment_view_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_entitlement_view_spec.js', diff --git a/lms/static/sass/_build-lms-v1.scss b/lms/static/sass/_build-lms-v1.scss index a6afb4dbf4..3081a4b704 100644 --- a/lms/static/sass/_build-lms-v1.scss +++ b/lms/static/sass/_build-lms-v1.scss @@ -39,6 +39,7 @@ // shared - platform @import 'multicourse/home'; @import 'multicourse/dashboard'; +@import 'multicourse/entitlement_dashboard'; @import 'multicourse/account'; @import 'multicourse/courses'; @import 'multicourse/course_about'; diff --git a/lms/static/sass/multicourse/_dashboard.scss b/lms/static/sass/multicourse/_dashboard.scss index 1af3598aed..d66357ff5e 100644 --- a/lms/static/sass/multicourse/_dashboard.scss +++ b/lms/static/sass/multicourse/_dashboard.scss @@ -1534,6 +1534,12 @@ a.fade-cover { #unenroll-modal { margin-top: -60px; + + .modal-form-error { + background: tint($red, 95%); + margin-left: $baseline; + margin-right: $baseline; + } } .reasons_survey { diff --git a/lms/static/sass/multicourse/_entitlement_dashboard.scss b/lms/static/sass/multicourse/_entitlement_dashboard.scss new file mode 100644 index 0000000000..bc6a89f78c --- /dev/null +++ b/lms/static/sass/multicourse/_entitlement_dashboard.scss @@ -0,0 +1,69 @@ +.entitlement-actions-wrapper { + @extend .wrapper-action-more; + + .entitlement-action { + @extend .action; + } + + .entitlement-action-more { + @extend .action-more; + } + + .entitlement-actions-dropdown { + @extend .actions-dropdown; + + .entitlement-actions-dropdown-list { + @extend .actions-dropdown-list; + + .entitlement-actions-item { + @extend .actions-item; + } + } + } +} + +.entitlement-unenrollment-modal { + @extend .modal; + + margin-top: -3*$baseline; + + .entitlement-unenrollment-modal-inner-wrapper { + @extend .inner-wrapper; + + .entitlement-unenrollment-modal-close-btn { + @extend .close-modal; + } + + .entitlement-unenrollment-modal-header { + @extend .unenroll-header; + } + + .entitlement-unenrollment-modal-error-text { + @extend .modal-form-error; + + background: tint($red, 95%); + margin-left: $baseline; + margin-right: $baseline; + } + + .entitlement-unenrollment-modal-error-text-visible { + display: block; + } + + .entitlement-unenrollment-modal-submit-wrapper { + margin-bottom: $baseline*0.6; + position: relative; + z-index: 2; + padding: $baseline ($baseline*2); + + .entitlement-unenrollment-modal-submit { + display: block; + height: auto; + margin: 0 auto; + width: 100%; + white-space: normal; + } + } + } +} + diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 1c9a51f756..a9d179f5e8 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -60,6 +60,12 @@ from student.models import CourseEnrollment isEdx: false }); + <%static:require_module module_name="js/learner_dashboard/entitlement_unenrollment_factory" class_name="EntitlementUnenrollmentFactory"> + EntitlementUnenrollmentFactory({ + dashboardPath: "${reverse('dashboard') | n, js_escaped_string}", + signInPath: "${reverse('signin_user') | n, js_escaped_string}" + }); + % if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'): <%static:require_module module_name="course_search/js/dashboard_search_factory" class_name="DashboardSearchFactory"> DashboardSearchFactory(); @@ -161,6 +167,7 @@ from student.models import CourseEnrollment session_id = enrollment.course_id show_courseware_link = (session_id in show_courseware_links_for) cert_status = cert_statuses.get(session_id) + can_refund_entitlement = entitlement and entitlement.is_entitlement_refundable() can_unenroll = (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False credit_status = credit_statuses.get(session_id) show_email_settings = (session_id in show_email_settings_for) @@ -173,7 +180,7 @@ from student.models import CourseEnrollment show_consent_link = (session_id in consent_required_courses) course_overview = enrollment.course_overview %> - <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' /> + <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_refund_entitlement=can_refund_entitlement, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' /> % endfor @@ -322,3 +329,5 @@ from student.models import CourseEnrollment + +<%include file="dashboard/_dashboard_entitlement_unenrollment_modal.html"/> diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index f6ee89513e..9d5f52fae5 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -1,4 +1,4 @@ -<%page args="course_overview, enrollment, entitlement, entitlement_session, course_card_index, is_unfulfilled_entitlement, is_fulfilled_entitlement, entitlement_available_sessions, entitlement_expiration_date, entitlement_expired_at, show_courseware_link, cert_status, can_unenroll, credit_status, show_email_settings, course_mode_info, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, related_programs, display_course_modes_on_dashboard, show_consent_link, enterprise_customer_name" expression_filter="h"/> +<%page args="course_overview, enrollment, entitlement, entitlement_session, course_card_index, is_unfulfilled_entitlement, is_fulfilled_entitlement, entitlement_available_sessions, entitlement_expiration_date, entitlement_expired_at, show_courseware_link, cert_status, can_refund_entitlement, can_unenroll, credit_status, show_email_settings, course_mode_info, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, related_programs, display_course_modes_on_dashboard, show_consent_link, enterprise_customer_name" expression_filter="h"/> <%! import urllib @@ -237,7 +237,12 @@ from util.course import get_link_for_about_page, get_encoded_course_sharing_utm_ % endif % endif - % if not entitlement: + ## Right now, the gear dropdown for entitlements only contains the 'unenroll' link, so we should hide the + ## gear altogether if the user is unable to unenroll/refund their entitlement. Later, when we add more options + ## to the gear dropdown, we can remove this check. + % if entitlement and can_refund_entitlement: + <%include file='_dashboard_entitlement_actions.html' args='course_overview=course_overview,entitlement=entitlement,dashboard_index=dashboard_index, can_refund_entitlement=can_refund_entitlement'/> + % elif not entitlement:
+ +
+ +
+
diff --git a/lms/templates/dashboard/_dashboard_entitlement_unenrollment_modal.html b/lms/templates/dashboard/_dashboard_entitlement_unenrollment_modal.html new file mode 100644 index 0000000000..b7fd511b4d --- /dev/null +++ b/lms/templates/dashboard/_dashboard_entitlement_unenrollment_modal.html @@ -0,0 +1,32 @@ +<%page expression_filter="h"/> + +<%! +from django.utils.translation import ugettext as _ +%> + + diff --git a/themes/edx.org/lms/templates/dashboard.html b/themes/edx.org/lms/templates/dashboard.html index 27d4e18783..6f92689d18 100644 --- a/themes/edx.org/lms/templates/dashboard.html +++ b/themes/edx.org/lms/templates/dashboard.html @@ -62,6 +62,12 @@ from student.models import CourseEnrollment isEdx: true }); + <%static:require_module module_name="js/learner_dashboard/entitlement_unenrollment_factory" class_name="EntitlementUnenrollmentFactory"> + EntitlementUnenrollmentFactory({ + dashboardPath: "${reverse('dashboard') | n, js_escaped_string}", + signInPath: "${reverse('signin_user') | n, js_escaped_string}" + }); + % if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'): <%static:require_module module_name="course_search/js/dashboard_search_factory" class_name="DashboardSearchFactory"> DashboardSearchFactory(); @@ -157,6 +163,7 @@ from student.models import CourseEnrollment session_id = enrollment.course_id show_courseware_link = (session_id in show_courseware_links_for) cert_status = cert_statuses.get(session_id) + can_refund_entitlement = entitlement and entitlement.is_entitlement_refundable() can_unenroll = (not cert_status) or cert_status.get('can_unenroll') if not unfulfilled_entitlement else False credit_status = credit_statuses.get(session_id) show_email_settings = (session_id in show_email_settings_for) @@ -169,7 +176,7 @@ from student.models import CourseEnrollment show_consent_link = (session_id in consent_required_courses) course_overview = enrollment.course_overview %> - <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' /> + <%include file='dashboard/_dashboard_course_listing.html' args='course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions=entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_refund_entitlement=can_refund_entitlement, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, related_programs=related_programs, display_course_modes_on_dashboard=display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name' /> % endfor % else: @@ -332,3 +339,5 @@ from student.models import CourseEnrollment <%include file='dashboard/_reason_survey.html' /> + +<%include file="dashboard/_dashboard_entitlement_unenrollment_modal.html"/>