wip
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 },
|
||||
});
|
||||
|
||||
@@ -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: {},
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user