diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx
index 03f5399..adac380 100644
--- a/src/account-settings/AccountSettingsPage.jsx
+++ b/src/account-settings/AccountSettingsPage.jsx
@@ -1,7 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { injectIntl, intlShape } from 'react-intl';
+import {
+ injectIntl,
+ intlShape,
+ getLocale,
+ getCountryList,
+ getLanguageList,
+} from '@edx/frontend-i18n'; // eslint-disable-line
import messages from './AccountSettingsPage.messages';
@@ -11,10 +17,30 @@ import { pageSelector } from './selectors';
import { PageLoading } from '../common';
import EditableField from './components/EditableField';
import PasswordReset from './components/PasswordReset';
-import { yearOfBirthOptions, yearOfBirthDefault } from './constants';
+import {
+ YEAR_OF_BIRTH_OPTIONS,
+ EDUCATION_LEVELS,
+ GENDER_OPTIONS,
+} from './constants';
class AccountSettingsPage extends React.Component {
+ constructor(props) {
+ super(props);
+ this.countryOptions = getCountryList(getLocale())
+ .map(({ code, name }) => ({ value: code, label: name }));
+ this.languageProficiencyOptions = getLanguageList(getLocale())
+ .map(({ code, name }) => ({ value: code, label: name }));
+ this.educationLevels = EDUCATION_LEVELS.map(key => ({
+ value: key,
+ label: props.intl.formatMessage(messages[`account.settings.field.education.levels.${key}`]),
+ }));
+ this.genderOptions = GENDER_OPTIONS.map(key => ({
+ value: key,
+ label: props.intl.formatMessage(messages[`account.settings.field.gender.options.${key}`]),
+ }));
+ }
+
componentDidMount() {
this.props.fetchAccount();
}
@@ -48,10 +74,35 @@ class AccountSettingsPage extends React.Component {
name="year_of_birth"
type="select"
label={this.props.intl.formatMessage(messages['account.settings.field.dob'])}
- options={yearOfBirthOptions}
- defaultValue={yearOfBirthDefault}
+ options={YEAR_OF_BIRTH_OPTIONS}
/>
+
+
+
+ (v.length ? v[0].code : null)}
+ reverseTransform={v => ([{ code: v }])}
+ label={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies'])}
+ />
diff --git a/src/account-settings/AccountSettingsPage.messages.jsx b/src/account-settings/AccountSettingsPage.messages.jsx
index a70c2aa..88e2d21 100644
--- a/src/account-settings/AccountSettingsPage.messages.jsx
+++ b/src/account-settings/AccountSettingsPage.messages.jsx
@@ -16,6 +16,16 @@ const messages = defineMessages({
defaultMessage: 'Error: {error}',
description: 'Message when data failed to load',
},
+ 'account.settings.section.account.information': {
+ id: 'account.settings.section.account.information',
+ defaultMessage: 'Account Information',
+ description: 'The basic account information section heading.',
+ },
+ 'account.settings.section.account.information.description': {
+ id: 'account.settings.section.account.information.description',
+ defaultMessage: 'These settings include basic information about your account.',
+ description: 'The basic account information section heading description.',
+ },
'account.settings.field.username': {
id: 'account.settings.field.username',
defaultMessage: 'Username',
@@ -41,15 +51,98 @@ const messages = defineMessages({
defaultMessage: 'Year of birth',
description: 'Label for account settings year of birth field.',
},
- 'account.settings.section.account.information': {
- id: 'account.settings.section.account.information',
- defaultMessage: 'Account Information',
- description: 'The basic account information section heading.',
+ 'account.settings.field.country': {
+ id: 'account.settings.field.country',
+ defaultMessage: 'Country',
+ description: 'Label for account settings country field.',
},
- 'account.settings.section.account.information.description': {
- id: 'account.settings.section.account.information.description',
- defaultMessage: 'These settings include basic information about your account.',
- description: 'The basic account information section heading description.',
+
+ 'account.settings.field.education': {
+ id: 'account.settings.field.education',
+ defaultMessage: 'Education',
+ description: 'Label for account settings education field.',
+ },
+ 'account.settings.field.education.levels.null': {
+ id: 'account.settings.field.education.levels.null',
+ defaultMessage: 'Select a level of education',
+ description: 'Placeholder for the education levels dropdown.',
+ },
+ 'account.settings.field.education.levels.p': {
+ id: 'account.settings.field.education.levels.p',
+ defaultMessage: 'Doctorate',
+ description: 'Selected by the user if their highest level of education is a doctorate degree.',
+ },
+ 'account.settings.field.education.levels.m': {
+ id: 'account.settings.field.education.levels.m',
+ defaultMessage: "Master's or professional degree",
+ description: "Selected by the user if their highest level of education is a master's or professional degree from a college or university.",
+ },
+ 'account.settings.field.education.levels.b': {
+ id: 'account.settings.field.education.levels.b',
+ defaultMessage: "Bachelor's Degree",
+ description: "Selected by the user if their highest level of education is a four year college or university bachelor's degree.",
+ },
+ 'account.settings.field.education.levels.a': {
+ id: 'account.settings.field.education.levels.a',
+ defaultMessage: "Associate's degree",
+ description: "Selected by the user if their highest level of education is an associate's degree. 1-2 years of college or university.",
+ },
+ 'account.settings.field.education.levels.hs': {
+ id: 'account.settings.field.education.levels.hs',
+ defaultMessage: 'Secondary/high school',
+ description: 'Selected by the user if their highest level of education is secondary or high school. 9-12 years of education.',
+ },
+ 'account.settings.field.education.levels.jhs': {
+ id: 'account.settings.field.education.levels.jhs',
+ defaultMessage: 'Junior secondary/junior high/middle school',
+ description: 'Selected by the user if their highest level of education is junior or middle school. 6-8 years of education.',
+ },
+ 'account.settings.field.education.levels.el': {
+ id: 'account.settings.field.education.levels.el',
+ defaultMessage: 'Elementary/primary school',
+ description: 'Selected by the user if their highest level of education is elementary or primary school. 1-5 years of education.',
+ },
+ 'account.settings.field.education.levels.none': {
+ id: 'account.settings.field.education.levels.none',
+ defaultMessage: 'No formal education',
+ description: 'Selected by the user to describe their education.',
+ },
+ 'account.settings.field.education.levels.o': {
+ id: 'account.settings.field.education.levels.o',
+ defaultMessage: 'Other education',
+ description: 'Selected by the user if they have a type of education not described by the other choices.',
+ },
+
+ 'account.settings.field.gender': {
+ id: 'account.settings.field.gender',
+ defaultMessage: 'Gender',
+ description: 'Label for account settings gender field.',
+ },
+ 'account.settings.field.gender.options.null': {
+ id: 'account.settings.field.gender.options.null',
+ defaultMessage: 'Select a gender',
+ description: 'Placeholder for the gender options dropdown.',
+ },
+ 'account.settings.field.gender.options.f': {
+ id: 'account.settings.field.gender.options.f',
+ defaultMessage: 'Female',
+ description: 'The label for the female gender option.',
+ },
+ 'account.settings.field.gender.options.m': {
+ id: 'account.settings.field.gender.options.m',
+ defaultMessage: 'Male',
+ description: 'The label for the male gender option.',
+ },
+ 'account.settings.field.gender.options.o': {
+ id: 'account.settings.field.gender.options.o',
+ defaultMessage: 'Other',
+ description: 'The label for catch-all gender option.',
+ },
+
+ 'account.settings.field.language.proficiencies': {
+ id: 'account.settings.field.language.proficiencies',
+ defaultMessage: 'Spoken Languages',
+ description: 'Label for account settings spoken languages field.',
},
});
diff --git a/src/account-settings/components/EditableField.jsx b/src/account-settings/components/EditableField.jsx
index 5d2b70b..e41eb96 100644
--- a/src/account-settings/components/EditableField.jsx
+++ b/src/account-settings/components/EditableField.jsx
@@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
-import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
-import { Button } from '@edx/paragon';
+import { FormattedMessage } from 'react-intl';
+import { injectIntl, intlShape } from '@edx/frontend-i18n'; // eslint-disable-line
+import { Button, StatefulButton } from '@edx/paragon';
import Input from './temp/Input';
import ValidationFormGroup from './temp/ValidationFormGroup';
@@ -23,7 +24,9 @@ function EditableField(props) {
name,
label,
type,
- value,
+ value: propValue,
+ options,
+ saveState,
error,
confirmationMessageDefinition,
confirmationValue,
@@ -35,19 +38,36 @@ 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);
+ if (selectedOption) return selectedOption.label;
+ }
+ return rawValue;
+ };
const handleSubmit = (e) => {
e.preventDefault();
- const data = {};
- new FormData(e.target).forEach((v, k) => { data[k] = v; });
+ const data = {
+ [name]: reverseTransform(new FormData(e.target).get(name)),
+ };
onSubmit(name, data);
};
const handleChange = (e) => {
- onChange(name, e.target.value);
+ onChange(name, reverseTransform(e.target.value));
};
const handleEdit = () => {
@@ -60,7 +80,9 @@ function EditableField(props) {
const renderConfirmationMessage = () => {
if (!confirmationMessageDefinition || !confirmationValue) return null;
- return intl.formatMessage(confirmationMessageDefinition, { value: confirmationValue });
+ return intl.formatMessage(confirmationMessageDefinition, {
+ value: transformValue(confirmationValue),
+ });
};
return (
@@ -82,17 +104,36 @@ function EditableField(props) {
type={type}
value={value}
onChange={handleChange}
+ options={options}
{...others}
/>
-
+
+ ),
+ }}
+ onClick={(e) => {
+ // Swallow clicks if the state is pending.
+ // We do this instead of disabling the button to prevent
+ // it from losing focus (disabled elements cannot have focus).
+ // Disabling it would causes upstream issues in focus management.
+ // Swallowing the onSubmit event on the form would be better, but
+ // we would have to add that logic for every field given our
+ // current structure of the application.
+ if (saveState === 'pending') e.preventDefault();
+ }}
+ disabledStates={[]}
+ />