diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx
index fe2d42d..4946654 100644
--- a/src/account-settings/AccountSettingsPage.jsx
+++ b/src/account-settings/AccountSettingsPage.jsx
@@ -18,6 +18,7 @@ import { PageLoading } from '../common';
import EditableField from './components/EditableField';
import PasswordReset from './components/PasswordReset';
import ThirdPartyAuth from './components/ThirdPartyAuth';
+
import {
YEAR_OF_BIRTH_OPTIONS,
EDUCATION_LEVELS,
@@ -101,11 +102,27 @@ class AccountSettingsPage extends React.Component {
name="language_proficiencies"
type="select"
options={this.languageProficiencyOptions}
- transformValue={v => (v.length ? v[0].code : null)}
- reverseTransform={v => ([{ code: v }])}
label={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies'])}
/>
+
+
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}
+ {this.props.intl.formatMessage(messages['account.settings.section.social.media.description'])}
+
+
+
diff --git a/src/account-settings/AccountSettingsPage.messages.jsx b/src/account-settings/AccountSettingsPage.messages.jsx
index 88e2d21..374e9a8 100644
--- a/src/account-settings/AccountSettingsPage.messages.jsx
+++ b/src/account-settings/AccountSettingsPage.messages.jsx
@@ -144,6 +144,32 @@ const messages = defineMessages({
defaultMessage: 'Spoken Languages',
description: 'Label for account settings spoken languages field.',
},
+
+ 'account.settings.section.social.media': {
+ id: 'account.settings.section.social.media',
+ defaultMessage: 'Social Media Links',
+ description: 'Section header for social media links settings',
+ },
+ 'account.settings.section.social.media.description': {
+ id: 'account.settings.section.social.media.description',
+ defaultMessage: 'Optionally, link your personal accounts to the social media icons on your edX profile.',
+ description: 'Section subheader for social media links settings',
+ },
+ 'account.settings.field.social.platform.name.linkedin': {
+ id: 'account.settings.field.social.platform.name.linkedin',
+ defaultMessage: 'LinkedIn',
+ description: 'Label for LinkedIn',
+ },
+ 'account.settings.field.social.platform.name.twitter': {
+ id: 'account.settings.field.social.platform.name.twitter',
+ defaultMessage: 'Twitter',
+ description: 'Label for Twitter',
+ },
+ 'account.settings.field.social.platform.name.facebook': {
+ id: 'account.settings.field.social.platform.name.facebook',
+ defaultMessage: 'Facebook',
+ description: 'Label for Facebook',
+ },
});
export default messages;
diff --git a/src/account-settings/components/EditableField.jsx b/src/account-settings/components/EditableField.jsx
index 6a67550..a390643 100644
--- a/src/account-settings/components/EditableField.jsx
+++ b/src/account-settings/components/EditableField.jsx
@@ -24,7 +24,7 @@ function EditableField(props) {
name,
label,
type,
- value: propValue,
+ value,
options,
saveState,
error,
@@ -38,18 +38,12 @@ function EditableField(props) {
isEditing,
isEditable,
intl,
- transformValue,
- reverseTransform,
...others
} = props;
const id = `field-${name}`;
- const value = transformValue(propValue);
const getValue = (rawValue) => {
if (options) {
- if (Array.isArray(rawValue)) {
- return rawValue.map(getValue).join(', ');
- }
// Use == instead of === to prevent issues when HTML casts numbers as strings
// eslint-disable-next-line eqeqeq
const selectedOption = options.find(option => option.value == rawValue);
@@ -60,14 +54,11 @@ function EditableField(props) {
const handleSubmit = (e) => {
e.preventDefault();
- const data = {
- [name]: reverseTransform(new FormData(e.target).get(name)),
- };
- onSubmit(name, data);
+ onSubmit(name, new FormData(e.target).get(name));
};
const handleChange = (e) => {
- onChange(name, reverseTransform(e.target.value));
+ onChange(name, e.target.value);
};
const handleEdit = () => {
@@ -81,7 +72,7 @@ function EditableField(props) {
const renderConfirmationMessage = () => {
if (!confirmationMessageDefinition || !confirmationValue) return null;
return intl.formatMessage(confirmationMessageDefinition, {
- value: transformValue(confirmationValue),
+ value: confirmationValue,
});
};
@@ -190,8 +181,6 @@ EditableField.propTypes = {
isEditing: PropTypes.bool,
isEditable: PropTypes.bool,
intl: intlShape.isRequired,
- transformValue: PropTypes.func,
- reverseTransform: PropTypes.func,
};
EditableField.defaultProps = {
@@ -205,8 +194,6 @@ EditableField.defaultProps = {
helpText: undefined,
isEditing: false,
isEditable: true,
- transformValue: v => v,
- reverseTransform: v => v,
};
diff --git a/src/account-settings/reducers.js b/src/account-settings/reducers.js
index 93c19e6..ad585b5 100644
--- a/src/account-settings/reducers.js
+++ b/src/account-settings/reducers.js
@@ -60,6 +60,9 @@ const accountSettingsReducer = (state = defaultState, action) => {
return {
...state,
openFormId: action.payload.formId,
+ saveState: null,
+ errors: {},
+ drafts: {},
};
case CLOSE_FORM:
dispatcherIsOpenForm = action.payload.formId === state.openFormId;
diff --git a/src/account-settings/sagas.js b/src/account-settings/sagas.js
index d03c964..1da5d6c 100644
--- a/src/account-settings/sagas.js
+++ b/src/account-settings/sagas.js
@@ -46,8 +46,8 @@ export function* handleSaveAccount(action) {
yield put(saveAccountBegin());
const username = yield select(usernameSelector);
- const { commitValues } = action.payload;
- const savedValues = yield call(ApiService.patchAccount, username, commitValues);
+ const { commitValues, formId } = action.payload;
+ const savedValues = yield call(ApiService.patchAccount, username, { [formId]: commitValues });
yield put(saveAccountSuccess(savedValues, commitValues));
yield put(closeForm(action.payload.formId));
} catch (e) {
diff --git a/src/account-settings/service.js b/src/account-settings/service.js
index 2b5a8f7..ae8cf3b 100644
--- a/src/account-settings/service.js
+++ b/src/account-settings/service.js
@@ -7,7 +7,13 @@ let config = {
PASSWORD_RESET_URL: null,
};
-let apiClient = null; // eslint-disable-line no-unused-vars
+const SOCIAL_PLATFORMS = [
+ { id: 'twitter', key: 'social_link_twitter' },
+ { id: 'facebook', key: 'social_link_facebook' },
+ { id: 'linkedin', key: 'social_link_linkedin' },
+];
+
+let apiClient = null;
function validateConfiguration(newConfig) {
Object.keys(config).forEach((key) => {
@@ -17,30 +23,75 @@ function validateConfiguration(newConfig) {
});
}
-function handleRequestError(error) {
- if (error.response && error.response.data.field_errors) {
- const apiError = Object.create(error);
- apiError.fieldErrors = Object.entries(error.response.data.field_errors)
- .reduce((acc, [k, v]) => {
- acc[k] = v.user_message;
- return acc;
- }, {});
-
- throw apiError;
- }
- throw error;
-}
-
export function configureApiService(newConfig, newApiClient) {
validateConfiguration(newConfig);
config = pick(newConfig, Object.keys(config));
apiClient = newApiClient;
}
+
+function unpackFieldErrors(fieldErrors) {
+ const unpackedFieldErrors = fieldErrors;
+ if (fieldErrors.social_links) {
+ SOCIAL_PLATFORMS.forEach(({ key }) => {
+ unpackedFieldErrors[key] = fieldErrors.social_links;
+ });
+ }
+ return Object.entries(unpackedFieldErrors)
+ .reduce((acc, [k, v]) => {
+ acc[k] = v.user_message;
+ return acc;
+ }, {});
+}
+
+function unpackAccountResponseData(data) {
+ const unpackedData = data;
+
+ 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 }) => {
+ if (commitData[key]) {
+ packedData.social_links = [{ platform: id, social_link: commitData[key] }];
+ }
+ delete packedData[key];
+ });
+
+ if (commitData.language_proficiencies) {
+ packedData.language_proficiencies = [{ code: commitData.language_proficiencies }];
+ }
+ return packedData;
+}
+
+
+function handleRequestError(error) {
+ if (error.response && error.response.data.field_errors) {
+ const apiError = Object.create(error);
+ apiError.fieldErrors = unpackFieldErrors(error.response.data.field_errors);
+ throw apiError;
+ }
+ throw error;
+}
+
+
export async function getAccount(username) {
const { data } = await apiClient.get(`${config.ACCOUNTS_API_BASE_URL}/${username}`);
-
- return data;
+ return unpackAccountResponseData(data);
}
export async function patchAccount(username, commitValues) {
@@ -50,11 +101,11 @@ export async function patchAccount(username, commitValues) {
const { data } = await apiClient.patch(
`${config.ACCOUNTS_API_BASE_URL}/${username}`,
- commitValues,
+ packAccountCommitData(commitValues),
requestConfig,
).catch(handleRequestError);
- return data;
+ return unpackAccountResponseData(data);
}
export async function postResetPassword() {
@@ -65,7 +116,6 @@ export async function postResetPassword() {
return data;
}
-
export async function getThirdPartyAuthProviders() {
const { data } = await apiClient.get(`${config.LMS_BASE_URL}/api/third_party_auth/v0/providers/user_status`)
.catch(handleRequestError);