diff --git a/src/components/formik-forms/formik-input.jsx b/src/components/formik-forms/formik-input.jsx
index 26bff6a82..908fc8890 100644
--- a/src/components/formik-forms/formik-input.jsx
+++ b/src/components/formik-forms/formik-input.jsx
@@ -11,6 +11,7 @@ require('./formik-input.scss');
const FormikInput = ({
className,
error,
+ toolTip,
validationClassName,
wrapperClassName,
...props
@@ -31,10 +32,17 @@ const FormikInput = ({
)}
{...props}
/>
- {error && (
+ {error ? (
+ ) : toolTip && (
+
)}
@@ -44,6 +52,7 @@ const FormikInput = ({
FormikInput.propTypes = {
className: PropTypes.string,
error: PropTypes.string,
+ toolTip: PropTypes.string,
type: PropTypes.string,
validationClassName: PropTypes.string,
wrapperClassName: PropTypes.string
diff --git a/src/components/formik-forms/formik-select.jsx b/src/components/formik-forms/formik-select.jsx
index 4578d8a64..548e335e9 100644
--- a/src/components/formik-forms/formik-select.jsx
+++ b/src/components/formik-forms/formik-select.jsx
@@ -39,6 +39,7 @@ const FormikSelect = ({
)}
diff --git a/src/components/forms/validation-message.jsx b/src/components/forms/validation-message.jsx
index 10c05ce13..717128e39 100644
--- a/src/components/forms/validation-message.jsx
+++ b/src/components/forms/validation-message.jsx
@@ -5,14 +5,24 @@ const React = require('react');
require('./validation-message.scss');
const ValidationMessage = props => (
-
+
{props.message}
);
ValidationMessage.propTypes = {
className: PropTypes.string,
- message: PropTypes.string
+ message: PropTypes.string,
+ mode: PropTypes.string
};
module.exports = ValidationMessage;
diff --git a/src/components/forms/validation-message.scss b/src/components/forms/validation-message.scss
index d10e30e27..acb19820a 100644
--- a/src/components/forms/validation-message.scss
+++ b/src/components/forms/validation-message.scss
@@ -11,7 +11,6 @@
margin-left: $arrow-border-width;
border: 1px solid $active-gray;
border-radius: 5px;
- background-color: $ui-orange;
padding: 1rem;
max-width: 18.75rem;
min-height: 1rem;
@@ -31,7 +30,6 @@
border-left: 1px solid $active-gray;
border-radius: 5px;
- background-color: $ui-orange;
width: $arrow-border-width;
height: $arrow-border-width;
@@ -52,3 +50,19 @@
}
}
}
+
+.validation-error {
+ background-color: $ui-orange;
+
+ &:before {
+ background-color: $ui-orange;
+ }
+}
+
+.validation-info {
+ background-color: $ui-blue;
+
+ &:before {
+ background-color: $ui-blue;
+ }
+}
diff --git a/src/components/join-flow/username-step.jsx b/src/components/join-flow/username-step.jsx
index 5e56440b8..f588360cb 100644
--- a/src/components/join-flow/username-step.jsx
+++ b/src/components/join-flow/username-step.jsx
@@ -21,16 +21,26 @@ class UsernameStep extends React.Component {
super(props);
bindAll(this, [
'handleChangeShowPassword',
+ 'handleFocused',
'handleValidSubmit',
'validatePasswordIfPresent',
'validatePasswordConfirmIfPresent',
'validateUsernameIfPresent',
'validateForm'
]);
+ this.state = {
+ focused: null,
+ showPassword: false
+ };
}
handleChangeShowPassword () {
this.setState({showPassword: !this.state.showPassword});
}
+ // track the currently focused input field, to determine whether each field should
+ // display a tooltip. (We only display it if a field is focused and has never been touched.)
+ handleFocused (fieldName) {
+ this.setState({focused: fieldName});
+ }
// 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
@@ -109,7 +119,9 @@ class UsernameStep extends React.Component {
handleSubmit,
isSubmitting,
setFieldError,
+ setFieldTouched,
setFieldValue,
+ touched,
validateField,
values
} = props;
@@ -135,14 +147,18 @@ class UsernameStep extends React.Component {
id="username"
name="username"
placeholder={this.props.intl.formatMessage({id: 'general.username'})}
+ toolTip={this.state.focused === 'username' && !touched.username &&
+ this.props.intl.formatMessage({id: 'registration.usernameAdviceShort'})}
validate={this.validateUsernameIfPresent}
validationClassName="validation-full-width-input"
/* eslint-disable react/jsx-no-bind */
onBlur={() => validateField('username')}
onChange={e => {
setFieldValue('username', e.target.value);
+ setFieldTouched('username');
setFieldError('username', null);
}}
+ onFocus={() => this.handleFocused('username')}
/* eslint-enable react/jsx-no-bind */
/>
@@ -157,6 +173,8 @@ class UsernameStep extends React.Component {
id="password"
name="password"
placeholder={this.props.intl.formatMessage({id: 'general.password'})}
+ toolTip={this.state.focused === 'password' && !touched.password &&
+ this.props.intl.formatMessage({id: 'registration.passwordAdviceShort'})}
type={values.showPassword ? 'text' : 'password'}
/* eslint-disable react/jsx-no-bind */
validate={password => this.validatePasswordIfPresent(password, values.username)}
@@ -164,8 +182,10 @@ class UsernameStep extends React.Component {
onBlur={() => validateField('password')}
onChange={e => {
setFieldValue('password', e.target.value);
+ setFieldTouched('password');
setFieldError('password', null);
}}
+ onFocus={() => this.handleFocused('password')}
/* eslint-enable react/jsx-no-bind */
/>
@@ -187,13 +213,13 @@ class UsernameStep extends React.Component {
values.passwordConfirm)
}
validationClassName="validation-full-width-input"
- onBlur={() =>
- validateField('passwordConfirm')
- }
+ onBlur={() => validateField('passwordConfirm')}
onChange={e => {
setFieldValue('passwordConfirm', e.target.value);
+ setFieldTouched('passwordConfirm');
setFieldError('passwordConfirm', null);
}}
+ onFocus={() => this.handleFocused('passwordConfirm')}
/* eslint-enable react/jsx-no-bind */
/>
diff --git a/src/l10n.json b/src/l10n.json
index 92393beb0..e161a64e1 100644
--- a/src/l10n.json
+++ b/src/l10n.json
@@ -180,6 +180,7 @@
"registration.nextStep": "Next Step",
"registration.notYou": "Not you? Log in as another user",
"registration.optIn": "Send me updates on using Scratch in educational settings",
+ "registration.passwordAdviceShort": "Write it down so you remember. Don’t share it with others!",
"registration.personalStepTitle": "Personal Information",
"registration.personalStepDescription": "Your individual responses will not be displayed publicly, and will be kept confidential and secure",
"registration.private": "Scratch will always keep this information private.",
@@ -190,6 +191,7 @@
"registration.usernameStepDescription": "Fill in the following forms to request an account. The approval process may take up to one day.",
"registration.usernameStepDescriptionNonEducator": "Create projects, share ideas, make friends. It’s free!",
"registration.usernameStepRealName": "Please do not use any portion of your real name in your username.",
+ "registration.usernameAdviceShort": "Don't use your real name",
"registration.studentUsernameStepDescription": "You can make games, animations, and stories using Scratch. Setting up an account is easy and it's free. Fill in the form below to get started.",
"registration.studentUsernameStepHelpText": "Already have a Scratch account?",
"registration.studentUsernameStepTooltip": "You'll need to create a new Scratch account to join this class.",
diff --git a/test/unit/components/formik-input.test.jsx b/test/unit/components/formik-input.test.jsx
index 0a59f428c..9d4585c69 100644
--- a/test/unit/components/formik-input.test.jsx
+++ b/test/unit/components/formik-input.test.jsx
@@ -4,15 +4,29 @@ import FormikInput from '../../../src/components/formik-forms/formik-input.jsx';
import {Formik} from 'formik';
describe('FormikInput', () => {
- test('No validation message without an error', () => {
+ test('No validation message without an error or a tooltip', () => {
const component = mountWithIntl(
);
expect(component.find('ValidationMessage').exists()).toEqual(false);
+ expect(component.find('div.validation-error').exists()).toEqual(false);
+ expect(component.find('div.validation-info').exists()).toEqual(false);
+ });
+
+ test('No validation message with blank error or tooltip', () => {
+ const component = mountWithIntl(
+
+
+
+ );
+ expect(component.find('ValidationMessage').exists()).toEqual(false);
+ expect(component.find('div.validation-error').exists()).toEqual(false);
+ expect(component.find('div.validation-info').exists()).toEqual(false);
});
test('Validation message shown when error given', () => {
@@ -24,5 +38,34 @@ describe('FormikInput', () => {
);
expect(component.find('ValidationMessage').exists()).toEqual(true);
+ expect(component.find('div.validation-error').exists()).toEqual(true);
+ expect(component.find('div.validation-info').exists()).toEqual(false);
+ });
+
+ test('Tooltip shown when tooltip given', () => {
+ const component = mountWithIntl(
+
+
+
+ );
+ expect(component.find('ValidationMessage').exists()).toEqual(true);
+ expect(component.find('div.validation-error').exists()).toEqual(false);
+ expect(component.find('div.validation-info').exists()).toEqual(true);
+ });
+
+ test('If both error and tooltip messages, error takes precedence', () => {
+ const component = mountWithIntl(
+
+
+
+ );
+ expect(component.find('ValidationMessage').exists()).toEqual(true);
+ expect(component.find('div.validation-error').exists()).toEqual(true);
+ expect(component.find('div.validation-info').exists()).toEqual(false);
});
});