improve username suggestions experience (#264)

Also refactored some code.

VAN-52
This commit is contained in:
Waheed Ahmed
2021-05-05 17:14:17 +05:00
parent a5b5a5e597
commit c12d180bff
6 changed files with 117 additions and 34 deletions

View File

@@ -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));

View File

@@ -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);

View File

@@ -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,
});

View File

@@ -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;
}

View File

@@ -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,
);

View 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: [],
},
);
});
});