diff --git a/common/test/acceptance/tests/lms/test_account_settings.py b/common/test/acceptance/tests/lms/test_account_settings.py index 044dfa90fb..994b9c669d 100644 --- a/common/test/acceptance/tests/lms/test_account_settings.py +++ b/common/test/acceptance/tests/lms/test_account_settings.py @@ -163,7 +163,7 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, AcceptanceTest): 'fields': [ 'Username', 'Full Name', - 'Email Address', + 'Email Address (Sign In)', 'Password', 'Language', 'Country or Region of Residence', @@ -289,7 +289,7 @@ class AccountSettingsPageTest(AccountSettingsTestMixin, AcceptanceTest): self.visit_account_settings_page() self._test_text_field( u'email', - u'Email Address', + u'Email Address (Sign In)', email, u'test@example.com' + XSS_INJECTION, [u'me@here.com', u'you@there.com'], diff --git a/lms/djangoapps/student_account/test/test_views.py b/lms/djangoapps/student_account/test/test_views.py index 87e51d0ac2..37722ff1fc 100644 --- a/lms/djangoapps/student_account/test/test_views.py +++ b/lms/djangoapps/student_account/test/test_views.py @@ -35,6 +35,7 @@ from lms.djangoapps.commerce.tests import factories from lms.djangoapps.commerce.tests.mocks import mock_get_orders from openedx.core.djangoapps.oauth_dispatch.tests import factories as dot_factories from openedx.core.djangoapps.programs.tests.mixins import ProgramsApiConfigMixin +from openedx.core.djangoapps.site_configuration.tests.factories import SiteFactory from openedx.core.djangoapps.site_configuration.tests.mixins import SiteMixin from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme_context from openedx.core.djangoapps.user_api.accounts.api import activate_account, create_account @@ -727,8 +728,10 @@ class AccountSettingsViewTest(ThirdPartyAuthTestMixin, TestCase, ProgramsApiConf MessageMiddleware().process_request(self.request) messages.error(self.request, 'Facebook is already in use.', extra_tags='Auth facebook') - def test_context(self): - + @mock.patch('student_account.views.get_enterprise_learner_data') + def test_context(self, mock_get_enterprise_learner_data): + self.request.site = SiteFactory.create() + mock_get_enterprise_learner_data.return_value = [] context = account_settings_context(self.request) user_accounts_api_url = reverse("accounts_api", kwargs={'username': self.user.username}) @@ -751,6 +754,59 @@ class AccountSettingsViewTest(ThirdPartyAuthTestMixin, TestCase, ProgramsApiConf self.assertEqual(context['auth']['providers'][0]['name'], 'Facebook') self.assertEqual(context['auth']['providers'][1]['name'], 'Google') + self.assertEqual(context['sync_learner_profile_data'], False) + self.assertEqual(context['edx_support_url'], settings.SUPPORT_SITE_LINK) + self.assertEqual(context['enterprise_name'], None) + self.assertEqual( + context['enterprise_readonly_account_fields'], {'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS} + ) + + @mock.patch('student_account.views.get_enterprise_learner_data') + @mock.patch('student_account.views.third_party_auth.provider.Registry.get') + def test_context_for_enterprise_learner( + self, mock_get_auth_provider, mock_get_enterprise_learner_data + ): + dummy_enterprise_customer = { + 'uuid': 'real-ent-uuid', + 'name': 'Dummy Enterprise', + 'identity_provider': 'saml-ubc' + } + mock_get_enterprise_learner_data.return_value = [ + {'enterprise_customer': dummy_enterprise_customer} + ] + self.request.site = SiteFactory.create() + mock_get_auth_provider.return_value.sync_learner_profile_data = True + context = account_settings_context(self.request) + + user_accounts_api_url = reverse("accounts_api", kwargs={'username': self.user.username}) + self.assertEqual(context['user_accounts_api_url'], user_accounts_api_url) + + user_preferences_api_url = reverse('preferences_api', kwargs={'username': self.user.username}) + self.assertEqual(context['user_preferences_api_url'], user_preferences_api_url) + + for attribute in self.FIELDS: + self.assertIn(attribute, context['fields']) + + self.assertEqual( + context['user_accounts_api_url'], reverse("accounts_api", kwargs={'username': self.user.username}) + ) + self.assertEqual( + context['user_preferences_api_url'], reverse('preferences_api', kwargs={'username': self.user.username}) + ) + + self.assertEqual(context['duplicate_provider'], 'facebook') + self.assertEqual(context['auth']['providers'][0]['name'], 'Facebook') + self.assertEqual(context['auth']['providers'][1]['name'], 'Google') + + self.assertEqual( + context['sync_learner_profile_data'], mock_get_auth_provider.return_value.sync_learner_profile_data + ) + self.assertEqual(context['edx_support_url'], settings.SUPPORT_SITE_LINK) + self.assertEqual(context['enterprise_name'], dummy_enterprise_customer['name']) + self.assertEqual( + context['enterprise_readonly_account_fields'], {'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS} + ) + def test_view(self): """ Test that all fields are visible diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index 3659c60c8d..2358cf8edc 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -40,7 +40,7 @@ from openedx.core.djangoapps.user_api.errors import ( ) from openedx.core.lib.edx_api_utils import get_edx_api_data from openedx.core.lib.time_zone_utils import TIME_ZONE_CHOICES -from openedx.features.enterprise_support.api import enterprise_customer_for_request +from openedx.features.enterprise_support.api import enterprise_customer_for_request, get_enterprise_learner_data from student.helpers import destroy_oauth_tokens, get_next_url_for_login_page from student.models import UserProfile from student.views import register_user as old_register_view @@ -567,6 +567,22 @@ def account_settings_context(request): 'order_history': user_orders } + enterprise_customer_name = None + sync_learner_profile_data = False + enterprise_learner_data = get_enterprise_learner_data(site=request.site, user=request.user) + if enterprise_learner_data: + enterprise_customer_name = enterprise_learner_data[0]['enterprise_customer']['name'] + enterprise_idp = enterprise_learner_data[0]['enterprise_customer']['identity_provider'] + identity_provider = third_party_auth.provider.Registry.get(provider_id=enterprise_idp) + sync_learner_profile_data = identity_provider.sync_learner_profile_data if identity_provider else False + + context['sync_learner_profile_data'] = sync_learner_profile_data + context['edx_support_url'] = configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK) + context['enterprise_name'] = enterprise_customer_name + context['enterprise_readonly_account_fields'] = { + 'fields': settings.ENTERPRISE_READONLY_ACCOUNT_FIELDS + } + if third_party_auth.is_enabled(): # If the account on the third party provider is already connected with another edX account, # we display a message to the user. diff --git a/lms/envs/common.py b/lms/envs/common.py index a1a562e2aa..b5ed8435ca 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -3395,6 +3395,12 @@ ENTERPRISE_EXCLUDED_REGISTRATION_FIELDS = { 'year_of_birth', 'mailing_address', } +ENTERPRISE_READONLY_ACCOUNT_FIELDS = [ + 'username', + 'name', + 'email', + 'country', +] ENTERPRISE_CUSTOMER_COOKIE_NAME = 'enterprise_customer_uuid' BASE_COOKIE_DOMAIN = 'localhost' diff --git a/lms/static/js/spec/student_account/account_settings_factory_spec.js b/lms/static/js/spec/student_account/account_settings_factory_spec.js index 4b098bf8db..61f4c2a65f 100644 --- a/lms/static/js/spec/student_account/account_settings_factory_spec.js +++ b/lms/static/js/spec/student_account/account_settings_factory_spec.js @@ -148,4 +148,183 @@ define(['backbone', }); }); }); + + describe('edx.user.AccountSettingsFactory', function() { + var createEnterpriseLearnerAccountSettingsPage = function() { + var context = AccountSettingsPage( + Helpers.FIELDS_DATA, + [], + Helpers.AUTH_DATA, + Helpers.PASSWORD_RESET_SUPPORT_LINK, + Helpers.USER_ACCOUNTS_API_URL, + Helpers.USER_PREFERENCES_API_URL, + 1, + Helpers.PLATFORM_NAME, + Helpers.CONTACT_EMAIL, + true, + '', + + Helpers.SYNC_LEARNER_PROFILE_DATA, + Helpers.ENTERPRISE_NAME, + Helpers.ENTERPRISE_READ_ONLY_ACCOUNT_FIELDS, + Helpers.EDX_SUPPORT_URL + ); + return context.accountSettingsView; + }; + + var requests; + var accountInfoTab = { + BASIC_ACCOUNT_INFORMATION: 0, + ADDITIONAL_INFORMATION: 1 + }; + var basicAccountInfoFields = { + USERNAME: 0, + FULL_NAME: 1, + EMAIL_ADDRESS: 2, + PASSWORD: 3, + LANGUAGE: 4, + COUNTRY: 5, + TIMEZONE: 6 + }; + var additionalInfoFields = { + EDUCATION: 0, + GENDER: 1, + YEAR_OF_BIRTH: 2, + PREFERRED_LANGUAGE: 3 + }; + + beforeEach(function() { + setFixtures('
'); + }); + + it('shows loading error when UserAccountModel fails to load for enterprise learners', function() { + var accountSettingsView, request; + requests = AjaxHelpers.requests(this); + + accountSettingsView = createEnterpriseLearnerAccountSettingsPage(); + + Helpers.expectLoadingErrorIsVisible(accountSettingsView, false); + + request = requests[0]; + expect(request.method).toBe('GET'); + expect(request.url).toBe(Helpers.USER_ACCOUNTS_API_URL); + + AjaxHelpers.respondWithError(requests, 500); + Helpers.expectLoadingErrorIsVisible(accountSettingsView, true); + }); + + it('shows loading error when UserPreferencesModel fails to load for enterprise learners', function() { + var accountSettingsView, request; + requests = AjaxHelpers.requests(this); + + accountSettingsView = createEnterpriseLearnerAccountSettingsPage(); + + Helpers.expectLoadingErrorIsVisible(accountSettingsView, false); + + request = requests[0]; + expect(request.method).toBe('GET'); + expect(request.url).toBe(Helpers.USER_ACCOUNTS_API_URL); + + AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData()); + Helpers.expectLoadingErrorIsVisible(accountSettingsView, false); + + request = requests[1]; + expect(request.method).toBe('GET'); + expect(request.url).toBe('/user_api/v1/preferences/time_zones/?country_code=1'); + AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE); + + request = requests[2]; + expect(request.method).toBe('GET'); + expect(request.url).toBe(Helpers.USER_PREFERENCES_API_URL); + + AjaxHelpers.respondWithError(requests, 500); + Helpers.expectLoadingErrorIsVisible(accountSettingsView, true); + }); + + it('renders fields after the models are successfully fetched for enterprise learners', function() { + var accountSettingsView; + requests = AjaxHelpers.requests(this); + + accountSettingsView = createEnterpriseLearnerAccountSettingsPage(); + + Helpers.expectLoadingErrorIsVisible(accountSettingsView, false); + + AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData()); + AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE); + AjaxHelpers.respondWithJson(requests, Helpers.createUserPreferencesData()); + + accountSettingsView.render(); + + Helpers.expectLoadingErrorIsVisible(accountSettingsView, false); + Helpers.expectSettingsSectionsAndFieldsToBeRenderedWithMessage(accountSettingsView); + }); + + it('expects all fields to behave correctly for enterprise learners', function() { + var accountSettingsView, i, view, sectionsData, textFields, dropdownFields; + requests = AjaxHelpers.requests(this); + + accountSettingsView = createEnterpriseLearnerAccountSettingsPage(); + + AjaxHelpers.respondWithJson(requests, Helpers.createAccountSettingsData()); + AjaxHelpers.respondWithJson(requests, Helpers.TIME_ZONE_RESPONSE); + AjaxHelpers.respondWithJson(requests, Helpers.createUserPreferencesData()); + AjaxHelpers.respondWithJson(requests, {}); // Page viewed analytics event + + sectionsData = accountSettingsView.options.tabSections.aboutTabSections; + + expect(sectionsData[accountInfoTab.BASIC_ACCOUNT_INFORMATION].fields.length).toBe(7); + + // Verify that username, name and email fields are readonly + textFields = [ + sectionsData[accountInfoTab.BASIC_ACCOUNT_INFORMATION].fields[basicAccountInfoFields.USERNAME], + sectionsData[accountInfoTab.BASIC_ACCOUNT_INFORMATION].fields[basicAccountInfoFields.FULL_NAME], + sectionsData[accountInfoTab.BASIC_ACCOUNT_INFORMATION].fields[basicAccountInfoFields.EMAIL_ADDRESS] + ]; + for (i = 0; i < textFields.length; i++) { + view = textFields[i].view; + + FieldViewsSpecHelpers.verifyReadonlyTextField(view, { + title: view.options.title, + valueAttribute: view.options.valueAttribute, + helpMessage: view.options.helpMessage, + validValue: 'My Name', + defaultValue: '' + }, requests); + } + + // Verify un-editable country dropdown field + view = sectionsData[ + accountInfoTab.BASIC_ACCOUNT_INFORMATION + ].fields[basicAccountInfoFields.COUNTRY].view; + + FieldViewsSpecHelpers.verifyReadonlyDropDownField(view, { + title: view.options.title, + valueAttribute: view.options.valueAttribute, + helpMessage: '', + validValue: Helpers.FIELD_OPTIONS[1][0], + editable: 'never', + defaultValue: null + }); + + expect(sectionsData[accountInfoTab.ADDITIONAL_INFORMATION].fields.length).toBe(4); + dropdownFields = [ + sectionsData[accountInfoTab.ADDITIONAL_INFORMATION].fields[additionalInfoFields.EDUCATION], + sectionsData[accountInfoTab.ADDITIONAL_INFORMATION].fields[additionalInfoFields.GENDER], + sectionsData[accountInfoTab.ADDITIONAL_INFORMATION].fields[additionalInfoFields.YEAR_OF_BIRTH] + ]; + _.each(dropdownFields, function(field) { + view = field.view; + FieldViewsSpecHelpers.verifyDropDownField(view, { + title: view.options.title, + valueAttribute: view.options.valueAttribute, + helpMessage: '', + validValue: Helpers.FIELD_OPTIONS[1][0], // dummy option for dropdown field + invalidValue1: Helpers.FIELD_OPTIONS[2][0], // dummy option for dropdown field + invalidValue2: Helpers.FIELD_OPTIONS[3][0], // dummy option for dropdown field + validationError: 'Nope, this will not do!', + defaultValue: null + }, requests); + }); + }); + }); }); diff --git a/lms/static/js/spec/student_account/account_settings_view_spec.js b/lms/static/js/spec/student_account/account_settings_view_spec.js index d97eebc341..046570c99f 100644 --- a/lms/static/js/spec/student_account/account_settings_view_spec.js +++ b/lms/static/js/spec/student_account/account_settings_view_spec.js @@ -20,6 +20,9 @@ define(['backbone', var aboutSectionsData = [ { title: 'Basic Account Information', + messageType: 'info', + message: 'Your profile settings are managed by Test Enterprise. ' + + 'Contact your administrator or edX Support for help.', fields: [ { view: new FieldViews.ReadonlyFieldView({ diff --git a/lms/static/js/spec/student_account/helpers.js b/lms/static/js/spec/student_account/helpers.js index a03ac6aaa9..699629bae5 100644 --- a/lms/static/js/spec/student_account/helpers.js +++ b/lms/static/js/spec/student_account/helpers.js @@ -10,6 +10,14 @@ define(['underscore'], function(_) { var PASSWORD_RESET_SUPPORT_LINK = 'https://support.edx.org/hc/en-us/articles/206212088-What-if-I-did-not-receive-a-password-reset-message-'; // eslint-disable-line max-len var PLATFORM_NAME = 'edX'; var CONTACT_EMAIL = 'info@example.com'; + + var SYNC_LEARNER_PROFILE_DATA = true; + var ENTERPRISE_NAME = 'Test Enterprise'; + var ENTERPRISE_READ_ONLY_ACCOUNT_FIELDS = { + fields: ['username', 'name', 'email', 'country'] + }; + var EDX_SUPPORT_URL = 'https://support.edx.org/'; + var PROFILE_IMAGE = { image_url_large: '/media/profile-images/image.jpg', has_image: true @@ -167,6 +175,36 @@ define(['underscore'], function(_) { }); }; + var expectSettingsSectionsAndFieldsToBeRenderedWithMessage = function(accountSettingsView, fieldsAreRendered) { + var sectionFieldElements; + var sectionsData = accountSettingsView.options.tabSections.aboutTabSections; + + var sectionElements = accountSettingsView.$('#aboutTabSections-tabpanel .section'); + expect(sectionElements.length).toBe(sectionsData.length); + + _.each(sectionElements, function(sectionElement, sectionIndex) { + expect($(sectionElement).find('.section-header').text() + .trim()).toBe(sectionsData[sectionIndex].title); + + if (!_.isUndefined(sectionsData[sectionIndex].message)) { + expect($(sectionElement).find('.account-settings-section-message span').html() + .trim()).toBe(String(sectionsData[sectionIndex].message)); + } + + sectionFieldElements = $(sectionElement).find('.u-field'); + + if (fieldsAreRendered === false) { + expect(sectionFieldElements.length).toBe(0); + } else { + expect(sectionFieldElements.length).toBe(sectionsData[sectionIndex].fields.length); + + _.each(sectionFieldElements, function(sectionFieldElement, fieldIndex) { + expectElementContainsField(sectionFieldElement, sectionsData[sectionIndex].fields[fieldIndex]); + }); + } + }); + }; + var expectSettingsSectionsButNotFieldsToBeRendered = function(accountSettingsView) { expectSettingsSectionsAndFieldsToBeRendered(accountSettingsView, false); }; @@ -181,6 +219,12 @@ define(['underscore'], function(_) { PASSWORD_RESET_SUPPORT_LINK: PASSWORD_RESET_SUPPORT_LINK, PLATFORM_NAME: PLATFORM_NAME, CONTACT_EMAIL: CONTACT_EMAIL, + + SYNC_LEARNER_PROFILE_DATA: SYNC_LEARNER_PROFILE_DATA, + ENTERPRISE_NAME: ENTERPRISE_NAME, + ENTERPRISE_READ_ONLY_ACCOUNT_FIELDS: ENTERPRISE_READ_ONLY_ACCOUNT_FIELDS, + EDX_SUPPORT_URL: EDX_SUPPORT_URL, + PROFILE_IMAGE: PROFILE_IMAGE, FIELD_OPTIONS: FIELD_OPTIONS, TIME_ZONE_RESPONSE: TIME_ZONE_RESPONSE, @@ -195,6 +239,7 @@ define(['underscore'], function(_) { expectLoadingErrorIsVisible: expectLoadingErrorIsVisible, expectElementContainsField: expectElementContainsField, expectSettingsSectionsButNotFieldsToBeRendered: expectSettingsSectionsButNotFieldsToBeRendered, - expectSettingsSectionsAndFieldsToBeRendered: expectSettingsSectionsAndFieldsToBeRendered + expectSettingsSectionsAndFieldsToBeRendered: expectSettingsSectionsAndFieldsToBeRendered, + expectSettingsSectionsAndFieldsToBeRenderedWithMessage: expectSettingsSectionsAndFieldsToBeRenderedWithMessage }; }); diff --git a/lms/static/js/spec/views/fields_helpers.js b/lms/static/js/spec/views/fields_helpers.js index c27849726f..d64f293adf 100644 --- a/lms/static/js/spec/views/fields_helpers.js +++ b/lms/static/js/spec/views/fields_helpers.js @@ -244,6 +244,28 @@ define(['backbone', } }; + var verifyReadonlyField = function(view, data) { + if (data.editable === 'toggle') { + expect(view.el).toHaveClass('mode-placeholder'); + expectTitleToContain(view, data.title); + expectMessageContains(view, view.indicators.canEdit); + view.$el.click(); + } else { + expectTitleAndMessageToContain(view, data.title, data.helpMessage); + } + expect(view.el).toHaveClass('u-field-readonly'); + + if (view.fieldValue() !== null) { + expect(view.fieldValue()).not.toContain(data.validValue); + } + }; + + var verifyUneditableDropdownField = function(view, data) { + expectTitleAndMessageToContain(view, data.title, data.helpMessage); + expect(view.el).toHaveClass('u-field-dropdown'); + expect(view.el).toHaveClass('editable-never'); + }; + var verifyTextField = function(view, data, requests) { verifyEditableField(view, _.extend({ valueSelector: '.u-field-value', @@ -252,6 +274,12 @@ define(['backbone', requests); }; + var verifyReadonlyTextField = function(view, data) { + verifyReadonlyField(view, _.extend({ + valueSelector: '.u-field-value' + }, data)); + }; + var verifyDropDownField = function(view, data, requests) { verifyEditableField(view, _.extend({ valueSelector: '.u-field-value', @@ -260,6 +288,12 @@ define(['backbone', ), requests); }; + var verifyReadonlyDropDownField = function(view, data) { + verifyUneditableDropdownField(view, _.extend({ + valueSelector: '.editable-never' + }, data)); + }; + return { SELECT_OPTIONS: SELECT_OPTIONS, UserAccountModel: UserAccountModel, @@ -274,7 +308,9 @@ define(['backbone', verifySuccessMessageReset: verifySuccessMessageReset, verifyEditableField: verifyEditableField, verifyTextField: verifyTextField, + verifyReadonlyTextField: verifyReadonlyTextField, verifyDropDownField: verifyDropDownField, + verifyReadonlyDropDownField: verifyReadonlyDropDownField, verifyPersistence: verifyPersistence }; }); diff --git a/lms/static/js/student_account/views/account_section_view.js b/lms/static/js/student_account/views/account_section_view.js index 06339b750b..6c1b00a16c 100644 --- a/lms/static/js/student_account/views/account_section_view.js +++ b/lms/static/js/student_account/views/account_section_view.js @@ -5,8 +5,9 @@ 'jquery', 'underscore', 'backbone', + 'edx-ui-toolkit/js/utils/html-utils', 'text!templates/student_account/account_settings_section.underscore' - ], function(gettext, $, _, Backbone, sectionTemplate) { + ], function(gettext, $, _, Backbone, HtmlUtils, sectionTemplate) { var AccountSectionView = Backbone.View.extend({ initialize: function(options) { @@ -16,6 +17,7 @@ render: function() { this.$el.html(_.template(sectionTemplate)({ + HtmlUtils: HtmlUtils, sections: this.options.sections, tabName: this.options.tabName, tabLabel: this.options.tabLabel diff --git a/lms/static/js/student_account/views/account_settings_factory.js b/lms/static/js/student_account/views/account_settings_factory.js index bb03663999..60ca4be0ef 100644 --- a/lms/static/js/student_account/views/account_settings_factory.js +++ b/lms/static/js/student_account/views/account_settings_factory.js @@ -21,12 +21,19 @@ platformName, contactEmail, allowEmailChange, - socialPlatforms + socialPlatforms, + + syncLearnerProfileData, + enterpriseName, + enterpriseReadonlyAccountFields, + edxSupportUrl ) { var $accountSettingsElement, userAccountModel, userPreferencesModel, aboutSectionsData, accountsSectionData, ordersSectionData, accountSettingsView, showAccountSettingsPage, showLoadingError, orderNumber, getUserField, userFields, timeZoneDropdownField, countryDropdownField, - emailFieldView, socialFields, platformData; + emailFieldView, socialFields, platformData, + aboutSectionMessageType, aboutSectionMessage, fullnameFieldView, countryFieldView, + fullNameFieldData, emailFieldData, countryFieldData; $accountSettingsElement = $('.wrapper-account-settings'); @@ -36,30 +43,80 @@ userPreferencesModel = new UserPreferencesModel(); userPreferencesModel.url = userPreferencesApiUrl; - if (allowEmailChange) { - emailFieldView = { - view: new AccountSettingsFieldViews.EmailFieldView({ - model: userAccountModel, - title: gettext('Email Address'), - valueAttribute: 'email', - helpMessage: StringUtils.interpolate( - gettext('The email address you use to sign in. Communications from {platform_name} and your courses are sent to this address.'), // eslint-disable-line max-len - {platform_name: platformName} + if (syncLearnerProfileData && enterpriseName) { + aboutSectionMessageType = 'info'; + aboutSectionMessage = HtmlUtils.interpolateHtml( + gettext('Your profile settings are managed by {enterprise_name}. Contact your administrator or {link_start}edX Support{link_end} for help.'), // eslint-disable-line max-len + { + enterprise_name: enterpriseName, + link_start: HtmlUtils.HTML( + StringUtils.interpolate( + '', { + edx_support_url: edxSupportUrl + } + ) ), - persistChanges: true - }) + link_end: HtmlUtils.HTML('') + } + ); + } + + emailFieldData = { + model: userAccountModel, + title: gettext('Email Address (Sign In)'), + valueAttribute: 'email', + helpMessage: StringUtils.interpolate( + gettext('You receive messages from {platform_name} and course teams at this address.'), // eslint-disable-line max-len + {platform_name: platformName} + ), + persistChanges: true + }; + if (!allowEmailChange || (syncLearnerProfileData && enterpriseReadonlyAccountFields.fields.indexOf('email') !== -1)) { // eslint-disable-line max-len + emailFieldView = { + view: new AccountSettingsFieldViews.ReadonlyFieldView(emailFieldData) }; } else { emailFieldView = { - view: new AccountSettingsFieldViews.ReadonlyFieldView({ - model: userAccountModel, - title: gettext('Email Address'), - valueAttribute: 'email', - helpMessage: StringUtils.interpolate( - gettext('The email address you use to sign in. Communications from {platform_name} and your courses are sent to this address. To change the email address, please contact {contact_email}.'), // eslint-disable-line max-len - {platform_name: platformName, contact_email: contactEmail} - ) - }) + view: new AccountSettingsFieldViews.EmailFieldView(emailFieldData) + }; + } + + fullNameFieldData = { + model: userAccountModel, + title: gettext('Full Name'), + valueAttribute: 'name', + helpMessage: gettext('The name that is used for ID verification and that appears on your certificates.'), // eslint-disable-line max-len, + persistChanges: true + }; + if (syncLearnerProfileData && enterpriseReadonlyAccountFields.fields.indexOf('name') !== -1) { + fullnameFieldView = { + view: new AccountSettingsFieldViews.ReadonlyFieldView(fullNameFieldData) + }; + } else { + fullnameFieldView = { + view: new AccountSettingsFieldViews.TextFieldView(fullNameFieldData) + }; + } + + countryFieldData = { + model: userAccountModel, + required: true, + title: gettext('Country or Region of Residence'), + valueAttribute: 'country', + options: fieldsData.country.options, + persistChanges: true, + helpMessage: gettext('The country or region where you live.') + }; + if (syncLearnerProfileData && enterpriseReadonlyAccountFields.fields.indexOf('country') !== -1) { + countryFieldData.editable = 'never'; + countryFieldView = { + view: new AccountSettingsFieldViews.DropdownFieldView( + countryFieldData + ) + }; + } else { + countryFieldView = { + view: new AccountSettingsFieldViews.DropdownFieldView(countryFieldData) }; } @@ -67,6 +124,10 @@ { title: gettext('Basic Account Information'), subtitle: gettext('These settings include basic information about your account.'), + + messageType: aboutSectionMessageType, + message: aboutSectionMessage, + fields: [ { view: new AccountSettingsFieldViews.ReadonlyFieldView({ @@ -74,26 +135,12 @@ title: gettext('Username'), valueAttribute: 'username', helpMessage: StringUtils.interpolate( - gettext('The name that identifies you throughout {platform_name}. You cannot change your username.'), // eslint-disable-line max-len + gettext('The name that identifies you on {platform_name}. You cannot change your username.'), // eslint-disable-line max-len {platform_name: platformName} ) }) }, - { - view: new AccountSettingsFieldViews.TextFieldView({ - model: userAccountModel, - title: gettext('Full Name'), - valueAttribute: 'name', - helpMessage: HtmlUtils.interpolateHtml( - gettext('The name that is used for ID verification and that appears on your certificates. Other learners see your full name if you have selected {bold_start}Full Profile{bold_end} for profile visibility. Make sure to enter your name exactly as it appears on your photo ID, including any non-Roman characters.'), // eslint-disable-line max-len - { - bold_start: HtmlUtils.HTML(''), - bold_end: HtmlUtils.HTML('') - } - ), - persistChanges: true - }) - }, + fullnameFieldView, emailFieldView, { view: new AccountSettingsFieldViews.PasswordFieldView({ @@ -105,10 +152,7 @@ passwordResetSupportUrl: passwordResetSupportUrl, linkTitle: gettext('Reset Your Password'), linkHref: fieldsData.password.url, - helpMessage: StringUtils.interpolate( - gettext('When you select "Reset Your Password", a message will be sent to the email address for your {platform_name} account. Click the link in the message to reset your password.'), // eslint-disable-line max-len - {platform_name: platformName} - ) + helpMessage: gettext('Check your email account for instructions to reset your password.') // eslint-disable-line max-len }) }, { @@ -126,17 +170,7 @@ persistChanges: true }) }, - { - view: new AccountSettingsFieldViews.DropdownFieldView({ - model: userAccountModel, - required: true, - title: gettext('Country or Region of Residence'), - valueAttribute: 'country', - options: fieldsData.country.options, - persistChanges: true, - helpMessage: gettext('The country or region where you live.') - }) - }, + countryFieldView, { view: new AccountSettingsFieldViews.TimeZoneFieldView({ model: userPreferencesModel, diff --git a/lms/static/sass/views/_account-settings.scss b/lms/static/sass/views/_account-settings.scss index fb67fd7afa..457fa67c47 100644 --- a/lms/static/sass/views/_account-settings.scss +++ b/lms/static/sass/views/_account-settings.scss @@ -78,7 +78,7 @@ @include appearance(none); - display:block; + display: block; padding: ($baseline/4); &:hover, @@ -101,7 +101,7 @@ border-bottom: none; .account-nav-link { - border-bottom: 4px solid theme-color("light"); + border-bottom: 4px solid theme-color("light"); } } } @@ -130,6 +130,59 @@ padding-bottom: 10px; } + .account-settings-section-message { + font-size: 16px; + line-height: 22px; + margin-top: 15px; + margin-bottom: 30px; + + .alert-message { + color: #292b2c; + font-family: $font-family-sans-serif; + position: relative; + padding: 10px 10px 10px 35px; + border: 1px solid transparent; + border-radius: 0; + box-shadow: none; + margin-bottom: 8px; + + & > .fa { + position: absolute; + left: 11px; + top: 13px; + font-size: 16px; + } + + span { + display: block; + + a { + text-decoration: underline; + } + } + } + + .success { + background-color: #ecfaec; + border-color: #b9edb9; + } + + .info { + background-color: #d8edf8; + border-color: #bbdff2; + } + + .warning { + background-color: #fcf8e3; + border-color: #faebcc; + } + + .error { + background-color: #f2dede; + border-color: #ebccd1; + } + } + .account-settings-section-body { .u-field { border-bottom: 2px solid $m-gray-l4; @@ -176,6 +229,7 @@ font-size: 1rem; line-height: 1; color: $dark-gray; + white-space: nowrap; } .field-input { @@ -219,8 +273,7 @@ padding-top: $baseline; padding-bottom: $baseline; line-height: normal; - flex-direction: row; - flex-wrap: wrap; + flex-flow: row wrap; span { padding: $baseline; @@ -301,7 +354,9 @@ font-weight: $font-semibold; padding: 0; - &:focus, &:hover, &:active { + &:focus, + &:hover, + &:active { background-color: transparent; color: $m-blue-d3; border: none; @@ -372,10 +427,10 @@ display: flex; flex-wrap: nowrap; - u-field-order-number, - u-field-order-date, - u-field-order-price, - u-field-order-link, { + .u-field-order-number, + .u-field-order-date, + .u-field-order-price, + .u-field-order-link { width: auto; float: none; flex-grow: 1; @@ -388,8 +443,43 @@ } } - .u-field-readonly .u-field-value { + .u-field { + &.u-field-dropdown, &.editable-never &.mode-display { + .u-field-value { + margin-bottom: ($baseline); + + .u-field-title { + font-size: 16px; + line-height: 22px; + margin-bottom: 18px; + } + + .u-field-value-readonly { + font-size: 22px; + color: #636c72; + line-height: 30px; + white-space: nowrap; + } + } + } + } + + .u-field-readonly .u-field-title { + font-size: 16px; + color: #636c72; + line-height: 22px; padding-top: ($baseline/2); + padding-bottom: 0; + margin-bottom: 8px !important; + } + + .u-field-readonly .u-field-value { + font-size: 22px; + color: #636c72; + line-height: 30px; + padding-top: 8px; + padding-bottom: ($baseline); + white-space: nowrap; } .u-field-orderHistory { @@ -402,7 +492,8 @@ border-bottom: 1px solid $m-gray-l4; } - &:hover, &:focus { + &:hover, + &:focus { background-color: $light-gray4; } } @@ -413,7 +504,8 @@ margin-bottom: 0; padding-bottom: 0; - &:hover, &:focus { + &:hover, + &:focus { background-color: transparent; } diff --git a/lms/templates/student_account/account_settings.html b/lms/templates/student_account/account_settings.html index de271d908c..cc21dbcfb1 100644 --- a/lms/templates/student_account/account_settings.html +++ b/lms/templates/student_account/account_settings.html @@ -38,7 +38,12 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str platformName = '${ static.get_platform_name() | n, js_escaped_string }', contactEmail = '${ static.get_contact_email_address() | n, js_escaped_string }', allowEmailChange = ${ bool(settings.FEATURES['ALLOW_EMAIL_ADDRESS_CHANGE']) | n, dump_js_escaped_json }, - socialPlatforms = ${ settings.SOCIAL_PLATFORMS | n, dump_js_escaped_json }; + socialPlatforms = ${ settings.SOCIAL_PLATFORMS | n, dump_js_escaped_json }, + + syncLearnerProfileData = ${ bool(sync_learner_profile_data) | n, dump_js_escaped_json }, + enterpriseName = '${ enterprise_name | n, js_escaped_string }', + enterpriseReadonlyAccountFields = ${ enterprise_readonly_account_fields | n, dump_js_escaped_json }, + edxSupportUrl = '${ edx_support_url | n, js_escaped_string }'; AccountSettingsFactory( fieldsData, @@ -51,7 +56,12 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str platformName, contactEmail, allowEmailChange, - socialPlatforms + socialPlatforms, + + syncLearnerProfileData, + enterpriseName, + enterpriseReadonlyAccountFields, + edxSupportUrl ); %static:require_module> %block> diff --git a/lms/templates/student_account/account_settings_section.underscore b/lms/templates/student_account/account_settings_section.underscore index 11bbbfef82..02d6dc78ad 100644 --- a/lms/templates/student_account/account_settings_section.underscore +++ b/lms/templates/student_account/account_settings_section.underscore @@ -4,9 +4,19 @@ <% _.each(sections, function(section) { %><%- section.subtitle %>
<% } %> + + <% if (section.message) { %> + + <% } %> +