feat: add social links (#11)

This commit is contained in:
Adam Butterworth
2019-04-30 16:56:16 -04:00
committed by GitHub
parent b7049c1567
commit 12fd62ffa8
6 changed files with 124 additions and 41 deletions

View File

@@ -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'])}
/>
<ThirdPartyAuth />
<h2>{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}</h2>
<p>{this.props.intl.formatMessage(messages['account.settings.section.social.media.description'])}</p>
<EditableField
name="social_link_linkedIn"
type="text"
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.linkedin'])}
/>
<EditableField
name="social_link_facebook"
type="text"
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.facebook'])}
/>
<EditableField
name="social_link_twitter"
type="text"
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.twitter'])}
/>
</div>
</div>
</div>

View File

@@ -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;

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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);