Merge pull request #3236 from benjiwheeler/join-flow-highlighting

handle username validation errors states better
This commit is contained in:
Benjamin Wheeler 2019-08-13 17:52:00 -04:00 committed by GitHub
commit 46de5a23e1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 81 additions and 25 deletions

View file

@ -5,9 +5,8 @@ import {Field} from 'formik';
const ValidationMessage = require('../forms/validation-message.jsx');
require('./input.scss');
require('../forms/input.scss');
require('../forms/row.scss');
require('./formik-input.scss');
const FormikInput = ({
className,
@ -27,6 +26,7 @@ const FormikInput = ({
<Field
className={classNames(
'input',
{fail: error},
className
)}
{...props}

View file

@ -0,0 +1,28 @@
@import "../../colors";
.input {
height: 2.75rem;
border-radius: .5rem;
background-color: $ui-white;
margin-bottom: .5rem;
transition: all .5s ease;
border: 1px solid $active-gray;
padding: 0 1rem;
color: $type-gray;
font-size: .875rem;
&:focus {
box-shadow: 0 0 0 .25rem $ui-blue-25percent;
outline: none;
border: 1px solid $ui-blue;
}
&.fail {
border: 1px solid $ui-orange;
&:focus {
box-shadow: 0 0 0 .25rem $ui-orange-25percent;
outline: none;
}
}
}

View file

@ -36,20 +36,26 @@ class UsernameStep extends React.Component {
// we allow username to be empty on blur, since you might not have typed anything yet
validateUsernameIfPresent (username) {
if (!username) return null; // skip validation if username is blank; null indicates valid
// if username is not blank, run both local and remote validations
const localResult = validate.validateUsernameLocally(username);
if (localResult.valid) {
return validate.validateUsernameRemotely(username).then(
remoteResult => {
if (remoteResult.valid) return null;
// there may be multiple validation errors. Prioritize vulgarity, then
// length, then having invalid chars, then all other remote reports
if (remoteResult.valid === false && remoteResult.errMsgId === 'registration.validationUsernameVulgar') {
return this.props.intl.formatMessage({id: remoteResult.errMsgId});
} else if (localResult.valid === false) {
return this.props.intl.formatMessage({id: localResult.errMsgId});
} else if (remoteResult.valid === false) {
return this.props.intl.formatMessage({id: remoteResult.errMsgId});
}
return null;
}
);
}
return this.props.intl.formatMessage({id: localResult.errMsgId});
}
validatePasswordIfPresent (password) {
validatePasswordIfPresent (password, username) {
if (!password) return null; // skip validation if password is blank; null indicates valid
const localResult = validate.validatePassword(password);
const localResult = validate.validatePassword(password, username);
if (localResult.valid) return null;
return this.props.intl.formatMessage({id: localResult.errMsgId});
}
@ -69,13 +75,10 @@ class UsernameStep extends React.Component {
if (!usernameResult.valid) {
errors.username = this.props.intl.formatMessage({id: usernameResult.errMsgId});
}
const passwordResult = validate.validatePassword(values.password);
const passwordResult = validate.validatePassword(values.password, values.username);
if (!passwordResult.valid) {
errors.password = this.props.intl.formatMessage({id: passwordResult.errMsgId});
}
if (values.password === values.username) {
errors.password = this.props.intl.formatMessage({id: 'registration.validationPasswordNotUsername'});
}
const passwordConfirmResult = validate.validatePasswordConfirm(values.password, values.passwordConfirm);
if (!passwordConfirmResult.valid) {
errors.passwordConfirm = this.props.intl.formatMessage({id: passwordConfirmResult.errMsgId});
@ -105,6 +108,8 @@ class UsernameStep extends React.Component {
errors,
handleSubmit,
isSubmitting,
setFieldError,
setFieldValue,
validateField,
values
} = props;
@ -123,15 +128,20 @@ class UsernameStep extends React.Component {
</div>
<FormikInput
className={classNames(
'join-flow-input',
{fail: errors.username}
'join-flow-input'
)}
error={errors.username}
id="username"
name="username"
validate={this.validateUsernameIfPresent}
validationClassName="validation-full-width-input"
onBlur={() => validateField('username')} // eslint-disable-line react/jsx-no-bind
/* eslint-disable react/jsx-no-bind */
onBlur={() => validateField('username')}
onChange={e => {
setFieldValue('username', e.target.value);
setFieldError('username', null);
}}
/* eslint-enable react/jsx-no-bind */
/>
<div className="join-flow-password-section">
<div className="join-flow-input-title">
@ -139,23 +149,25 @@ class UsernameStep extends React.Component {
</div>
<FormikInput
className={classNames(
'join-flow-input',
{fail: errors.password}
'join-flow-input'
)}
error={errors.password}
id="password"
name="password"
type={this.state.showPassword ? 'text' : 'password'}
validate={this.validatePasswordIfPresent}
validationClassName="validation-full-width-input"
/* eslint-disable react/jsx-no-bind */
validate={password => this.validatePasswordIfPresent(password, values.username)}
validationClassName="validation-full-width-input"
onBlur={() => validateField('password')}
onChange={e => {
setFieldValue('password', e.target.value);
setFieldError('password', null);
}}
/* eslint-enable react/jsx-no-bind */
/>
<FormikInput
className={classNames(
'join-flow-input',
{fail: errors.passwordConfirm}
'join-flow-input'
)}
error={errors.passwordConfirm}
id="passwordConfirm"
@ -170,6 +182,10 @@ class UsernameStep extends React.Component {
onBlur={() =>
validateField('passwordConfirm')
}
onChange={e => {
setFieldValue('passwordConfirm', e.target.value);
setFieldError('passwordConfirm', null);
}}
/* eslint-enable react/jsx-no-bind */
/>
<div className="join-flow-input-title">

View file

@ -40,13 +40,21 @@ module.exports.validateUsernameRemotely = username => (
})
);
module.exports.validatePassword = password => {
/**
* Validate password value, optionally also considering username value
* @param {string} password password value to validate
* @param {string} username username value to compare
* @return {object} {valid: boolean, errMsgId: string}
*/
module.exports.validatePassword = (password, username) => {
if (!password) {
return {valid: false, errMsgId: 'general.required'};
} else if (password.length < 6) {
return {valid: false, errMsgId: 'registration.validationPasswordLength'};
} else if (password === 'password') {
return {valid: false, errMsgId: 'registration.validationPasswordNotEquals'};
} else if (username && password === username) {
return {valid: false, errMsgId: 'registration.validationPasswordNotUsername'};
}
return {valid: true};
};

View file

@ -39,6 +39,10 @@ describe('unit test lib/validate.js', () => {
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordLength'});
response = validate.validatePassword('password');
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordNotEquals'});
response = validate.validatePassword('abcdefg', 'abcdefg');
expect(response).toEqual({valid: false, errMsgId: 'registration.validationPasswordNotUsername'});
response = validate.validatePassword('abcdefg', 'abcdefG');
expect(response).toEqual({valid: true});
});
test('validate password confirm', () => {