From 6721869a2da451f438aecd1bc24d85076e56479b Mon Sep 17 00:00:00 2001 From: Rick Reilly Date: Thu, 13 Feb 2020 12:47:15 -0500 Subject: [PATCH] wip --- src/registration/RegistrationPage.jsx | 16 +-- src/registration/data/actions.js | 10 +- src/registration/data/reducers.js | 5 - src/registration/data/sagas.js | 112 ++------------- src/registration/data/selectors.js | 161 ---------------------- src/registration/data/service.js | 189 ++------------------------ 6 files changed, 32 insertions(+), 461 deletions(-) delete mode 100644 src/registration/data/selectors.js diff --git a/src/registration/RegistrationPage.jsx b/src/registration/RegistrationPage.jsx index bbddb06..e293673 100644 --- a/src/registration/RegistrationPage.jsx +++ b/src/registration/RegistrationPage.jsx @@ -35,7 +35,7 @@ class RegistrationPage extends React.Component { } handleSubmit = (e) => { - console.log('clicked submit', e); + console.log('submit', e); e.preventDefault(); } @@ -47,12 +47,12 @@ class RegistrationPage extends React.Component { } validateInput(inputName, value) { - let inputErrors = this.state.errors; - let emailValid = this.state.emailValid; - let nameValid = this.state.nameValid; - let usernameValid = this.state.usernameValid; - let passwordValid = this.state.passwordValid; - let countryValid = this.state.countryValid; + const inputErrors = this.state.errors; + let { emailValid } = this.state; + let { nameValid } = this.state; + let { usernameValid } = this.state; + let { passwordValid } = this.state; + let { countryValid } = this.state; switch (inputName) { case 'email': @@ -218,4 +218,4 @@ class RegistrationPage extends React.Component { } } -export default RegistrationPage; +export default connect(RegistrationPage); diff --git a/src/registration/data/actions.js b/src/registration/data/actions.js index 0ea436d..b4fd8b5 100644 --- a/src/registration/data/actions.js +++ b/src/registration/data/actions.js @@ -4,21 +4,19 @@ export const REGISTER_NEW_USER = new AsyncActionType('REGISTRATION', 'REGISTER_N // SAVE SETTINGS ACTIONS -export const registerNewUser = (formId, commitValues) => ({ +export const registerNewUser = registrationInfo => ({ type: REGISTER_NEW_USER.BASE, - payload: { formId, commitValues }, + payload: { registrationInfo }, }); export const registerNewUserBegin = () => ({ type: REGISTER_NEW_USER.BEGIN, }); -export const registerNewUserSuccess = (values, confirmationValues) => ({ +export const registerNewUserSuccess = () => ({ type: REGISTER_NEW_USER.SUCCESS, - payload: { values, confirmationValues }, }); -export const registerNewUserFailure = ({ fieldErrors, message }) => ({ +export const registerNewUserFailure = () => ({ type: REGISTER_NEW_USER.FAILURE, - payload: { errors: fieldErrors, message }, }); diff --git a/src/registration/data/reducers.js b/src/registration/data/reducers.js index 80d9ed4..262de8d 100644 --- a/src/registration/data/reducers.js +++ b/src/registration/data/reducers.js @@ -2,11 +2,6 @@ import { REGISTER_NEW_USER, } from './actions'; -import { reducer as deleteAccountReducer, DELETE_ACCOUNT } from '../delete-account'; -import { reducer as siteLanguageReducer, FETCH_SITE_LANGUAGES } from '../site-language'; -import { reducer as resetPasswordReducer, RESET_PASSWORD } from '../reset-password'; -import { reducer as thirdPartyAuthReducer, DISCONNECT_AUTH } from '../third-party-auth'; - export const defaultState = { registrationResult: {}, }; diff --git a/src/registration/data/sagas.js b/src/registration/data/sagas.js index fe9c8b6..4dfa92f 100644 --- a/src/registration/data/sagas.js +++ b/src/registration/data/sagas.js @@ -1,118 +1,30 @@ -import { call, put, delay, takeEvery, all } from 'redux-saga/effects'; - -import { publish } from '@edx/frontend-platform'; -import { getLocale, handleRtl, LOCALE_CHANGED } from '@edx/frontend-platform/i18n'; -import { getAuthenticatedUser } from '@edx/frontend-platform/auth'; +import { call, put, takeEvery } from 'redux-saga/effects'; // Actions import { - FETCH_SETTINGS, - fetchSettingsBegin, - fetchSettingsSuccess, - fetchSettingsFailure, - closeForm, - SAVE_SETTINGS, - saveSettingsBegin, - saveSettingsSuccess, - saveSettingsFailure, - savePreviousSiteLanguage, - FETCH_TIME_ZONES, - fetchTimeZones, - fetchTimeZonesSuccess, + REGISTER_NEW_USER, + registerNewUserBegin, + registerNewUserFailure, + registerNewUserSuccess, } from './actions'; -// Sub-modules -import { saga as deleteAccountSaga } from '../delete-account'; -import { saga as resetPasswordSaga } from '../reset-password'; -import { - saga as siteLanguageSaga, - patchPreferences, - postSetLang, -} from '../site-language'; -import { saga as thirdPartyAuthSaga } from '../third-party-auth'; // Services -import { getSettings, patchSettings, getTimeZones } from './service'; +import { postNewUser } from './service'; -export function* handleFetchSettings() { +export function* handleNewUserRegistration(action) { try { - yield put(fetchSettingsBegin()); - const { username, roles: userRoles } = getAuthenticatedUser(); + yield put(registerNewUserBegin()); - const { - thirdPartyAuthProviders, profileDataManager, timeZones, ...values - } = yield call( - getSettings, - username, - userRoles, - ); + yield call(postNewUser, action.payload.registrationInfo); - if (values.country) yield put(fetchTimeZones(values.country)); - - yield put(fetchSettingsSuccess({ - values, - thirdPartyAuthProviders, - profileDataManager, - timeZones, - })); + yield put(registerNewUserSuccess()); } catch (e) { - yield put(fetchSettingsFailure(e.message)); + yield put(registerNewUserFailure()); throw e; } } -export function* handleSaveSettings(action) { - try { - yield put(saveSettingsBegin()); - - const { username } = getAuthenticatedUser(); - const { commitValues, formId } = action.payload; - const commitData = { [formId]: commitValues }; - let savedValues = null; - if (formId === 'siteLanguage') { - const previousSiteLanguage = getLocale(); - // The following two requests need to be done sequentially, with patching preferences before - // the post to setlang. They used to be done in parallel, but this might create ambiguous - // behavior. - yield call(patchPreferences, username, { prefLang: commitValues }); - yield call(postSetLang, commitValues); - - yield put(savePreviousSiteLanguage(previousSiteLanguage)); - - publish(LOCALE_CHANGED, getLocale()); - handleRtl(); - savedValues = commitData; - } else { - savedValues = yield call(patchSettings, username, commitData); - } - yield put(saveSettingsSuccess(savedValues, commitData)); - if (savedValues.country) yield put(fetchTimeZones(savedValues.country)); - yield delay(1000); - yield put(closeForm(action.payload.formId)); - } catch (e) { - if (e.fieldErrors) { - yield put(saveSettingsFailure({ fieldErrors: e.fieldErrors })); - } else { - yield put(saveSettingsFailure(e.message)); - throw e; - } - } -} - -export function* handleFetchTimeZones(action) { - const response = yield call(getTimeZones, action.payload.country); - yield put(fetchTimeZonesSuccess(response, action.payload.country)); -} - - export default function* saga() { - yield takeEvery(FETCH_SETTINGS.BASE, handleFetchSettings); - yield takeEvery(SAVE_SETTINGS.BASE, handleSaveSettings); - yield takeEvery(FETCH_TIME_ZONES.BASE, handleFetchTimeZones); - yield all([ - deleteAccountSaga(), - siteLanguageSaga(), - resetPasswordSaga(), - thirdPartyAuthSaga(), - ]); + yield takeEvery(REGISTER_NEW_USER.BASE, handleNewUserRegistration); } diff --git a/src/registration/data/selectors.js b/src/registration/data/selectors.js deleted file mode 100644 index 6d58b75..0000000 --- a/src/registration/data/selectors.js +++ /dev/null @@ -1,161 +0,0 @@ -import { createSelector, createStructuredSelector } from 'reselect'; - -import { siteLanguageOptionsSelector, siteLanguageListSelector } from '../site-language'; - -export const storeName = 'accountSettings'; - -export const accountSettingsSelector = state => ({ ...state[storeName] }); - -const editableFieldNameSelector = (state, props) => props.name; - -const valuesSelector = createSelector( - accountSettingsSelector, - accountSettings => accountSettings.values, -); - -const draftsSelector = createSelector( - accountSettingsSelector, - accountSettings => accountSettings.drafts, -); - -const previousSiteLanguageSelector = createSelector( - accountSettingsSelector, - accountSettings => accountSettings.previousSiteLanguage, -); - -const editableFieldErrorSelector = createSelector( - editableFieldNameSelector, - accountSettingsSelector, - (name, accountSettings) => accountSettings.errors[name], -); - -const editableFieldConfirmationValuesSelector = createSelector( - editableFieldNameSelector, - accountSettingsSelector, - (name, accountSettings) => accountSettings.confirmationValues[name], -); - -const isEditingSelector = createSelector( - editableFieldNameSelector, - accountSettingsSelector, - (name, accountSettings) => accountSettings.openFormId === name, -); - -const saveStateSelector = createSelector( - accountSettingsSelector, - accountSettings => accountSettings.saveState, -); - -export const editableFieldSelector = createStructuredSelector({ - error: editableFieldErrorSelector, - confirmationValue: editableFieldConfirmationValuesSelector, - saveState: saveStateSelector, - isEditing: isEditingSelector, -}); - -export const profileDataManagerSelector = createSelector( - accountSettingsSelector, - accountSettings => accountSettings.profileDataManager, -); - -export const staticFieldsSelector = createSelector( - accountSettingsSelector, - accountSettings => (accountSettings.profileDataManager ? ['name', 'email', 'country'] : []), -); - -export const hiddenFieldsSelector = createSelector( - accountSettingsSelector, - accountSettings => (accountSettings.profileDataManager ? [] : ['secondary_email']), -); - -/** - * If there's no draft present at all (undefined), use the original committed value. - */ -function chooseFormValue(draft, committed) { - return draft !== undefined ? draft : committed; -} - -const formValuesSelector = createSelector( - valuesSelector, - draftsSelector, - (values, drafts) => { - const formValues = {}; - Object.entries(values).forEach(([name, value]) => { - formValues[name] = chooseFormValue(drafts[name], value) || ''; - }); - return formValues; - }, -); - -const transformTimeZonesToOptions = timeZoneArr => timeZoneArr - .map(({ time_zone, description }) => ({ // eslint-disable-line camelcase - value: time_zone, label: description, - })); - -const timeZonesSelector = createSelector( - accountSettingsSelector, - accountSettings => transformTimeZonesToOptions(accountSettings.timeZones), -); - -const countryTimeZonesSelector = createSelector( - accountSettingsSelector, - accountSettings => transformTimeZonesToOptions(accountSettings.countryTimeZones), -); - -const activeAccountSelector = createSelector( - accountSettingsSelector, - accountSettings => accountSettings.values.is_active, -); - -export const siteLanguageSelector = createSelector( - previousSiteLanguageSelector, - draftsSelector, - (previousValue, drafts) => ({ - previousValue, - draft: drafts.siteLanguage, - }), -); - -export const betaLanguageBannerSelector = createStructuredSelector({ - siteLanguageList: siteLanguageListSelector, - siteLanguage: siteLanguageSelector, -}); - -export const accountSettingsPageSelector = createSelector( - accountSettingsSelector, - siteLanguageOptionsSelector, - siteLanguageSelector, - formValuesSelector, - profileDataManagerSelector, - staticFieldsSelector, - hiddenFieldsSelector, - timeZonesSelector, - countryTimeZonesSelector, - activeAccountSelector, - ( - accountSettings, - siteLanguageOptions, - siteLanguage, - formValues, - profileDataManager, - staticFields, - hiddenFields, - timeZoneOptions, - countryTimeZoneOptions, - activeAccount, - ) => ({ - siteLanguageOptions, - siteLanguage, - loading: accountSettings.loading, - loaded: accountSettings.loaded, - loadingError: accountSettings.loadingError, - timeZoneOptions, - countryTimeZoneOptions, - isActive: activeAccount, - formValues, - profileDataManager, - staticFields, - hiddenFields, - tpaProviders: accountSettings.thirdPartyAuth.providers, - }), -); diff --git a/src/registration/data/service.js b/src/registration/data/service.js index c709c81..d126359 100644 --- a/src/registration/data/service.js +++ b/src/registration/data/service.js @@ -1,194 +1,21 @@ import { getConfig } from '@edx/frontend-platform'; import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; -import pick from 'lodash.pick'; -import omit from 'lodash.omit'; -import isEmpty from 'lodash.isempty'; -import { handleRequestError, unpackFieldErrors } from './utils'; -import { getThirdPartyAuthProviders } from '../third-party-auth'; - - -function unpackAccountResponseData(data) { - const unpackedData = data; - - // This is handled by preferences - delete unpackedData.time_zone; - - SOCIAL_PLATFORMS.forEach(({ id, key }) => { - const platformData = data.social_links.find(({ platform }) => platform === id); - unpackedData[key] = typeof platformData === 'object' ? platformData.social_link : ''; - }); - - if (Array.isArray(data.language_proficiencies)) { - if (data.language_proficiencies.length) { - unpackedData.language_proficiencies = data.language_proficiencies[0].code; - } else { - unpackedData.language_proficiencies = ''; - } - } - - return unpackedData; -} - -function packAccountCommitData(commitData) { - const packedData = commitData; - - SOCIAL_PLATFORMS.forEach(({ id, key }) => { - // Skip missing values. Empty strings are valid values and should be preserved. - if (commitData[key] === undefined) return; - - packedData.social_links = [{ platform: id, social_link: commitData[key] }]; - delete packedData[key]; - }); - - if (commitData.language_proficiencies !== undefined) { - if (commitData.language_proficiencies) { - packedData.language_proficiencies = [{ code: commitData.language_proficiencies }]; - } else { - // An empty string should be sent as an array. - packedData.language_proficiencies = []; - } - } - - if (commitData.year_of_birth !== undefined) { - if (commitData.year_of_birth) { - packedData.year_of_birth = commitData.year_of_birth; - } else { - // An empty string should be sent as null. - packedData.year_of_birth = null; - } - } - return packedData; -} - -export async function getAccount(username) { - const { data } = await getAuthenticatedHttpClient() - .get(`${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`); - return unpackAccountResponseData(data); -} - -export async function patchAccount(username, commitValues) { +export default async function postNewUser(registrationInformation) { const requestConfig = { - headers: { 'Content-Type': 'application/merge-patch+json' }, + headers: { 'Content-Type': 'application/json' }, }; const { data } = await getAuthenticatedHttpClient() - .patch( - `${getConfig().LMS_BASE_URL}/api/user/v1/accounts/${username}`, - packAccountCommitData(commitValues), + .post( + `${getConfig().LMS_BASE_URL}user_api/v1/account/registration/`, + registrationInformation, requestConfig, ) - .catch((error) => { - const unpackFunction = (fieldErrors) => { - const unpackedFieldErrors = fieldErrors; - if (fieldErrors.social_links) { - SOCIAL_PLATFORMS.forEach(({ key }) => { - unpackedFieldErrors[key] = fieldErrors.social_links; - }); - } - return unpackFieldErrors(unpackedFieldErrors); - }; - handleRequestError(error, unpackFunction); + .catch((e) => { + console.log('You messed up'); + throw (e); }); - return unpackAccountResponseData(data); -} - -export async function getPreferences(username) { - const { data } = await getAuthenticatedHttpClient() - .get(`${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`); return data; } - -export async function patchPreferences(username, commitValues) { - const requestConfig = { headers: { 'Content-Type': 'application/merge-patch+json' } }; - const requestUrl = `${getConfig().LMS_BASE_URL}/api/user/v1/preferences/${username}`; - - // Ignore the success response, the API does not currently return any data. - await getAuthenticatedHttpClient() - .patch(requestUrl, commitValues, requestConfig).catch(handleRequestError); - - return commitValues; -} - -export async function getTimeZones(forCountry) { - const { data } = await getAuthenticatedHttpClient() - .get(`${getConfig().LMS_BASE_URL}/user_api/v1/preferences/time_zones/`, { - params: { country_code: forCountry }, - }) - .catch(handleRequestError); - - return data; -} - -/** - * Determine if the user's profile data is managed by a third-party identity provider. - */ -export async function getProfileDataManager(username, userRoles) { - const userRoleNames = userRoles.map(role => role.split(':')[0]); - - if (userRoleNames.includes('enterprise_learner')) { - const url = `${getConfig().LMS_BASE_URL}/enterprise/api/v1/enterprise-learner/?username=${username}`; - const { data } = await getAuthenticatedHttpClient().get(url).catch(handleRequestError); - - if ('results' in data) { - for (let i = 0; i < data.results.length; i += 1) { - const enterprise = data.results[i].enterprise_customer; - if (enterprise.sync_learner_profile_data) { - return enterprise.name; - } - } - } - } - - return null; -} - -/** - * A single function to GET everything considered a setting. - * Currently encapsulates Account, Preferences, and ThirdPartyAuth - */ -export async function getSettings(username, userRoles) { - const results = await Promise.all([ - getAccount(username), - getPreferences(username), - getThirdPartyAuthProviders(), - getProfileDataManager(username, userRoles), - getTimeZones(), - ]); - - return { - ...results[0], - ...results[1], - thirdPartyAuthProviders: results[2], - profileDataManager: results[3], - timeZones: results[4], - }; -} - -/** - * A single function to PATCH everything considered a setting. - * Currently encapsulates Account, Preferences, and ThirdPartyAuth - */ -export async function patchSettings(username, commitValues) { - // Note: time_zone exists in the return value from user/v1/accounts - // but it is always null and won't update. It also exists in - // user/v1/preferences where it does update. This is the one we use. - const preferenceKeys = ['time_zone']; - const accountCommitValues = omit(commitValues, preferenceKeys); - const preferenceCommitValues = pick(commitValues, preferenceKeys); - const patchRequests = []; - - if (!isEmpty(accountCommitValues)) { - patchRequests.push(patchAccount(username, accountCommitValues)); - } - if (!isEmpty(preferenceCommitValues)) { - patchRequests.push(patchPreferences(username, preferenceCommitValues)); - } - - const results = await Promise.all(patchRequests); - // Assigns in order of requests. Preference keys - // will override account keys. Notably time_zone. - const combinedResults = Object.assign({}, ...results); - return combinedResults; -}