Compare commits

...

1 Commits

Author SHA1 Message Date
sundasnoreen12
3355a014e6 feat: implemented restricted countries functionality 2025-02-24 13:44:39 +05:00
9 changed files with 72 additions and 6 deletions

View File

@@ -13,9 +13,15 @@ export const getThirdPartyAuthContextBegin = () => ({
type: THIRD_PARTY_AUTH_CONTEXT.BEGIN, 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, type: THIRD_PARTY_AUTH_CONTEXT.SUCCESS,
payload: { fieldDescriptions, optionalFields, thirdPartyAuthContext }, payload: {
fieldDescriptions, optionalFields, thirdPartyAuthContext, countries,
},
}); });
export const getThirdPartyAuthContextFailure = () => ({ export const getThirdPartyAuthContextFailure = () => ({

View File

@@ -35,6 +35,7 @@ const reducer = (state = defaultState, action = {}) => {
optionalFields: action.payload.optionalFields, optionalFields: action.payload.optionalFields,
thirdPartyAuthContext: action.payload.thirdPartyAuthContext, thirdPartyAuthContext: action.payload.thirdPartyAuthContext,
thirdPartyAuthApiStatus: COMPLETE_STATE, thirdPartyAuthApiStatus: COMPLETE_STATE,
countries: action.payload.countries,
}; };
} }
case THIRD_PARTY_AUTH_CONTEXT.FAILURE: case THIRD_PARTY_AUTH_CONTEXT.FAILURE:

View File

@@ -8,6 +8,7 @@ import {
THIRD_PARTY_AUTH_CONTEXT, THIRD_PARTY_AUTH_CONTEXT,
} from './actions'; } from './actions';
import { import {
getCountryList,
getThirdPartyAuthContext, getThirdPartyAuthContext,
} from './service'; } from './service';
import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions'; import { setCountryFromThirdPartyAuthContext } from '../../register/data/actions';
@@ -18,9 +19,10 @@ export function* fetchThirdPartyAuthContext(action) {
const { const {
fieldDescriptions, optionalFields, thirdPartyAuthContext, fieldDescriptions, optionalFields, thirdPartyAuthContext,
} = yield call(getThirdPartyAuthContext, action.payload.urlParams); } = yield call(getThirdPartyAuthContext, action.payload.urlParams);
const countries = (yield call(getCountryList)) || [];
yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode)); yield put(setCountryFromThirdPartyAuthContext(thirdPartyAuthContext.countryCode));
yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext)); yield put(getThirdPartyAuthContextSuccess(fieldDescriptions, optionalFields, thirdPartyAuthContext, countries));
} catch (e) { } catch (e) {
yield put(getThirdPartyAuthContextFailure()); yield put(getThirdPartyAuthContextFailure());
logError(e); logError(e);

View File

@@ -1,5 +1,8 @@
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; 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 // eslint-disable-next-line import/prefer-default-export
export async function getThirdPartyAuthContext(urlParams) { export async function getThirdPartyAuthContext(urlParams) {
@@ -23,3 +26,28 @@ export async function getThirdPartyAuthContext(urlParams) {
thirdPartyAuthContext: data.contextData || {}, 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 [];
}
}

View File

@@ -8,6 +8,11 @@ import * as api from '../service';
const { loggingService } = initializeMockLogging(); const { loggingService } = initializeMockLogging();
jest.mock('../service', () => ({
getCountryList: jest.fn(),
getThirdPartyAuthContext: jest.fn(),
}));
describe('fetchThirdPartyAuthContext', () => { describe('fetchThirdPartyAuthContext', () => {
const params = { const params = {
payload: { urlParams: {} }, payload: { urlParams: {} },
@@ -31,6 +36,7 @@ describe('fetchThirdPartyAuthContext', () => {
thirdPartyAuthContext: data, thirdPartyAuthContext: data,
fieldDescriptions: {}, fieldDescriptions: {},
optionalFields: {}, optionalFields: {},
countries: [],
})); }));
const dispatched = []; const dispatched = [];
@@ -44,7 +50,7 @@ describe('fetchThirdPartyAuthContext', () => {
expect(dispatched).toEqual([ expect(dispatched).toEqual([
actions.getThirdPartyAuthContextBegin(), actions.getThirdPartyAuthContextBegin(),
setCountryFromThirdPartyAuthContext(), setCountryFromThirdPartyAuthContext(),
actions.getThirdPartyAuthContextSuccess({}, {}, data), actions.getThirdPartyAuthContextSuccess({}, {}, data, []),
]); ]);
getThirdPartyAuthContext.mockClear(); getThirdPartyAuthContext.mockClear();
}); });

View File

@@ -37,3 +37,6 @@ export const VALID_EMAIL_REGEX = '(^[-!#$%&\'*+/=?^_`{}|~0-9A-Z]+(\\.[-!#$%&\'*+
// things like auto-enrollment upon login and registration. // 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 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 REDIRECT = 'redirect';
export const FIELD_LABELS = {
COUNTRY: 'country',
};

View File

@@ -85,6 +85,7 @@ const RegistrationPage = (props) => {
const providers = useSelector(state => state.commonComponents.thirdPartyAuthContext.providers); const providers = useSelector(state => state.commonComponents.thirdPartyAuthContext.providers);
const secondaryProviders = useSelector(state => state.commonComponents.thirdPartyAuthContext.secondaryProviders); const secondaryProviders = useSelector(state => state.commonComponents.thirdPartyAuthContext.secondaryProviders);
const pipelineUserDetails = useSelector(state => state.commonComponents.thirdPartyAuthContext.pipelineUserDetails); const pipelineUserDetails = useSelector(state => state.commonComponents.thirdPartyAuthContext.pipelineUserDetails);
const countries = useSelector(state => state.commonComponents.countries);
const backendValidations = useSelector(getBackendValidations); const backendValidations = useSelector(getBackendValidations);
const queryParams = useMemo(() => getAllPossibleQueryParams(), []); const queryParams = useMemo(() => getAllPossibleQueryParams(), []);
@@ -358,6 +359,7 @@ const RegistrationPage = (props) => {
setFormFields={setConfigurableFormFields} setFormFields={setConfigurableFormFields}
autoSubmitRegisterForm={autoSubmitRegForm} autoSubmitRegisterForm={autoSubmitRegForm}
fieldDescriptions={fieldDescriptions} fieldDescriptions={fieldDescriptions}
countries={countries}
/> />
<StatefulButton <StatefulButton
id="register-user" id="register-user"

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react'; import React, { useCallback, useEffect, useMemo } from 'react';
import { getConfig } from '@edx/frontend-platform'; import { getConfig } from '@edx/frontend-platform';
import { getCountryList, getLocale, useIntl } from '@edx/frontend-platform/i18n'; import { getCountryList, getLocale, useIntl } from '@edx/frontend-platform/i18n';
@@ -31,13 +31,13 @@ const ConfigurableRegistrationForm = (props) => {
setFieldErrors, setFieldErrors,
setFormFields, setFormFields,
autoSubmitRegistrationForm, autoSubmitRegistrationForm,
countries,
} = props; } = props;
/** The reason for adding the entry 'United States' is that Chrome browser aut-fill the form with the 'Unites /** 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 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. 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 showTermsOfServiceAndHonorCode = false;
let showCountryField = false; let showCountryField = false;
@@ -70,6 +70,18 @@ const ConfigurableRegistrationForm = (props) => {
} }
}, [autoSubmitRegistrationForm]); // eslint-disable-line react-hooks/exhaustive-deps }, [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) => { const handleErrorChange = (fieldName, error) => {
if (fieldName) { if (fieldName) {
setFieldErrors(prevErrors => ({ setFieldErrors(prevErrors => ({
@@ -231,11 +243,16 @@ ConfigurableRegistrationForm.propTypes = {
setFieldErrors: PropTypes.func.isRequired, setFieldErrors: PropTypes.func.isRequired,
setFormFields: PropTypes.func.isRequired, setFormFields: PropTypes.func.isRequired,
autoSubmitRegistrationForm: PropTypes.bool, autoSubmitRegistrationForm: PropTypes.bool,
countries: PropTypes.arrayOf(PropTypes.shape({
code: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
})),
}; };
ConfigurableRegistrationForm.defaultProps = { ConfigurableRegistrationForm.defaultProps = {
fieldDescriptions: {}, fieldDescriptions: {},
autoSubmitRegistrationForm: false, autoSubmitRegistrationForm: false,
countries: [],
}; };
export default ConfigurableRegistrationForm; export default ConfigurableRegistrationForm;

View File

@@ -185,6 +185,7 @@ describe('ConfigurableRegistrationForm', () => {
}, },
}, },
autoSubmitRegistrationForm: true, autoSubmitRegistrationForm: true,
countries: [{ code: 'AX', name: 'Åland Islands' }, { code: 'AL', name: 'Albania' }],
}; };
render(routerWrapper(reduxWrapper( render(routerWrapper(reduxWrapper(