From da54fa7e29a204c479698fea0d3b97b7cc4d3584 Mon Sep 17 00:00:00 2001 From: Karim Hamidou Date: Fri, 15 Jan 2016 11:27:14 -0800 Subject: [PATCH] [N1] Validate input in the signup dialog Summary: This diff bundles a number of small usability fixes to the "Create account" window. It notably: - forces users to enter required fields before moving on to the next step - validates email addresses and domain names Test Plan: Tested manually by going through all the possible auth flows. Reviewers: evan, juan, bengotow Reviewed By: bengotow Subscribers: bengotow Projects: #edgehill Differential Revision: https://phab.nylas.com/D2377 --- .../onboarding/lib/account-settings-page.cjsx | 61 ++++++++++++++++- .../onboarding/lib/account-types.coffee | 68 ++++++++++++++++--- src/regexp-utils.coffee | 3 + 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/internal_packages/onboarding/lib/account-settings-page.cjsx b/internal_packages/onboarding/lib/account-settings-page.cjsx index a4e14a9b7..93349f21f 100644 --- a/internal_packages/onboarding/lib/account-settings-page.cjsx +++ b/internal_packages/onboarding/lib/account-settings-page.cjsx @@ -1,4 +1,5 @@ React = require 'react' +_ = require 'underscore' {ipcRenderer, dialog, remote} = require 'electron' {RetinaImg} = require 'nylas-component-kit' {EdgehillAPI, NylasAPI, APIError, Actions} = require 'nylas-exports' @@ -88,12 +89,59 @@ class AccountSettingsPage extends React.Component settings[field] = event.target.checked else settings[field] = formatter(event.target.value) + + setting_field = _.find(@state.provider.settings, ((e) -> return e['name'] == field)) + + # If the field defines an isValid method, try to validate + # the input. + if setting_field?.isValid? + if not setting_field.isValid(event.target.value) + errorFields = _.uniq(@state.errorFieldNames.concat Array(field)) + @setState({errorFieldNames: errorFields}) + else + errorFields = _.uniq((x for x in @state.errorFieldNames when x != field)) + @setState({errorFieldNames: errorFields}) + @setState({settings}) + _noFormErrors: => + allFields = @state.provider.fields.concat(@state.provider.settings || []) + fieldsOnThisPage = allFields.filter(@_fieldOnCurrentPage) + fieldNames = _.pluck(fieldsOnThisPage, 'name') + return _.intersection(fieldNames, @state.errorFieldNames).length == 0 + + _fieldRequired: (f) => + return f?.required == true + + _allRequiredFieldsFilled: => + allFields = @state.provider.fields.concat(@state.provider.settings || []) + requiredFields = allFields.filter(@_fieldOnCurrentPage).filter(@_fieldRequired) + fields = _.extend(@state.fields, @state.settings) + + for field in requiredFields + fieldName = field['name'] + if not (fieldName of fields) or fields[fieldName] == '' + return false + + return true + _onValueChanged: (event) => field = event.target.dataset.field fields = @state.fields fields[field] = event.target.value + + provider_field = _.find(@state.provider.fields, ((e) -> return e['name'] == field)) + + # If the field defines an isValid method, try to validate + # the input. + if provider_field?.isValid? + if not provider_field.isValid(event.target.value) + errorFields = _.uniq(@state.errorFieldNames.concat [field]) + @setState({errorFieldNames: errorFields}) + else + errorFields = _.uniq((x for x in @state.errorFieldNames when x != field)) + @setState({errorFieldNames: errorFields}) + @setState({fields}) _onFieldKeyPress: (event) => @@ -180,14 +228,23 @@ class AccountSettingsPage extends React.Component _renderButton: => pages = @state.provider.pages || [] if pages.length > @state.pageNumber+1 - + # We're not on the last page. + if @_noFormErrors() and @_allRequiredFieldsFilled() + + else + # Disable the "Continue" button if the fields haven't been filled correctly. + else if @state.provider.name isnt 'gmail' if @state.tryingToAuthenticate else - + if @_noFormErrors() and @_allRequiredFieldsFilled() + + else + # Disable the "Add Account" button if the fields haven't been filled correctly. + _onNextButton: (event) => @setState(pageNumber: @state.pageNumber + 1) diff --git a/internal_packages/onboarding/lib/account-types.coffee b/internal_packages/onboarding/lib/account-types.coffee index da4f5ba33..9b912c51a 100644 --- a/internal_packages/onboarding/lib/account-types.coffee +++ b/internal_packages/onboarding/lib/account-types.coffee @@ -1,9 +1,15 @@ +RegExpUtils = require('nylas-exports').RegExpUtils +validEmail = (address) -> + return RegExpUtils.emailRegex().test(address) + +validDomain = (domain) -> + return RegExpUtils.domainRegex().test(domain) Providers = [ { name: 'gmail' - displayName: 'Gmail' + displayName: 'Gmail or Google Apps' icon: 'ic-settings-account-gmail.png' header_icon: 'setup-icon-provider-gmail.png' color: '#e99999' @@ -14,17 +20,23 @@ Providers = [ icon: 'ic-settings-account-eas.png' header_icon: 'setup-icon-provider-exchange.png' color: '#1ea2a3' + pages: ['Set up your Exchange account', 'Advanced settings'] fields: [ { name: 'name' type: 'text' placeholder: 'Ashton Letterman' label: 'Name' + required: true + page: 0 }, { name: 'email' - type: 'text' + type: 'email' placeholder: 'you@example.com' label: 'Email' + isValid: validEmail + required: true + page: 0 } ] settings: [ @@ -33,16 +45,21 @@ Providers = [ type: 'text' placeholder: 'MYCORP\\bob (if known)' label: 'Username (optional)' + page: 1 }, { name: 'password' type: 'password' placeholder: 'Password' label: 'Password' + required: true + page: 1 }, { - name: 'eas_server_host' - type: 'text' - placeholder: 'mail.company.com' - label: 'Exchange server (optional)' + name: 'eas_server_host' + type: 'text' + placeholder: 'mail.company.com' + label: 'Exchange server (optional)' + isValid: validDomain + page: 1 } ] }, { @@ -57,11 +74,16 @@ Providers = [ type: 'text' placeholder: 'Ashton Letterman' label: 'Name' + required: true + page: 0 }, { name: 'email' - type: 'text' + type: 'email' placeholder: 'you@icloud.com' label: 'Email' + isValid: validEmail + required: true + page: 0 } ] settings: [{ @@ -69,6 +91,8 @@ Providers = [ type: 'password' placeholder: 'Password' label: 'Password' + required: true + page: 0 }] }, { name: 'outlook' @@ -82,11 +106,16 @@ Providers = [ type: 'text' placeholder: 'Ashton Letterman' label: 'Name' + required: true + page: 0 }, { name: 'email' - type: 'text' + type: 'email' placeholder: 'you@hotmail.com' label: 'Email' + isValid: validEmail + required: true + page: 0 } ] settings: [{ @@ -94,6 +123,8 @@ Providers = [ type: 'password' placeholder: 'Password' label: 'Password' + required: true + page: 0 }] }, { name: 'yahoo' @@ -107,11 +138,16 @@ Providers = [ type: 'text' placeholder: 'Ashton Letterman' label: 'Name' + required: true + page: 0 }, { name: 'email' - type: 'text' + type: 'email' placeholder: 'you@yahoo.com' label: 'Email' + isValid: validEmail + required: true + page: 0 } ] settings: [{ @@ -119,6 +155,7 @@ Providers = [ type: 'password' placeholder: 'Password' label: 'Password' + required: true }] }, { name: 'imap' @@ -133,12 +170,15 @@ Providers = [ placeholder: 'Ashton Letterman' label: 'Name' page: 0 + required: true }, { name: 'email' - type: 'text' + type: 'email' placeholder: 'you@example.com' label: 'Email' + isValid: validEmail page: 0 + required: true } ] settings: [ @@ -148,6 +188,8 @@ Providers = [ placeholder: 'imap.domain.com' label: 'IMAP Server' page: 1 + required: true + isValid: validDomain }, { name: 'imap_port' type: 'text' @@ -170,18 +212,22 @@ Providers = [ placeholder: 'Username' label: 'Username' page: 1 + required: true }, { name: 'imap_password' type: 'password' placeholder: 'Password' label: 'Password' page: 1 + required: true }, { name: 'smtp_host' type: 'text' placeholder: 'smtp.domain.com' label: 'SMTP Server' page: 2 + required: true + isValid: validDomain }, { name: 'smtp_port' type: 'text' @@ -204,12 +250,14 @@ Providers = [ placeholder: 'Username' label: 'Username' page: 2 + required: true }, { name: 'smtp_password' type: 'password' placeholder: 'Password' label: 'Password' page: 2 + required: true } ] # }, { diff --git a/src/regexp-utils.coffee b/src/regexp-utils.coffee index 14756e9e1..e38838838 100644 --- a/src/regexp-utils.coffee +++ b/src/regexp-utils.coffee @@ -13,6 +13,9 @@ RegExpUtils = # https://en.wikipedia.org/wiki/Email_address#Local_part emailRegex: -> new RegExp(/([a-z.A-Z0-9!#$%&'*+\-/=?^_`{|}~;:]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,63})/g) + # http://stackoverflow.com/a/16463966 + domainRegex: -> new RegExp(/^(?!:\/\/)([a-zA-Z0-9]+\.)?[a-zA-Z0-9][a-zA-Z0-9-]+\.[a-zA-Z]{2,11}?$/i) + # https://regex101.com/r/zG7aW4/3 imageTagRegex: -> /]*src="([^"]*)"[^>]*>/g