Denali — Change Email Address Patch

This topic applies to

Applies to

SuiteCommerce Advanced | Denali

To update a Denali 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
   ,   cart_bootstrap
   ,   login
   ,   Application;
With
var SiteSettings
   ,   parameters
   ,   siteType
   ,   Environment
   ,   Language
   ,   Currency
   ,   Error
   ,   cart_bootstrap
   ,   login
   ,   Application
   ,   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'

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

  , ProfileModel
  , LoginView
  , RegisterView
  , CheckoutAsGuestView
  , BackboneCompositeView
  , Configuration

  , 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'

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

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

  , 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>

Modify MyAccountApplication/SuiteScript/my_account.ssp

Replace
<%

  var SiteSettings
  , siteType
  , Environment
  , Language
  , Currency
  , Error
  , Application;

  try
  {
    Application = require('Application');
    SiteSettings = require('SiteSettings.Model').get();
    siteType = SiteSettings.sitetype;

    Environment = Application.getEnvironment(session, request);

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

    // Access control, if you are not loged this will send you to the log in page
    if (!session.isLoggedIn() || session.getCustomer().isGuest())
    {
      var parameters = request.getAllParameters();

      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);
    }
  } catch (e) {
    Error = Application.processError(e);
  }

%>
With
<%

  var SiteSettings
  , siteType
  , Environment
  , Language
  , Currency
  , Error
  , Application
  , parameters
  , email_change_verification
  ;

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

    Environment = Application.getEnvironment(session, request);

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

    // 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;
      }
    }
  } 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_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"]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 ()
': '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 ()

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
define(
  'Profile.Model'
, ['SC.Model', 'Utils']
, function (SCModel, Utils)
With
define(
  'Profile.Model'
, ['SC.Model', 'Models.Init', 'Utils']
, function (SCModel, ModelsInit, Utils)
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)
{
  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@email.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@email.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" name="email" >{{email}}</p>
With
<p class="profile-information-input-email" id="email" name="email" >{{email}} | <a class="profile-information-change-email-address" data-action="change-email">{{translate 'Change Address'}}</a></p>