feat: add social links (#11)
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user