From 09dc21eb0e82efd9457a417434e9c43bd43d40f6 Mon Sep 17 00:00:00 2001 From: sundasnoreen12 Date: Thu, 20 Feb 2025 16:05:50 +0500 Subject: [PATCH 1/4] feat: implemented restricted country implementation --- src/common-components/data/actions.js | 10 +++++-- src/common-components/data/constants.js | 4 +++ src/common-components/data/reducers.js | 1 + src/common-components/data/sagas.js | 5 +++- src/common-components/data/service.js | 28 +++++++++++++++++++ .../data/tests/sagas.test.js | 8 +++++- src/register/RegistrationPage.jsx | 2 ++ .../ConfigurableRegistrationForm.jsx | 23 +++++++++++---- 8 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/common-components/data/actions.js b/src/common-components/data/actions.js index f86ddd08..0c7d3bcb 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/constants.js b/src/common-components/data/constants.js index 4f9ec72c..d071dd19 100644 --- a/src/common-components/data/constants.js +++ b/src/common-components/data/constants.js @@ -77,3 +77,7 @@ export const progressiveProfilingFields = { }, }, }; + +export const FIELD_LABELS = { + COUNTRY: 'country', +}; diff --git a/src/common-components/data/reducers.js b/src/common-components/data/reducers.js index c2150cda..afa86d9b 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 65105866..8f221840 100644 --- a/src/common-components/data/sagas.js +++ b/src/common-components/data/sagas.js @@ -10,6 +10,7 @@ import { } from './actions'; import { progressiveProfilingFields, registerFields } from './constants'; import { + getCountryList, getThirdPartyAuthContext, } from './service'; import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions'; @@ -20,6 +21,7 @@ export function* fetchThirdPartyAuthContext(action) { const { fieldDescriptions, optionalFields, thirdPartyAuthContext, } = yield call(getThirdPartyAuthContext, action.payload.urlParams); + const countries = (yield call(getCountryList)) || []; yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode)); // hard code country field, level of education and gender fields @@ -28,9 +30,10 @@ export function* fetchThirdPartyAuthContext(action) { registerFields, progressiveProfilingFields, thirdPartyAuthContext, + countries, )); } else { - yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext)); + yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext, countries)); } } catch (e) { yield put(getThirdPartyAuthContextFailure()); diff --git a/src/common-components/data/service.js b/src/common-components/data/service.js index 51df2135..e418e6d1 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 './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 f3bc07ab..ab19f54b 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/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index 275281d3..1bb41185 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -93,6 +93,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(), []); @@ -392,6 +393,7 @@ const RegistrationPage = (props) => { setFormFields={setConfigurableFormFields} autoSubmitRegisterForm={autoSubmitRegForm} fieldDescriptions={fieldDescriptions} + countries={countries} /> { setFieldErrors, setFormFields, autoSubmitRegistrationForm, + countries, } = props; const dispatch = useDispatch(); @@ -45,10 +46,6 @@ const ConfigurableRegistrationForm = (props) => { 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' }]).filter(country => country.code !== 'RU') - ), []); - let showTermsOfServiceAndHonorCode = false; let showCountryField = false; @@ -82,6 +79,17 @@ 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' }].filter(country => country.code !== 'RU')), []), [removeDisabledCountries]); + const handleErrorChange = (fieldName, error) => { if (fieldName) { setFieldErrors(prevErrors => ({ @@ -262,11 +270,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; From 206c4c887bee64961a3e2894f7d226b11eec24ed Mon Sep 17 00:00:00 2001 From: sundasnoreen12 Date: Thu, 20 Feb 2025 21:13:07 +0500 Subject: [PATCH 2/4] test: refactor test file --- src/register/components/ConfigurableRegistrationForm.jsx | 2 +- .../components/tests/ConfigurableRegistrationForm.test.jsx | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/register/components/ConfigurableRegistrationForm.jsx b/src/register/components/ConfigurableRegistrationForm.jsx index 1f2ed0a6..01922280 100644 --- a/src/register/components/ConfigurableRegistrationForm.jsx +++ b/src/register/components/ConfigurableRegistrationForm.jsx @@ -88,7 +88,7 @@ const ConfigurableRegistrationForm = (props) => { }, [countries]); const countryList = useMemo(() => removeDisabledCountries( - getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }].filter(country => country.code !== 'RU')), []), [removeDisabledCountries]); + getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }]), []), [removeDisabledCountries]); const handleErrorChange = (fieldName, error) => { if (fieldName) { diff --git a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx index fd423b92..559cf909 100644 --- a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx +++ b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx @@ -192,6 +192,7 @@ describe('ConfigurableRegistrationForm', () => { }, }, autoSubmitRegistrationForm: true, + countries: [{ code: 'AX', name: 'Åland Islands' }, { code: 'AL', name: 'Albania' }], }; render(routerWrapper(reduxWrapper( From cca87bd16afcc234e6f4c8885e7f76565cdc568e Mon Sep 17 00:00:00 2001 From: sundasnoreen12 Date: Sat, 22 Feb 2025 15:18:29 +0500 Subject: [PATCH 3/4] fix: fixed issue by removing empty array --- src/register/components/ConfigurableRegistrationForm.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/register/components/ConfigurableRegistrationForm.jsx b/src/register/components/ConfigurableRegistrationForm.jsx index 01922280..cd1f36b1 100644 --- a/src/register/components/ConfigurableRegistrationForm.jsx +++ b/src/register/components/ConfigurableRegistrationForm.jsx @@ -88,7 +88,7 @@ const ConfigurableRegistrationForm = (props) => { }, [countries]); const countryList = useMemo(() => removeDisabledCountries( - getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }]), []), [removeDisabledCountries]); + getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }])), [removeDisabledCountries]); const handleErrorChange = (fieldName, error) => { if (fieldName) { From a2ad9d5248c35b0c5a023dbea8869a5df8f2036f Mon Sep 17 00:00:00 2001 From: sundasnoreen12 Date: Mon, 24 Feb 2025 14:52:20 +0500 Subject: [PATCH 4/4] refactor: optimize code --- src/common-components/data/actions.js | 4 ++-- src/common-components/data/reducers.js | 2 +- src/common-components/data/sagas.js | 11 ++++++++--- src/common-components/data/service.js | 2 +- src/common-components/data/tests/sagas.test.js | 2 +- src/register/RegistrationPage.jsx | 4 ++-- .../components/ConfigurableRegistrationForm.jsx | 13 ++++++------- .../tests/ConfigurableRegistrationForm.test.jsx | 2 +- 8 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/common-components/data/actions.js b/src/common-components/data/actions.js index 0c7d3bcb..96871445 100644 --- a/src/common-components/data/actions.js +++ b/src/common-components/data/actions.js @@ -17,10 +17,10 @@ export const getThirdPartyAuthContextSuccess = ( fieldDescriptions, optionalFields, thirdPartyAuthContext, - countries) => ({ + countriesCodesList) => ({ type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS, payload: { - fieldDescriptions, optionalFields, thirdPartyAuthContext, countries, + fieldDescriptions, optionalFields, thirdPartyAuthContext, countriesCodesList, }, }); diff --git a/src/common-components/data/reducers.js b/src/common-components/data/reducers.js index afa86d9b..b51f989f 100644 --- a/src/common-components/data/reducers.js +++ b/src/common-components/data/reducers.js @@ -35,7 +35,7 @@ const reducer = (state = defaultState, action = {}) => { optionalFields: action.payload.optionalFields, thirdPartyAuthContext: action.payload.thirdPartyAuthContext, thirdPartyAuthApiStatus: COMPLETE_STATE, - countries: action.payload.countries, + countriesCodesList: action.payload.countriesCodesList, }; } case THIRD_PARTY_AUTH_CONTEXT.FAILURE: diff --git a/src/common-components/data/sagas.js b/src/common-components/data/sagas.js index 8f221840..62de3eab 100644 --- a/src/common-components/data/sagas.js +++ b/src/common-components/data/sagas.js @@ -21,7 +21,7 @@ export function* fetchThirdPartyAuthContext(action) { const { fieldDescriptions, optionalFields, thirdPartyAuthContext, } = yield call(getThirdPartyAuthContext, action.payload.urlParams); - const countries = (yield call(getCountryList)) || []; + const countriesCodesList = (yield call(getCountryList)) || []; yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode)); // hard code country field, level of education and gender fields @@ -30,10 +30,15 @@ export function* fetchThirdPartyAuthContext(action) { registerFields, progressiveProfilingFields, thirdPartyAuthContext, - countries, + countriesCodesList, )); } else { - yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext, countries)); + yield put(getThirdPartyAuthContextSuccess( + fieldDescriptions, + optionalFields, + thirdPartyAuthContext, + countriesCodesList, + )); } } catch (e) { yield put(getThirdPartyAuthContextFailure()); diff --git a/src/common-components/data/service.js b/src/common-components/data/service.js index e418e6d1..5f2a8e79 100644 --- a/src/common-components/data/service.js +++ b/src/common-components/data/service.js @@ -30,7 +30,7 @@ export async function getThirdPartyAuthContext(urlParams) { function extractCountryList(data) { return data?.fields .find(({ name }) => name === FIELD_LABELS.COUNTRY) - ?.options?.map(({ value, name }) => ({ code: value, name })) || []; + ?.options?.map(({ value }) => (value)) || []; } export async function getCountryList() { diff --git a/src/common-components/data/tests/sagas.test.js b/src/common-components/data/tests/sagas.test.js index ab19f54b..f29e2217 100644 --- a/src/common-components/data/tests/sagas.test.js +++ b/src/common-components/data/tests/sagas.test.js @@ -36,7 +36,7 @@ describe('fetchThirdPartyAuthContext', () => { thirdPartyAuthContext: data, fieldDescriptions: {}, optionalFields: {}, - countries: [], + countriesCodesList: [], })); const dispatched = []; diff --git a/src/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index 1bb41185..50236e80 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -93,7 +93,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 countriesCodesList = useSelector(state => state.commonComponents.countriesCodesList); const backendValidations = useSelector(getBackendValidations); const queryParams = useMemo(() => getAllPossibleQueryParams(), []); @@ -393,7 +393,7 @@ const RegistrationPage = (props) => { setFormFields={setConfigurableFormFields} autoSubmitRegisterForm={autoSubmitRegForm} fieldDescriptions={fieldDescriptions} - countries={countries} + countriesCodesList={countriesCodesList} /> { setFieldErrors, setFormFields, autoSubmitRegistrationForm, - countries, + countriesCodesList, } = props; const dispatch = useDispatch(); @@ -80,12 +80,11 @@ const ConfigurableRegistrationForm = (props) => { }, [autoSubmitRegistrationForm]); // eslint-disable-line react-hooks/exhaustive-deps const removeDisabledCountries = useCallback((countryList) => { - if (!countries.length) { + if (!countriesCodesList.length) { return countryList; } - const allowedCountries = new Set(countries.map(({ code }) => code)); - return countryList.filter(({ code }) => allowedCountries.has(code)); - }, [countries]); + return countryList.filter(({ code }) => countriesCodesList.find(x => x === code)); + }, [countriesCodesList]); const countryList = useMemo(() => removeDisabledCountries( getCountryList(getLocale()).concat([{ code: 'US', name: 'United States' }])), [removeDisabledCountries]); @@ -270,7 +269,7 @@ ConfigurableRegistrationForm.propTypes = { setFieldErrors: PropTypes.func.isRequired, setFormFields: PropTypes.func.isRequired, autoSubmitRegistrationForm: PropTypes.bool, - countries: PropTypes.arrayOf(PropTypes.shape({ + countriesCodesList: PropTypes.arrayOf(PropTypes.shape({ code: PropTypes.string.isRequired, name: PropTypes.string.isRequired, })), @@ -279,7 +278,7 @@ ConfigurableRegistrationForm.propTypes = { ConfigurableRegistrationForm.defaultProps = { fieldDescriptions: {}, autoSubmitRegistrationForm: false, - countries: [], + countriesCodesList: [], }; export default ConfigurableRegistrationForm; diff --git a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx index 559cf909..ce858d56 100644 --- a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx +++ b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx @@ -192,7 +192,7 @@ describe('ConfigurableRegistrationForm', () => { }, }, autoSubmitRegistrationForm: true, - countries: [{ code: 'AX', name: 'Åland Islands' }, { code: 'AL', name: 'Albania' }], + countriesCodesList: [{ code: 'AX', name: 'Åland Islands' }, { code: 'AL', name: 'Albania' }], }; render(routerWrapper(reduxWrapper(