Merge pull request #120 from edx/adeel/van_325_form_fields_ux_feedback

Form fields ux feedback
This commit is contained in:
adeel khan
2021-02-07 17:33:49 +05:00
committed by GitHub
13 changed files with 584 additions and 650 deletions

View File

@@ -0,0 +1,148 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { injectIntl } from '@edx/frontend-platform/i18n';
import {
Form,
Input,
ValidationFormGroup,
} from '@edx/paragon';
const AuthnCustomValidationFormGroup = (props) => {
const { onClick, onChange, onBlur } = props;
const [showHelpText, setShowHelpText] = useState(false);
const [showLabelText, setShowLabelText] = useState(false);
const [showPlaceholder, setShowPlaceHolder] = useState(true);
// handler code that need to be invoked via input
const onClickHandler = (e, clickCb) => {
setShowHelpText(true);
setShowLabelText(true);
setShowPlaceHolder(false);
if (clickCb) {
clickCb(e);
}
};
const onBlurHandler = (e, blurCb) => {
setShowHelpText(false);
setShowLabelText(false);
setShowPlaceHolder(true);
if (blurCb) {
blurCb(e);
}
};
const onChangeHandler = (e, changeCb) => {
if (changeCb) {
changeCb(e);
}
};
const onOptionalHandler = (e, clickCb) => { clickCb(e); };
const showLabel = () => {
const fieldLabel = (!props.optionalFieldCheckbox && showLabelText) ? (
<Form.Label htmlFor={props.for} className="h6 pt-10">
{props.label}
</Form.Label>
) : <span />;
return fieldLabel;
};
const showOptional = () => {
const additionalField = props.optionalFieldCheckbox ? (
<p role="presentation" id="additionalFields" className="mb-0 small" onClick={(e) => onOptionalHandler(e, onClick)}>
{props.checkboxMessage}
</p>
) : <span />;
return additionalField;
};
const inputProps = {
name: props.name,
id: props.for,
type: props.type,
value: props.value,
};
inputProps.placeholder = showPlaceholder ? props.label : '';
inputProps.onChange = (e) => onChangeHandler(e, onChange);
inputProps.onClick = (e) => onClickHandler(e, onClick);
inputProps.onBlur = (e) => onBlurHandler(e, onBlur);
inputProps.onFocus = (e) => onClickHandler(e, null);
if (props.type === 'select') {
inputProps.options = props.selectOptions;
}
if (props.type === 'checkbox') {
inputProps.checked = props.isChecked;
}
const validationGroupProps = {
for: props.for,
};
if (!props.optionalFieldCheckbox) {
validationGroupProps.invalid = props.invalid;
validationGroupProps.invalidMessage = props.invalidMessage;
validationGroupProps.helpText = showHelpText ? props.helpText : '';
} else {
validationGroupProps.className = props.optionalFieldCheckbox ? 'custom-control pt-10 mb-0' : '';
}
if (props.className) {
validationGroupProps.className = props.className;
}
return (
<ValidationFormGroup
{...validationGroupProps}
>
{showLabel()}
<Input
{...inputProps}
required
/>
{showOptional()}
</ValidationFormGroup>
);
};
AuthnCustomValidationFormGroup.defaultProps = {
name: '',
for: '',
label: '',
optionalFieldCheckbox: false,
type: '',
value: '',
invalid: false,
invalidMessage: '',
helpText: '',
className: '',
onClick: null,
onBlur: null,
onChange: null,
isChecked: false,
checkboxMessage: '',
selectOptions: null,
};
AuthnCustomValidationFormGroup.propTypes = {
name: PropTypes.string,
for: PropTypes.string,
label: PropTypes.string,
type: PropTypes.string,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool,
]),
invalid: PropTypes.bool,
invalidMessage: PropTypes.string,
helpText: PropTypes.string,
className: PropTypes.string,
isChecked: PropTypes.bool,
optionalFieldCheckbox: PropTypes.bool,
onClick: PropTypes.func,
onBlur: PropTypes.func,
onChange: PropTypes.func,
checkboxMessage: PropTypes.string,
selectOptions: PropTypes.arrayOf(PropTypes.shape({
key: PropTypes.string,
value: PropTypes.string,
})),
};
export default injectIntl(AuthnCustomValidationFormGroup);

View File

@@ -7,6 +7,7 @@ export { default as SocialAuthProviders } from './SocialAuthProviders';
export { default as ThirdPartyAuthAlert } from './ThirdPartyAuthAlert';
export { default as InstitutionLogistration } from './InstitutionLogistration';
export { RenderInstitutionButton } from './InstitutionLogistration';
export { default as AuthnValidationFormGroup } from './AuthnValidationFormGroup';
export { default as reducer } from './data/reducers';
export { default as saga } from './data/sagas';
export { storeName } from './data/selectors';

View File

@@ -11,9 +11,7 @@ import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import {
Alert,
Form,
Input,
StatefulButton,
ValidationFormGroup,
} from '@edx/paragon';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faSpinner } from '@fortawesome/free-solid-svg-icons';
@@ -21,7 +19,11 @@ import { faSpinner } from '@fortawesome/free-solid-svg-icons';
import { forgotPassword } from './data/actions';
import { forgotPasswordResultSelector } from './data/selectors';
import RequestInProgressAlert from './RequestInProgressAlert';
import messages from './messages';
import {
AuthnValidationFormGroup,
} from '../common-components';
import APIFailureMessage from '../common-components/APIFailureMessage';
import { INTERNAL_SERVER_ERROR, LOGIN_PAGE, VALID_EMAIL_REGEX } from '../data/constants';
import LoginHelpLinks from '../login/LoginHelpLinks';
@@ -95,26 +97,20 @@ const ForgotPasswordPage = (props) => {
<p className="mb-4">
{intl.formatMessage(messages['forgot.password.page.instructions'])}
</p>
<ValidationFormGroup
className="mb-0 w-100"
for="email"
<AuthnValidationFormGroup
label={intl.formatMessage(messages['forgot.password.page.email.field.label'])}
for="forgot-password-input"
name="email"
type="email"
invalid={validationError !== ''}
invalidMessage={validationError}
placeholder="username@domain.com"
value={values.email}
onBlur={() => getValidationMessage(values.email)}
onChange={e => setFieldValue('email', e.target.value)}
helpText={intl.formatMessage(messages['forgot.password.email.help.text'], { platformName })}
>
<Form.Label htmlFor="forgot-password-input" className="h6 mr-1">
{intl.formatMessage(messages['forgot.password.page.email.field.label'])}
</Form.Label>
<Input
name="email"
id="forgot-password-input"
type="email"
placeholder="username@domain.com"
value={values.email}
onBlur={() => getValidationMessage(values.email)}
onChange={e => setFieldValue('email', e.target.value)}
/>
</ValidationFormGroup>
className="mb-0 w-100"
/>
<LoginHelpLinks page="forgot-password" />
<StatefulButton
type="submit"

View File

@@ -150,7 +150,7 @@ describe('ForgotPasswordPage', () => {
});
forgotPasswordPage.update();
expect(forgotPasswordPage.find('#email-invalid-feedback').text()).toEqual(validationMessage);
expect(forgotPasswordPage.find('#forgot-password-input-invalid-feedback').text()).toEqual(validationMessage);
});
it('check cookie rendered', () => {

View File

@@ -23,29 +23,22 @@ exports[`ForgotPasswordPage should match default section snapshot 1`] = `
<div
className="form-group mb-0 w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="forgot-password-input"
>
Email
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="forgot-password-input"
name="email"
onBlur={[Function]}
onChange={[Function]}
placeholder="username@domain.com"
onClick={[Function]}
onFocus={[Function]}
placeholder="Email"
required={true}
type="email"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
The email address you used to register with edX
</small>
<span />
</div>
<button
className="mt-2 field-link small"
@@ -144,29 +137,22 @@ exports[`ForgotPasswordPage should match forbidden section snapshot 1`] = `
<div
className="form-group mb-0 w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="forgot-password-input"
>
Email
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="forgot-password-input"
name="email"
onBlur={[Function]}
onChange={[Function]}
placeholder="username@domain.com"
onClick={[Function]}
onFocus={[Function]}
placeholder="Email"
required={true}
type="email"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
The email address you used to register with edX
</small>
<span />
</div>
<button
className="mt-2 field-link small"
@@ -250,29 +236,22 @@ exports[`ForgotPasswordPage should match pending section snapshot 1`] = `
<div
className="form-group mb-0 w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="forgot-password-input"
>
Email
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="forgot-password-input"
name="email"
onBlur={[Function]}
onChange={[Function]}
placeholder="username@domain.com"
onClick={[Function]}
onFocus={[Function]}
placeholder="Email"
required={true}
type="email"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
The email address you used to register with edX
</small>
<span />
</div>
<button
className="mt-2 field-link small"

View File

@@ -5,7 +5,7 @@ import { getConfig } from '@edx/frontend-platform';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
import {
Form, Hyperlink, Input, StatefulButton, ValidationFormGroup,
Form, Hyperlink, StatefulButton,
} from '@edx/paragon';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
@@ -23,7 +23,7 @@ import LoginFailureMessage from './LoginFailure';
import messages from './messages';
import {
RedirectLogistration, SocialAuthProviders, ThirdPartyAuthAlert, RenderInstitutionButton,
InstitutionLogistration,
InstitutionLogistration, AuthnValidationFormGroup,
} from '../common-components';
import {
DEFAULT_REDIRECT_URL, DEFAULT_STATE, LOGIN_PAGE, REGISTER_PAGE, ENTERPRISE_LOGIN_URL, PENDING_STATE,
@@ -209,42 +209,30 @@ class LoginPage extends React.Component {
{intl.formatMessage(messages['sign.in.heading'])}
</h3>
<Form className="m-0">
<ValidationFormGroup
<AuthnValidationFormGroup
label={intl.formatMessage(messages['email.label'])}
for="email"
name="email"
type="email"
invalid={this.state.errors.email !== ''}
invalidMessage={this.state.email === '' ? intl.formatMessage(messages['email.validation.message']) : intl.formatMessage(messages['email.format.validation.message'])}
className="mb-0 w-100"
placeholder="username@domain.com"
value={this.state.email}
onChange={(e) => this.handleOnChange(e)}
helpText={intl.formatMessage(messages['email.help.message'])}
>
<Form.Label htmlFor="loginEmail" className="h6 mr-1">
{intl.formatMessage(messages['email.label'])}
</Form.Label>
<Input
name="email"
id="loginEmail"
type="email"
placeholder="username@domain.com"
value={this.state.email}
onChange={e => this.handleOnChange(e)}
/>
</ValidationFormGroup>
<ValidationFormGroup
className="w-100"
/>
<AuthnValidationFormGroup
label={intl.formatMessage(messages['password.label'])}
for="password"
name="password"
type="password"
invalid={this.state.errors.password !== ''}
invalidMessage={intl.formatMessage(messages['password.validation.message'])}
className="mb-0 w-100"
>
<Form.Label htmlFor="loginPassword" className="h6 mr-1">
{intl.formatMessage(messages['password.label'])}
</Form.Label>
<Input
name="password"
id="loginPassword"
type="password"
value={this.state.password}
onChange={e => this.handleOnChange(e)}
/>
</ValidationFormGroup>
placeholder=""
value={this.state.password}
onChange={(e) => this.handleOnChange(e)}
/>
<LoginHelpLinks page={LOGIN_PAGE} />
<Hyperlink className="field-link mt-0 mb-3 small" destination={this.getEnterPriseLoginURL()}>
{intl.formatMessage(messages['enterprise.login.link.text'])}

View File

@@ -150,7 +150,7 @@ describe('LoginPage', () => {
const errorState = { email: null, password: '' };
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
loginPage.find('input#loginPassword').simulate('change', { target: { value: 'test', name: 'password' } });
loginPage.find('input#password').simulate('change', { target: { value: 'test', name: 'password' } });
loginPage.find('button.btn-brand').simulate('click');
expect(loginPage.find('LoginPage').state('errors')).toEqual(errorState);
@@ -160,7 +160,7 @@ describe('LoginPage', () => {
const errorState = { email: '', password: null };
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
loginPage.find('input#loginEmail').simulate('change', { target: { value: 'test@example.com', name: 'email' } });
loginPage.find('input#email').simulate('change', { target: { value: 'test@example.com', name: 'email' } });
loginPage.find('button.btn-brand').simulate('click');
expect(loginPage.find('LoginPage').state('errors')).toEqual(errorState);
});
@@ -328,7 +328,7 @@ describe('LoginPage', () => {
it('form only be scrollable on submission', () => {
const loginPage = mount(reduxWrapper(<IntlLoginPage {...props} />));
loginPage.find('input#loginPassword').simulate('change', { target: { value: 'test@example.com', name: 'password' } });
loginPage.find('input#password').simulate('change', { target: { value: 'test@example.com', name: 'password' } });
loginPage.find('button.btn-brand').simulate('click');
expect(loginPage.find(<IntlLoginFailureMessage />)).toBeTruthy();

View File

@@ -38,30 +38,24 @@ exports[`LoginPage should match TPA provider snapshot 1`] = `
className="m-0"
>
<div
className="form-group mb-0 w-100"
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginEmail"
>
Email
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="loginEmail"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
placeholder="username@domain.com"
onClick={[Function]}
onFocus={[Function]}
placeholder="Email"
required={true}
type="email"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
The email address you used to register with edX.
</small>
<span />
<strong
className="invalid-feedback"
id="email-invalid-feedback"
@@ -70,23 +64,24 @@ exports[`LoginPage should match TPA provider snapshot 1`] = `
</strong>
</div>
<div
className="form-group mb-0 w-100"
className="form-group"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginPassword"
>
Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="loginPassword"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder="Password"
required={true}
type="password"
value=""
/>
<span />
<strong
className="invalid-feedback"
id="password-invalid-feedback"
@@ -236,30 +231,24 @@ exports[`LoginPage should match default section snapshot 1`] = `
className="m-0"
>
<div
className="form-group mb-0 w-100"
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginEmail"
>
Email
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="loginEmail"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
placeholder="username@domain.com"
onClick={[Function]}
onFocus={[Function]}
placeholder="Email"
required={true}
type="email"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
The email address you used to register with edX.
</small>
<span />
<strong
className="invalid-feedback"
id="email-invalid-feedback"
@@ -268,23 +257,24 @@ exports[`LoginPage should match default section snapshot 1`] = `
</strong>
</div>
<div
className="form-group mb-0 w-100"
className="form-group"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginPassword"
>
Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="loginPassword"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder="Password"
required={true}
type="password"
value=""
/>
<span />
<strong
className="invalid-feedback"
id="password-invalid-feedback"
@@ -436,30 +426,24 @@ exports[`LoginPage should match forget password alert message snapshot 1`] = `
className="m-0"
>
<div
className="form-group mb-0 w-100"
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginEmail"
>
Email
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="loginEmail"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
placeholder="username@domain.com"
onClick={[Function]}
onFocus={[Function]}
placeholder="Email"
required={true}
type="email"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
The email address you used to register with edX.
</small>
<span />
<strong
className="invalid-feedback"
id="email-invalid-feedback"
@@ -468,23 +452,24 @@ exports[`LoginPage should match forget password alert message snapshot 1`] = `
</strong>
</div>
<div
className="form-group mb-0 w-100"
className="form-group"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginPassword"
>
Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="loginPassword"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder="Password"
required={true}
type="password"
value=""
/>
<span />
<strong
className="invalid-feedback"
id="password-invalid-feedback"
@@ -598,30 +583,24 @@ exports[`LoginPage should match pending button state snapshot 1`] = `
className="m-0"
>
<div
className="form-group mb-0 w-100"
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginEmail"
>
Email
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="loginEmail"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
placeholder="username@domain.com"
onClick={[Function]}
onFocus={[Function]}
placeholder="Email"
required={true}
type="email"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
The email address you used to register with edX.
</small>
<span />
<strong
className="invalid-feedback"
id="email-invalid-feedback"
@@ -630,23 +609,24 @@ exports[`LoginPage should match pending button state snapshot 1`] = `
</strong>
</div>
<div
className="form-group mb-0 w-100"
className="form-group"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginPassword"
>
Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="loginPassword"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder="Password"
required={true}
type="password"
value=""
/>
<span />
<strong
className="invalid-feedback"
id="password-invalid-feedback"
@@ -797,30 +777,24 @@ exports[`LoginPage should show error message 1`] = `
className="m-0"
>
<div
className="form-group mb-0 w-100"
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginEmail"
>
Email
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="loginEmail"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
placeholder="username@domain.com"
onClick={[Function]}
onFocus={[Function]}
placeholder="Email"
required={true}
type="email"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
The email address you used to register with edX.
</small>
<span />
<strong
className="invalid-feedback"
id="email-invalid-feedback"
@@ -829,23 +803,24 @@ exports[`LoginPage should show error message 1`] = `
</strong>
</div>
<div
className="form-group mb-0 w-100"
className="form-group"
>
<label
className="h6 mr-1 form-label"
htmlFor="loginPassword"
>
Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="loginPassword"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder="Password"
required={true}
type="password"
value=""
/>
<span />
<strong
className="invalid-feedback"
id="password-invalid-feedback"

View File

@@ -28,7 +28,7 @@ import { registrationRequestSelector } from './data/selectors';
import { thirdPartyAuthContextSelector } from '../common-components/data/selectors';
import {
RedirectLogistration, SocialAuthProviders, ThirdPartyAuthAlert, RenderInstitutionButton,
InstitutionLogistration,
InstitutionLogistration, AuthnValidationFormGroup,
} from '../common-components';
import RegistrationFailure from './RegistrationFailure';
import {
@@ -467,26 +467,35 @@ class RegistrationPage extends React.Component {
);
}
if (field.type === 'select') {
options = field.options.map((item) => ({
value: item.value,
label: item.name,
}));
options = field.options.map((item) => {
const option = {};
option.value = item.value;
option.label = item.name;
if (item.name === '--') {
option.label = `${field.label} (required)`;
option.disabled = true;
}
return option;
});
props.options = options;
props.onBlur = e => this.handleOnBlur(e);
props.onClick = e => this.handleOnClick(e);
props.onChange = e => this.handleOnChange(e);
}
return (
<ValidationFormGroup
<AuthnValidationFormGroup
label={field.label}
for={field.name}
name={field.name}
type={field.type}
key={field.name}
invalid={this.state.errors[stateVar] !== ''}
invalidMessage={field.errorMessages.required}
placeholder=""
className="mb-0"
>
<label htmlFor={field.name} className="h6 pt-10">{field.label} (required)</label>
<Input {...props} />
</ValidationFormGroup>
value={props.value}
onClick={(e) => this.handleOnClick(e)}
onBlur={(e) => this.handleOnBlur(e)}
onChange={(e) => this.handleOnChange(e)}
selectOptions={props.options}
/>
);
}
}
@@ -651,111 +660,80 @@ class RegistrationPage extends React.Component {
<hr className="mb-20 border-gray-200" />
<h3 className="mb-20">{intl.formatMessage(messages['create.a.new.account'])}</h3>
<Form className="form-group">
<ValidationFormGroup
<AuthnValidationFormGroup
label={intl.formatMessage(messages['fullname.label'])}
for="name"
name="name"
type="text"
invalid={this.state.errors.name !== ''}
invalidMessage={this.state.errors.name}
helpText="This name will be used by any certificates that you earn."
>
<label htmlFor="name" className="h6 pt-10">
{intl.formatMessage(messages['fullname.label'])}
</label>
<Input
name="name"
id="name"
type="text"
placeholder=""
value={this.state.name}
onChange={e => this.handleOnChange(e)}
onBlur={e => this.handleOnBlur(e)}
onClick={e => this.handleOnClick(e)}
required
/>
</ValidationFormGroup>
<ValidationFormGroup
placeholder=""
value={this.state.name}
onClick={(e) => this.handleOnClick(e)}
onBlur={(e) => this.handleOnBlur(e)}
onChange={(e) => this.handleOnChange(e)}
helpText={intl.formatMessage(messages['helptext.name'])}
/>
<AuthnValidationFormGroup
label={intl.formatMessage(messages['username.label'])}
for="username"
name="username"
type="text"
invalid={this.state.errors.username !== ''}
invalidMessage={this.state.errors.username}
helpText="The name that will identify you in your courses. It cannot be changed later."
>
<label htmlFor="username" className="h6 pt-10">
{intl.formatMessage(messages['username.label'])}
</label>
<Input
name="username"
id="username"
type="text"
placeholder=""
value={this.state.username}
maxLength="30"
onChange={e => this.handleOnChange(e)}
onBlur={e => this.handleOnBlur(e)}
onClick={e => this.handleOnClick(e)}
required
/>
</ValidationFormGroup>
<ValidationFormGroup
placeholder=""
value={this.state.username}
onClick={(e) => this.handleOnClick(e)}
onBlur={(e) => this.handleOnBlur(e)}
onChange={(e) => this.handleOnChange(e)}
helpText={intl.formatMessage(messages['helptext.username'])}
/>
<AuthnValidationFormGroup
label={intl.formatMessage(messages['register.page.email.label'])}
for="email"
name="email"
type="text"
invalid={this.state.errors.email !== ''}
invalidMessage={this.state.errors.email}
helpText="This is what you will use to login."
>
<label htmlFor="email" className="h6 pt-10">
{intl.formatMessage(messages['register.page.email.label'])}
</label>
<Input
name="email"
id="email"
type="email"
placeholder="username@domain.com"
value={this.state.email}
onChange={e => this.handleOnChange(e)}
onBlur={e => this.handleOnBlur(e)}
onClick={e => this.handleOnClick(e)}
required
/>
</ValidationFormGroup>
placeholder=""
value={this.state.email}
onClick={(e) => this.handleOnClick(e)}
onBlur={(e) => this.handleOnBlur(e)}
onChange={(e) => this.handleOnChange(e)}
helpText={intl.formatMessage(messages['helptext.email'])}
/>
{!currentProvider && (
<ValidationFormGroup
<AuthnValidationFormGroup
label={intl.formatMessage(messages['password.label'])}
for="password"
name="password"
type="password"
invalid={this.state.errors.password !== ''}
invalidMessage={this.state.errors.password}
helpText="Your password must contain at least 8 characters, including 1 letter & 1 number."
>
<label htmlFor="password" className="h6 pt-10">
{intl.formatMessage(messages['password.label'])}
</label>
<Input
name="password"
id="password"
type="password"
placeholder=""
value={this.state.password}
onChange={e => this.handleOnChange(e)}
onBlur={e => this.handleOnBlur(e)}
onClick={e => this.handleOnClick(e)}
required
/>
</ValidationFormGroup>
placeholder=""
value={this.state.password}
onClick={(e) => this.handleOnClick(e)}
onBlur={(e) => this.handleOnBlur(e)}
onChange={(e) => this.handleOnChange(e)}
helpText={intl.formatMessage(messages['helptext.password'])}
/>
)}
{ this.addExtraRequiredFields() }
<ValidationFormGroup
<AuthnValidationFormGroup
label=""
for="optional"
className="custom-control pt-10 mb-0"
>
<Input
name="optional"
id="optional"
type="checkbox"
value={this.state.enableOptionalField}
checked={this.state.enableOptionalField}
onChange={e => this.handleOnOptional(e)}
required
/>
<p role="presentation" id="additionalFields" className="mb-0 small" onClick={e => this.handleOnOptional(e)}>
{intl.formatMessage(messages['support.education.research'])}
</p>
</ValidationFormGroup>
name="optional"
type="checkbox"
invalidMessage=""
placeholder=""
value={this.state.enableOptionalField}
onClick={(e) => this.handleOnOptional(e)}
onBlur={null}
onChange={(e) => this.handleOnOptional(e)}
optionalFieldCheckbox
isChecked={this.state.enableOptionalField}
checkboxMessage={intl.formatMessage(messages['support.education.research'])}
/>
{ this.state.enableOptionalField ? this.addExtraOptionalFields() : null}
<StatefulButton
type="button"

View File

@@ -141,6 +141,26 @@ const messages = defineMessages({
defaultMessage: 'We couldn\'t create your account.',
description: 'error message when registration failure.',
},
'helptext.name': {
id: 'helptext.name',
defaultMessage: 'This name will be used by any certificates that you earn.',
description: '',
},
'helptext.username': {
id: 'helptext.username',
defaultMessage: 'The name that will identify you in your courses. It cannot be changed later.',
description: '',
},
'helptext.password': {
id: 'helptext.password',
defaultMessage: 'Your password must contain at least 8 characters, including 1 letter & 1 number.',
description: '',
},
'helptext.email': {
id: 'helptext.email',
defaultMessage: 'This is what you will use to login.',
description: '',
},
});
export default messages;

View File

@@ -48,90 +48,62 @@ exports[`./RegistrationPage.js should display no password field when current pro
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="name"
>
Full Name (required)
</label>
<span />
<input
aria-describedby="name-help-text"
aria-describedby=""
className="form-control"
id="name"
name="name"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Full Name (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="name-help-text"
>
This name will be used by any certificates that you earn.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="username"
>
Public Username (required)
</label>
<span />
<input
aria-describedby="username-help-text"
aria-describedby=""
className="form-control"
id="username"
maxLength="30"
name="username"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Public Username (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="username-help-text"
>
The name that will identify you in your courses. It cannot be changed later.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="email"
>
Email (required)
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder="username@domain.com"
onFocus={[Function]}
placeholder="Email (required)"
required={true}
type="email"
type="text"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
This is what you will use to login.
</small>
<span />
</div>
<div
className="form-group custom-control small"
@@ -166,13 +138,18 @@ exports[`./RegistrationPage.js should display no password field when current pro
<div
className="form-group custom-control pt-10 mb-0"
>
<span />
<input
aria-describedby=""
checked={false}
className="form-check-input"
id="optional"
name="optional"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder=""
required={true}
type="checkbox"
value={false}
@@ -245,119 +222,82 @@ exports[`./RegistrationPage.js should match TPA provider snapshot 1`] = `
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="name"
>
Full Name (required)
</label>
<span />
<input
aria-describedby="name-help-text"
aria-describedby=""
className="form-control"
id="name"
name="name"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Full Name (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="name-help-text"
>
This name will be used by any certificates that you earn.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="username"
>
Public Username (required)
</label>
<span />
<input
aria-describedby="username-help-text"
aria-describedby=""
className="form-control"
id="username"
maxLength="30"
name="username"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Public Username (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="username-help-text"
>
The name that will identify you in your courses. It cannot be changed later.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="email"
>
Email (required)
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder="username@domain.com"
onFocus={[Function]}
placeholder="Email (required)"
required={true}
type="email"
type="text"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
This is what you will use to login.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="password"
>
Password (required)
</label>
<span />
<input
aria-describedby="password-help-text"
aria-describedby=""
className="form-control"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Password (required)"
required={true}
type="password"
value=""
/>
<small
className="form-text text-muted"
id="password-help-text"
>
Your password must contain at least 8 characters, including 1 letter & 1 number.
</small>
<span />
</div>
<div
className="form-group custom-control small"
@@ -392,13 +332,18 @@ exports[`./RegistrationPage.js should match TPA provider snapshot 1`] = `
<div
className="form-group custom-control pt-10 mb-0"
>
<span />
<input
aria-describedby=""
checked={false}
className="form-check-input"
id="optional"
name="optional"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder=""
required={true}
type="checkbox"
value={false}
@@ -510,119 +455,82 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="name"
>
Full Name (required)
</label>
<span />
<input
aria-describedby="name-help-text"
aria-describedby=""
className="form-control"
id="name"
name="name"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Full Name (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="name-help-text"
>
This name will be used by any certificates that you earn.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="username"
>
Public Username (required)
</label>
<span />
<input
aria-describedby="username-help-text"
aria-describedby=""
className="form-control"
id="username"
maxLength="30"
name="username"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Public Username (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="username-help-text"
>
The name that will identify you in your courses. It cannot be changed later.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="email"
>
Email (required)
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder="username@domain.com"
onFocus={[Function]}
placeholder="Email (required)"
required={true}
type="email"
type="text"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
This is what you will use to login.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="password"
>
Password (required)
</label>
<span />
<input
aria-describedby="password-help-text"
aria-describedby=""
className="form-control"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Password (required)"
required={true}
type="password"
value=""
/>
<small
className="form-text text-muted"
id="password-help-text"
>
Your password must contain at least 8 characters, including 1 letter & 1 number.
</small>
<span />
</div>
<div
className="form-group custom-control small"
@@ -657,13 +565,18 @@ exports[`./RegistrationPage.js should match default section snapshot 1`] = `
<div
className="form-group custom-control pt-10 mb-0"
>
<span />
<input
aria-describedby=""
checked={false}
className="form-check-input"
id="optional"
name="optional"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder=""
required={true}
type="checkbox"
value={false}
@@ -736,119 +649,82 @@ exports[`./RegistrationPage.js should match pending button state snapshot 1`] =
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="name"
>
Full Name (required)
</label>
<span />
<input
aria-describedby="name-help-text"
aria-describedby=""
className="form-control"
id="name"
name="name"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Full Name (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="name-help-text"
>
This name will be used by any certificates that you earn.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="username"
>
Public Username (required)
</label>
<span />
<input
aria-describedby="username-help-text"
aria-describedby=""
className="form-control"
id="username"
maxLength="30"
name="username"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Public Username (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="username-help-text"
>
The name that will identify you in your courses. It cannot be changed later.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="email"
>
Email (required)
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder="username@domain.com"
onFocus={[Function]}
placeholder="Email (required)"
required={true}
type="email"
type="text"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
This is what you will use to login.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="password"
>
Password (required)
</label>
<span />
<input
aria-describedby="password-help-text"
aria-describedby=""
className="form-control"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Password (required)"
required={true}
type="password"
value=""
/>
<small
className="form-text text-muted"
id="password-help-text"
>
Your password must contain at least 8 characters, including 1 letter & 1 number.
</small>
<span />
</div>
<div
className="form-group custom-control small"
@@ -883,13 +759,18 @@ exports[`./RegistrationPage.js should match pending button state snapshot 1`] =
<div
className="form-group custom-control pt-10 mb-0"
>
<span />
<input
aria-describedby=""
checked={false}
className="form-check-input"
id="optional"
name="optional"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder=""
required={true}
type="checkbox"
value={false}
@@ -1005,119 +886,82 @@ exports[`./RegistrationPage.js should show error message on 409 on alert and bel
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="name"
>
Full Name (required)
</label>
<span />
<input
aria-describedby="name-help-text"
aria-describedby=""
className="form-control"
id="name"
name="name"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Full Name (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="name-help-text"
>
This name will be used by any certificates that you earn.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="username"
>
Public Username (required)
</label>
<span />
<input
aria-describedby="username-help-text"
aria-describedby=""
className="form-control"
id="username"
maxLength="30"
name="username"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Public Username (required)"
required={true}
type="text"
value=""
/>
<small
className="form-text text-muted"
id="username-help-text"
>
The name that will identify you in your courses. It cannot be changed later.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="email"
>
Email (required)
</label>
<span />
<input
aria-describedby="email-help-text"
aria-describedby=""
className="form-control"
id="email"
name="email"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder="username@domain.com"
onFocus={[Function]}
placeholder="Email (required)"
required={true}
type="email"
type="text"
value=""
/>
<small
className="form-text text-muted"
id="email-help-text"
>
This is what you will use to login.
</small>
<span />
</div>
<div
className="form-group"
>
<label
className="h6 pt-10"
htmlFor="password"
>
Password (required)
</label>
<span />
<input
aria-describedby="password-help-text"
aria-describedby=""
className="form-control"
id="password"
name="password"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
placeholder=""
onFocus={[Function]}
placeholder="Password (required)"
required={true}
type="password"
value=""
/>
<small
className="form-text text-muted"
id="password-help-text"
>
Your password must contain at least 8 characters, including 1 letter & 1 number.
</small>
<span />
</div>
<div
className="form-group custom-control small"
@@ -1152,13 +996,18 @@ exports[`./RegistrationPage.js should show error message on 409 on alert and bel
<div
className="form-group custom-control pt-10 mb-0"
>
<span />
<input
aria-describedby=""
checked={false}
className="form-check-input"
id="optional"
name="optional"
onBlur={[Function]}
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder=""
required={true}
type="checkbox"
value={false}

View File

@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
Alert, Form, Input, StatefulButton, ValidationFormGroup,
Alert, Form, StatefulButton,
} from '@edx/paragon';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { getQueryParameters } from '@edx/frontend-platform';
@@ -15,6 +15,9 @@ import { resetPasswordResultSelector } from './data/selectors';
import { validatePassword } from './data/service';
import InvalidTokenMessage from './InvalidToken';
import ResetSuccessMessage from './ResetSuccess';
import {
AuthnValidationFormGroup,
} from '../common-components';
import Spinner from './Spinner';
const ResetPasswordPage = (props) => {
@@ -50,6 +53,11 @@ const ResetPasswordPage = (props) => {
const handleNewPasswordChange = (e) => {
const newPassword = e.target.value;
setNewPasswordValue(newPassword);
};
const handleNewPasswordOnBlur = (e) => {
const newPassword = e.target.value;
setNewPasswordValue(newPassword);
if (newPassword === '') {
setPasswordValidValue(false);
@@ -115,41 +123,31 @@ const ResetPasswordPage = (props) => {
<p className="mb-4">
{intl.formatMessage(messages['reset.password.page.instructions'])}
</p>
<ValidationFormGroup
<AuthnValidationFormGroup
label={intl.formatMessage(messages['reset.password.page.new.field.label'])}
for="reset-password-input"
name="new-password1"
type="password"
invalid={!passwordValid}
invalidMessage={validationMessage}
placeholder=""
value={newPasswordInput}
onChange={e => handleNewPasswordChange(e)}
onBlur={e => handleNewPasswordOnBlur(e)}
className="w-100"
>
<Form.Label htmlFor="reset-password-input" className="h6 mr-1">
{intl.formatMessage(messages['reset.password.page.new.field.label'])}
</Form.Label>
<Input
name="new-password1"
id="reset-password-input"
type="password"
placeholder=""
onBlur={e => handleNewPasswordChange(e)}
/>
</ValidationFormGroup>
<ValidationFormGroup
/>
<AuthnValidationFormGroup
label={intl.formatMessage(messages['reset.password.page.confirm.field.label'])}
for="confirm-password-input"
name="new-password2"
type="password"
invalid={!passwordMatch}
invalidMessage={intl.formatMessage(messages['reset.password.page.invalid.match.message'])}
placeholder=""
value={confirmPasswordInput}
onChange={e => handleConfirmPasswordChange(e)}
className="w-100"
>
<Form.Label htmlFor="confirm-password-input" className="h6 mr-1">
{intl.formatMessage(messages['reset.password.page.confirm.field.label'])}
</Form.Label>
<Input
name="new-password2"
id="confirm-password-input"
type="password"
placeholder=""
value={confirmPasswordInput}
onChange={e => handleConfirmPasswordChange(e)}
/>
</ValidationFormGroup>
/>
<StatefulButton
type="submit"
className="btn-primary"

View File

@@ -63,41 +63,42 @@ exports[`ResetPasswordPage should match pending reset message section snapshot 1
<div
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="reset-password-input"
>
New Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="reset-password-input"
name="new-password1"
onBlur={[Function]}
placeholder=""
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder="New Password"
required={true}
type="password"
value=""
/>
<span />
</div>
<div
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="confirm-password-input"
>
Confirm Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="confirm-password-input"
name="new-password2"
onBlur={[Function]}
onChange={[Function]}
placeholder=""
onClick={[Function]}
onFocus={[Function]}
placeholder="Confirm Password"
required={true}
type="password"
value=""
/>
<span />
<strong
className="invalid-feedback"
id="confirm-password-input-invalid-feedback"
@@ -169,41 +170,42 @@ exports[`ResetPasswordPage should match reset password default section snapshot
<div
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="reset-password-input"
>
New Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="reset-password-input"
name="new-password1"
onBlur={[Function]}
placeholder=""
onChange={[Function]}
onClick={[Function]}
onFocus={[Function]}
placeholder="New Password"
required={true}
type="password"
value=""
/>
<span />
</div>
<div
className="form-group w-100"
>
<label
className="h6 mr-1 form-label"
htmlFor="confirm-password-input"
>
Confirm Password
</label>
<span />
<input
aria-describedby=""
className="form-control"
id="confirm-password-input"
name="new-password2"
onBlur={[Function]}
onChange={[Function]}
placeholder=""
onClick={[Function]}
onFocus={[Function]}
placeholder="Confirm Password"
required={true}
type="password"
value=""
/>
<span />
<strong
className="invalid-feedback"
id="confirm-password-input-invalid-feedback"