improve username suggestions experience (#264)
Also refactored some code. VAN-52
This commit is contained in:
@@ -4,7 +4,7 @@ import snakeCase from 'lodash.snakecase';
|
||||
import { connect } from 'react-redux';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import PropTypes from 'prop-types';
|
||||
import PropTypes, { string } from 'prop-types';
|
||||
|
||||
import { getConfig } from '@edx/frontend-platform';
|
||||
import { sendPageEvent, sendTrackEvent } from '@edx/frontend-platform/analytics';
|
||||
@@ -18,9 +18,13 @@ import {
|
||||
import { ExpandMore } from '@edx/paragon/icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
import { registerNewUser, resetRegistrationForm, fetchRealtimeValidations } from './data/actions';
|
||||
import {
|
||||
clearUsernameSuggestions, registerNewUser, resetRegistrationForm, fetchRealtimeValidations,
|
||||
} from './data/actions';
|
||||
import { FORM_SUBMISSION_ERROR } from './data/constants';
|
||||
import { registrationErrorSelector, registrationRequestSelector, validationsSelector } from './data/selectors';
|
||||
import {
|
||||
registrationErrorSelector, registrationRequestSelector, validationsSelector, usernameSuggestionsSelector,
|
||||
} from './data/selectors';
|
||||
import messages from './messages';
|
||||
import OptionalFields from './OptionalFields';
|
||||
import RegistrationFailure from './RegistrationFailure';
|
||||
@@ -221,16 +225,13 @@ class RegistrationPage extends React.Component {
|
||||
}
|
||||
|
||||
handleSuggestionClick = (suggestion) => {
|
||||
const fieldName = 'username';
|
||||
const payload = {
|
||||
is_authn_mfe: true,
|
||||
username: suggestion,
|
||||
};
|
||||
const { errors } = this.state;
|
||||
errors.username = '';
|
||||
this.setState({
|
||||
...payload,
|
||||
}, () => {
|
||||
this.validateInput(fieldName, suggestion, payload);
|
||||
username: suggestion,
|
||||
errors,
|
||||
});
|
||||
this.props.clearUsernameSuggestions();
|
||||
}
|
||||
|
||||
isFormValid(validations) {
|
||||
@@ -407,6 +408,7 @@ class RegistrationPage extends React.Component {
|
||||
helpText={[intl.formatMessage(messages['help.text.username.1']), intl.formatMessage(messages['help.text.username.2'])]}
|
||||
floatingLabel={intl.formatMessage(messages['registration.username.label'])}
|
||||
handleSuggestionClick={this.handleSuggestionClick}
|
||||
usernameSuggestions={this.props.usernameSuggestions}
|
||||
/>
|
||||
<FormGroup
|
||||
name="email"
|
||||
@@ -559,6 +561,7 @@ RegistrationPage.defaultProps = {
|
||||
},
|
||||
validationDecisions: null,
|
||||
statusCode: null,
|
||||
usernameSuggestions: [],
|
||||
};
|
||||
|
||||
RegistrationPage.propTypes = {
|
||||
@@ -596,7 +599,9 @@ RegistrationPage.propTypes = {
|
||||
password: PropTypes.string,
|
||||
username: PropTypes.string,
|
||||
}),
|
||||
clearUsernameSuggestions: PropTypes.func.isRequired,
|
||||
statusCode: PropTypes.number,
|
||||
usernameSuggestions: PropTypes.arrayOf(string),
|
||||
};
|
||||
|
||||
const mapStateToProps = state => {
|
||||
@@ -610,6 +615,7 @@ const mapStateToProps = state => {
|
||||
thirdPartyAuthContext,
|
||||
validationDecisions: validationsSelector(state),
|
||||
statusCode: state.register.statusCode,
|
||||
usernameSuggestions: usernameSuggestionsSelector(state),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -620,5 +626,6 @@ export default connect(
|
||||
fetchRealtimeValidations,
|
||||
registerNewUser,
|
||||
resetRegistrationForm,
|
||||
clearUsernameSuggestions,
|
||||
},
|
||||
)(injectIntl(RegistrationPage));
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes, { string } from 'prop-types';
|
||||
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Button } from '@edx/paragon';
|
||||
|
||||
import { usernameSuggestionsSelector } from './data/selectors';
|
||||
import { FormGroup } from '../common-components';
|
||||
import messages from './messages';
|
||||
|
||||
@@ -49,10 +47,4 @@ UsernameField.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
usernameSuggestions: usernameSuggestionsSelector(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
)(injectIntl(UsernameField));
|
||||
export default injectIntl(UsernameField);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { AsyncActionType } from '../../data/utils';
|
||||
export const REGISTER_NEW_USER = new AsyncActionType('REGISTRATION', 'REGISTER_NEW_USER');
|
||||
export const REGISTER_FORM_VALIDATIONS = new AsyncActionType('REGISTRATION', 'GET_FORM_VALIDATIONS');
|
||||
export const REGISTRATION_FORM = new AsyncActionType('REGISTRATION', 'REGISTRATION_FORM');
|
||||
export const REGISTER_CLEAR_USERNAME_SUGGESTIONS = 'REGISTRATION_CLEAR_USERNAME_SUGGESTIONS';
|
||||
|
||||
// Reset Form
|
||||
export const resetRegistrationForm = () => ({
|
||||
@@ -48,3 +49,7 @@ export const fetchRealtimeValidationsSuccess = (validations) => ({
|
||||
export const fetchRealtimeValidationsFailure = () => ({
|
||||
type: REGISTER_FORM_VALIDATIONS.FAILURE,
|
||||
});
|
||||
|
||||
export const clearUsernameSuggestions = () => ({
|
||||
type: REGISTER_CLEAR_USERNAME_SUGGESTIONS,
|
||||
});
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { REGISTRATION_FORM, REGISTER_NEW_USER, REGISTER_FORM_VALIDATIONS } from './actions';
|
||||
import {
|
||||
REGISTRATION_FORM, REGISTER_NEW_USER, REGISTER_FORM_VALIDATIONS, REGISTER_CLEAR_USERNAME_SUGGESTIONS,
|
||||
} from './actions';
|
||||
|
||||
import { DEFAULT_STATE, PENDING_STATE } from '../../data/constants';
|
||||
|
||||
@@ -8,6 +10,7 @@ export const defaultState = {
|
||||
formData: null,
|
||||
validations: null,
|
||||
statusCode: null,
|
||||
usernameSuggestions: [],
|
||||
};
|
||||
|
||||
const reducer = (state = defaultState, action) => {
|
||||
@@ -27,28 +30,39 @@ const reducer = (state = defaultState, action) => {
|
||||
...state,
|
||||
registrationResult: action.payload,
|
||||
};
|
||||
case REGISTER_NEW_USER.FAILURE:
|
||||
case REGISTER_NEW_USER.FAILURE: {
|
||||
const { usernameSuggestions } = action.payload;
|
||||
return {
|
||||
...state,
|
||||
registrationError: { ...action.payload },
|
||||
submitState: DEFAULT_STATE,
|
||||
validations: null,
|
||||
usernameSuggestions: usernameSuggestions || state.usernameSuggestions,
|
||||
};
|
||||
}
|
||||
case REGISTER_FORM_VALIDATIONS.BEGIN:
|
||||
return {
|
||||
...state,
|
||||
};
|
||||
case REGISTER_FORM_VALIDATIONS.SUCCESS:
|
||||
case REGISTER_FORM_VALIDATIONS.SUCCESS: {
|
||||
const { usernameSuggestions } = action.payload.validations;
|
||||
return {
|
||||
...state,
|
||||
validations: action.payload.validations,
|
||||
usernameSuggestions: usernameSuggestions || state.usernameSuggestions,
|
||||
};
|
||||
}
|
||||
case REGISTER_FORM_VALIDATIONS.FAILURE:
|
||||
return {
|
||||
...state,
|
||||
statusCode: 403,
|
||||
validations: null,
|
||||
};
|
||||
case REGISTER_CLEAR_USERNAME_SUGGESTIONS:
|
||||
return {
|
||||
...state,
|
||||
usernameSuggestions: [],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -39,15 +39,5 @@ export const validationsSelector = createSelector(
|
||||
|
||||
export const usernameSuggestionsSelector = createSelector(
|
||||
registerSelector,
|
||||
(register) => {
|
||||
const { registrationError, validations } = register;
|
||||
let usernameSuggestions = validations && validations.usernameSuggestions ? validations.usernameSuggestions : [];
|
||||
if (usernameSuggestions.length === 0) {
|
||||
usernameSuggestions = (
|
||||
registrationError && registrationError.usernameSuggestions ? registrationError.usernameSuggestions : []
|
||||
);
|
||||
}
|
||||
|
||||
return usernameSuggestions;
|
||||
},
|
||||
register => register.usernameSuggestions,
|
||||
);
|
||||
|
||||
75
src/register/data/tests/reducers.test.js
Normal file
75
src/register/data/tests/reducers.test.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import reducer from '../reducers';
|
||||
import {
|
||||
REGISTER_CLEAR_USERNAME_SUGGESTIONS, REGISTER_FORM_VALIDATIONS, REGISTER_NEW_USER,
|
||||
} from '../actions';
|
||||
import { DEFAULT_STATE } from '../../../data/constants';
|
||||
|
||||
describe('register reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {})).toEqual(
|
||||
{
|
||||
registrationError: {},
|
||||
registrationResult: {},
|
||||
formData: null,
|
||||
validations: null,
|
||||
statusCode: null,
|
||||
usernameSuggestions: [],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should set username suggestions upon validation failed case', () => {
|
||||
const state = { usernameSuggestions: [] };
|
||||
const validations = { usernameSuggestions: ['test12'] };
|
||||
const action = {
|
||||
type: REGISTER_FORM_VALIDATIONS.SUCCESS,
|
||||
payload: { validations },
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(state, action),
|
||||
).toEqual(
|
||||
{
|
||||
validations,
|
||||
usernameSuggestions: validations.usernameSuggestions,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should set username suggestions upon registration error case', () => {
|
||||
const state = { usernameSuggestions: [] };
|
||||
const payload = { usernameSuggestions: ['test12'] };
|
||||
const action = {
|
||||
type: REGISTER_NEW_USER.FAILURE,
|
||||
payload,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(state, action),
|
||||
).toEqual(
|
||||
{
|
||||
registrationError: payload,
|
||||
submitState: DEFAULT_STATE,
|
||||
usernameSuggestions: payload.usernameSuggestions,
|
||||
validations: null,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('should clear username suggestions from validations state', () => {
|
||||
const state = {
|
||||
usernameSuggestions: ['test_1'],
|
||||
};
|
||||
const action = {
|
||||
type: REGISTER_CLEAR_USERNAME_SUGGESTIONS,
|
||||
};
|
||||
|
||||
expect(
|
||||
reducer(state, action),
|
||||
).toEqual(
|
||||
{
|
||||
usernameSuggestions: [],
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user