diff --git a/src/register/ConfigurableRegistrationForm.jsx b/src/register/ConfigurableRegistrationForm.jsx index 0c8e247f..38169a99 100644 --- a/src/register/ConfigurableRegistrationForm.jsx +++ b/src/register/ConfigurableRegistrationForm.jsx @@ -1,10 +1,11 @@ import React, { useEffect } from 'react'; +import { useSelector } from 'react-redux'; import { getConfig } from '@edx/frontend-platform'; import { useIntl } from '@edx/frontend-platform/i18n'; import PropTypes from 'prop-types'; -import { FIELDS } from './data/constants'; +import { COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY, FIELDS } from './data/constants'; import { validateCountryField } from './data/utils'; import messages from './messages'; import { HonorCode, TermsOfService } from './registrationFields'; @@ -36,6 +37,7 @@ const ConfigurableRegistrationForm = (props) => { setFormFields, registrationEmbedded, } = props; + const backendCountryCode = useSelector(state => state.register.backendCountryCode); let showTermsOfServiceAndHonorCode = false; let showCountryField = false; @@ -54,6 +56,29 @@ const ConfigurableRegistrationForm = (props) => { } }); + useEffect(() => { + if (backendCountryCode && backendCountryCode !== formFields?.country?.countryCode) { + let countryCode = ''; + let countryDisplayValue = ''; + + const selectedCountry = countryList.find( + (country) => (country[COUNTRY_CODE_KEY].toLowerCase() === backendCountryCode.toLowerCase()), + ); + if (selectedCountry) { + countryCode = selectedCountry[COUNTRY_CODE_KEY]; + countryDisplayValue = selectedCountry[COUNTRY_DISPLAY_KEY]; + } + setFormFields(prevState => ( + { + ...prevState, + country: { + countryCode, displayValue: countryDisplayValue, + }, + } + )); + } + }, [backendCountryCode, countryList]); // eslint-disable-line react-hooks/exhaustive-deps + const handleOnChange = (event, countryValue = null) => { const { name } = event.target; let value; @@ -218,7 +243,7 @@ ConfigurableRegistrationForm.propTypes = { marketingEmailsOptIn: PropTypes.bool, }).isRequired, setFieldErrors: PropTypes.func.isRequired, - setFocusedField: PropTypes.func.isRequired, + setFocusedField: PropTypes.func, setFormFields: PropTypes.func.isRequired, registrationEmbedded: PropTypes.bool, }; @@ -226,6 +251,7 @@ ConfigurableRegistrationForm.propTypes = { ConfigurableRegistrationForm.defaultProps = { fieldDescriptions: {}, registrationEmbedded: false, + setFocusedField: () => {}, }; export default ConfigurableRegistrationForm; diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index c7627c40..840f36d4 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -23,8 +23,6 @@ import { setUserPipelineDataLoaded, } from './data/actions'; import { - COUNTRY_CODE_KEY, - COUNTRY_DISPLAY_KEY, FIELDS, FORM_SUBMISSION_ERROR, TPA_AUTHENTICATION_FAILURE, @@ -55,7 +53,6 @@ import { const RegistrationPage = (props) => { const { backedUpFormData, - backendCountryCode, backendValidations, fieldDescriptions, handleInstitutionLogin, @@ -68,7 +65,6 @@ const RegistrationPage = (props) => { submitState, thirdPartyAuthApiStatus, thirdPartyAuthContext, - usernameSuggestions, validationApiRateLimited, // Actions backupFormState, @@ -185,39 +181,6 @@ const RegistrationPage = (props) => { } }, [registrationErrorCode]); - useEffect(() => { - if (backendCountryCode && backendCountryCode !== configurableFormFields?.country?.countryCode) { - let countryCode = ''; - let countryDisplayValue = ''; - - const selectedCountry = countryList.find( - (country) => (country[COUNTRY_CODE_KEY].toLowerCase() === backendCountryCode.toLowerCase()), - ); - if (selectedCountry) { - countryCode = selectedCountry[COUNTRY_CODE_KEY]; - countryDisplayValue = selectedCountry[COUNTRY_DISPLAY_KEY]; - } - setConfigurableFormFields(prevState => ( - { - ...prevState, - country: { - countryCode, displayValue: countryDisplayValue, - }, - } - )); - } - }, [backendCountryCode, countryList]); // eslint-disable-line react-hooks/exhaustive-deps - - /** - * We need to remove the placeholder from the field, adding a space will do that. - * This is needed because we are placing the username suggestions on top of the field. - */ - useEffect(() => { - if (usernameSuggestions.length && !formFields.username) { - setFormFields(prevState => ({ ...prevState, username: ' ' })); - } - }, [usernameSuggestions, formFields]); - useEffect(() => { if (registrationResult.success) { // Optimizely registration conversion event @@ -393,23 +356,14 @@ const RegistrationPage = (props) => { const handleEmailSuggestionClosed = () => setEmailSuggestion({ suggestion: '', type: '' }); - const handleUsernameSuggestionClosed = () => props.resetUsernameSuggestions(); - const handleOnChange = (event) => { + console.log('test handleOnChange', event.target); const { name } = event.target; - let value = event.target.type === 'checkbox' ? event.target.checked : event.target.value; + const value = event.target.type === 'checkbox' ? event.target.checked : event.target.value; if (registrationError[name]) { clearBackendError(name); setErrors(prevErrors => ({ ...prevErrors, [name]: '' })); } - if (name === 'username') { - if (value.length > 30) { - return; - } - if (value.startsWith(' ')) { - value = value.trim(); - } - } setFormFields(prevState => ({ ...prevState, [name]: value })); }; @@ -430,22 +384,12 @@ const RegistrationPage = (props) => { }; const handleOnFocus = (event) => { - const { name, value } = event.target; + const { name } = event.target; setErrors(prevErrors => ({ ...prevErrors, [name]: '' })); clearBackendError(name); // Since we are removing the form errors from the focused field, we will // need to rerun the validation for focused field on form submission. setFocusedField(name); - - if (name === 'username') { - props.resetUsernameSuggestions(); - // If we added a space character to username field to display the suggestion - // remove it before user enters the input. This is to ensure user doesn't - // have a space prefixed to the username. - if (value === ' ') { - setFormFields(prevState => ({ ...prevState, [name]: '' })); - } - } }; const registerUser = () => { @@ -579,8 +523,6 @@ const RegistrationPage = (props) => { handleChange={handleOnChange} handleFocus={handleOnFocus} handleSuggestionClick={handleSuggestionClick} - handleUsernameSuggestionClose={handleUsernameSuggestionClosed} - usernameSuggestions={usernameSuggestions} errorMessage={errors.username} helpText={[formatMessage(messages['help.text.username.1']), formatMessage(messages['help.text.username.2'])]} floatingLabel={formatMessage(messages['registration.username.label'])} @@ -679,7 +621,6 @@ RegistrationPage.propTypes = { errors: PropTypes.shape({}), emailSuggestion: PropTypes.shape({}), }), - backendCountryCode: PropTypes.string, backendValidations: PropTypes.shape({ name: PropTypes.string, email: PropTypes.string, @@ -719,7 +660,6 @@ RegistrationPage.propTypes = { PropTypes.shape({}), ), }), - usernameSuggestions: PropTypes.arrayOf(PropTypes.string), userPipelineDataLoaded: PropTypes.bool, validationApiRateLimited: PropTypes.bool, // Actions @@ -728,7 +668,6 @@ RegistrationPage.propTypes = { getRegistrationDataFromBackend: PropTypes.func.isRequired, handleInstitutionLogin: PropTypes.func, registerNewUser: PropTypes.func.isRequired, - resetUsernameSuggestions: PropTypes.func.isRequired, setUserPipelineDetailsLoaded: PropTypes.func.isRequired, validateFromBackend: PropTypes.func.isRequired, }; @@ -748,7 +687,6 @@ RegistrationPage.defaultProps = { suggestion: '', type: '', }, }, - backendCountryCode: '', backendValidations: null, fieldDescriptions: {}, handleInstitutionLogin: null, @@ -770,7 +708,6 @@ RegistrationPage.defaultProps = { providers: [], secondaryProviders: [], }, - usernameSuggestions: [], userPipelineDataLoaded: false, validationApiRateLimited: false, }; @@ -781,7 +718,6 @@ export default connect( backupFormState: backupRegistrationFormBegin, clearBackendError: clearRegistertionBackendError, getRegistrationDataFromBackend: getThirdPartyAuthContext, - resetUsernameSuggestions: clearUsernameSuggestions, validateFromBackend: fetchRealtimeValidations, registerNewUser, setUserPipelineDetailsLoaded: setUserPipelineDataLoaded, diff --git a/src/register/registrationFields/UsernameField.jsx b/src/register/registrationFields/UsernameField.jsx index f30d55c8..4bf57014 100644 --- a/src/register/registrationFields/UsernameField.jsx +++ b/src/register/registrationFields/UsernameField.jsx @@ -1,21 +1,72 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; import { useIntl } from '@edx/frontend-platform/i18n'; import { Button, Icon, IconButton } from '@edx/paragon'; import { Close } from '@edx/paragon/icons'; -import PropTypes, { string } from 'prop-types'; +import PropTypes from 'prop-types'; import { FormGroup } from '../../common-components'; +import { clearUsernameSuggestions } from '../data/actions'; import messages from '../messages'; const UsernameField = (props) => { const { formatMessage } = useIntl(); + const dispatch = useDispatch(); + const { - handleSuggestionClick, handleUsernameSuggestionClose, usernameSuggestions, errorMessage, + value, + errorMessage, + handleChange, + handleFocus, } = props; let className = ''; let suggestedUsernameDiv = null; let iconButton = null; + const usernameSuggestions = useSelector(state => state.register.usernameSuggestions); + + /** + * We need to remove the placeholder from the field, adding a space will do that. + * This is needed because we are placing the username suggestions on top of the field. + */ + useEffect(() => { + if (usernameSuggestions.length && !value) { + handleChange({ target: { name: 'username', value: ' ' } }); + } + }, [handleChange, usernameSuggestions, value]); + + const handleOnChange = (event) => { + let username = event.target.value; + if (username.length > 30) { + return; + } + if (event.target.value.startsWith(' ')) { + username = username.trim(); + } + handleChange({ target: { name: 'username', value: username } }); + }; + + const handleOnFocus = (event) => { + const username = event.target.value; + dispatch(clearUsernameSuggestions()); + // If we added a space character to username field to display the suggestion + // remove it before user enters the input. This is to ensure user doesn't + // have a space prefixed to the username. + if (username === ' ') { + handleChange({ target: { name: 'username', value: '' } }); + } + handleFocus({ target: { name: 'username', value: username } }); + }; + + const handleSuggestionClick = (event, fieldName, suggestion = '') => { + event.preventDefault(); + handleFocus({ target: { name: 'username', value: suggestion } }); // to clear the error if any + handleChange({ target: { name: 'username', value: suggestion } }); // to set suggestion as value + dispatch(clearUsernameSuggestions()); + }; + + const handleUsernameSuggestionClose = () => dispatch(clearUsernameSuggestions()); + const suggestedUsernames = () => (
{formatMessage(messages['registration.username.suggestion.label'])} @@ -37,11 +88,12 @@ const UsernameField = (props) => { {iconButton}
); - if (usernameSuggestions.length > 0 && errorMessage && props.value === ' ') { + + if (usernameSuggestions.length > 0 && errorMessage && value === ' ') { className = 'username-suggestions__error'; iconButton = handleUsernameSuggestionClose()} variant="black" size="sm" className="username-suggestions__close__button" />; suggestedUsernameDiv = suggestedUsernames(); - } else if (usernameSuggestions.length > 0 && props.value === ' ') { + } else if (usernameSuggestions.length > 0 && value === ' ') { className = 'username-suggestions d-flex align-items-center'; iconButton = handleUsernameSuggestionClose()} variant="black" size="sm" className="username-suggestions__close__button" />; suggestedUsernameDiv = suggestedUsernames(); @@ -49,22 +101,20 @@ const UsernameField = (props) => { suggestedUsernameDiv = suggestedUsernames(); } return ( - + {suggestedUsernameDiv} ); }; UsernameField.defaultProps = { - usernameSuggestions: [], errorMessage: '', autoComplete: null, }; UsernameField.propTypes = { - usernameSuggestions: PropTypes.arrayOf(string), - handleSuggestionClick: PropTypes.func.isRequired, - handleUsernameSuggestionClose: PropTypes.func.isRequired, + handleChange: PropTypes.func.isRequired, + handleFocus: PropTypes.func.isRequired, errorMessage: PropTypes.string, name: PropTypes.string.isRequired, value: PropTypes.string.isRequired,