diff --git a/src/profile/__mocks__/invalidUser.mockStore.js b/src/profile/__mocks__/invalidUser.mockStore.js index 5e45bb2ac..ec62b98cc 100644 --- a/src/profile/__mocks__/invalidUser.mockStore.js +++ b/src/profile/__mocks__/invalidUser.mockStore.js @@ -29,6 +29,7 @@ module.exports = { drafts: {}, isLoadingProfile: false, isAuthenticatedUserProfile: true, + countriesCodesList: [], }, router: { location: { diff --git a/src/profile/__mocks__/savingEditedBio.mockStore.js b/src/profile/__mocks__/savingEditedBio.mockStore.js index 63551e823..7ba9a52f0 100644 --- a/src/profile/__mocks__/savingEditedBio.mockStore.js +++ b/src/profile/__mocks__/savingEditedBio.mockStore.js @@ -125,7 +125,8 @@ module.exports = { } ], drafts: {}, - isLoadingProfile: false + isLoadingProfile: false, + countriesCodesList: [], }, router: { location: { diff --git a/src/profile/__mocks__/viewOtherProfile.mockStore.js b/src/profile/__mocks__/viewOtherProfile.mockStore.js index e894d483e..b35f61d4e 100644 --- a/src/profile/__mocks__/viewOtherProfile.mockStore.js +++ b/src/profile/__mocks__/viewOtherProfile.mockStore.js @@ -86,6 +86,7 @@ module.exports = { drafts: {}, isLoadingProfile: false, learningGoal: 'advance_career', + countriesCodesList: [], }, router: { location: { diff --git a/src/profile/__mocks__/viewOwnProfile.mockStore.js b/src/profile/__mocks__/viewOwnProfile.mockStore.js index 9e9419eb0..b40d8f093 100644 --- a/src/profile/__mocks__/viewOwnProfile.mockStore.js +++ b/src/profile/__mocks__/viewOwnProfile.mockStore.js @@ -124,6 +124,7 @@ module.exports = { createdDate: '2019-03-04T19:31:39.896806Z' } ], + countriesCodesList:[{code:"AX"},{code:"AL"}], drafts: {}, isLoadingProfile: false }, diff --git a/src/profile/__snapshots__/ProfilePage.test.jsx.snap b/src/profile/__snapshots__/ProfilePage.test.jsx.snap index 7635041d8..6958f2e83 100644 --- a/src/profile/__snapshots__/ProfilePage.test.jsx.snap +++ b/src/profile/__snapshots__/ProfilePage.test.jsx.snap @@ -976,1256 +976,11 @@ exports[` Renders correctly in various states test country edit w >   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
({ type: FETCH_PROFILE.SUCCESS, account, preferences, courseCertificates, isAuthenticatedUserProfile, + countriesCodesList, }); export const fetchProfileReset = () => ({ diff --git a/src/profile/data/constants.js b/src/profile/data/constants.js index 1db167380..de97069fc 100644 --- a/src/profile/data/constants.js +++ b/src/profile/data/constants.js @@ -22,7 +22,12 @@ const SOCIAL = { }, }; +const FIELD_LABELS = { + COUNTRY: 'country', +}; + export { EDUCATION_LEVELS, SOCIAL, + FIELD_LABELS, }; diff --git a/src/profile/data/reducers.js b/src/profile/data/reducers.js index 33b29fb18..0d374c68b 100644 --- a/src/profile/data/reducers.js +++ b/src/profile/data/reducers.js @@ -22,6 +22,7 @@ export const initialState = { drafts: {}, isLoadingProfile: true, isAuthenticatedUserProfile: false, + countriesCodesList: [], }; const profilePage = (state = initialState, action = {}) => { @@ -42,6 +43,7 @@ const profilePage = (state = initialState, action = {}) => { courseCertificates: action.courseCertificates, isLoadingProfile: false, isAuthenticatedUserProfile: action.isAuthenticatedUserProfile, + countriesCodesList: action.countriesCodesList, }; case SAVE_PROFILE.BEGIN: return { diff --git a/src/profile/data/sagas.js b/src/profile/data/sagas.js index 7c9e409f8..e2ebba5c7 100644 --- a/src/profile/data/sagas.js +++ b/src/profile/data/sagas.js @@ -41,6 +41,7 @@ export function* handleFetchProfile(action) { let preferences = {}; let account = userAccount; let courseCertificates = null; + let countriesCodesList = []; try { yield put(fetchProfileBegin()); @@ -49,6 +50,7 @@ export function* handleFetchProfile(action) { const calls = [ call(ProfileApiService.getAccount, username), call(ProfileApiService.getCourseCertificates, username), + call(ProfileApiService.getCountryList), ]; if (isAuthenticatedUserProfile) { @@ -61,9 +63,9 @@ export function* handleFetchProfile(action) { const result = yield all(calls); if (isAuthenticatedUserProfile) { - [account, courseCertificates, preferences] = result; + [account, courseCertificates, countriesCodesList, preferences] = result; } else { - [account, courseCertificates] = result; + [account, courseCertificates, countriesCodesList] = result; } // Set initial visibility values for account @@ -89,6 +91,7 @@ export function* handleFetchProfile(action) { preferences, courseCertificates, isAuthenticatedUserProfile, + countriesCodesList, )); yield put(fetchProfileReset()); diff --git a/src/profile/data/sagas.test.js b/src/profile/data/sagas.test.js index 379c0cfd6..291ab1b11 100644 --- a/src/profile/data/sagas.test.js +++ b/src/profile/data/sagas.test.js @@ -19,6 +19,7 @@ jest.mock('./services', () => ({ getPreferences: jest.fn(), getAccount: jest.fn(), getCourseCertificates: jest.fn(), + getCountryList: jest.fn(), })); jest.mock('@edx/frontend-platform/auth', () => ({ @@ -68,17 +69,19 @@ describe('RootSaga', () => { const action = profileActions.fetchProfile('gonzo'); const gen = handleFetchProfile(action); - const result = [userAccount, [1, 2, 3], { preferences: 'stuff' }]; + const result = [userAccount, [1, 2, 3], [], { preferences: 'stuff' }]; expect(gen.next().value).toEqual(select(userAccountSelector)); expect(gen.next(selectorData).value).toEqual(put(profileActions.fetchProfileBegin())); expect(gen.next().value).toEqual(all([ call(ProfileApiService.getAccount, 'gonzo'), call(ProfileApiService.getCourseCertificates, 'gonzo'), + call(ProfileApiService.getCountryList), call(ProfileApiService.getPreferences, 'gonzo'), + ])); expect(gen.next(result).value) - .toEqual(put(profileActions.fetchProfileSuccess(userAccount, result[2], result[1], true))); + .toEqual(put(profileActions.fetchProfileSuccess(userAccount, result[3], result[1], true, []))); expect(gen.next().value).toEqual(put(profileActions.fetchProfileReset())); expect(gen.next().value).toBeUndefined(); }); @@ -88,6 +91,7 @@ describe('RootSaga', () => { username: 'gonzo', other: 'data', }; + const countriesCodesList = [{ code: 'AX' }, { code: 'AL' }]; getAuthenticatedUser.mockReturnValue(userAccount); const selectorData = { userAccount, @@ -96,16 +100,17 @@ describe('RootSaga', () => { const action = profileActions.fetchProfile('booyah'); const gen = handleFetchProfile(action); - const result = [{}, [1, 2, 3]]; + const result = [{}, [1, 2, 3], countriesCodesList]; expect(gen.next().value).toEqual(select(userAccountSelector)); expect(gen.next(selectorData).value).toEqual(put(profileActions.fetchProfileBegin())); expect(gen.next().value).toEqual(all([ call(ProfileApiService.getAccount, 'booyah'), call(ProfileApiService.getCourseCertificates, 'booyah'), + call(ProfileApiService.getCountryList), ])); expect(gen.next(result).value) - .toEqual(put(profileActions.fetchProfileSuccess(result[0], {}, result[1], false))); + .toEqual(put(profileActions.fetchProfileSuccess(result[0], {}, result[1], false, countriesCodesList))); expect(gen.next().value).toEqual(put(profileActions.fetchProfileReset())); expect(gen.next().value).toBeUndefined(); }); diff --git a/src/profile/data/selectors.js b/src/profile/data/selectors.js index b04493e6e..41341f5f0 100644 --- a/src/profile/data/selectors.js +++ b/src/profile/data/selectors.js @@ -23,6 +23,7 @@ export const isLoadingProfileSelector = state => state.profilePage.isLoadingProf export const currentlyEditingFieldSelector = state => state.profilePage.currentlyEditingField; export const accountErrorsSelector = state => state.profilePage.errors; export const isAuthenticatedUserProfileSelector = state => state.profilePage.isAuthenticatedUserProfile; +export const countriesCodesListSelector = state => state.profilePage.countriesCodesList; export const editableFormModeSelector = createSelector( profileAccountSelector, @@ -112,7 +113,14 @@ export const sortedLanguagesSelector = createSelector( export const sortedCountriesSelector = createSelector( localeSelector, - locale => getCountryList(locale), + countriesCodesListSelector, + profileAccountSelector, + (locale, countriesCodesList, profileAccount) => { + const countryList = getCountryList(locale); + const userCountry = profileAccount.country; + + return countryList.filter(({ code }) => code === userCountry || countriesCodesList.find(x => x === code)); + }, ); export const preferredLanguageSelector = createSelector( @@ -130,10 +138,12 @@ export const countrySelector = createSelector( editableFormSelector, sortedCountriesSelector, countryMessagesSelector, - (editableForm, sortedCountries, countryMessages) => ({ + countriesCodesListSelector, + (editableForm, translatedCountries, countryMessages, countriesCodesList) => ({ ...editableForm, - sortedCountries, + translatedCountries, countryMessages, + countriesCodesList, }), ); diff --git a/src/profile/data/services.js b/src/profile/data/services.js index 45bf68777..17f15a49b 100644 --- a/src/profile/data/services.js +++ b/src/profile/data/services.js @@ -2,6 +2,7 @@ import { ensureConfig, getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient as getHttpClient } from '@edx/frontend-platform/auth'; import { logError } from '@edx/frontend-platform/logging'; import { camelCaseObject, convertKeyNames, snakeCaseObject } from '../utils'; +import { FIELD_LABELS } from './constants'; ensureConfig(['LMS_BASE_URL'], 'Profile API service'); @@ -147,3 +148,21 @@ export async function getCourseCertificates(username) { return []; } } + +function extractCountryList(data) { + return data?.fields + .find(({ name }) => name === FIELD_LABELS.COUNTRY) + ?.options?.map(({ value }) => (value)) || []; +} + +export async function getCountryList() { + const url = `${getConfig().LMS_BASE_URL}/user_api/v1/account/registration/`; + + try { + const { data } = await getHttpClient().get(url); + return extractCountryList(data); + } catch (e) { + logError(e); + return []; + } +} diff --git a/src/profile/forms/Country.jsx b/src/profile/forms/Country.jsx index 0048017e6..2803145ec 100644 --- a/src/profile/forms/Country.jsx +++ b/src/profile/forms/Country.jsx @@ -23,6 +23,7 @@ class Country extends React.Component { this.handleSubmit = this.handleSubmit.bind(this); this.handleClose = this.handleClose.bind(this); this.handleOpen = this.handleOpen.bind(this); + this.isDisabledCountry = this.isDisabledCountry.bind(this); } handleChange(e) { @@ -46,6 +47,12 @@ class Country extends React.Component { this.props.openHandler(this.props.formId); } + isDisabledCountry = (country) => { + const { countriesCodesList } = this.props; + + return countriesCodesList.length > 0 && !countriesCodesList.find(code => code === country); + }; + render() { const { formId, @@ -55,7 +62,7 @@ class Country extends React.Component { saveState, error, intl, - sortedCountries, + translatedCountries, countryMessages, } = this.props; @@ -84,8 +91,8 @@ class Country extends React.Component { onChange={this.handleChange} > - {sortedCountries.map(({ code, name }) => ( - + {translatedCountries.map(({ code, name }) => ( + ))} {error !== null && ( @@ -153,7 +160,11 @@ Country.propTypes = { editMode: PropTypes.oneOf(['editing', 'editable', 'empty', 'static']), saveState: PropTypes.string, error: PropTypes.string, - sortedCountries: PropTypes.arrayOf(PropTypes.shape({ + translatedCountries: PropTypes.arrayOf(PropTypes.shape({ + code: PropTypes.string.isRequired, + name: PropTypes.string.isRequired, + })).isRequired, + countriesCodesList: PropTypes.arrayOf(PropTypes.shape({ code: PropTypes.string.isRequired, name: PropTypes.string.isRequired, })).isRequired,