From ffe306b60f2e202e7e9046b734387a3334cd5da2 Mon Sep 17 00:00:00 2001 From: sundasnoreen12 Date: Sat, 22 Feb 2025 15:34:54 +0500 Subject: [PATCH] feat: implemented restricted countires --- src/common-components/data/actions.js | 10 +++++-- src/common-components/data/constants.js | 3 ++ 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 | 20 +++++++++++-- .../ConfigurableRegistrationForm.test.jsx | 1 + 9 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 src/common-components/data/constants.js 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/constants.js b/src/common-components/data/constants.js new file mode 100644 index 0000000000..3ccdf06e28 --- /dev/null +++ b/src/common-components/data/constants.js @@ -0,0 +1,3 @@ +export const FIELD_LABELS = { + COUNTRY: 'country', +}; 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..aeb95b45cc 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'; @@ -19,8 +20,10 @@ export function* fetchThirdPartyAuthContext(action) { 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..e418e6d14f 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 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/register/RegistrationPage.jsx b/src/register/RegistrationPage.jsx index 8c5ea79f5b..600664c6c4 100644 --- a/src/register/RegistrationPage.jsx +++ b/src/register/RegistrationPage.jsx @@ -95,6 +95,7 @@ const RegistrationPage = (props) => { const [errors, setErrors] = useState({ ...backedUpFormData.errors }); const [errorCode, setErrorCode] = useState({ type: '', count: 0 }); const [formStartTime, setFormStartTime] = useState(null); + const countries = useSelector(state => state.commonComponents.countries); // temporary error state for embedded experience because we don't want to show errors on blur const [temporaryErrors, setTemporaryErrors] = useState({ ...backedUpFormData.errors }); @@ -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,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' }])), [removeDisabledCountries]); + const handleErrorChange = (fieldName, error) => { if (fieldName) { setFieldErrors(prevErrors => ({ @@ -231,11 +242,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; diff --git a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx index 3d66426074..7924f6751c 100644 --- a/src/register/components/tests/ConfigurableRegistrationForm.test.jsx +++ b/src/register/components/tests/ConfigurableRegistrationForm.test.jsx @@ -185,6 +185,7 @@ describe('ConfigurableRegistrationForm', () => { }, }, autoSubmitRegistrationForm: true, + countries: [{ code: 'AX', name: 'Ă…land Islands' }, { code: 'AL', name: 'Albania' }], }; render(routerWrapper(reduxWrapper(