Vinson — Change Email Address Patch

To update a Vinson implementation to give users the ability to change their email address, you’ll need to modify and add the files as detailed below.

Important:

This addition requires changes to templates, views, JavaScript, and SSP application files. Test changes thoroughly against your existing customizations before deploying to your published domain.

Modify CheckoutApplication/SuiteScript/checkout.ssp

Replace
            var SiteSettings
  , parameters
  , siteType
  , Environment
  , Language
  , Currency
  , Error
  , login
  , Application
  , environmentParameters 

        
With
            var SiteSettings
  , parameters
  , siteType
  , Environment
  , Language
  , Currency
  , Error
  , login
  , Application
  , environmentParameters
  , password_reset_expired
  ; 

        
Replace
            if (session.isChangePasswordRequest())
  {
    parameters.fragment = 'reset-password';
    login = true;
  } 

        
With
            if (parameters.passwdret)
  {
    try
    {
      if (session.isChangePasswordRequest())
      {
        parameters.fragment = 'reset-password';
        login = true;
      }
    }
    catch (e)
    {
      password_reset_expired = true;
    }

  } 

        
Replace
            <% if (Error) { %>
    <script>SC.ENVIRONMENT.contextError = <%= JSON.stringify(Error) %>;</script>
  <% } %> 

        
With
            <% if (Error) { %>
    <script>SC.ENVIRONMENT.contextError = <%= JSON.stringify(Error) %>;</script>
  <% } %>

  <% if (parameters.key) { %>
    <script>SC.ENVIRONMENT.email_verification_error = true;</script>
  <% } else if (password_reset_expired) { %>
    <script>SC.ENVIRONMENT.password_reset_expired_error = true;</script>
  <% } else if (parameters.passwdret && parameters.fragment !== 'reset-password') { %>
    <script>SC.ENVIRONMENT.password_reset_invalid_error = true;</script>
  <% } %> 

        

Modify LoginRegister/Javascript/LoginRegister.View.js

Replace
          define('LoginRegister.View'
, [ 'login_register.tpl'

  , 'Profile.Model'
  , 'LoginRegister.Login.View'
  , 'LoginRegister.Register.View'
  , 'LoginRegister.CheckoutAsGuest.View'
  , 'Backbone.CompositeView'
  , 'SC.Configuration'
  , 'Header.Simplified.View'
  , 'Footer.Simplified.View'

  , 'Backbone'
  , 'underscore'
  , 'Utils'
  ]
, function (
    login_register_tpl

  , ProfileModel
  , LoginView
  , RegisterView
  , CheckoutAsGuestView
  , BackboneCompositeView
  , Configuration
  , HeaderSimplifiedView
  , FooterSimplifiedView

  , Backbone
  , _
  ) 

        
With
          define('LoginRegister.View'
, [ 'login_register.tpl'

  , 'GlobalViews.Message.View'
  , 'Profile.Model'
  , 'LoginRegister.Login.View'
  , 'LoginRegister.Register.View'
  , 'LoginRegister.CheckoutAsGuest.View'
  , 'Backbone.CompositeView'
  , 'SC.Configuration'
  , 'Header.Simplified.View'
  , 'Footer.Simplified.View'

  , 'Backbone'
  , 'underscore'
  , 'Utils'
  ]
, function (
    login_register_tpl

  , GlobalViewsMessageView
  , ProfileModel
  , LoginView
  , RegisterView
  , CheckoutAsGuestView
  , BackboneCompositeView
  , Configuration
  , HeaderSimplifiedView
  , FooterSimplifiedView

  , Backbone
  , _
  ) 

        
Replace
          this.enableCheckoutAsGuest = is_checking_out && profile_model.get('isLoggedIn') === 'F' && (Configuration.getRegistrationType() === 'optional' || Configuration.getRegistrationType() === 'disabled');

BackboneCompositeView.add(this); 

        
With
          this.enableCheckoutAsGuest = is_checking_out && profile_model.get('isLoggedIn') === 'F' &&
  (Configuration.getRegistrationType() === 'optional' || Configuration.getRegistrationType() === 'disabled');

if (SC.ENVIRONMENT.email_verification_error)
{
  this.message = _('The validation process has failed. Please login into your account and click on the validation link again.').translate();
  delete SC.ENVIRONMENT.email_verification_error;
}
else if (SC.ENVIRONMENT.password_reset_invalid_error)
{
  this.message = _('Your reset password link is invalid. Request a new one using the Forgot Password link.').translate();
  delete SC.ENVIRONMENT.password_reset_invalid_error;
}
else if (SC.ENVIRONMENT.password_reset_expired_error)
{
  this.message = _('Your reset password link has expired. Request a new one using the Forgot Password link.').translate();
  delete SC.ENVIRONMENT.password_reset_expired_error;
}

BackboneCompositeView.add(this); 

        
Replace
          , 'Register': function ()
  {
    return new RegisterView(this.child_view_options);
  }
} 

        
With
          , 'Register': function ()
  {
    return new RegisterView(this.child_view_options);
  }
, 'Messages': function ()
  {
    if (this.message)
    {
      return new GlobalViewsMessageView({
          message: this.message
        , type: 'error'
        , closable: true
      });
    }
  }
} 

        

Modify LoginRegister/Templates/login_register.tpl

Replace
          <header class="login-register-header">
  {{#if showRegister}}
  <h1 class="login-register-title"> {{translate 'Log in | Register'}}</h1>
  {{else}}
  <h1 class="login-register-title"> {{translate 'Log in'}}</h1>
  {{/if}}
</header> 

        
With
          <header class="login-register-header">
  {{#if showRegister}}
  <h1 class="login-register-title"> {{translate 'Log in | Register'}}</h1>
  {{else}}
  <h1 class="login-register-title"> {{translate 'Log in'}}</h1>
  {{/if}}
</header>

<div data-view="Messages"></div> 

        

Modify MyAccountApplication/SuiteScript/my_account.ssp

Replace
          <%
  var SiteSettings
  , siteType
  , Environment
  , Language
  , Currency
  , Error
  , Application
  , environmentParameters
  ,   parameters
  , external_payment;

  try
  {
    SiteSettings = require('SiteSettings.Model').get();
    parameters = request.getAllParameters();

    // Access control, if you are not loged this will send you to the log in page
    if (!session.isLoggedIn2() || session.getCustomer().isGuest())
    {
      delete parameters.sitepath;
      parameters.origin = 'customercenter';

      if (parameters.fragment)
      {
        parameters.origin_hash = parameters.fragment;
        delete parameters.fragment;
      }

      return nlapiSetRedirectURL('EXTERNAL', SiteSettings.touchpoints.login, null, false, parameters);
    }

    Application = require('Application');
    Environment = Application.getEnvironment(request);
    environmentParameters = [];
    siteType = SiteSettings.sitetype;

    Language = Environment.currentLanguage && Environment.currentLanguage.locale || '';
    Currency = Environment.currencyCodeSpecifiedOnUrl;

    environmentParameters.push('lang=' + Language);
    environmentParameters.push('cur=' + Currency);

    _.each(require('ExternalPayment.Model').getParametersFromRequest(request), function(value, key) {
      environmentParameters.push(key.concat('=', value));
    });
  }
  catch (e)
  {
    Error = Application.processError(e);
  }

%> 

        
With
          <%
  var SiteSettings
  , siteType
  , Environment
  , Language
  , Currency
  , Error
  , Application
  , environmentParameters
  ,   parameters
  , external_payment
  , email_change_verification
  ;

  try
  {
    Application = require('Application');
    Environment = Application.getEnvironment(request);
    environmentParameters = [];
    SiteSettings = require('SiteSettings.Model').get();
    parameters = request.getAllParameters();

    // Access control, if you are not loged this will send you to the log in page
    if (!session.isLoggedIn2() || session.getCustomer().isGuest())
    {
      delete parameters.sitepath;
      parameters.origin = 'customercenter';

      if (parameters.fragment)
      {
        parameters.origin_hash = parameters.fragment;
        delete parameters.fragment;
      }

      return nlapiSetRedirectURL('EXTERNAL', SiteSettings.touchpoints.login, null, false, parameters);
    }
    else if (session.isLoggedIn2() && parameters.key)
    {
      try
      {
        session.verifyEmailChange(parameters.key)
        email_change_verification = true;
      }
      catch (e)
      {
        email_change_verification = e.details;
      }
    }

    siteType = SiteSettings.sitetype;

    Language = Environment.currentLanguage && Environment.currentLanguage.locale || '';
    Currency = Environment.currencyCodeSpecifiedOnUrl;

    environmentParameters.push('lang=' + Language);
    environmentParameters.push('cur=' + Currency);

    _.each(require('ExternalPayment.Model').getParametersFromRequest(request), function(value, key) {
      environmentParameters.push(key.concat('=', value));
    });
  }
  catch (e)
  {
    Error = Application.processError(e);
  }

%> 

        
Replace
          <% if (Error) { %>
<script>SC.ENVIRONMENT.contextError = <%= JSON.stringify(Error) %>;</script>
<% } %> 

        
With
          <% if (Error) { %>
<script>SC.ENVIRONMENT.contextError = <%= JSON.stringify(Error) %>;</script>
<% } %>
<% if (email_change_verification) { %>
<script>SC.SESSION.email_change_verification = '<%= email_change_verification %>';</script>
<% } %> 

        

Modify Overview/Javascript/Overview.Home.View.js

Replace
          define('Overview.Home.View'
, [
    'SC.Configuration'
  , 'Overview.Banner.View'
  , 'Overview.Profile.View'
  , 'Overview.Payment.View'
  , 'Overview.Shipping.View'
  , 'Backbone.CollectionView'
  , 'OrderHistory.List.Tracking.Number.View'
  , 'RecordViews.View'
  , 'Handlebars'

  , 'overview_home.tpl'

  , 'Backbone'
  , 'Backbone.CompositeView'
  , 'underscore'
  , 'Utils'
  ]
, function(
    Configuration
  , OverviewBannerView
  , OverviewProfileView
  , OverviewPaymentView
  , OverviewShippingView
  , BackboneCollectionView
  , OrderHistoryListTrackingNumberView
  , RecordViewsView
  , Handlebars

  , overview_home_tpl

  , Backbone
  , BackboneCompositeView
  , _
  ) 

        
With
          define('Overview.Home.View'
, [
    'SC.Configuration'
  , 'GlobalViews.Message.View'
  , 'Overview.Banner.View'
  , 'Overview.Profile.View'
  , 'Overview.Payment.View'
  , 'Overview.Shipping.View'
  , 'Backbone.CollectionView'
  , 'OrderHistory.List.Tracking.Number.View'
  , 'RecordViews.View'
  , 'Handlebars'

  , 'overview_home.tpl'

  , 'Backbone'
  , 'Backbone.CompositeView'
  , 'underscore'
  , 'Utils'
  ]
, function(
    Configuration
  , GlobalViewsMessageView
  , OverviewBannerView
  , OverviewProfileView
  , OverviewPaymentView
  , OverviewShippingView
  , BackboneCollectionView
  , OrderHistoryListTrackingNumberView
  , RecordViewsView
  , Handlebars

  , overview_home_tpl

  , Backbone
  , BackboneCompositeView
  , _
  ) 

        
Replace
            this.creditcards.on('reset destroy change add', this.showContent, this);
} 

        
With
            this.creditcards.on('reset destroy change add', this.showContent, this);

  if (SC.SESSION.email_change_verification)
  {
    this.email_change_verification = SC.SESSION.email_change_verification;
    delete SC.SESSION.email_change_verification;
  }
} 

        
Replace
          , 'Overview.Shipping': function()
  {
    return new OverviewShippingView({ model: this.defaultShippingAddress });
  } 

        
With
          , 'Overview.Shipping': function()
  {
    return new OverviewShippingView({ model: this.defaultShippingAddress });
  }

, 'Overview.Messages': function ()
  {
    if (this.email_change_verification)
    {
      return new GlobalViewsMessageView({
          message: this.email_change_verification === 'true' ? _('Your email has been changed successfully to <strong>').translate() + this.model.get('email') + '</strong>' : this.email_change_verification
        , type: this.email_change_verification === 'true' ? 'success' : 'error'
        , closable: true
      });
    }
  } 

        

Modify Overview/Templates/overview_home.tpl

Replace
          <section class="overview-home">
  <div class="overview-home-orders" data-permissions="{{purchasesPermissions}}"> 

        
With
          <section class="overview-home">
  <div data-view="Overview.Messages"></div>
  <div class="overview-home-orders" data-permissions="{{purchasesPermissions}}"> 

        

Add Profile/Javascript/Profile.ChangeEmailAddress.Model.js

          // Profile.ChangeEmailAddress.Model.js
// ===================================--
// View Model for changing user's email
// @module Profile
define(
  'Profile.ChangeEmailAddress.Model'
, [
    'Backbone'
  , 'underscore'
  , 'Utils'
  ]
, function (
    Backbone
  , _
  )
{
  'use strict';

  // @class Profile.ChangeEmailAddress.Model @extends Backbone.Model
  return Backbone.Model.extend(
  {
    urlRoot: 'services/Profile.Service.ss'
  , validation: {
      current_password:  { required: true, msg: _('Current password is required').translate() }
    , confirm_email: [
        { required: true, msg: _('Confirm Email is required').translate() }
      , { equalTo: 'new_email', msg: _('New Email and Confirm New Email do not match').translate() }
      ]
    , new_email: { required: true, msg: _('New Email is required').translate() }
    }
  });
}); 

        

Add Profile/Javascript/Profile.ChangeEmailAddress.View.js

          // @module Profile
define(
  'Profile.ChangeEmailAddress.View'
, [
    'GlobalViews.Message.View'
  , 'Backbone.FormView'
  , 'SC.Configuration'

  , 'profile_change_email.tpl'

  , 'Backbone'
  , 'underscore'
  , 'Utils'
  ]
, function (
    GlobalViewsMessageView
  , BackboneFormView
  , Configuration

  , profile_change_email_tpl

  , Backbone
  , _
  )
{
  'use strict';

  // @class Profile.ChangeEmailAddress.View @extends Backbone.View
  return Backbone.View.extend({

    template: profile_change_email_tpl

  , page_header: _('Change Email').translate()

  , title: _('Change Email').translate()

  , events: {
      'submit form': 'saveFormCustom'
    }

  , bindings: {
      '[name="current_password"]': 'current_password'
    , '[name="new_email"]': 'new_email'
    , '[name="confirm_email"]': 'confirm_email'
    }

  , initialize: function()
    {
      Backbone.View.prototype.initialize.apply(this, arguments);
      BackboneFormView.add(this);
    }

  , saveFormCustom: function ()
    {
      this.new_email = this.$('[name="new_email"]').val();
      BackboneFormView.saveForm.apply(this, arguments);
    }

  , showSuccess: function (placeholder)
    {
      var global_view_message = new GlobalViewsMessageView({
          message: _('A confirmation email has been sent to <strong>').translate() + this.new_email + '</strong>'
        , type: 'success'
        , closable: true
      });

      placeholder.html(global_view_message.render().$el.html());
    }
  });
}); 

        

Modify Profile/Javascript/Profile.Information.View.js

Replace
          define(
  'Profile.Information.View'
, [
    'SC.Configuration'
  , 'GlobalViews.Message.View'
  , 'Backbone.FormView'

  , 'profile_information.tpl'

  , 'Backbone'
  , 'underscore'
  , 'jQuery'
  , 'Utils'
  ]
, function (
    Configuration
  , GlobalViewsMessageView
  , BackboneFormView

  , profile_information_tpl

  , Backbone
  , _
  , jQuery
  )
{
  'use strict';

  // @class Profile.Information.View @extends Backbone.View
  return Backbone.View.extend({

    template: profile_information_tpl
  , page_header: _('Profile Information').translate()
  , title: _('Profile Information').translate()
  , attributes: {'class': 'ProfileInformationView'}
  , events: {
      'submit form': 'saveForm'
    , 'change input[data-type="phone"]': 'formatPhone'
    }

  , bindings: {
      '[name="firstname"]': 'firstname'
    , '[name="lastname"]': 'lastname'
    , '[name="companyname"]': 'companyname'
    , '[name="phone"]': 'phone'
    }

  , initialize: function()
    {
      BackboneFormView.add(this);
    }

  , formatPhone: function (e)
    {
      var $target = jQuery(e.target);
      $target.val(_($target.val()).formatPhone());
    }

  , showSuccess: function () 

        
With
          define(
  'Profile.Information.View'
, [
    'SC.Configuration'
  , 'GlobalViews.Message.View'
  , 'Backbone.FormView'

  , 'Profile.ChangeEmailAddress.Model'
  , 'Profile.ChangeEmailAddress.View'

  , 'profile_information.tpl'

  , 'Backbone'
  , 'underscore'
  , 'jQuery'
  , 'Utils'
  ]
, function (
    Configuration
  , GlobalViewsMessageView
  , BackboneFormView

  , ProfileChangeEmailModel
  , ProfileChangeEmailView

  , profile_information_tpl

  , Backbone
  , _
  , jQuery
  )
{
  'use strict';

  // @class Profile.Information.View @extends Backbone.View
  return Backbone.View.extend({

    template: profile_information_tpl
  , page_header: _('Profile Information').translate()
  , title: _('Profile Information').translate()
  , attributes: {'class': 'ProfileInformationView'}
  , events: {
      'submit form': 'saveForm'
    , 'change input[data-type="phone"]': 'formatPhone'
    , 'click [data-action="change-email"]': 'changeEmail'
    }

  , bindings: {
      '[name="firstname"]': 'firstname'
    , '[name="lastname"]': 'lastname'
    , '[name="companyname"]': 'companyname'
    , '[name="phone"]': 'phone'
    }

  , initialize: function(options)
    {
      BackboneFormView.add(this);
      this.application = options.application;
    }

  , formatPhone: function (e)
    {
      var $target = jQuery(e.target);
      $target.val(_($target.val()).formatPhone());
    }

  , changeEmail: function ()
    {
      var model = new ProfileChangeEmailModel(this.model.attributes);

      var view = new ProfileChangeEmailView({
        application: this.application
      , model: model
      });

      var self = this;

      model.on('save', function () {
        view.showSuccess(self.$('[data-type="alert-placeholder"]'));
      });

      view.useLayoutError = true;

      this.application.getLayout().showInModal(view);
    }

  , showSuccess: function () 

        

Add Profile/Sass/_profile-change-email.scss

          .profile-change-email-button-back {
    @extend .button-back;
}

.profile-change-email-button-back-icon {
    @extend .button-back-icon;
}

.profile-change-email-form-label {
    display: inline-block;
}

.profile-change-email-form-group-label-required {
    @extend .input-required;
}

.profile-change-email {
    @extend .address-edit;
}

.profile-change-email-form-title {}
.profile-change-email-form-area {}

.profile-change-email-form {
    margin-top: $sc-base-margin * 3;
}

.profile-change-email-form-group,
.profile-change-email-form-actions {
    @extend .control-group;
}

.profile-change-email-form-group-label {
    @extend .input-label
}

.profile-change-email-form-group-label-required {
}

.profile-change-email-form-group-input {
    @extend .input-large
}

.profile-change-email-form-actions-change {
    @extend .button-primary;
    @extend .button-medium;
}

.profile-change-email-group-form-controls{}

.profile-change-email-form-info-block{} 

        

Modify Profile/SuiteScript/Profile.Model.js

Replace
          if (data.email && data.email !== this.currentSettings.email && data.email === data.confirm_email)
{
  if(data.isGuest === 'T')
  {
    customerUpdate.email = data.email;
  }
  else
  {
    login.changeEmail(data.current_password, data.email, true);
  }
} 

        
With
          if (data.email && data.email !== this.currentSettings.email && data.email === data.confirm_email && data.isGuest === 'T')
{
  customerUpdate.email = data.email;
}
else if (data.new_email && data.new_email === data.confirm_email && data.new_email !== this.currentSettings.email)
{
  ModelsInit.session.login({
    email: data.email
  , password: data.current_password
  });
  login.changeEmail(data.current_password, data.new_email, true);
} 

        

Add Profile/Templates/profile_change_email.tpl

          <section class="profile-change-email">
  <div data-type="alert-placeholder"></div>
  <div class="profile-change-email-form-area">
    <form class="profile-change-email-form">
      <fieldset>
        <small class="profile-change-email-form-label">{{translate 'Required'}} <span class="profile-change-email-form-group-label-required">*</span></small>

        <div class="profile-change-email-form-group" data-input="new_email" data-validation="control-group">
          <label class="profile-change-email-form-group-label" for="new_email">{{translate 'New Email'}} <span class="profile-change-email-form-group-label-required">*</span></label>
          <div  class="profile-change-email-group-form-controls" data-validation="control">
            <input type="email" class="profile-change-email-form-group-input" id="new_email" name="new_email" value="" placeholder="{{translate 'your@example.com'}}">
          </div>
        </div>

        <div class="profile-change-email-form-group" data-input="confirm_email" data-validation="control-group">
          <label class="profile-change-email-form-group-label" for="confirm_email">{{translate 'Confirm New Email'}} <span class="profile-change-email-form-group-label-required">*</span></label>
          <div  class="profile-change-email-group-form-controls" data-validation="control">
            <input type="email" class="profile-change-email-form-group-input" id="confirm_email" name="confirm_email" value="" placeholder="{{translate 'your@example.com'}}">
          </div>
        </div>

        <div class="profile-change-email-form-group" data-input="current_email" data-validation="control-group">
          <label class="profile-change-email-form-group-label" for="current_password">{{translate 'Password'}} <span class="profile-change-email-form-group-label-required">*</span></label>
          <div  class="profile-change-email-group-form-controls" data-validation="control">
            <input type="password" class="profile-change-email-form-group-input" id="current_password" name="current_password" value="">
          </div>
        </div>
      </fieldset>
      <p class="profile-change-email-form-info-block"><small> {{translate 'You will still be able to login with your current email address and password until your new email address is verified.'}} </small></p>
      <div class="profile-change-email-form-actions">
        <button type="submit" class="profile-change-email-form-actions-change">{{translate 'Send Verification Email'}}</button>
      </div>
    </form>
  </div>
</section> 

        

Modify Profile/Templates/profile_information.tpl

Replace
          <p class="profile-information-input-email" id="email">{{email}}</p> 

        
With
          <p class="profile-information-input-email" id="email">{{email}} | <a class="profile-information-change-email-address" data-action="change-email">{{translate 'Change Address'}}</a></p> 

        

General Notices