diff --git a/src/register/ConfigurableRegistrationForm.jsx b/src/register/ConfigurableRegistrationForm.jsx index 844949f0..36bc66a9 100644 --- a/src/register/ConfigurableRegistrationForm.jsx +++ b/src/register/ConfigurableRegistrationForm.jsx @@ -54,13 +54,13 @@ const ConfigurableRegistrationForm = (props) => { }); const handleOnChange = (event, countryValue = null) => { - const { name, type } = event.target; + const { name } = event.target; let value; if (countryValue) { value = { ...countryValue }; } else { value = event.target.type === 'checkbox' ? event.target.checked : event.target.value; - if (type === 'checkbox') { + if (event.target.type === 'checkbox') { setFieldErrors(prevErrors => ({ ...prevErrors, [name]: '' })); } } diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index ef049455..24c832d5 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -190,25 +190,27 @@ const RegistrationPage = (props) => { }, [registrationErrorCode]); useEffect(() => { - let countryCode = ''; - let countryDisplayValue = ''; + 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, - }, + const selectedCountry = countryList.find( + (country) => (country[COUNTRY_CODE_KEY].toLowerCase() === backendCountryCode.toLowerCase()), + ); + if (selectedCountry) { + countryCode = selectedCountry[COUNTRY_CODE_KEY]; + countryDisplayValue = selectedCountry[COUNTRY_DISPLAY_KEY]; } - )); - }, [backendCountryCode, countryList]); + 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. diff --git a/src/register/data/constants.js b/src/register/data/constants.js index 0c0f68fb..34d75e4b 100644 --- a/src/register/data/constants.js +++ b/src/register/data/constants.js @@ -173,6 +173,3 @@ export const DEFAULT_TOP_LEVEL_DOMAINS = [ export const COUNTRY_CODE_KEY = 'code'; export const COUNTRY_DISPLAY_KEY = 'name'; - -export const EXPAND_MORE_ICON = 'expand-more'; -export const EXPAND_LESS_ICON = 'expand-less'; diff --git a/src/register/data/utils.js b/src/register/data/utils.js index 99f3ba0c..190cb539 100644 --- a/src/register/data/utils.js +++ b/src/register/data/utils.js @@ -97,8 +97,7 @@ export function validateCountryField(value, countryList, errorMessage) { // evaluated and set its value as a valid value. const selectedCountry = countryList.find( (country) => ( - // When translations apply extra space added in country value so we should - // trim that. + // When translations are applied, extra space added in country value, so we should trim that. country[COUNTRY_DISPLAY_KEY].toLowerCase().trim() === normalizedValue || country[COUNTRY_CODE_KEY].toLowerCase().trim() === normalizedValue ), diff --git a/src/register/registrationFields/CountryField.jsx b/src/register/registrationFields/CountryField.jsx index cbe8440e..19af5ff9 100644 --- a/src/register/registrationFields/CountryField.jsx +++ b/src/register/registrationFields/CountryField.jsx @@ -1,153 +1,61 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React from 'react'; import { useIntl } from '@edx/frontend-platform/i18n'; -import { Icon, IconButton } from '@edx/paragon'; -import { ExpandLess, ExpandMore } from '@edx/paragon/icons'; +import { FormAutosuggest, FormAutosuggestOption, FormControlFeedback } from '@edx/paragon'; import PropTypes from 'prop-types'; -import { FormGroup } from '../../common-components'; -import { - COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY, EXPAND_LESS_ICON, EXPAND_MORE_ICON, -} from '../data/constants'; +import { COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY } from '../data/constants'; import messages from '../messages'; const CountryField = (props) => { const { countryList, selectedCountry } = props; - - const dropdownRef = useRef(null); const { formatMessage } = useIntl(); - const [errorMessage, setErrorMessage] = useState(props.errorMessage); - const [dropDownItems, setDropDownItems] = useState([]); - const [displayValue, setDisplayValue] = useState(''); - const [trailingIcon, setTrailingIcon] = useState(EXPAND_MORE_ICON); - const onBlurHandler = (event, itemClicked = false, countryName = '') => { - const { name } = event.target; - const relatedName = event.relatedTarget ? event.relatedTarget.name : ''; - // For a better user experience, do not validate when focus out from 'country' field - // and focus on 'countryItem' or 'countryExpand' button. - if ((relatedName === 'countryItem' || relatedName === 'countryExpand') && name === 'country') { - return; - } - const countryValue = itemClicked ? countryName : displayValue; - if (props.onBlurHandler) { - props.onBlurHandler({ target: { name: 'country', value: countryValue } }); - } - setTrailingIcon(EXPAND_MORE_ICON); - setDropDownItems([]); - }; - - const getDropdownItems = (countryToFind = null) => { - let updatedCountryList = countryList; - if (countryToFind) { - updatedCountryList = countryList.filter( - (option) => (option.name.toLowerCase().includes(countryToFind.toLowerCase())), - ); - } - - return updatedCountryList.map((country) => { - const countryName = country[COUNTRY_DISPLAY_KEY]; - return ( - - ); - }); + const handleSelected = (value) => { + if (props.onBlurHandler) { props.onBlurHandler({ target: { name: 'country', value } }); } }; const onFocusHandler = (event) => { - const { name, value } = event.target; - setDropDownItems(getDropdownItems(name === 'country' ? value : displayValue)); - setTrailingIcon(EXPAND_LESS_ICON); - setErrorMessage(''); if (props.onFocusHandler) { props.onFocusHandler(event); } }; - const onChangeHandler = (event) => { - const filteredItems = getDropdownItems(event.target.value); - setDropDownItems(filteredItems); - setDisplayValue(event.target.value); - if (props.onChangeHandler) { props.onChangeHandler(event, { countryCode: '', displayValue: event.target.value }); } - }; - - const handleOnClickOutside = () => { - setTrailingIcon(EXPAND_MORE_ICON); - setDropDownItems([]); - }; - - const handleTrailingIconClick = () => { - if (trailingIcon === EXPAND_MORE_ICON) { - setDropDownItems(getDropdownItems()); - setTrailingIcon(EXPAND_LESS_ICON); - } else { - setDropDownItems([]); - setTrailingIcon(EXPAND_MORE_ICON); + const onChangeHandler = (value) => { + if (props.onChangeHandler) { + props.onChangeHandler({ target: { name: 'country' } }, { countryCode: '', displayValue: value }); } }; - useEffect(() => { - const handleClickOutside = (event) => { - if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { - handleOnClickOutside(); - } - }; - document.addEventListener('click', handleClickOutside, true); - return () => { - document.removeEventListener('click', handleClickOutside, true); - }; - }, []); - - useEffect(() => { - if (selectedCountry.displayValue) { - setDisplayValue(selectedCountry.displayValue); - } - }, [selectedCountry]); - - useEffect(() => { - setErrorMessage(props.errorMessage); - }, [props.errorMessage]); + const getCountryList = () => countryList.map((country) => ( + + {country[COUNTRY_DISPLAY_KEY]} + + )); return ( -
- + {}} - onClick={handleTrailingIconClick} - onFocus={() => {}} - /> - )} - value={displayValue} - errorMessage={errorMessage} - handleChange={onChangeHandler} - handleBlur={onBlurHandler} - handleFocus={onFocusHandler} - /> -
- { dropDownItems?.length > 0 ? dropDownItems : null } -
+ aria-label="form autosuggest" + name="country" + value={selectedCountry.displayValue || ''} + onSelected={(value) => handleSelected(value)} + onFocus={(e) => onFocusHandler(e)} + onBlur={(e) => handleSelected(e.target.value)} + onChange={(value) => onChangeHandler(value)} + > + {getCountryList()} +
+ {props.errorMessage !== '' && ( + + {props.errorMessage} + + )}
); }; diff --git a/src/register/tests/RegistrationPage.test.jsx b/src/register/tests/RegistrationPage.test.jsx index 2a580eb1..bb41c4c1 100644 --- a/src/register/tests/RegistrationPage.test.jsx +++ b/src/register/tests/RegistrationPage.test.jsx @@ -121,8 +121,8 @@ describe('RegistrationPage', () => { registrationPage.find('input#username').simulate('change', { target: { value: payload.username, name: 'username' } }); registrationPage.find('input#email').simulate('change', { target: { value: payload.email, name: 'email' } }); - registrationPage.find('input#country').simulate('change', { target: { value: payload.country, name: 'country' } }); - registrationPage.find('input#country').simulate('blur', { target: { value: payload.country, name: 'country' } }); + registrationPage.find('input[name="country"]').simulate('change', { target: { value: payload.country, name: 'country' } }); + registrationPage.find('input[name="country"]').simulate('blur', { target: { value: payload.country, name: 'country' } }); if (!isThirdPartyAuth) { registrationPage.find('input#password').simulate('change', { target: { value: payload.password, name: 'password' } }); @@ -307,7 +307,7 @@ describe('RegistrationPage', () => { registrationPage.find('input#password').simulate('blur', { target: { value: '', name: 'password' } }); expect(registrationPage.find('div[feedback-for="password"]').text()).toContain(emptyFieldValidation.password); - registrationPage.find('input#country').simulate('blur', { target: { value: '', name: 'country' } }); + registrationPage.find('input[name="country"]').simulate('blur', { target: { value: '', name: 'country' } }); expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(emptyFieldValidation.country); }); @@ -332,7 +332,7 @@ describe('RegistrationPage', () => { it('should run validations for focused field on form submission', () => { const registrationPage = mount(reduxWrapper()); - registrationPage.find('input#country').simulate('focus'); + registrationPage.find('input[name="country"]').simulate('focus'); registrationPage.find('button.btn-brand').simulate('click'); expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(emptyFieldValidation.country); @@ -443,7 +443,7 @@ describe('RegistrationPage', () => { expect(registrationPage.find('div[feedback-for="password"]').exists()).toBeFalsy(); expect(registrationPage.find('div[feedback-for="country"]').text()).toEqual(emptyFieldValidation.country); - registrationPage.find('input#country').simulate('focus'); + registrationPage.find('input[name="country"]').simulate('focus'); expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy(); }); @@ -988,7 +988,7 @@ describe('RegistrationPage', () => { }); const registrationPage = mount(reduxWrapper()); - expect(registrationPage.find('input#country').props().value).toEqual('Pakistan'); + expect(registrationPage.find('input[name="country"]').props().value).toEqual('Pakistan'); }); it('should display error message based on the error code returned by API', () => { @@ -1041,7 +1041,7 @@ describe('RegistrationPage', () => { getLocale.mockImplementation(() => ('ar-ae')); const registrationPage = mount(reduxWrapper()); - registrationPage.find('input#country').simulate('focus'); + registrationPage.find('input[name="country"]').simulate('click'); registrationPage.find('button.dropdown-item').at(0).simulate('click', { target: { value: 'أفغانستان ', name: 'countryItem' } }); expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy(); }); @@ -1064,18 +1064,6 @@ describe('RegistrationPage', () => { registrationPage.find('input#email').simulate('change', { target: { value: 'a@gmail.com', name: 'email' } }); expect(registrationPage.find('div[feedback-for="email"]').exists()).toBeFalsy(); }); - - it('should set country in component state when form is translated using browser translations', () => { - getLocale.mockImplementation(() => ('en-us')); - - store.dispatch = jest.fn(store.dispatch); - - const registrationPage = mount(reduxWrapper()); - registrationPage.find('input#country').simulate('focus'); - registrationPage.find('button.dropdown-item').at(0).simulate('click', { target: { value: undefined, name: undefined, parentElement: { parentElement: { value: 'Afghanistan' } } } }); - expect(registrationPage.find('input#country').props().value).toEqual('Afghanistan'); - expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy(); - }); }); describe('Test Configurable Fields', () => { @@ -1104,6 +1092,7 @@ describe('RegistrationPage', () => { }); it('should submit form with fields returned by backend in payload', () => { + getLocale.mockImplementation(() => ('en-us')); jest.spyOn(global.Date, 'now').mockImplementation(() => 0); store = mockStore({ ...initialState, @@ -1204,16 +1193,6 @@ describe('RegistrationPage', () => { expect(registrationPage.find('#profession-error').last().text()).toEqual(professionError); }); - it('should not remove errors from form fields when country is selected by clicking on expand button', () => { - const registrationPage = mount(reduxWrapper()); - registrationPage.find('button.btn-brand').simulate('click'); - expect(registrationPage.find('div[feedback-for="name"]').exists()).toBeTruthy(); - - registrationPage.find('button[name="countryExpand"]').simulate('click'); - registrationPage.find('button.dropdown-item').at(0).simulate('click', { target: { value: 'Pakistan', name: 'countryItem' } }); - expect(registrationPage.find('div[feedback-for="name"]').exists()).toBeTruthy(); - }); - it('should check TOS and honor code fields if they exist when auto submitting register form', () => { getLocale.mockImplementation(() => ('en-us')); store = mockStore({ diff --git a/src/sass/_registration.scss b/src/sass/_registration.scss index 5b4f93a0..63baf2c8 100644 --- a/src/sass/_registration.scss +++ b/src/sass/_registration.scss @@ -1,3 +1,7 @@ .register-stateful-button-width { min-width: 14.4rem; } + +.pgn__form-autosuggest__wrapper > .pgn__form-group { + margin-bottom: 0 !important; +}