diff --git a/common/static/js/spec_helpers/edx.utils.validate.js b/common/static/js/spec_helpers/edx.utils.validate.js new file mode 100644 index 0000000000..293d881ee7 --- /dev/null +++ b/common/static/js/spec_helpers/edx.utils.validate.js @@ -0,0 +1,71 @@ +var edx = edx || {}; + +(function( $, _ ) { + 'use strict'; + + edx.utils = edx.utils || {}; + + var utils = (function(){ + var _fn = { + validate: { + + field: function( el ) { + var $el = $(el); + + return _fn.validate.required( $el ) && + _fn.validate.charLength( $el ) && + _fn.validate.email.valid( $el ); + }, + + charLength: function( $el ) { + // Cannot assume there will be both min and max + var min = $el.attr('minlength') || 0, + max = $el.attr('maxlength') || false, + chars = $el.val().length, + within = false; + + // if max && min && within the range + if ( min <= chars && ( max && chars <= max ) ) { + within = true; + } else if ( min <= chars && !max ) { + within = true; + } + + return within; + }, + + required: function( $el ) { + return $el.attr('required') ? $el.val() : true; + }, + + email: { + // This is the same regex used to validate email addresses in Django 1.4 + regex: new RegExp( + [ + '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+)*', + '|^"([\\001-\\010\\013\\014\\016-\\037!#-\\[\\]-\\177]|\\\\[\\001-\\011\\013\\014\\016-\\177])*"', + ')@((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\\.)+[A-Z]{2,6}\\.?$)', + '|\\[(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\]$' + ].join(''), 'i' + ), + + valid: function( $el ) { + return $el.data('email') ? _fn.validate.email.format( $el.val() ) : true; + }, + + format: function( str ) { + return _fn.validate.email.regex.test( str ); + } + } + } + }; + + return { + validate: _fn.validate.field + }; + + })(); + + edx.utils.validate = utils.validate + +})( jQuery, _ ); \ No newline at end of file diff --git a/lms/envs/common.py b/lms/envs/common.py index 8ccc48ad0b..2769f413d9 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -286,7 +286,7 @@ FEATURES = { 'ENABLE_VIDEO_ABSTRACTION_LAYER_API': False, # Enable the new dashboard, account, and profile pages - 'ENABLE_NEW_DASHBOARD': False, + 'ENABLE_NEW_DASHBOARD': True, } # Ignore static asset files on import which match this pattern @@ -1025,7 +1025,17 @@ instructor_dash_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'coffee/src/ins # JavaScript used by the student account and profile pages # These are not courseware, so they do not need many of the courseware-specific # JavaScript modules. -student_account_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/student_account/**/*.js')) +student_account_js = [ + 'js/common_helpers/edx.utils.validate.js', + 'js/student_account/models/LoginModel.js', + 'js/student_account/models/RegisterModel.js', + 'js/student_account/models/PasswordResetModel.js', + 'js/student_account/views/LoginView.js', + 'js/student_account/views/RegisterView.js', + 'js/student_account/views/PasswordResetView.js', + 'js/student_account/views/AccessView.js', + 'js/student_account/accessApp.js', +] student_profile_js = sorted(rooted_glob(PROJECT_ROOT / 'static', 'js/student_profile/**/*.js')) PIPELINE_CSS = { diff --git a/lms/static/js/student_account/accessApp.js b/lms/static/js/student_account/accessApp.js new file mode 100644 index 0000000000..b3e3a893b9 --- /dev/null +++ b/lms/static/js/student_account/accessApp.js @@ -0,0 +1,13 @@ +var edx = edx || {}; + +(function($) { + 'use strict'; + + edx.student = edx.student || {}; + edx.student.account = edx.student.account || {}; + + return new edx.student.account.AccessView({ + mode: $('#login-and-registration-container').data('initial-mode') || 'login', + thirdPartyAuth: $('#login-and-registration-container').data('third-party-auth-providers') || false + }); +})(jQuery); \ No newline at end of file diff --git a/lms/static/js/student_account/models/LoginModel.js b/lms/static/js/student_account/models/LoginModel.js new file mode 100644 index 0000000000..7930d281ca --- /dev/null +++ b/lms/static/js/student_account/models/LoginModel.js @@ -0,0 +1,52 @@ +var edx = edx || {}; + +(function($, _, Backbone, gettext) { + 'use strict'; + + edx.student = edx.student || {}; + edx.student.account = edx.student.account || {}; + + edx.student.account.LoginModel = Backbone.Model.extend({ + + defaults: { + email: '', + password: '', + remember: false + }, + + urlRoot: '', + + initialize: function( obj ) { + this.urlRoot = obj.url; + }, + + sync: function(method, model) { + var headers = { + 'X-CSRFToken': $.cookie('csrftoken') + }; + + $.ajax({ + url: model.urlRoot, + type: 'POST', + data: model.attributes, + headers: headers + }) + .done(function() { + var query = window.location.search, + url = '/dashboard'; + + model.trigger('sync'); + + // If query string in url go back to that page + if ( query.length > 1 ) { + url = query.substring( query.indexOf('=') + 1 ); + } + + window.location.href = url; + }) + .fail( function( error ) { + model.trigger('error', error); + }); + } + }); +})(jQuery, _, Backbone, gettext); \ No newline at end of file diff --git a/lms/static/js/student_account/models/PasswordResetModel.js b/lms/static/js/student_account/models/PasswordResetModel.js new file mode 100644 index 0000000000..c62d83c1be --- /dev/null +++ b/lms/static/js/student_account/models/PasswordResetModel.js @@ -0,0 +1,38 @@ +var edx = edx || {}; + +(function($, _, Backbone, gettext) { + 'use strict'; + + edx.student = edx.student || {}; + edx.student.account = edx.student.account || {}; + + edx.student.account.PasswordResetModel = Backbone.Model.extend({ + + defaults: { + email: '' + }, + + urlRoot: '/account/password', + + sync: function(method, model) { + var headers = { + 'X-CSRFToken': $.cookie('csrftoken') + }; + + // Is just expecting email address + $.ajax({ + url: model.urlRoot, + type: 'POST', + data: model.attributes, + headers: headers + }) + .done(function() { + model.trigger('success'); + }) + .fail( function( error ) { + console.log('RegisterModel.save() FAILURE!!!!!'); + model.trigger('error', error); + }); + } + }); +})(jQuery, _, Backbone, gettext); \ No newline at end of file diff --git a/lms/static/js/student_account/models/RegisterModel.js b/lms/static/js/student_account/models/RegisterModel.js new file mode 100644 index 0000000000..a389a1c1f3 --- /dev/null +++ b/lms/static/js/student_account/models/RegisterModel.js @@ -0,0 +1,60 @@ +var edx = edx || {}; + +(function($, _, Backbone, gettext) { + 'use strict'; + + edx.student = edx.student || {}; + edx.student.account = edx.student.account || {}; + + edx.student.account.RegisterModel = Backbone.Model.extend({ + + defaults: { + email: '', + name: '', + username: '', + password: '', + level_of_education: '', + gender: '', + year_of_birth: '', + mailing_address: '', + goals: '', + termsofservice: false + }, + + urlRoot: '', + + initialize: function( obj ) { + this.urlRoot = obj.url; + }, + + sync: function(method, model) { + var headers = { + 'X-CSRFToken': $.cookie('csrftoken') + }; + + $.ajax({ + url: model.urlRoot, + type: 'POST', + data: model.attributes, + headers: headers + }) + .done(function() { + var query = window.location.search, + url = '/dashboard'; + + model.trigger('sync'); + + // If query string in url go back to that page + if ( query.length > 1 ) { + url = query.substring( query.indexOf('=') + 1 ); + } + + window.location.href = url; + }) + .fail( function( error ) { + console.log('RegisterModel.save() FAILURE!!!!!'); + model.trigger('error', error); + }); + } + }); +})(jQuery, _, Backbone, gettext); \ No newline at end of file diff --git a/lms/static/js/student_account/views/AccessView.js b/lms/static/js/student_account/views/AccessView.js new file mode 100644 index 0000000000..fb0e61df8c --- /dev/null +++ b/lms/static/js/student_account/views/AccessView.js @@ -0,0 +1,88 @@ +var edx = edx || {}; + +(function($, _, Backbone, gettext) { + 'use strict'; + + edx.student = edx.student || {}; + edx.student.account = edx.student.account || {}; + + edx.student.account.AccessView = Backbone.View.extend({ + el: '#login-and-registration-container', + + tpl: $('#access-tpl').html(), + + events: { + 'change .form-toggle': 'toggleForm' + }, + + subview: { + login: {}, + register: {}, + passwordHelp: {} + }, + + // The form currently loaded + activeForm: '', + + initialize: function( obj ) { + this.activeForm = obj.mode; + console.log(obj); + + this.render(); + }, + + render: function() { + $(this.el).html( _.template( this.tpl, { + mode: this.activeForm + })); + + this.postRender(); + + return this; + }, + + postRender: function() { + // Load the default form + this.loadForm( this.activeForm ); + this.$header = $(this.el).find('.js-login-register-header'); + }, + + loadForm: function( type ) { + if ( type === 'login' ) { + this.subview.login = new edx.student.account.LoginView(); + + // Listen for 'password-help' event to toggle sub-views + this.listenTo( this.subview.login, 'password-help', this.resetPassword ); + } else if ( type === 'register' ) { + this.subview.register = new edx.student.account.RegisterView(); + } else if ( type === 'reset' ) { + this.subview.passwordHelp = new edx.student.account.PasswordResetView(); + } + }, + + resetPassword: function() { + this.$header.addClass('hidden'); + $(this.el).find('.form-type').addClass('hidden'); + this.loadForm('reset'); + }, + + toggleForm: function( e ) { + var type = $(e.currentTarget).val(), + $form = $('#' + type + '-form'); + + if ( !this.form.isLoaded( $form ) ) { + this.loadForm( type ); + } + + $(this.el).find('.form-wrapper').addClass('hidden'); + $form.removeClass('hidden'); + }, + + form: { + isLoaded: function( $form ) { + return $form.html().length > 0; + } + } + }); + +})(jQuery, _, Backbone, gettext); \ No newline at end of file diff --git a/lms/static/js/student_account/views/LoginView.js b/lms/static/js/student_account/views/LoginView.js new file mode 100644 index 0000000000..20d3298087 --- /dev/null +++ b/lms/static/js/student_account/views/LoginView.js @@ -0,0 +1,163 @@ +var edx = edx || {}; + +(function($, _, Backbone, gettext) { + 'use strict'; + + edx.student = edx.student || {}; + edx.student.account = edx.student.account || {}; + + edx.student.account.LoginView = Backbone.View.extend({ + tagName: 'form', + + el: '#login-form', + + tpl: $('#login-tpl').html(), + + fieldTpl: $('#form_field-tpl').html(), + + events: { + 'click .js-login': 'submitForm', + 'click .forgot-password': 'forgotPassword' + }, + + errors: [], + + $form: {}, + + initialize: function() { + this.getInitialData(); + }, + + // Renders the form. + render: function( html ) { + var fields = html || ''; + + $(this.el).html( _.template( this.tpl, { + fields: fields + })); + + this.postRender(); + + return this; + }, + + postRender: function() { + var $container = $(this.el); + + this.$form = $container.find('form'); + this.$errors = $container.find('.error-msg'); + }, + + getInitialData: function() { + var that = this; + + $.ajax({ + type: 'GET', + dataType: 'json', + url: '/user_api/v1/account/login_session/', + success: function( data ) { + console.log(data); + that.buildForm( data.fields ); + that.initModel( data.submit_url, data.method ); + }, + error: function( jqXHR, textStatus, errorThrown ) { + console.log('fail ', errorThrown); + } + }); + }, + + initModel: function( url ) { + this.model = new edx.student.account.LoginModel({ + url: url + }); + + this.listenTo( this.model, 'error', function( error ) { + console.log(error.status, ' error: ', error.responseText); + }); + }, + + buildForm: function( data ) { + var html = [], + i, + len = data.length, + fieldTpl = this.fieldTpl; + + for ( i=0; i +

Welcome!

+

Please log in to continue

+ + +
+

+ checked<% } %> > + +

+
+
+ +
+

+ checked<% } %>> + +

+
+
+ +
\ No newline at end of file diff --git a/lms/templates/student_account/form_field.underscore b/lms/templates/student_account/form_field.underscore new file mode 100644 index 0000000000..3d6c2b0851 --- /dev/null +++ b/lms/templates/student_account/form_field.underscore @@ -0,0 +1,41 @@ +

+ <% if ( type !== 'checkbox' ) { %> + <% } %> + + <% } %> + + <% if( form === 'login' && name === 'password' ) { %> + Forgot password? + <% } %> + + <% if ( type === 'select' ) { %> + + <% } else if ( type === 'textarea' ) { %> + + <% } else { %> + minlength="<%= restrictions.min_length %>"<% } %> + <% if ( restrictions.max_length ) { %> maxlength="<%= restrictions.max_length %>"<% } %> + <% if ( required ) { %> required<% } %> + /> + <% } %> + + <% if ( type === 'checkbox' ) { %> + <% } %> + + <% } %> + + <%= instructions %> +

\ No newline at end of file diff --git a/lms/templates/student_account/login.underscore b/lms/templates/student_account/login.underscore new file mode 100644 index 0000000000..1aadf57f20 --- /dev/null +++ b/lms/templates/student_account/login.underscore @@ -0,0 +1,12 @@ +
+ + <%= fields %> + + + +
\ No newline at end of file diff --git a/lms/templates/student_account/login_and_register.html b/lms/templates/student_account/login_and_register.html index 19ed498fd8..629d0d1297 100644 --- a/lms/templates/student_account/login_and_register.html +++ b/lms/templates/student_account/login_and_register.html @@ -12,16 +12,13 @@ <%block name="header_extras"> -% for template_name in ["account"]: +% for template_name in ["account", "access", "form_field", "login", "register", "password_reset"]: % endfor -

Login and Registration!

- -

This is a placeholder for the combined login and registration form

## TODO: Use JavaScript to populate this div with ## the actual registration/login forms (loaded asynchronously from the user API) @@ -47,6 +44,7 @@ ## Note that this list may be empty. ##
diff --git a/lms/templates/student_account/password_reset.underscore b/lms/templates/student_account/password_reset.underscore new file mode 100644 index 0000000000..156e374405 --- /dev/null +++ b/lms/templates/student_account/password_reset.underscore @@ -0,0 +1,27 @@ +
+

Password assistance

+
+ +
+
+

Please enter your email address below and we will send you instructions for setting a new password.

+
+ + + <%= fields %> + + +
+
+ + + +
\ No newline at end of file diff --git a/lms/templates/student_account/register.underscore b/lms/templates/student_account/register.underscore new file mode 100644 index 0000000000..874d72530d --- /dev/null +++ b/lms/templates/student_account/register.underscore @@ -0,0 +1,15 @@ +
+ + + + + <%= fields %> + + + +
\ No newline at end of file