diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx
index 46127b9..269378d 100644
--- a/src/account-settings/AccountSettingsPage.jsx
+++ b/src/account-settings/AccountSettingsPage.jsx
@@ -36,12 +36,24 @@ class AccountSettingsPage extends React.Component {
super(props);
this.educationLevels = EDUCATION_LEVELS.map(key => ({
value: key,
- label: props.intl.formatMessage(messages[`account.settings.field.education.levels.${key}`]),
+ label: props.intl.formatMessage(messages[`account.settings.field.education.levels.${key || 'empty'}`]),
}));
this.genderOptions = GENDER_OPTIONS.map(key => ({
value: key,
- label: props.intl.formatMessage(messages[`account.settings.field.gender.options.${key}`]),
+ label: props.intl.formatMessage(messages[`account.settings.field.gender.options.${key || 'empty'}`]),
}));
+ this.languageProficiencyOptions = [{
+ value: '',
+ label: props.intl.formatMessage(messages['account.settings.field.language_proficiencies.options.empty']),
+ }].concat(props.languageProficiencyOptions);
+ this.yearOfBirthOptions = [{
+ value: '',
+ label: props.intl.formatMessage(messages['account.settings.field.year_of_birth.options.empty']),
+ }].concat(YEAR_OF_BIRTH_OPTIONS);
+ this.countryOptions = [{
+ value: '',
+ label: props.intl.formatMessage(messages['account.settings.field.country.options.empty']),
+ }].concat(props.countryOptions);
}
componentDidMount() {
@@ -67,6 +79,16 @@ class AccountSettingsPage extends React.Component {
return concatTimeZoneOptions;
});
+ isEditable(fieldName) {
+ return !this.props.staticFields.includes(fieldName);
+ }
+
+ isManagedProfile() {
+ // Enterprise customer profiles are managed by their organizations. We determine whether
+ // a profile is managed or not by the presence of the profileDataManager prop.
+ return Boolean(this.props.profileDataManager);
+ }
+
handleEditableFieldChange = (name, value) => {
this.props.updateDraft(name, value);
};
@@ -97,7 +119,7 @@ class AccountSettingsPage extends React.Component {
}
renderManagedProfileMessage() {
- if (!this.props.profileDataManager) {
+ if (!this.isManagedProfile()) {
return null;
}
@@ -126,6 +148,15 @@ class AccountSettingsPage extends React.Component {
);
}
+ renderEmptyStaticFieldMessage() {
+ if (this.isManagedProfile()) {
+ return this.props.intl.formatMessage(messages['account.settings.static.field.empty'], {
+ enterprise: this.props.profileDataManager,
+ });
+ }
+ return this.props.intl.formatMessage(messages['account.settings.static.field.empty.no.admin']);
+ }
+
renderSecondaryEmailField(editableFieldProps) {
if (this.props.hiddenFields.includes('secondary_email')) {
return null;
@@ -135,6 +166,7 @@ class AccountSettingsPage extends React.Component {
{this.renderSecondaryEmailField(editableFieldProps)}
@@ -197,17 +239,23 @@ class AccountSettingsPage extends React.Component {
name="year_of_birth"
type="select"
label={this.props.intl.formatMessage(messages['account.settings.field.dob'])}
+ emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.dob.empty'])}
value={this.props.formValues.year_of_birth}
- options={YEAR_OF_BIRTH_OPTIONS}
+ options={this.yearOfBirthOptions}
{...editableFieldProps}
/>
@@ -223,6 +271,7 @@ class AccountSettingsPage extends React.Component {
value={this.props.formValues.level_of_education}
options={this.educationLevels}
label={this.props.intl.formatMessage(messages['account.settings.field.education'])}
+ emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.education.empty'])}
{...editableFieldProps}
/>
@@ -254,6 +305,7 @@ class AccountSettingsPage extends React.Component {
type="text"
value={this.props.formValues.social_link_linkedin}
label={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.linkedin'])}
+ emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.social.platform.name.linkedin.empty'])}
{...editableFieldProps}
/>
@@ -293,6 +347,7 @@ class AccountSettingsPage extends React.Component {
value={this.props.formValues.time_zone || ''}
options={timeZoneOptions}
label={this.props.intl.formatMessage(messages['account.settings.field.time.zone'])}
+ emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.time.zone.empty'])}
helpText={this.props.intl.formatMessage(messages['account.settings.field.time.zone.description'])}
{...editableFieldProps}
onSubmit={(formId, value) => {
@@ -378,7 +433,7 @@ AccountSettingsPage.propTypes = {
name: PropTypes.string,
email: PropTypes.string,
secondary_email: PropTypes.string,
- year_of_birth: PropTypes.number,
+ year_of_birth: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
country: PropTypes.string,
level_of_education: PropTypes.string,
gender: PropTypes.string,
diff --git a/src/account-settings/AccountSettingsPage.messages.jsx b/src/account-settings/AccountSettingsPage.messages.jsx
index 396a66c..0ceb832 100644
--- a/src/account-settings/AccountSettingsPage.messages.jsx
+++ b/src/account-settings/AccountSettingsPage.messages.jsx
@@ -76,6 +76,11 @@ const messages = defineMessages({
defaultMessage: 'Full name',
description: 'Label for account settings name field.',
},
+ 'account.settings.field.full.name.empty': {
+ id: 'account.settings.field.full.name.empty',
+ defaultMessage: 'Add name',
+ description: 'Placeholder for empty account settings name field.',
+ },
'account.settings.field.full.name.help.text': {
id: 'account.settings.field.full.name.help.text',
defaultMessage: 'The name that is used for ID verification and that appears on your certificates.',
@@ -86,6 +91,11 @@ const messages = defineMessages({
defaultMessage: 'Email address (Sign in)',
description: 'Label for account settings email field.',
},
+ 'account.settings.field.email.empty': {
+ id: 'account.settings.field.email.empty',
+ defaultMessage: 'Add email address',
+ description: 'Placeholder for empty account settings email field.',
+ },
'account.settings.field.email.confirmation': {
id: 'account.settings.field.email.confirmation',
defaultMessage: 'We’ve sent a confirmation message to {value}. Click the link in the message to update your email address.',
@@ -101,6 +111,11 @@ const messages = defineMessages({
defaultMessage: 'Recovery email address',
description: 'Label for account settings recovery email field.',
},
+ 'account.settings.field.secondary.email.empty': {
+ id: 'account.settings.field.secondary.email.empty',
+ defaultMessage: 'Add a recovery email address',
+ description: 'Placeholder for empty account settings recovery email field.',
+ },
'account.settings.field.secondary.email.confirmation': {
id: 'account.settings.field.secondary.email.confirmation',
defaultMessage: 'We’ve sent a confirmation message to {value}. Click the link in the message to update your recovery email address.',
@@ -116,14 +131,34 @@ const messages = defineMessages({
defaultMessage: 'Year of birth',
description: 'Label for account settings year of birth field.',
},
+ 'account.settings.field.dob.empty': {
+ id: 'account.settings.field.dob.empty',
+ defaultMessage: 'Add year of birth',
+ description: 'Placeholder for empty account settings year of birth field.',
+ },
+ 'account.settings.field.year_of_birth.options.empty': {
+ id: 'account.settings.field.year_of_birth.options.empty',
+ defaultMessage: 'Select a year of birth',
+ description: 'Option for empty value on account settings year of birth field.',
+ },
'account.settings.field.country': {
id: 'account.settings.field.country',
defaultMessage: 'Country',
description: 'Label for account settings country field.',
},
+ 'account.settings.field.country.empty': {
+ id: 'account.settings.field.country.empty',
+ defaultMessage: 'Add country',
+ description: 'Placeholder for empty account settings country field.',
+ },
+ 'account.settings.field.country.options.empty': {
+ id: 'account.settings.field.country.options.empty',
+ defaultMessage: 'Select a Country',
+ description: 'Option for empty value on account settings country field.',
+ },
'account.settings.field.site.language': {
id: 'account.settings.field.site.language',
- defaultMessage: 'Site Language',
+ defaultMessage: 'Site language',
description: 'Label for account settings site language field.',
},
'account.settings.field.site.language.help.text': {
@@ -136,8 +171,13 @@ const messages = defineMessages({
defaultMessage: 'Education',
description: 'Label for account settings education field.',
},
- 'account.settings.field.education.levels.null': {
- id: 'account.settings.field.education.levels.null',
+ 'account.settings.field.education.empty': {
+ id: 'account.settings.field.education.empty',
+ defaultMessage: 'Add level of education',
+ description: 'Placeholder for empty account settings education field.',
+ },
+ 'account.settings.field.education.levels.empty': {
+ id: 'account.settings.field.education.levels.empty',
defaultMessage: 'Select a level of education',
description: 'Placeholder for the education levels dropdown.',
},
@@ -192,8 +232,13 @@ const messages = defineMessages({
defaultMessage: 'Gender',
description: 'Label for account settings gender field.',
},
- 'account.settings.field.gender.options.null': {
- id: 'account.settings.field.gender.options.null',
+ 'account.settings.field.gender.empty': {
+ id: 'account.settings.field.gender.empty',
+ defaultMessage: 'Add gender',
+ description: 'Placeholder for empty account settings gender field.',
+ },
+ 'account.settings.field.gender.options.empty': {
+ id: 'account.settings.field.gender.options.empty',
defaultMessage: 'Select a gender',
description: 'Placeholder for the gender options dropdown.',
},
@@ -214,14 +259,29 @@ const messages = defineMessages({
},
'account.settings.field.language.proficiencies': {
id: 'account.settings.field.language.proficiencies',
- defaultMessage: 'Spoken Languages',
+ defaultMessage: 'Spoken languages',
description: 'Label for account settings spoken languages field.',
},
+ 'account.settings.field.language.proficiencies.empty': {
+ id: 'account.settings.field.language.proficiencies.empty',
+ defaultMessage: 'Add a spoken language',
+ description: 'Placeholder for empty account settings spoken languages field.',
+ },
+ 'account.settings.field.language_proficiencies.options.empty': {
+ id: 'account.settings.field.language_proficiencies.options.empty',
+ defaultMessage: 'Select a Language',
+ description: 'Option for an empty value on account settings spoken languages field.',
+ },
'account.settings.field.time.zone': {
id: 'account.settings.field.time.zone',
- defaultMessage: 'Time Zone',
+ defaultMessage: 'Time zone',
description: 'Label for time zone settings field.',
},
+ 'account.settings.field.time.zone.empty': {
+ id: 'account.settings.field.time.zone.empty',
+ defaultMessage: 'Set time zone',
+ description: 'Placeholder for empty for time zone settings field.',
+ },
'account.settings.field.time.zone.description': {
id: 'account.settings.field.time.zone.description',
defaultMessage: 'Select the time zone for displaying course dates. If you do not specify a time zone, course dates, including assignment deadlines, will be displayed in your browser’s local time zone.',
@@ -258,16 +318,34 @@ const messages = defineMessages({
defaultMessage: 'LinkedIn',
description: 'Label for LinkedIn',
},
+ 'account.settings.field.social.platform.name.linkedin.empty': {
+ id: 'account.settings.field.social.platform.name.linkedin.empty',
+ defaultMessage: 'Add LinkedIn profile',
+ description: 'Placeholder for an empty LinkedIn field',
+ },
+
'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.twitter.empty': {
+ id: 'account.settings.field.social.platform.name.twitter.empty',
+ defaultMessage: 'Add Twitter profile',
+ description: 'Placeholder for an empty Twitter field',
+ },
+
'account.settings.field.social.platform.name.facebook': {
id: 'account.settings.field.social.platform.name.facebook',
defaultMessage: 'Facebook',
description: 'Label for Facebook',
},
+ 'account.settings.field.social.platform.name.facebook.empty': {
+ id: 'account.settings.field.social.platform.name.facebook.empty',
+ defaultMessage: 'Add Facebook profile',
+ description: 'Placeholder for an empty Facebook field',
+ },
+
'account.settings.delete.account.header': {
id: 'account.settings.delete.account.header',
@@ -396,6 +474,16 @@ const messages = defineMessages({
defaultMessage: 'Edit',
description: 'The edit button on an editable field',
},
+ 'account.settings.static.field.empty': {
+ id: 'account.settings.static.field.empty',
+ defaultMessage: 'No value set. Contact your {enterprise} administrator to make changes.',
+ description: 'The placeholder for an empty but uneditable field',
+ },
+ 'account.settings.static.field.empty.no.admin': {
+ id: 'account.settings.static.field.empty.no.admin',
+ defaultMessage: 'No value set.',
+ description: 'The placeholder for an empty but uneditable field when there is no administrator',
+ },
});
export default messages;
diff --git a/src/account-settings/components/EditableField.jsx b/src/account-settings/components/EditableField.jsx
index 24b34ff..a6b3405 100644
--- a/src/account-settings/components/EditableField.jsx
+++ b/src/account-settings/components/EditableField.jsx
@@ -20,6 +20,7 @@ function EditableField(props) {
const {
name,
label,
+ emptyLabel,
type,
value,
options,
@@ -39,16 +40,6 @@ function EditableField(props) {
} = props;
const id = `field-${name}`;
- const getValue = (rawValue) => {
- if (options) {
- // 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);
- if (selectedOption) return selectedOption.label;
- }
- return rawValue;
- };
-
const handleSubmit = (e) => {
e.preventDefault();
onSubmit(name, new FormData(e.target).get(name));
@@ -66,6 +57,26 @@ function EditableField(props) {
onCancel(name);
};
+ const renderEmptyLabel = () => {
+ if (isEditable) {
+ return ;
+ }
+ return {emptyLabel};
+ };
+
+ const renderValue = (rawValue) => {
+ if (!rawValue) return renderEmptyLabel();
+
+ if (options) {
+ // 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);
+ if (selectedOption) return selectedOption.label;
+ }
+
+ return rawValue;
+ };
+
const renderConfirmationMessage = () => {
if (!confirmationMessageDefinition || !confirmationValue) return null;
return intl.formatMessage(confirmationMessageDefinition, {
@@ -135,7 +146,7 @@ function EditableField(props) {
) : null}
-
{getValue(value)}
+ {renderValue(value)}
{renderConfirmationMessage() || helpText}
),
@@ -148,6 +159,7 @@ function EditableField(props) {
EditableField.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ emptyLabel: PropTypes.node,
type: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
options: PropTypes.arrayOf(PropTypes.shape({
@@ -177,6 +189,7 @@ EditableField.defaultProps = {
options: undefined,
saveState: undefined,
label: undefined,
+ emptyLabel: undefined,
error: undefined,
confirmationMessageDefinition: undefined,
confirmationValue: undefined,
diff --git a/src/account-settings/components/EmailField.jsx b/src/account-settings/components/EmailField.jsx
index 427f87f..f27df09 100644
--- a/src/account-settings/components/EmailField.jsx
+++ b/src/account-settings/components/EmailField.jsx
@@ -21,6 +21,7 @@ function EmailField(props) {
const {
name,
label,
+ emptyLabel,
value,
saveState,
error,
@@ -82,6 +83,18 @@ function EmailField(props) {
);
+ const renderEmptyLabel = () => {
+ if (isEditable) {
+ return ;
+ }
+ return {emptyLabel};
+ };
+
+ const renderValue = () => {
+ if (confirmationValue) return renderConfirmationValue();
+ return value || renderEmptyLabel();
+ };
+
return (
) : null}
- {confirmationValue ? renderConfirmationValue() : value}
+ {renderValue()}
{renderConfirmationMessage() || {helpText}
}
),
@@ -156,6 +169,7 @@ function EmailField(props) {
EmailField.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ emptyLabel: PropTypes.node,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
saveState: PropTypes.oneOf(['default', 'pending', 'complete', 'error']),
error: PropTypes.string,
@@ -179,6 +193,7 @@ EmailField.defaultProps = {
value: undefined,
saveState: undefined,
label: undefined,
+ emptyLabel: undefined,
error: undefined,
confirmationMessageDefinition: undefined,
confirmationValue: undefined,
diff --git a/src/account-settings/constants/index.js b/src/account-settings/constants/index.js
index 1bbfa16..c6e2552 100644
--- a/src/account-settings/constants/index.js
+++ b/src/account-settings/constants/index.js
@@ -12,7 +12,7 @@ export const YEAR_OF_BIRTH_OPTIONS = (() => {
})();
export const EDUCATION_LEVELS = [
- null,
+ '',
'p',
'm',
'b',
@@ -25,7 +25,7 @@ export const EDUCATION_LEVELS = [
];
export const GENDER_OPTIONS = [
- null,
+ '',
'f',
'm',
'o',
diff --git a/src/account-settings/selectors.js b/src/account-settings/selectors.js
index 80f6b92..e09f1f3 100644
--- a/src/account-settings/selectors.js
+++ b/src/account-settings/selectors.js
@@ -111,7 +111,7 @@ const formValuesSelector = createSelector(
(values, drafts) => {
const formValues = {};
Object.entries(values).forEach(([name, value]) => {
- formValues[name] = chooseFormValue(drafts[name], value);
+ formValues[name] = chooseFormValue(drafts[name], value) || '';
});
return formValues;
},
diff --git a/src/account-settings/service.js b/src/account-settings/service.js
index 31385a4..cea805d 100644
--- a/src/account-settings/service.js
+++ b/src/account-settings/service.js
@@ -74,8 +74,22 @@ function packAccountCommitData(commitData) {
delete packedData[key];
});
- if (commitData.language_proficiencies) {
- packedData.language_proficiencies = [{ code: commitData.language_proficiencies }];
+ 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;
}