Revert "refactor: use paragon Autosuggest field for country field (#897)" (#900)

This reverts commit 3c89afea4a.
This commit is contained in:
Syed Sajjad Hussain Shah
2023-05-12 13:01:22 +05:00
committed by GitHub
parent 3c89afea4a
commit f0105f0094
6 changed files with 170 additions and 56 deletions

View File

@@ -54,13 +54,13 @@ const ConfigurableRegistrationForm = (props) => {
});
const handleOnChange = (event, countryValue = null) => {
const { name } = event.target;
const { name, type } = event.target;
let value;
if (countryValue) {
value = { ...countryValue };
} else {
value = event.target.type === 'checkbox' ? event.target.checked : event.target.value;
if (event.target.type === 'checkbox') {
if (type === 'checkbox') {
setFieldErrors(prevErrors => ({ ...prevErrors, [name]: '' }));
}
}

View File

@@ -173,3 +173,6 @@ 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';

View File

@@ -93,10 +93,14 @@ export function validateCountryField(value, countryList, errorMessage) {
if (value) {
const normalizedValue = value.toLowerCase();
// Handling a case here where user enters a valid country code that needs to be
// evaluated and set its value as a valid value.
const selectedCountry = countryList.find(
(country) => (
// When translations are applied, extra space added in country value, so we should trim that.
// When translations apply 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
),
);
if (selectedCountry) {

View File

@@ -1,62 +1,154 @@
import React from 'react';
import React, { useEffect, useRef, useState } from 'react';
import { useIntl } from '@edx/frontend-platform/i18n';
import { FormAutosuggest, FormAutosuggestOption, FormControlFeedback } from '@edx/paragon';
import { Icon, IconButton } from '@edx/paragon';
import { ExpandLess, ExpandMore } from '@edx/paragon/icons';
import PropTypes from 'prop-types';
import { COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY } from '../data/constants';
import { FormGroup } from '../../common-components';
import {
COUNTRY_CODE_KEY, COUNTRY_DISPLAY_KEY, EXPAND_LESS_ICON, EXPAND_MORE_ICON,
} from '../data/constants';
import messages from '../messages';
const CountryField = (props) => {
const { countryList, selectedCountry } = props;
const { formatMessage } = useIntl();
const handleSelected = (value) => {
if (props.onBlurHandler) { props.onBlurHandler({ target: { name: 'country', value } }); }
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 (
<button
type="button"
name="countryItem"
className="dropdown-item data-hj-suppress"
value={countryName}
key={country[COUNTRY_CODE_KEY]}
onClick={(event) => onBlurHandler(event, true, countryName)}
/* This event will prevent the blur event to be fired,
as blur event is having higher priority than click event and restricts the click event.
*/
onMouseDown={(event) => event.preventDefault()}
>
{countryName.length > 30 ? countryName.substring(0, 30).concat('...') : countryName}
</button>
);
});
};
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 = (value) => {
if (props.onChangeHandler) {
props.onChangeHandler({ target: { name: 'country' } }, { countryCode: '', displayValue: value });
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 getCountryList = () => countryList.map((country) => (
<FormAutosuggestOption key={country[COUNTRY_CODE_KEY]}>
{country[COUNTRY_DISPLAY_KEY]}
</FormAutosuggestOption>
));
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]);
return (
<>
<FormAutosuggest
floatingLabel={formatMessage(messages['registration.country.label'])}
aria-label="form autosuggest"
<div ref={dropdownRef} className="mb-4">
<FormGroup
as="input"
name="country"
value={selectedCountry.displayValue || ''}
onSelected={(value) => handleSelected(value)}
onFocus={(e) => onFocusHandler(e)}
onBlur={(e) => handleSelected(e.target.value)}
onChange={(value) => onChangeHandler(value)}
>
{getCountryList()}
</FormAutosuggest>
{props.errorMessage !== '' && (
<FormControlFeedback
key="error"
className="form-text-size"
hasIcon={false}
feedback-for="country"
type="invalid"
>
{props.errorMessage}
</FormControlFeedback>
)}
</>
autoComplete="chrome-off"
className="mb-0"
floatingLabel={formatMessage(messages['registration.country.label'])}
trailingElement={(
<IconButton
name="countryExpand"
size="sm"
variant="secondary"
alt="expand-dropdown"
iconAs={Icon}
src={trailingIcon === EXPAND_MORE_ICON ? ExpandMore : ExpandLess}
onBlur={() => {}}
onClick={handleTrailingIconClick}
onFocus={() => {}}
/>
)}
value={displayValue}
errorMessage={errorMessage}
handleChange={onChangeHandler}
handleBlur={onBlurHandler}
handleFocus={onFocusHandler}
/>
<div className="dropdown-container">
{ dropDownItems?.length > 0 ? dropDownItems : null }
</div>
</div>
);
};

View File

@@ -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[name="country"]').simulate('change', { target: { value: payload.country, name: 'country' } });
registrationPage.find('input[name="country"]').simulate('blur', { target: { value: payload.country, name: 'country' } });
registrationPage.find('input#country').simulate('change', { target: { value: payload.country, name: 'country' } });
registrationPage.find('input#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[name="country"]').simulate('blur', { target: { value: '', name: 'country' } });
registrationPage.find('input#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(<IntlRegistrationPage {...props} />));
registrationPage.find('input[name="country"]').simulate('focus');
registrationPage.find('input#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[name="country"]').simulate('focus');
registrationPage.find('input#country').simulate('focus');
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
});
@@ -986,11 +986,9 @@ describe('RegistrationPage', () => {
backendCountryCode: 'PK',
},
});
const expectedPropValue = { countryCode: 'PK', displayValue: 'Pakistan' };
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />)).find('RegistrationPage');
expect(registrationPage.find('CountryField').prop('selectedCountry')).toEqual(expectedPropValue);
expect(registrationPage.find('input[name="country"]').exists()).toBeTruthy();
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
expect(registrationPage.find('input#country').props().value).toEqual('Pakistan');
});
it('should display error message based on the error code returned by API', () => {
@@ -1043,7 +1041,7 @@ describe('RegistrationPage', () => {
getLocale.mockImplementation(() => ('ar-ae'));
const registrationPage = mount(reduxWrapper(<IntlRegistrationPage {...props} />));
registrationPage.find('input[name="country"]').simulate('click');
registrationPage.find('input#country').simulate('focus');
registrationPage.find('button.dropdown-item').at(0).simulate('click', { target: { value: 'أفغانستان ', name: 'countryItem' } });
expect(registrationPage.find('div[feedback-for="country"]').exists()).toBeFalsy();
});
@@ -1066,6 +1064,18 @@ 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(<IntlRegistrationPage {...props} />));
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', () => {
@@ -1094,7 +1104,6 @@ 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,
@@ -1195,6 +1204,16 @@ 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(<IntlRegistrationPage {...props} />));
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({

View File

@@ -1,7 +1,3 @@
.register-stateful-button-width {
min-width: 14.4rem;
}
.pgn__form-autosuggest__wrapper > .pgn__form-group {
margin-bottom: 0 !important;
}