diff --git a/src/common-components/data/actions.js b/src/common-components/data/actions.js index f86ddd0851..0c7d3bcb23 100644 --- a/src/common-components/data/actions.js +++ b/src/common-components/data/actions.js @@ -13,9 +13,15 @@ export const getThirdPartyAuthContextBegin = () => ({ type: THIRD_PARTY_AUTH_CONTEXT.BEGIN, }); -export const getThirdPartyAuthContextSuccess = (fieldDescriptions, optionalFields, thirdPartyAuthContext) => ({ +export const getThirdPartyAuthContextSuccess = ( + fieldDescriptions, + optionalFields, + thirdPartyAuthContext, + countries) => ({ type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS, - payload: { fieldDescriptions, optionalFields, thirdPartyAuthContext }, + payload: { + fieldDescriptions, optionalFields, thirdPartyAuthContext, countries, + }, }); export const getThirdPartyAuthContextFailure = () => ({ diff --git a/src/common-components/data/reducers.js b/src/common-components/data/reducers.js index c2150cda80..afa86d9b3d 100644 --- a/src/common-components/data/reducers.js +++ b/src/common-components/data/reducers.js @@ -35,6 +35,7 @@ const reducer = (state = defaultState, action = {}) => { optionalFields: action.payload.optionalFields, thirdPartyAuthContext: action.payload.thirdPartyAuthContext, thirdPartyAuthApiStatus: COMPLETE_STATE, + countries: action.payload.countries, }; } case THIRD_PARTY_AUTH_CONTEXT.FAILURE: diff --git a/src/common-components/data/sagas.js b/src/common-components/data/sagas.js index ffe0be37c6..5910076e38 100644 --- a/src/common-components/data/sagas.js +++ b/src/common-components/data/sagas.js @@ -8,6 +8,7 @@ import { THIRD_PARTY_AUTH_CONTEXT, } from './actions'; import { + getCountryList, getThirdPartyAuthContext, } from './service'; import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions'; @@ -18,9 +19,10 @@ export function* fetchThirdPartyAuthContext(action) { const { fieldDescriptions, optionalFields, thirdPartyAuthContext, } = yield call(getThirdPartyAuthContext, action.payload.urlParams); + const countries = (yield call(getCountryList)) || []; yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode)); - yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext)); + yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext, countries)); } catch (e) { yield put(getThirdPartyAuthContextFailure()); logError(e); diff --git a/src/common-components/data/service.js b/src/common-components/data/service.js index 51df2135de..7085fa87ab 100644 --- a/src/common-components/data/service.js +++ b/src/common-components/data/service.js @@ -1,5 +1,8 @@ import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { logError } from '@edx/frontend-platform/logging'; + +import { FIELD_LABELS } from '../../data/constants'; // eslint-disable-next-line import/prefer-default-export export async function getThirdPartyAuthContext(urlParams) { @@ -23,3 +26,28 @@ export async function getThirdPartyAuthContext(urlParams) { thirdPartyAuthContext: data.contextData || {}, }; } + +function extractCountryList(data) { + return data?.fields + .find(({ name }) => name === FIELD_LABELS.COUNTRY) + ?.options?.map(({ value, name }) => ({ code: value, name })) || []; +} + +export async function getCountryList() { + try { + const requestConfig = { + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + isPublic: true, + }; + + const { data } = await getAuthenticatedHttpClient() + .get( + `${getConfig().LMS_BASE_URL}/user_api/v1/account/registration/`, + requestConfig, + ); + return extractCountryList(data); + } catch (e) { + logError(e); + return []; + } +} diff --git a/src/common-components/data/tests/sagas.test.js b/src/common-components/data/tests/sagas.test.js index f3bc07abf9..ab19f54bb8 100644 --- a/src/common-components/data/tests/sagas.test.js +++ b/src/common-components/data/tests/sagas.test.js @@ -8,6 +8,11 @@ import * as api from '../service'; const { loggingService } = initializeMockLogging(); +jest.mock('../service', () => ({ + getCountryList: jest.fn(), + getThirdPartyAuthContext: jest.fn(), +})); + describe('fetchThirdPartyAuthContext', () => { const params = { payload: { urlParams: {} }, @@ -31,6 +36,7 @@ describe('fetchThirdPartyAuthContext', () => { thirdPartyAuthContext: data, fieldDescriptions: {}, optionalFields: {}, + countries: [], })); const dispatched = []; @@ -44,7 +50,7 @@ describe('fetchThirdPartyAuthContext', () => { expect(dispatched).toEqual([ actions.getThirdPartyAuthContextBegin(), setCountryFromThirdPartyAuthContext(), - actions.getThirdPartyAuthContextSuccess({}, {}, data), + actions.getThirdPartyAuthContextSuccess({}, {}, data, []), ]); getThirdPartyAuthContext.mockClear(); }); diff --git a/src/data/constants.js b/src/data/constants.js index 90fdf75f2b..8a6e6a7bc3 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -37,3 +37,6 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+ // things like auto-enrollment upon login and registration. export const AUTH_PARAMS = ['course_id', 'enrollment_action', 'course_mode', 'email_opt_in', 'purchase_workflow', 'next', 'register_for_free', 'track', 'is_account_recovery', 'variant', 'host', 'cta']; export const REDIRECT = 'redirect'; +export const FIELD_LABELS = { + COUNTRY: 'country', +}; diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index 8c5ea79f5b..2a40b37c3d 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -85,6 +85,7 @@ const RegistrationPage = (props) => { const providers = useSelector(state => state.commonComponents.thirdPartyAuthContext.providers); const secondaryProviders = useSelector(state => state.commonComponents.thirdPartyAuthContext.secondaryProviders); const pipelineUserDetails = useSelector(state => state.commonComponents.thirdPartyAuthContext.pipelineUserDetails); + const countries = useSelector(state => state.commonComponents.countries); const backendValidations = useSelector(getBackendValidations); const queryParams = useMemo(() => getAllPossibleQueryParams(), []); @@ -358,6 +359,7 @@ const RegistrationPage = (props) => { setFormFields={setConfigurableFormFields} autoSubmitRegisterForm={autoSubmitRegForm} fieldDescriptions={fieldDescriptions} + countries={countries} /> { setFieldErrors, setFormFields, autoSubmitRegistrationForm, + countries, } = props; /** The reason for adding the entry 'United States' is that Chrome browser aut-fill the form with the 'Unites States' instead of 'United States of America' which does not exist in country dropdown list and gets the user confused and unable to create an account. So we added the United States entry in the dropdown list. */ - const countryList = useMemo(() => getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }]), []); let showTermsOfServiceAndHonorCode = false; let showCountryField = false; @@ -70,6 +70,18 @@ const ConfigurableRegistrationForm = (props) => { } }, [autoSubmitRegistrationForm]); // eslint-disable-line react-hooks/exhaustive-deps + const removeDisabledCountries = useCallback((countryList) => { + if (!countries.length) { + return countryList; + } + const allowedCountries = new Set(countries.map(({ code }) => code)); + + return countryList.filter(({ code }) => allowedCountries.has(code)); + }, [countries]); + + const countryList = useMemo(() => removeDisabledCountries( + getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }]), []), [removeDisabledCountries]); + const handleErrorChange = (fieldName, error) => { if (fieldName) { setFieldErrors(prevErrors => ({ @@ -231,11 +243,16 @@ ConfigurableRegistrationForm.propTypes = { setFieldErrors: PropTypes.func.isRequired, setFormFields: PropTypes.func.isRequired, autoSubmitRegistrationForm: PropTypes.bool, + countries: PropTypes.arrayOf(PropTypes.shape({ + code: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + })), }; ConfigurableRegistrationForm.defaultProps = { fieldDescriptions: {}, autoSubmitRegistrationForm: false, + countries: [], }; export default ConfigurableRegistrationForm;