From aa7d9a0bb7c7767e03ed339e81f721a0835b2706 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 10 Jan 2018 11:46:03 -0600 Subject: Refactor dispatcher sessions path --- app/assets/javascripts/pages/sessions/new/index.js | 11 ++ .../pages/sessions/new/oauth_remember_me.js | 32 +++++ .../pages/sessions/new/signin_tabs_memoizer.js | 53 ++++++++ .../pages/sessions/new/username_validator.js | 133 +++++++++++++++++++++ 4 files changed, 229 insertions(+) create mode 100644 app/assets/javascripts/pages/sessions/new/index.js create mode 100644 app/assets/javascripts/pages/sessions/new/oauth_remember_me.js create mode 100644 app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js create mode 100644 app/assets/javascripts/pages/sessions/new/username_validator.js (limited to 'app/assets/javascripts/pages/sessions/new') diff --git a/app/assets/javascripts/pages/sessions/new/index.js b/app/assets/javascripts/pages/sessions/new/index.js new file mode 100644 index 00000000000..f163557babc --- /dev/null +++ b/app/assets/javascripts/pages/sessions/new/index.js @@ -0,0 +1,11 @@ +import UsernameValidator from './username_validator'; +import SigninTabsMemoizer from './signin_tabs_memoizer'; +import OAuthRememberMe from './oauth_remember_me'; + +export default () => { + new UsernameValidator(); // eslint-disable-line no-new + new SigninTabsMemoizer(); // eslint-disable-line no-new + new OAuthRememberMe({ // eslint-disable-line no-new + container: $('.omniauth-container'), + }).bindEvents(); +}; diff --git a/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js new file mode 100644 index 00000000000..ffc2dd6bbca --- /dev/null +++ b/app/assets/javascripts/pages/sessions/new/oauth_remember_me.js @@ -0,0 +1,32 @@ +/** + * OAuth-based login buttons have a separate "remember me" checkbox. + * + * Toggling this checkbox adds/removes a `remember_me` parameter to the + * login buttons' href, which is passed on to the omniauth callback. + **/ + +export default class OAuthRememberMe { + constructor(opts = {}) { + this.container = opts.container || ''; + this.loginLinkSelector = '.oauth-login'; + } + + bindEvents() { + $('#remember_me', this.container).on('click', this.toggleRememberMe); + } + + // eslint-disable-next-line class-methods-use-this + toggleRememberMe(event) { + const rememberMe = $(event.target).is(':checked'); + + $('.oauth-login', this.container).each((i, element) => { + const href = $(element).attr('href'); + + if (rememberMe) { + $(element).attr('href', `${href}?remember_me=1`); + } else { + $(element).attr('href', href.replace('?remember_me=1', '')); + } + }); + } +} diff --git a/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js new file mode 100644 index 00000000000..20255398047 --- /dev/null +++ b/app/assets/javascripts/pages/sessions/new/signin_tabs_memoizer.js @@ -0,0 +1,53 @@ +/* eslint no-param-reassign: ["error", { "props": false }]*/ +/* eslint no-new: "off" */ +import AccessorUtilities from './lib/utils/accessor'; + +/** + * Memorize the last selected tab after reloading a page. + * Does that setting the current selected tab in the localStorage + */ +export default class SigninTabsMemoizer { + constructor({ currentTabKey = 'current_signin_tab', tabSelector = 'ul.nav-tabs' } = {}) { + this.currentTabKey = currentTabKey; + this.tabSelector = tabSelector; + this.isLocalStorageAvailable = AccessorUtilities.isLocalStorageAccessSafe(); + + this.bootstrap(); + } + + bootstrap() { + const tabs = document.querySelectorAll(this.tabSelector); + if (tabs.length > 0) { + tabs[0].addEventListener('click', (e) => { + if (e.target && e.target.nodeName === 'A') { + const anchorName = e.target.getAttribute('href'); + this.saveData(anchorName); + } + }); + } + + this.showTab(); + } + + showTab() { + const anchorName = this.readData(); + if (anchorName) { + const tab = document.querySelector(`${this.tabSelector} a[href="${anchorName}"]`); + if (tab) { + tab.click(); + } + } + } + + saveData(val) { + if (!this.isLocalStorageAvailable) return undefined; + + return window.localStorage.setItem(this.currentTabKey, val); + } + + readData() { + if (!this.isLocalStorageAvailable) return null; + + return window.localStorage.getItem(this.currentTabKey); + } +} diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js new file mode 100644 index 00000000000..bb34d5d2008 --- /dev/null +++ b/app/assets/javascripts/pages/sessions/new/username_validator.js @@ -0,0 +1,133 @@ +/* eslint-disable comma-dangle, consistent-return, class-methods-use-this, arrow-parens, no-param-reassign, max-len */ + +import _ from 'underscore'; + +const debounceTimeoutDuration = 1000; +const invalidInputClass = 'gl-field-error-outline'; +const successInputClass = 'gl-field-success-outline'; +const unavailableMessageSelector = '.username .validation-error'; +const successMessageSelector = '.username .validation-success'; +const pendingMessageSelector = '.username .validation-pending'; +const invalidMessageSelector = '.username .gl-field-error'; + +export default class UsernameValidator { + constructor() { + this.inputElement = $('#new_user_username'); + this.inputDomElement = this.inputElement.get(0); + this.state = { + available: false, + valid: false, + pending: false, + empty: true + }; + + const debounceTimeout = _.debounce((username) => { + this.validateUsername(username); + }, debounceTimeoutDuration); + + this.inputElement.on('keyup.username_check', () => { + const username = this.inputElement.val(); + + this.state.valid = this.inputDomElement.validity.valid; + this.state.empty = !username.length; + + if (this.state.valid) { + return debounceTimeout(username); + } + + this.renderState(); + }); + + // Override generic field validation + this.inputElement.on('invalid', this.interceptInvalid.bind(this)); + } + + renderState() { + // Clear all state + this.clearFieldValidationState(); + + if (this.state.valid && this.state.available) { + return this.setSuccessState(); + } + + if (this.state.empty) { + return this.clearFieldValidationState(); + } + + if (this.state.pending) { + return this.setPendingState(); + } + + if (!this.state.available) { + return this.setUnavailableState(); + } + + if (!this.state.valid) { + return this.setInvalidState(); + } + } + + interceptInvalid(event) { + event.preventDefault(); + event.stopPropagation(); + } + + validateUsername(username) { + if (this.state.valid) { + this.state.pending = true; + this.state.available = false; + this.renderState(); + return $.ajax({ + type: 'GET', + url: `${gon.relative_url_root}/users/${username}/exists`, + dataType: 'json', + success: (res) => this.setAvailabilityState(res.exists) + }); + } + } + + setAvailabilityState(usernameTaken) { + if (usernameTaken) { + this.state.valid = false; + this.state.available = false; + } else { + this.state.available = true; + } + this.state.pending = false; + this.renderState(); + } + + clearFieldValidationState() { + this.inputElement.siblings('p').hide(); + + this.inputElement.removeClass(invalidInputClass) + .removeClass(successInputClass); + } + + setUnavailableState() { + const $usernameUnavailableMessage = this.inputElement.siblings(unavailableMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $usernameUnavailableMessage.show(); + } + + setSuccessState() { + const $usernameSuccessMessage = this.inputElement.siblings(successMessageSelector); + this.inputElement.addClass(successInputClass).removeClass(invalidInputClass); + $usernameSuccessMessage.show(); + } + + setPendingState() { + const $usernamePendingMessage = $(pendingMessageSelector); + if (this.state.pending) { + $usernamePendingMessage.show(); + } else { + $usernamePendingMessage.hide(); + } + } + + setInvalidState() { + const $inputErrorMessage = $(invalidMessageSelector); + this.inputElement.addClass(invalidInputClass).removeClass(successInputClass); + $inputErrorMessage.show(); + } +} -- cgit v1.2.1