From 13ac08a41f90e09f25a792874640020f72a0ab15 Mon Sep 17 00:00:00 2001 From: Uman Shahzad Date: Thu, 25 May 2017 19:35:05 -0400 Subject: [PATCH] Allow configurable password reset support link. Defaults to SUPPORT_SITE_LINK intentionally. Also fixed surrounding quality tests, and overall quality improvements. --- cms/envs/common.py | 1 + lms/djangoapps/student_account/views.py | 6 + lms/envs/aws.py | 4 +- lms/envs/bok_choy.env.json | 1 + lms/envs/common.py | 1 + lms/envs/test.py | 1 + .../account_settings_factory_spec.js | 49 +-- lms/static/js/spec/student_account/helpers.js | 102 +++-- .../js/student_account/views/AccessView.js | 2 + .../js/student_account/views/LoginView.js | 410 +++++++++--------- .../views/account_settings_factory.js | 14 +- .../views/account_settings_fields.js | 25 +- .../student_account/account_settings.html | 1 + 13 files changed, 334 insertions(+), 283 deletions(-) diff --git a/cms/envs/common.py b/cms/envs/common.py index 96287aae80..aa5a50feb1 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -94,6 +94,7 @@ from lms.envs.common import ( HELP_TOKENS_BOOKS, SUPPORT_SITE_LINK, + PASSWORD_RESET_SUPPORT_LINK, ACTIVATION_EMAIL_SUPPORT_LINK, CONTACT_EMAIL, diff --git a/lms/djangoapps/student_account/views.py b/lms/djangoapps/student_account/views.py index 3a835a098f..23fbaf45d0 100644 --- a/lms/djangoapps/student_account/views.py +++ b/lms/djangoapps/student_account/views.py @@ -133,6 +133,9 @@ def login_and_registration_form(request, initial_mode="login"): 'third_party_auth_hint': third_party_auth_hint or '', 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), 'support_link': configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), + 'password_reset_support_link': configuration_helpers.get_value( + 'PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK + ) or settings.SUPPORT_SITE_LINK, 'account_activation_messages': account_activation_messages, # Include form descriptions retrieved from the user API. @@ -560,6 +563,9 @@ def account_settings_context(request): } }, 'platform_name': configuration_helpers.get_value('PLATFORM_NAME', settings.PLATFORM_NAME), + 'password_reset_support_link': configuration_helpers.get_value( + 'PASSWORD_RESET_SUPPORT_LINK', settings.PASSWORD_RESET_SUPPORT_LINK + ) or settings.SUPPORT_SITE_LINK, 'user_accounts_api_url': reverse("accounts_api", kwargs={'username': user.username}), 'user_preferences_api_url': reverse('preferences_api', kwargs={'username': user.username}), 'disable_courseware_js': True, diff --git a/lms/envs/aws.py b/lms/envs/aws.py index 8505b58a1d..3e36051340 100644 --- a/lms/envs/aws.py +++ b/lms/envs/aws.py @@ -309,9 +309,11 @@ ENABLE_COMPREHENSIVE_THEMING = ENV_TOKENS.get('ENABLE_COMPREHENSIVE_THEMING', EN # Marketing link overrides MKTG_URL_LINK_MAP.update(ENV_TOKENS.get('MKTG_URL_LINK_MAP', {})) +# Intentional defaults. SUPPORT_SITE_LINK = ENV_TOKENS.get('SUPPORT_SITE_LINK', SUPPORT_SITE_LINK) +PASSWORD_RESET_SUPPORT_LINK = ENV_TOKENS.get('PASSWORD_RESET_SUPPORT_LINK', SUPPORT_SITE_LINK) ACTIVATION_EMAIL_SUPPORT_LINK = ENV_TOKENS.get( - 'ACTIVATION_EMAIL_SUPPORT_LINK', SUPPORT_SITE_LINK # Intentional default. + 'ACTIVATION_EMAIL_SUPPORT_LINK', SUPPORT_SITE_LINK ) # Mobile store URL overrides diff --git a/lms/envs/bok_choy.env.json b/lms/envs/bok_choy.env.json index 4bec01ab1f..f28db03fdc 100644 --- a/lms/envs/bok_choy.env.json +++ b/lms/envs/bok_choy.env.json @@ -137,6 +137,7 @@ "SITE_NAME": "localhost:8003", "STATIC_URL_BASE": "/static/", "SUPPORT_SITE_LINK": "https://support.example.com", + "PASSWORD_RESET_SUPPORT_LINK": "https://support.example.com/password-reset-help.html", "ACTIVATION_EMAIL_SUPPORT_LINK": "https://support.example.com/activation-email-help.html", "SYSLOG_SERVER": "", "TECH_SUPPORT_EMAIL": "technical@example.com", diff --git a/lms/envs/common.py b/lms/envs/common.py index 973cac74c6..bb4656451e 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2271,6 +2271,7 @@ MKTG_URL_LINK_MAP = { STATIC_TEMPLATE_VIEW_DEFAULT_FILE_EXTENSION = 'html' SUPPORT_SITE_LINK = '' +PASSWORD_RESET_SUPPORT_LINK = '' ACTIVATION_EMAIL_SUPPORT_LINK = '' ############################# SOCIAL MEDIA SHARING ############################# diff --git a/lms/envs/test.py b/lms/envs/test.py index 0bb120d69b..65198c04ac 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -361,6 +361,7 @@ MKTG_URL_LINK_MAP = { } SUPPORT_SITE_LINK = 'https://support.example.com' +PASSWORD_RESET_SUPPORT_LINK = 'https://support.example.com/password-reset-help.html' ACTIVATION_EMAIL_SUPPORT_LINK = 'https://support.example.com/activation-email-help.html' ############################ STATIC FILES ############################# 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 ff99a4af7e..95fd417b37 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 @@ -14,50 +14,15 @@ define(['backbone', 'use strict'; describe('edx.user.AccountSettingsFactory', function() { - var FIELDS_DATA = { - 'country': { - 'options': Helpers.FIELD_OPTIONS - }, 'gender': { - 'options': Helpers.FIELD_OPTIONS - }, 'language': { - 'options': Helpers.FIELD_OPTIONS - }, 'level_of_education': { - 'options': Helpers.FIELD_OPTIONS - }, 'password': { - 'url': '/password_reset' - }, 'year_of_birth': { - 'options': Helpers.FIELD_OPTIONS - }, 'preferred_language': { - 'options': Helpers.FIELD_OPTIONS - }, 'time_zone': { - 'options': Helpers.FIELD_OPTIONS - } - }; - - var AUTH_DATA = { - 'providers': [ - { - 'id': 'oa2-network1', - 'name': 'Network1', - 'connected': true, - 'accepts_logins': 'true', - 'connect_url': 'yetanother1.com/auth/connect', - 'disconnect_url': 'yetanother1.com/auth/disconnect' - }, - { - 'id': 'oa2-network2', - 'name': 'Network2', - 'connected': true, - 'accepts_logins': 'true', - 'connect_url': 'yetanother2.com/auth/connect', - 'disconnect_url': 'yetanother2.com/auth/disconnect' - } - ] - }; - var createAccountSettingsPage = function() { var context = AccountSettingsPage( - FIELDS_DATA, [], AUTH_DATA, Helpers.USER_ACCOUNTS_API_URL, Helpers.USER_PREFERENCES_API_URL, 'edX' + Helpers.FIELDS_DATA, + [], + Helpers.AUTH_DATA, + Helpers.PASSWORD_RESET_SUPPORT_LINK, + Helpers.USER_ACCOUNTS_API_URL, + Helpers.USER_PREFERENCES_API_URL, + Helpers.PLATFORM_NAME ); return context.accountSettingsView; }; diff --git a/lms/static/js/spec/student_account/helpers.js b/lms/static/js/spec/student_account/helpers.js index fbbce9d8d4..7978f01a2d 100644 --- a/lms/static/js/spec/student_account/helpers.js +++ b/lms/static/js/spec/student_account/helpers.js @@ -7,12 +7,63 @@ define(['underscore'], function(_) { var IMAGE_UPLOAD_API_URL = '/api/profile_images/v0/staff/upload'; var IMAGE_REMOVE_API_URL = '/api/profile_images/v0/staff/remove'; var FIND_COURSES_URL = '/courses'; - + 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 PROFILE_IMAGE = { image_url_large: '/media/profile-images/image.jpg', has_image: true }; - + var FIELD_OPTIONS = [ + ['0', 'Option 0'], + ['1', 'Option 1'], + ['2', 'Option 2'], + ['3', 'Option 3'] + ]; + var TIME_ZONE_RESPONSE = [{ + time_zone: 'America/Guyana', + description: 'America/Guyana (ECT, UTC-0500)' + }]; + var FIELDS_DATA = { + country: { + options: FIELD_OPTIONS + }, gender: { + options: FIELD_OPTIONS + }, language: { + options: FIELD_OPTIONS + }, level_of_education: { + options: FIELD_OPTIONS + }, password: { + url: '/password_reset' + }, year_of_birth: { + options: FIELD_OPTIONS + }, preferred_language: { + options: FIELD_OPTIONS + }, time_zone: { + options: FIELD_OPTIONS + } + }; + var AUTH_DATA = { + providers: [ + { + id: 'oa2-network1', + name: 'Network1', + connected: true, + accepts_logins: 'true', + connect_url: 'yetanother1.com/auth/connect', + disconnect_url: 'yetanother1.com/auth/disconnect' + }, + { + id: 'oa2-network2', + name: 'Network2', + connected: true, + accepts_logins: 'true', + connect_url: 'yetanother2.com/auth/connect', + disconnect_url: 'yetanother2.com/auth/disconnect' + } + ] + }; + var IMAGE_MAX_BYTES = 1024 * 1024; + var IMAGE_MIN_BYTES = 100; var DEFAULT_ACCOUNT_SETTINGS_DATA = { username: 'student', name: 'Student', @@ -28,35 +79,19 @@ define(['underscore'], function(_) { profile_image: PROFILE_IMAGE, accomplishments_shared: false }; - - var createAccountSettingsData = function(options) { - return _.extend(_.extend({}, DEFAULT_ACCOUNT_SETTINGS_DATA), options); - }; - var DEFAULT_USER_PREFERENCES_DATA = { 'pref-lang': '2', 'time_zone': null }; + var createAccountSettingsData = function(options) { + return _.extend(_.extend({}, DEFAULT_ACCOUNT_SETTINGS_DATA), options); + }; + var createUserPreferencesData = function(options) { return _.extend(_.extend({}, DEFAULT_USER_PREFERENCES_DATA), options); }; - var FIELD_OPTIONS = [ - ['0', 'Option 0'], - ['1', 'Option 1'], - ['2', 'Option 2'], - ['3', 'Option 3'] - ]; - - var TIME_ZONE_RESPONSE = [{ - time_zone: 'America/Guyana', - description: 'America/Guyana (ECT, UTC-0500)' - }]; - - var IMAGE_MAX_BYTES = 1024 * 1024; - var IMAGE_MIN_BYTES = 100; - var expectLoadingIndicatorIsVisible = function(view, visible) { if (visible) { expect($('.ui-loading-indicator')).not.toHaveClass('is-hidden'); @@ -88,10 +123,6 @@ define(['underscore'], function(_) { } }; - var expectSettingsSectionsButNotFieldsToBeRendered = function(accountSettingsView) { - expectSettingsSectionsAndFieldsToBeRendered(accountSettingsView, false); - }; - var expectSettingsSectionsAndFieldsToBeRendered = function(accountSettingsView, fieldsAreRendered) { var sectionsData = accountSettingsView.options.tabSections.aboutTabSections; @@ -99,7 +130,8 @@ define(['underscore'], function(_) { expect(sectionElements.length).toBe(sectionsData.length); _.each(sectionElements, function(sectionElement, sectionIndex) { - expect($(sectionElement).find('.section-header').text().trim()).toBe(sectionsData[sectionIndex].title); + expect($(sectionElement).find('.section-header').text() + .trim()).toBe(sectionsData[sectionIndex].title); var sectionFieldElements = $(sectionElement).find('.u-field'); @@ -115,6 +147,10 @@ define(['underscore'], function(_) { }); }; + var expectSettingsSectionsButNotFieldsToBeRendered = function(accountSettingsView) { + expectSettingsSectionsAndFieldsToBeRendered(accountSettingsView, false); + }; + return { USER_ACCOUNTS_API_URL: USER_ACCOUNTS_API_URL, USER_PREFERENCES_API_URL: USER_PREFERENCES_API_URL, @@ -122,13 +158,17 @@ define(['underscore'], function(_) { FIND_COURSES_URL: FIND_COURSES_URL, IMAGE_UPLOAD_API_URL: IMAGE_UPLOAD_API_URL, IMAGE_REMOVE_API_URL: IMAGE_REMOVE_API_URL, - IMAGE_MAX_BYTES: IMAGE_MAX_BYTES, - IMAGE_MIN_BYTES: IMAGE_MIN_BYTES, + PASSWORD_RESET_SUPPORT_LINK: PASSWORD_RESET_SUPPORT_LINK, + PLATFORM_NAME: PLATFORM_NAME, PROFILE_IMAGE: PROFILE_IMAGE, - createAccountSettingsData: createAccountSettingsData, - createUserPreferencesData: createUserPreferencesData, FIELD_OPTIONS: FIELD_OPTIONS, TIME_ZONE_RESPONSE: TIME_ZONE_RESPONSE, + FIELDS_DATA: FIELDS_DATA, + AUTH_DATA: AUTH_DATA, + IMAGE_MAX_BYTES: IMAGE_MAX_BYTES, + IMAGE_MIN_BYTES: IMAGE_MIN_BYTES, + createAccountSettingsData: createAccountSettingsData, + createUserPreferencesData: createUserPreferencesData, expectLoadingIndicatorIsVisible: expectLoadingIndicatorIsVisible, expectLoadingErrorIsVisible: expectLoadingErrorIsVisible, expectElementContainsField: expectElementContainsField, diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js index 0302d8024a..1049b6de94 100644 --- a/lms/static/js/student_account/views/AccessView.js +++ b/lms/static/js/student_account/views/AccessView.js @@ -72,6 +72,7 @@ this.platformName = options.platform_name; this.supportURL = options.support_link; + this.passwordResetSupportUrl = options.password_reset_support_link; this.createAccountOption = options.account_creation_allowed; // The login view listens for 'sync' events from the reset model @@ -129,6 +130,7 @@ accountActivationMessages: this.accountActivationMessages, platformName: this.platformName, supportURL: this.supportURL, + passwordResetSupportUrl: this.passwordResetSupportUrl, createAccountOption: this.createAccountOption }); diff --git a/lms/static/js/student_account/views/LoginView.js b/lms/static/js/student_account/views/LoginView.js index f66a7fc131..bcdd578f0f 100644 --- a/lms/static/js/student_account/views/LoginView.js +++ b/lms/static/js/student_account/views/LoginView.js @@ -5,206 +5,226 @@ 'underscore', 'gettext', 'edx-ui-toolkit/js/utils/html-utils', + 'edx-ui-toolkit/js/utils/string-utils', 'js/student_account/views/FormView', 'text!templates/student_account/form_success.underscore', 'text!templates/student_account/form_status.underscore' - ], - function($, _, gettext, HtmlUtils, FormView, formSuccessTpl, formStatusTpl) { - return FormView.extend({ - el: '#login-form', - tpl: '#login-tpl', - events: { - 'click .js-login': 'submitForm', - 'click .forgot-password': 'forgotPassword', - 'click .login-provider': 'thirdPartyAuth' - }, - formType: 'login', - requiredStr: '', - optionalStr: '', - submitButton: '.js-login', - formSuccessTpl: formSuccessTpl, - formStatusTpl: formStatusTpl, - authWarningJsHook: 'js-auth-warning', - passwordResetSuccessJsHook: 'js-password-reset-success', - defaultFormErrorsTitle: gettext('We couldn\'t sign you in.'), + ], function( + $, _, gettext, + HtmlUtils, + StringUtils, + FormView, + formSuccessTpl, + formStatusTpl + ) { + return FormView.extend({ + el: '#login-form', + tpl: '#login-tpl', + events: { + 'click .js-login': 'submitForm', + 'click .forgot-password': 'forgotPassword', + 'click .login-provider': 'thirdPartyAuth' + }, + formType: 'login', + requiredStr: '', + optionalStr: '', + submitButton: '.js-login', + formSuccessTpl: formSuccessTpl, + formStatusTpl: formStatusTpl, + authWarningJsHook: 'js-auth-warning', + passwordResetSuccessJsHook: 'js-password-reset-success', + defaultFormErrorsTitle: gettext('We couldn\'t sign you in.'), - preRender: function(data) { - this.providers = data.thirdPartyAuth.providers || []; - this.hasSecondaryProviders = ( - data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length + preRender: function(data) { + this.providers = data.thirdPartyAuth.providers || []; + this.hasSecondaryProviders = ( + data.thirdPartyAuth.secondaryProviders && data.thirdPartyAuth.secondaryProviders.length + ); + this.currentProvider = data.thirdPartyAuth.currentProvider || ''; + this.errorMessage = data.thirdPartyAuth.errorMessage || ''; + this.platformName = data.platformName; + this.resetModel = data.resetModel; + this.supportURL = data.supportURL; + this.passwordResetSupportUrl = data.passwordResetSupportUrl; + this.createAccountOption = data.createAccountOption; + this.accountActivationMessages = data.accountActivationMessages; + + this.listenTo(this.model, 'sync', this.saveSuccess); + this.listenTo(this.resetModel, 'sync', this.resetEmail); + }, + + render: function(html) { + var fields = html || ''; + + $(this.el).html(_.template(this.tpl)({ + // We pass the context object to the template so that + // we can perform variable interpolation using sprintf + context: { + fields: fields, + currentProvider: this.currentProvider, + providers: this.providers, + hasSecondaryProviders: this.hasSecondaryProviders, + platformName: this.platformName, + createAccountOption: this.createAccountOption + } + })); + + this.postRender(); + + return this; + }, + + postRender: function() { + var formErrorsTitle; + this.$container = $(this.el); + this.$form = this.$container.find('form'); + this.$formFeedback = this.$container.find('.js-form-feedback'); + this.$submitButton = this.$container.find(this.submitButton); + + if (this.errorMessage) { + formErrorsTitle = _.sprintf( + gettext('An error occurred when signing you in to %s.'), + this.platformName ); - this.currentProvider = data.thirdPartyAuth.currentProvider || ''; - this.errorMessage = data.thirdPartyAuth.errorMessage || ''; - this.platformName = data.platformName; - this.resetModel = data.resetModel; - this.supportURL = data.supportURL; - this.createAccountOption = data.createAccountOption; - this.accountActivationMessages = data.accountActivationMessages; - - this.listenTo(this.model, 'sync', this.saveSuccess); - this.listenTo(this.resetModel, 'sync', this.resetEmail); - }, - - render: function(html) { - var fields = html || ''; - - $(this.el).html(_.template(this.tpl)({ - // We pass the context object to the template so that - // we can perform variable interpolation using sprintf - context: { - fields: fields, - currentProvider: this.currentProvider, - providers: this.providers, - hasSecondaryProviders: this.hasSecondaryProviders, - platformName: this.platformName, - createAccountOption: this.createAccountOption - } - })); - - this.postRender(); - - return this; - }, - - postRender: function() { - var formErrorsTitle; - this.$container = $(this.el); - this.$form = this.$container.find('form'); - this.$formFeedback = this.$container.find('.js-form-feedback'); - this.$submitButton = this.$container.find(this.submitButton); - - if (this.errorMessage) { - formErrorsTitle = _.sprintf( - gettext('An error occurred when signing you in to %s.'), - this.platformName - ); - this.renderErrors(formErrorsTitle, [this.errorMessage]); - } else if (this.currentProvider) { - /* If we're already authenticated with a third-party - * provider, try logging in. The easiest way to do this - * is to simply submit the form. - */ - this.model.save(); - } - - // Display account activation success or error messages. - this.renderAccountActivationMessages(); - }, - - renderAccountActivationMessages: function() { - _.each(this.accountActivationMessages, this.renderAccountActivationMessage, this); - }, - - renderAccountActivationMessage: function(message) { - this.renderFormFeedback(this.formStatusTpl, { - jsHook: message.tags, - message: HtmlUtils.HTML(message.message) - }); - }, - - forgotPassword: function(event) { - event.preventDefault(); - - this.trigger('password-help'); - this.clearPasswordResetSuccess(); - }, - - postFormSubmission: function() { - this.clearPasswordResetSuccess(); - }, - - resetEmail: function() { - var email = $('#password-reset-email').val(), - successTitle = gettext('Check Your Email'), - successMessageHtml = HtmlUtils.interpolateHtml( - gettext('{paragraphStart}You entered {boldStart}{email}{boldEnd}. If this email address is associated with your {platform_name} account, we will send a message with password reset instructions to this email address.{paragraphEnd}' + // eslint-disable-line max-len - '{paragraphStart}If you do not receive a password reset message, verify that you entered the correct email address, or check your spam folder.{paragraphEnd}' + // eslint-disable-line max-len - '{paragraphStart}If you need further assistance, {anchorStart}contact technical support{anchorEnd}.{paragraphEnd}'), { // eslint-disable-line max-len - boldStart: HtmlUtils.HTML(''), - boldEnd: HtmlUtils.HTML(''), - paragraphStart: HtmlUtils.HTML('

'), - paragraphEnd: HtmlUtils.HTML('

'), - email: email, - platform_name: this.platformName, - anchorStart: HtmlUtils.HTML(''), - anchorEnd: HtmlUtils.HTML('') - } - ); - - this.clearFormErrors(); - this.clearPasswordResetSuccess(); - - this.renderFormFeedback(this.formSuccessTpl, { - jsHook: this.passwordResetSuccessJsHook, - title: successTitle, - messageHtml: successMessageHtml - }); - }, - - thirdPartyAuth: function(event) { - var providerUrl = $(event.currentTarget).data('provider-url') || ''; - - if (providerUrl) { - window.location.href = providerUrl; - } - }, - - saveSuccess: function() { - this.trigger('auth-complete'); - this.clearPasswordResetSuccess(); - }, - - saveError: function(error) { - var msg = error.responseText; - if (error.status === 0) { - msg = gettext('An error has occurred. Check your Internet connection and try again.'); - } else if (error.status === 500) { - msg = gettext('An error has occurred. Try refreshing the page, or check your Internet connection.'); - } - this.errors = ['
  • ' + msg + '
  • ']; - this.clearPasswordResetSuccess(); - - /* If we've gotten a 403 error, it means that we've successfully - * authenticated with a third-party provider, but we haven't - * linked the account to an EdX account. In this case, - * we need to prompt the user to enter a little more information - * to complete the registration process. - */ - if (error.status === 403 && - error.responseText === 'third-party-auth' && - this.currentProvider) { - this.clearFormErrors(); - this.renderAuthWarning(); - } else { - this.renderErrors(this.defaultFormErrorsTitle, this.errors); - } - this.toggleDisableButton(false); - }, - - renderAuthWarning: function() { - var message = _.sprintf( - gettext('You have successfully signed into %(currentProvider)s, but your %(currentProvider)s' + - ' account does not have a linked %(platformName)s account. To link your accounts,' + - ' sign in now using your %(platformName)s password.'), - {currentProvider: this.currentProvider, platformName: this.platformName} - ); - - this.clearAuthWarning(); - this.renderFormFeedback(this.formStatusTpl, { - jsHook: this.authWarningJsHook, - message: message - }); - }, - - clearPasswordResetSuccess: function() { - var query = '.' + this.passwordResetSuccessJsHook; - this.clearFormFeedbackItems(query); - }, - - clearAuthWarning: function() { - var query = '.' + this.authWarningJsHook; - this.clearFormFeedbackItems(query); + this.renderErrors(formErrorsTitle, [this.errorMessage]); + } else if (this.currentProvider) { + /* If we're already authenticated with a third-party + * provider, try logging in. The easiest way to do this + * is to simply submit the form. + */ + this.model.save(); } - }); + + // Display account activation success or error messages. + this.renderAccountActivationMessages(); + }, + + renderAccountActivationMessages: function() { + _.each(this.accountActivationMessages, this.renderAccountActivationMessage, this); + }, + + renderAccountActivationMessage: function(message) { + this.renderFormFeedback(this.formStatusTpl, { + jsHook: message.tags, + message: HtmlUtils.HTML(message.message) + }); + }, + + forgotPassword: function(event) { + event.preventDefault(); + + this.trigger('password-help'); + this.clearPasswordResetSuccess(); + }, + + postFormSubmission: function() { + this.clearPasswordResetSuccess(); + }, + + resetEmail: function() { + var email = $('#password-reset-email').val(), + successTitle = gettext('Check Your Email'), + successMessageHtml = HtmlUtils.interpolateHtml( + gettext('{paragraphStart}You entered {boldStart}{email}{boldEnd}. If this email address is associated with your {platform_name} account, we will send a message with password reset instructions to this email address.{paragraphEnd}' + // eslint-disable-line max-len + '{paragraphStart}If you do not receive a password reset message, verify that you entered the correct email address, or check your spam folder.{paragraphEnd}' + // eslint-disable-line max-len + '{paragraphStart}If you need further assistance, {anchorStart}contact technical support{anchorEnd}.{paragraphEnd}'), { // eslint-disable-line max-len + boldStart: HtmlUtils.HTML(''), + boldEnd: HtmlUtils.HTML(''), + paragraphStart: HtmlUtils.HTML('

    '), + paragraphEnd: HtmlUtils.HTML('

    '), + email: email, + platform_name: this.platformName, + anchorStart: HtmlUtils.HTML( + StringUtils.interpolate( + '', { + passwordResetSupportUrl: this.passwordResetSupportUrl + } + ) + ), + anchorEnd: HtmlUtils.HTML('') + } + ); + + this.clearFormErrors(); + this.clearPasswordResetSuccess(); + + this.renderFormFeedback(this.formSuccessTpl, { + jsHook: this.passwordResetSuccessJsHook, + title: successTitle, + messageHtml: successMessageHtml + }); + }, + + thirdPartyAuth: function(event) { + var providerUrl = $(event.currentTarget).data('provider-url') || ''; + + if (providerUrl) { + window.location.href = providerUrl; + } + }, + + saveSuccess: function() { + this.trigger('auth-complete'); + this.clearPasswordResetSuccess(); + }, + + saveError: function(error) { + var msg = error.responseText; + if (error.status === 0) { + msg = gettext('An error has occurred. Check your Internet connection and try again.'); + } else if (error.status === 500) { + msg = gettext('An error has occurred. Try refreshing the page, or check your Internet connection.'); // eslint-disable-line max-len + } + this.errors = [ + StringUtils.interpolate( + '
  • {msg}
  • ', { + msg: msg + } + ) + ]; + this.clearPasswordResetSuccess(); + + /* If we've gotten a 403 error, it means that we've successfully + * authenticated with a third-party provider, but we haven't + * linked the account to an EdX account. In this case, + * we need to prompt the user to enter a little more information + * to complete the registration process. + */ + if (error.status === 403 && + error.responseText === 'third-party-auth' && + this.currentProvider) { + this.clearFormErrors(); + this.renderAuthWarning(); + } else { + this.renderErrors(this.defaultFormErrorsTitle, this.errors); + } + this.toggleDisableButton(false); + }, + + renderAuthWarning: function() { + var message = _.sprintf( + gettext('You have successfully signed into %(currentProvider)s, but your %(currentProvider)s' + + ' account does not have a linked %(platformName)s account. To link your accounts,' + + ' sign in now using your %(platformName)s password.'), + {currentProvider: this.currentProvider, platformName: this.platformName} + ); + + this.clearAuthWarning(); + this.renderFormFeedback(this.formStatusTpl, { + jsHook: this.authWarningJsHook, + message: message + }); + }, + + clearPasswordResetSuccess: function() { + var query = '.' + this.passwordResetSuccessJsHook; + this.clearFormFeedbackItems(query); + }, + + clearAuthWarning: function() { + var query = '.' + this.authWarningJsHook; + this.clearFormFeedbackItems(query); + } }); + }); }).call(this, define || RequireJS.define); 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 c8a7f0c22f..1fa127e78b 100644 --- a/lms/static/js/student_account/views/account_settings_factory.js +++ b/lms/static/js/student_account/views/account_settings_factory.js @@ -13,6 +13,7 @@ fieldsData, ordersHistoryData, authData, + passwordResetSupportUrl, userAccountsApiUrl, userPreferencesApiUrl, accountUserId, @@ -76,6 +77,7 @@ screenReaderTitle: gettext('Reset Your Password'), valueAttribute: 'password', emailAttribute: 'email', + passwordResetSupportUrl: passwordResetSupportUrl, linkTitle: gettext('Reset Your Password'), linkHref: fieldsData.password.url, helpMessage: StringUtils.interpolate( @@ -191,7 +193,7 @@ ), fields: _.map(authData.providers, function(provider) { return { - 'view': new AccountSettingsFieldViews.AuthFieldView({ + view: new AccountSettingsFieldViews.AuthFieldView({ title: provider.name, valueAttribute: 'auth-' + provider.id, helpMessage: '', @@ -208,10 +210,10 @@ ordersHistoryData.unshift( { - 'title': gettext('ORDER NAME'), - 'order_date': gettext('ORDER PLACED'), - 'price': gettext('TOTAL'), - 'number': gettext('ORDER NUMBER') + title: gettext('ORDER NAME'), + order_date: gettext('ORDER PLACED'), + price: gettext('TOTAL'), + number: gettext('ORDER NUMBER') } ); @@ -228,7 +230,7 @@ orderNumber = 'orderId'; } return { - 'view': new AccountSettingsFieldViews.OrderHistoryFieldView({ + view: new AccountSettingsFieldViews.OrderHistoryFieldView({ totalPrice: order.price, orderId: order.number, orderDate: order.order_date, diff --git a/lms/static/js/student_account/views/account_settings_fields.js b/lms/static/js/student_account/views/account_settings_fields.js index 6e54d5fe14..88d229a747 100644 --- a/lms/static/js/student_account/views/account_settings_fields.js +++ b/lms/static/js/student_account/views/account_settings_fields.js @@ -25,8 +25,7 @@ field_order_history_template, StringUtils, HtmlUtils - ) - { + ) { var AccountSettingsFieldViews = { ReadonlyFieldView: FieldViews.ReadonlyFieldView.extend({ fieldTemplate: field_readonly_account_template @@ -44,7 +43,7 @@ this.indicators.success, StringUtils.interpolate( gettext('We\'ve sent a confirmation message to {new_email_address}. Click the link in the message to update your email address.'), // eslint-disable-line max-len - {'new_email_address': this.fieldValue()} + {new_email_address: this.fieldValue()} ) ); } @@ -53,7 +52,7 @@ fieldTemplate: field_dropdown_account_template, saveSucceeded: function() { var data = { - 'language': this.modelValue() + language: this.modelValue() }; var view = this; @@ -189,9 +188,19 @@ successMessage: function() { return HtmlUtils.joinHtml( this.indicators.success, - StringUtils.interpolate( - gettext('We\'ve sent a message to {email_address}. Click the link in the message to reset your password.'), // eslint-disable-line max-len - {'email_address': this.model.get(this.options.emailAttribute)} + HtmlUtils.interpolateHtml( + gettext('We\'ve sent a message to {email}. Click the link in the message to reset your password. Didn\'t receive the message? Contact {anchorStart}technical support{anchorEnd}.'), // eslint-disable-line max-len + { + email: this.model.get(this.options.emailAttribute), + anchorStart: HtmlUtils.HTML( + StringUtils.interpolate( + '', { + passwordResetSupportUrl: this.options.passwordResetSupportUrl + } + ) + ), + anchorEnd: HtmlUtils.HTML('') + } ) ); } @@ -209,7 +218,7 @@ saveValue: function() { if (this.persistChanges === true) { var attributes = {}, - value = this.fieldValue() ? [{'code': this.fieldValue()}] : []; + value = this.fieldValue() ? [{code: this.fieldValue()}] : []; attributes[this.options.valueAttribute] = value; this.saveAttributes(attributes); } diff --git a/lms/templates/student_account/account_settings.html b/lms/templates/student_account/account_settings.html index c8653c0a9b..fdfd520aaa 100644 --- a/lms/templates/student_account/account_settings.html +++ b/lms/templates/student_account/account_settings.html @@ -41,6 +41,7 @@ from openedx.core.djangolib.js_utils import dump_js_escaped_json, js_escaped_str fieldsData, ordersHistoryData, authData, + '${ password_reset_support_link | n, js_escaped_string }', '${ user_accounts_api_url | n, js_escaped_string }', '${ user_preferences_api_url | n, js_escaped_string }', ${ user.id | n, dump_js_escaped_json },