diff --git a/.env b/.env
index 34dbdf6..b8a67f6 100644
--- a/.env
+++ b/.env
@@ -5,6 +5,7 @@ CSRF_TOKEN_API_PATH=null
ECOMMERCE_BASE_URL=null
LANGUAGE_PREFERENCE_COOKIE_NAME=null
LMS_BASE_URL=null
+DEMOGRAPHICS_BASE_URL=null
LOGIN_URL=null
LOGOUT_URL=null
MARKETING_SITE_BASE_URL=null
diff --git a/.env.development b/.env.development
index 072c5fd..df652a1 100644
--- a/.env.development
+++ b/.env.development
@@ -8,6 +8,7 @@ LMS_BASE_URL='http://localhost:18000'
LOGIN_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/logout'
MARKETING_SITE_BASE_URL='http://localhost:18000'
+DEMOGRAPHICS_BASE_URL='http://localhost:18360'
NODE_ENV='development'
ORDER_HISTORY_URL='localhost:1996/orders'
PORT=1997
@@ -18,3 +19,4 @@ SUPPORT_URL='http://localhost:18000/support'
USER_INFO_COOKIE_NAME='edx-user-info'
# Temporary, Remove this once we are ready to release the feature.
COACHING_ENABLED=true
+ENABLE_DEMOGRAPHICS_COLLECTION=true
diff --git a/.env.test b/.env.test
index 155b7bf..f82034f 100644
--- a/.env.test
+++ b/.env.test
@@ -8,6 +8,7 @@ LMS_BASE_URL='http://localhost:18000'
LOGIN_URL='http://localhost:18000/login'
LOGOUT_URL='http://localhost:18000/logout'
MARKETING_SITE_BASE_URL='http://localhost:18000'
+DEMOGRAPHICS_BASE_URL='http://localhost:18360'
NODE_ENV=null
ORDER_HISTORY_URL='localhost:1996/orders'
REFRESH_ACCESS_TOKEN_ENDPOINT='http://localhost:18000/login_refresh'
@@ -16,3 +17,4 @@ SITE_NAME='edX'
SUPPORT_URL='http://localhost:18000/support'
USER_INFO_COOKIE_NAME='edx-user-info'
COACHING_ENABLED=''
+ENABLE_DEMOGRAPHICS_COLLECTION=''
diff --git a/package-lock.json b/package-lock.json
index a67d297..587ebf6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11646,6 +11646,11 @@
"resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz",
"integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM="
},
+ "lodash.pickby": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz",
+ "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8="
+ },
"lodash.snakecase": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz",
diff --git a/package.json b/package.json
index b45c4ab..7c45cf1 100755
--- a/package.json
+++ b/package.json
@@ -53,6 +53,7 @@
"lodash.merge": "4.6.2",
"lodash.omit": "4.5.0",
"lodash.pick": "4.4.0",
+ "lodash.pickby": "^4.6.0",
"lodash.snakecase": "4.1.1",
"memoize-one": "5.1.1",
"newrelic": "5.13.1",
diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx
index 9a14da3..7365b9a 100644
--- a/src/account-settings/AccountSettingsPage.jsx
+++ b/src/account-settings/AccountSettingsPage.jsx
@@ -36,6 +36,7 @@ import {
} from './data/constants';
import { fetchSiteLanguages } from './site-language';
import CoachingToggle from './coaching/CoachingToggle';
+import DemographicsSection from './demographics/DemographicsSection';
class AccountSettingsPage extends React.Component {
constructor(props, context) {
@@ -363,7 +364,9 @@ class AccountSettingsPage extends React.Component {
/>
}
-
+ {getConfig().ENABLE_DEMOGRAPHICS_COLLECTION &&
+
+ }
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])}
diff --git a/src/account-settings/AccountSettingsPage.messages.jsx b/src/account-settings/AccountSettingsPage.messages.jsx
index 20e40ba..6c5535d 100644
--- a/src/account-settings/AccountSettingsPage.messages.jsx
+++ b/src/account-settings/AccountSettingsPage.messages.jsx
@@ -46,6 +46,11 @@ const messages = defineMessages({
defaultMessage: 'Profile Information',
description: 'The profile information section heading.',
},
+ 'account.settings.section.demographics.information': {
+ id: 'account.settings.section.demographics.information',
+ defaultMessage: 'Optional Information',
+ description: 'The optional information section heading.',
+ },
'account.settings.section.site.preferences': {
id: 'account.settings.section.site.preferences',
defaultMessage: 'Site Preferences',
@@ -287,6 +292,7 @@ const messages = defineMessages({
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',
diff --git a/src/account-settings/EditableField.jsx b/src/account-settings/EditableField.jsx
index cdb57c6..a14d873 100644
--- a/src/account-settings/EditableField.jsx
+++ b/src/account-settings/EditableField.jsx
@@ -23,6 +23,7 @@ function EditableField(props) {
emptyLabel,
type,
value,
+ userSuppliedValue,
options,
saveState,
error,
@@ -66,15 +67,22 @@ function EditableField(props) {
const renderValue = (rawValue) => {
if (!rawValue) return renderEmptyLabel();
+ let value = 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;
+ if (selectedOption) {
+ value = selectedOption.label;
+ };
}
- return rawValue;
+ if (userSuppliedValue) {
+ value += `: ${userSuppliedValue}`;
+ }
+
+ return value;
};
const renderConfirmationMessage = () => {
@@ -106,6 +114,7 @@ function EditableField(props) {
options={options}
{...others}
/>
+ <>{others.children}>
+
+
+ {intl.formatMessage(messages['account.settings.section.demographics.information'])}
+
+
{intl.formatMessage(messages['account.settings.section.social.media'])}
diff --git a/src/account-settings/data/actions.js b/src/account-settings/data/actions.js
index 1ae2f22..8f43a22 100644
--- a/src/account-settings/data/actions.js
+++ b/src/account-settings/data/actions.js
@@ -100,9 +100,9 @@ export const savePreviousSiteLanguage = previousSiteLanguage => ({
payload: { previousSiteLanguage },
});
-export const saveMultipleSettings = settingsArray => ({
+export const saveMultipleSettings = (settingsArray, form = null) => ({
type: SAVE_MULTIPLE_SETTINGS.BASE,
- payload: { settingsArray },
+ payload: { settingsArray, form },
});
export const saveMultipleSettingsBegin = () => ({
diff --git a/src/account-settings/data/constants.js b/src/account-settings/data/constants.js
index 0ac9ed7..a647bec 100644
--- a/src/account-settings/data/constants.js
+++ b/src/account-settings/data/constants.js
@@ -112,3 +112,92 @@ const COUNTRY_STATES_MAP = {
export function getStatesList(country) {
return country && COUNTRY_STATES_MAP[country.toUpperCase()];
}
+
+export const SELF_DESCRIBE = 'self-describe';
+export const DEMOGRAPHICS_GENDER_OPTIONS = [
+ '',
+ 'woman',
+ 'man',
+ 'non-binary',
+ SELF_DESCRIBE,
+];
+
+export const OTHER = 'other';
+export const DEMOGRAPHICS_ETHNICITY_OPTIONS = [
+ 'american-indian-or-alaska-native',
+ 'asian',
+ 'black-or-african-american',
+ 'hispanic-latin-spanish',
+ 'middle-eastern-or-north-african',
+ 'native-hawaiian-or-pacific-islander',
+ 'white',
+ OTHER,
+];
+
+export const DEMOGRAPHICS_INCOME_OPTIONS = [
+ '',
+ 'less-than-10k',
+ '10k-25k',
+ '25k-50k',
+ '50k-75k',
+ 'over-100k',
+ 'unsure',
+];
+
+export const DEMOGRAPHICS_MILITARY_HISTORY_OPTIONS = [
+ '',
+ 'never-served',
+ 'training',
+ 'active',
+ 'previously-active',
+];
+
+export const DEMOGRAPHICS_EDUCATION_LEVEL_OPTIONS = [
+ '',
+ 'no-high-school',
+ 'some-high-school',
+ 'high-school-ged-equivalent',
+ 'some-college',
+ 'associates',
+ 'bachelors',
+ 'masters',
+ 'professional',
+ 'doctorate',
+];
+
+export const DEMOGRAPHICS_WORK_STATUS_OPTIONS = [
+ '',
+ 'full-time',
+ 'part-time',
+ 'not-employed-looking',
+ 'not-employed-not-looking',
+ 'unable',
+ 'retired',
+ 'other',
+];
+
+export const DEMOGRAPHICS_WORK_SECTOR_OPTIONS = [
+ '',
+ 'accommodation-food',
+ 'administrative-support-waste-remediation',
+ 'agriculture-forestry-fishing-hunting',
+ 'arts-entertainment-recreation',
+ 'construction',
+ 'educational',
+ 'finance-insurance',
+ 'healthcare-social',
+ 'information',
+ 'management',
+ 'manufacturing',
+ 'mining-quarry-oil-gas',
+ 'professional-scientific-technical',
+ 'public-admin',
+ 'real-estate',
+ 'retail',
+ 'transport-warehousing',
+ 'utilities',
+ 'trade',
+ 'other',
+];
+
+export const DECLINED = 'declined';
diff --git a/src/account-settings/data/sagas.js b/src/account-settings/data/sagas.js
index 2b5e553..3ab3ca9 100644
--- a/src/account-settings/data/sagas.js
+++ b/src/account-settings/data/sagas.js
@@ -106,11 +106,11 @@ export function* handleSaveSettings(action) {
// handles mutiple settings saved at once, in order, and stops executing on first failure.
-export function* handleSaveMultipleSettings(settings) {
+export function* handleSaveMultipleSettings(action) {
try {
yield put(saveMultipleSettingsBegin());
const { username, userId } = getAuthenticatedUser();
- const { settingsArray } = settings.payload;
+ const { settingsArray, form } = action.payload;
for (let i = 0; i < settingsArray.length; i += 1) {
const { formId, commitValues } = settingsArray[i];
yield put(saveSettingsBegin());
@@ -118,7 +118,11 @@ export function* handleSaveMultipleSettings(settings) {
const savedSettings = yield call(patchSettings, username, commitData, userId);
yield put(saveSettingsSuccess(savedSettings, commitData));
}
- yield put(saveMultipleSettingsSuccess(settings));
+ yield put(saveMultipleSettingsSuccess(action));
+ if (form) {
+ yield delay(1000);
+ yield put(closeForm(form));
+ }
} catch (e) {
if (e.fieldErrors) {
yield put(saveMultipleSettingsFailure({ fieldErrors: e.fieldErrors }));
diff --git a/src/account-settings/data/selectors.js b/src/account-settings/data/selectors.js
index f2289ff..d441dd2 100644
--- a/src/account-settings/data/selectors.js
+++ b/src/account-settings/data/selectors.js
@@ -191,3 +191,15 @@ export const coachingConsentPageSelector = createSelector(
formErrors: errors,
}),
);
+
+export const demographicsSectionSelector = createSelector(
+ formValuesSelector,
+ draftsSelector,
+ (
+ formValues,
+ drafts,
+ ) => ({
+ formValues,
+ drafts,
+ }),
+);
diff --git a/src/account-settings/data/service.js b/src/account-settings/data/service.js
index c265bf0..683d50d 100644
--- a/src/account-settings/data/service.js
+++ b/src/account-settings/data/service.js
@@ -1,12 +1,14 @@
import { getConfig } from '@edx/frontend-platform';
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
import pick from 'lodash.pick';
+import pickBy from 'lodash.pickby';
import omit from 'lodash.omit';
import isEmpty from 'lodash.isempty';
import { handleRequestError, unpackFieldErrors } from './utils';
import { getThirdPartyAuthProviders } from '../third-party-auth';
import { getCoachingPreferences, patchCoachingPreferences } from '../coaching/data/service';
+import { getDemographics, patchDemographics } from '../demographics/data/service';
const SOCIAL_PLATFORMS = [
{ id: 'twitter', key: 'social_link_twitter' },
@@ -161,6 +163,7 @@ export async function getSettings(username, userRoles, userId) {
getProfileDataManager(username, userRoles),
getTimeZones(),
getConfig().COACHING_ENABLED && getCoachingPreferences(userId),
+ getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographics(userId),
]);
return {
@@ -170,6 +173,7 @@ export async function getSettings(username, userRoles, userId) {
profileDataManager: results[3],
timeZones: results[4],
coaching: results[5],
+ ...results[6], // demographics
};
}
@@ -183,9 +187,11 @@ export async function patchSettings(username, commitValues, userId) {
// user/v1/preferences where it does update. This is the one we use.
const preferenceKeys = ['time_zone'];
const coachingKeys = ['coaching'];
+ const isDemographicsKey = (value, key) => key.includes('demographics');
const accountCommitValues = omit(commitValues, preferenceKeys, coachingKeys);
const preferenceCommitValues = pick(commitValues, preferenceKeys);
const coachingCommitValues = pick(commitValues, coachingKeys);
+ const demographicsCommitValues = pickBy(commitValues, isDemographicsKey);
const patchRequests = [];
if (!isEmpty(accountCommitValues)) {
@@ -197,6 +203,9 @@ export async function patchSettings(username, commitValues, userId) {
if (!isEmpty(coachingCommitValues)) {
patchRequests.push(patchCoachingPreferences(userId, coachingCommitValues));
}
+ if (!isEmpty(demographicsCommitValues)) {
+ patchRequests.push(patchDemographics(userId, demographicsCommitValues));
+ }
const results = await Promise.all(patchRequests);
// Assigns in order of requests. Preference keys
diff --git a/src/account-settings/demographics/Checkboxes.jsx b/src/account-settings/demographics/Checkboxes.jsx
new file mode 100644
index 0000000..4a037f1
--- /dev/null
+++ b/src/account-settings/demographics/Checkboxes.jsx
@@ -0,0 +1,78 @@
+import React, { useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+import { CheckBox } from '@edx/paragon';
+import { DECLINED } from '../data/constants';
+
+export const Checkboxes = (props) => {
+ const {
+ id,
+ options,
+ values,
+ onChange,
+ } = props;
+
+ const [selected, setSelected] = useState(values);
+ useEffect(() => {
+ onChange(id, selected)
+ }, [selected])
+
+ const handleToggle = (value, option) => {
+ // If the user checked 'declined', uncheck all other options
+ if (value && option == DECLINED) {
+ setSelected([DECLINED]);
+ return;
+ }
+
+ // If option checked, make sure this option is in `selected` (and remove 'declined')
+ if (value && !selected.includes(option)) {
+ const newSelected = selected.filter(i => i !== DECLINED).concat(option);
+ setSelected(newSelected);
+ }
+
+ // If unchecked, make sure this option is NOT in `seleted`
+ if (!value) {
+ setSelected(selected.filter(i => i !== option));
+ }
+ }
+
+ const renderCheckboxes = () => {
+ return options.map((option, index) => {
+ const isFirst = index == 0;
+ const isChecked = selected.includes(option.value);
+ return (
+
+ handleToggle(value, option.value)}
+ />
+
+ )
+ })
+ }
+
+ return (
+
+ {renderCheckboxes()}
+
+ )
+}
+
+Checkboxes.propTypes = {
+ id: PropTypes.string,
+ options: PropTypes.array,
+ values: PropTypes.array,
+ onChange: PropTypes.func,
+};
+
+Checkboxes.defaultProps = {
+ options: [],
+ values: [],
+}
+
+export default Checkboxes;
diff --git a/src/account-settings/demographics/DemographicsSection.jsx b/src/account-settings/demographics/DemographicsSection.jsx
new file mode 100644
index 0000000..be6167f
--- /dev/null
+++ b/src/account-settings/demographics/DemographicsSection.jsx
@@ -0,0 +1,263 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { connect } from 'react-redux';
+import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
+import { Input } from '@edx/paragon';
+import memoize from 'memoize-one';
+
+import { demographicsSectionSelector } from '../data/selectors';
+import { saveMultipleSettings, updateDraft } from '../data/actions';
+import EditableField from '../EditableField';
+import Checkboxes from './Checkboxes';
+import messages from './DemographicsSection.messages';
+import {
+ SELF_DESCRIBE,
+ DEMOGRAPHICS_GENDER_OPTIONS,
+ DEMOGRAPHICS_ETHNICITY_OPTIONS,
+ DEMOGRAPHICS_INCOME_OPTIONS,
+ DEMOGRAPHICS_MILITARY_HISTORY_OPTIONS,
+ DEMOGRAPHICS_EDUCATION_LEVEL_OPTIONS,
+ OTHER,
+ DEMOGRAPHICS_WORK_STATUS_OPTIONS,
+ DEMOGRAPHICS_WORK_SECTOR_OPTIONS,
+ DECLINED,
+} from '../data/constants';
+
+class DemographicsSection extends React.Component {
+ constructor(props, context) {
+ super(props, context)
+ }
+
+ getLocalizedOptions = memoize((locale) => ({
+ demographicsGenderOptions: DEMOGRAPHICS_GENDER_OPTIONS.map(key => ({
+ value: key,
+ label: this.props.intl.formatMessage(messages[`account.settings.field.demographics.gender.options.${key || 'empty'}`]),
+ })).concat(this.getDeclinedOption()),
+ demographicsEthnicityOptions: DEMOGRAPHICS_ETHNICITY_OPTIONS.map(key => ({
+ value: key,
+ label: this.props.intl.formatMessage(messages[`account.settings.field.demographics.ethnicity.options.${key || 'empty'}`]),
+ })).concat(this.getDeclinedOption()),
+ demographicsIncomeOptions: DEMOGRAPHICS_INCOME_OPTIONS.map(key => ({
+ value: key,
+ label: this.props.intl.formatMessage(messages[`account.settings.field.demographics.income.options.${key || 'empty'}`]),
+ })).concat(this.getDeclinedOption()),
+ demographicsMilitaryHistoryOptions: DEMOGRAPHICS_MILITARY_HISTORY_OPTIONS.map(key => ({
+ value: key,
+ label: this.props.intl.formatMessage(messages[`account.settings.field.demographics.military_history.options.${key || 'empty'}`]),
+ })).concat(this.getDeclinedOption()),
+ demographicsEducationLevelOptions: DEMOGRAPHICS_EDUCATION_LEVEL_OPTIONS.map(key => ({
+ value: key,
+ label: this.props.intl.formatMessage(messages[`account.settings.field.demographics.education_level.options.${key || 'empty'}`]),
+ })).concat(this.getDeclinedOption()),
+ demographicsWorkStatusOptions: DEMOGRAPHICS_WORK_STATUS_OPTIONS.map(key => ({
+ value: key,
+ label: this.props.intl.formatMessage(messages[`account.settings.field.demographics.work_status.options.${key || 'empty'}`]),
+ })).concat(this.getDeclinedOption()),
+ demographicsWorkSectorOptions: DEMOGRAPHICS_WORK_SECTOR_OPTIONS.map(key => ({
+ value: key,
+ label: this.props.intl.formatMessage(messages[`account.settings.field.demographics.work_sector.options.${key || 'empty'}`]),
+ })).concat(this.getDeclinedOption()),
+ }));
+
+ getDeclinedOption() {
+ return [{
+ value: DECLINED,
+ label: this.props.intl.formatMessage(messages[`account.settings.field.demographics.options.declined`])
+ }]
+ }
+
+ ethnicityFieldDisplay = () => {
+ const ethnicities = this.props.formValues.demographics_user_ethnicity;
+ return ethnicities.map((e) => {
+ if (e == DECLINED) {
+ return this.props.intl.formatMessage(messages[`account.settings.field.demographics.options.declined`]);
+ }
+ return this.props.intl.formatMessage(messages[`account.settings.field.demographics.ethnicity.options.${e}`])
+ }).join(", ")
+ }
+
+ handleEditableFieldChange = (name, value) => {
+ this.props.updateDraft(name, value);
+ };
+
+ handleSubmit = (formId, values) => {
+ // We have some custom fields in this section. Instead of relying on the
+ // submitted values, submit the values stored in 'drafts'.
+ const drafts = this.props.drafts;
+ const settingsArray = []
+ for (let field in drafts) {
+ settingsArray.push({
+ formId: field,
+ commitValues: drafts[field]
+ })
+ }
+
+ this.props.saveMultipleSettings(settingsArray, formId);
+ };
+
+ render() {
+ const editableFieldProps = {
+ onChange: this.handleEditableFieldChange,
+ onSubmit: this.handleSubmit,
+ };
+
+ const {
+ demographicsGenderOptions,
+ demographicsEthnicityOptions,
+ demographicsIncomeOptions,
+ demographicsMilitaryHistoryOptions,
+ demographicsEducationLevelOptions,
+ demographicsWorkStatusOptions,
+ demographicsWorkSectorOptions,
+ } = this.getLocalizedOptions(this.context.locale);
+
+ const showSelfDescribe = this.props.formValues.demographics_gender == SELF_DESCRIBE;
+ const showWorkStatusDescribe = this.props.formValues.demographics_work_status == OTHER;
+
+ return (
+
+
+ {this.props.intl.formatMessage(messages['account.settings.section.demographics.information'])}
+
+
+
+ {showSelfDescribe &&
+ this.handleEditableFieldChange(`demographics_gender_description`, e.target.value)}
+ aria-label={this.props.intl.formatMessage(messages['account.settings.field.demographics.gender_description'])}
+ className="mt-1"
+ />
+ }
+
+
+
+
+
+
+
+
+
+ {showWorkStatusDescribe &&
+ this.handleEditableFieldChange(`demographics_work_status_description`, e.target.value)}
+ aria-label={this.props.intl.formatMessage(messages['account.settings.field.demographics.work_status_description'])}
+ className="mt-1"
+ />
+ }
+
+
+
+
+ )
+ }
+}
+
+DemographicsSection.propTypes = {
+ intl: intlShape.isRequired,
+ formValues: PropTypes.shape({
+ demographics_gender: PropTypes.string,
+ demographics_user_ethnicity: PropTypes.array,
+ demographics_income: PropTypes.string,
+ demographics_military_history: PropTypes.string,
+ demographics_learner_education_level: PropTypes.string,
+ demographics_parent_education_level: PropTypes.string,
+ demographics_work_status: PropTypes.string,
+ demographics_current_work_sector: PropTypes.string,
+ demographics_future_work_sector: PropTypes.string,
+ }).isRequired,
+ updateDraft: PropTypes.func.isRequired
+};
+
+export default connect(demographicsSectionSelector, {
+ saveMultipleSettings,
+ updateDraft,
+})(injectIntl(DemographicsSection))
diff --git a/src/account-settings/demographics/DemographicsSection.messages.jsx b/src/account-settings/demographics/DemographicsSection.messages.jsx
new file mode 100644
index 0000000..622b3e7
--- /dev/null
+++ b/src/account-settings/demographics/DemographicsSection.messages.jsx
@@ -0,0 +1,456 @@
+import { defineMessages } from '@edx/frontend-platform/i18n';
+
+const messages = defineMessages({
+ 'account.settings.section.demographics.information': {
+ id: 'account.settings.section.demographics.information',
+ defaultMessage: 'Optional Information',
+ description: 'The optional information section heading.',
+ },
+ 'account.settings.field.demographics.gender': {
+ id: 'account.settings.field.demographics.gender',
+ defaultMessage: 'Gender identity',
+ description: 'Label for account settings gender identity field.',
+ },
+ 'account.settings.field.demographics.gender.empty': {
+ id: 'account.settings.field.demographics.gender.empty',
+ defaultMessage: 'Add gender identity',
+ description: 'Placeholder for empty account settings gender identity field.',
+ },
+ 'account.settings.field.demographics.gender.options.empty': {
+ id: 'account.settings.field.demographics.gender.options.empty',
+ defaultMessage: 'Select a gender identity',
+ description: 'Placeholder for the gender identity options dropdown.',
+ },
+ 'account.settings.field.demographics.gender.options.woman': {
+ id: 'account.settings.field.demographics.gender.options.woman',
+ defaultMessage: 'Woman',
+ description: 'The label for the woman gender identity option.',
+ },
+ 'account.settings.field.demographics.gender.options.man': {
+ id: 'account.settings.field.demographics.gender.options.man',
+ defaultMessage: 'Man',
+ description: 'The label for the man gender identity option.',
+ },
+ 'account.settings.field.demographics.gender.options.non-binary': {
+ id: 'account.settings.field.demographics.gender.options.non-binary',
+ defaultMessage: 'Non-binary',
+ description: 'The label for the non-binary gender identity option.',
+ },
+ 'account.settings.field.demographics.gender.options.self-describe': {
+ id: 'account.settings.field.demographics.gender.options.self-describe',
+ defaultMessage: 'Prefer to self-describe',
+ description: 'The label for self-describe gender identity option.',
+ },
+ 'account.settings.field.demographics.gender_description': {
+ id: 'account.settings.field.demographics.gender_description',
+ defaultMessage: 'Gender identity description',
+ description: 'Label for account settings gender identity description field.',
+ },
+ 'account.settings.field.demographics.gender_description.empty': {
+ id: 'account.settings.field.demographics.gender_description.empty',
+ defaultMessage: 'Enter description',
+ description: 'Placeholder for empty account settings gender identity field.',
+ },
+ 'account.settings.field.demographics.ethnicity': {
+ id: 'account.settings.field.demographics.ethnicity',
+ defaultMessage: 'Race/Ethnicity identity',
+ description: 'Label for account settings ethnic background field.',
+ },
+ 'account.settings.field.demographics.ethnicity.empty': {
+ id: 'account.settings.field.demographics.ethnicity.empty',
+ defaultMessage: 'Add race/ethnicity identity',
+ description: 'Placeholder for empty account settings ethnic background field.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.empty': {
+ id: 'account.settings.field.demographics.ethnicity.options.empty',
+ defaultMessage: 'Select all that apply', // TODO: Is this the desired text?
+ description: 'Placeholder for the ethnic background options field.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.american-indian-or-alaska-native': {
+ id: 'account.settings.field.demographics.ethnicity.options.american-indian-or-alaska-native',
+ defaultMessage: 'American Indian or Alaska Native',
+ description: 'The label for the American Indian or Alaska Native ethnicity option.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.asian': {
+ id: 'account.settings.field.demographics.ethnicity.options.asian',
+ defaultMessage: 'Asian',
+ description: 'The label for the Asian ethnicity option.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.black-or-african-american': {
+ id: 'account.settings.field.demographics.ethnicity.options.black-or-african-american',
+ defaultMessage: 'Black or African American',
+ description: 'The label for the Black or African American ethnicity option.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.hispanic-latin-spanish': {
+ id: 'account.settings.field.demographics.ethnicity.options.hispanic-latin-spanish',
+ defaultMessage: 'Hispanic, Latin, or Spanish origin',
+ description: 'The label for the Hispanic, Latin, or Spanish origin ethnicity option.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.middle-eastern-or-north-african': {
+ id: 'account.settings.field.demographics.ethnicity.options.middle-eastern-or-north-african',
+ defaultMessage: 'Middle Eastern or North African',
+ description: 'The label for the Middle Eastern or North African ethnicity option.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.native-hawaiian-or-pacific-islander': {
+ id: 'account.settings.field.demographics.ethnicity.options.native-hawaiian-or-pacific-islander',
+ defaultMessage: 'Native Hawaiian or Other Pacific Islander',
+ description: 'The label for the Native Hawaiian or Other Pacific Islander ethnicity option.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.white': {
+ id: 'account.settings.field.demographics.ethnicity.options.white',
+ defaultMessage: 'White',
+ description: 'The label for the White ethnicity option.',
+ },
+ 'account.settings.field.demographics.ethnicity.options.other': {
+ id: 'account.settings.field.demographics.ethnicity.options.other',
+ defaultMessage: 'Some other race, ethnicity, or origin',
+ description: 'The label for the Some other race, ethnicity, or origin ethnicity option.',
+ },
+ 'account.settings.field.demographics.income': {
+ id: 'account.settings.field.demographics.income',
+ defaultMessage: 'Family income',
+ description: 'Label for account settings household income field.',
+ },
+ 'account.settings.field.demographics.income.empty': {
+ id: 'account.settings.field.demographics.income.empty',
+ defaultMessage: 'Add family income',
+ description: 'Placeholder for empty account settings household income field.',
+ },
+ 'account.settings.field.demographics.income.options.empty': {
+ id: 'account.settings.field.demographics.income.options.empty',
+ defaultMessage: 'Select a family income range',
+ description: 'Placeholder for the household income dropdown.',
+ },
+ 'account.settings.field.demographics.income.options.less-than-10k': {
+ id: 'account.settings.field.demographics.income.options.less-than-10k',
+ defaultMessage: 'Less than US $10,000',
+ description: 'The label for the less than US $10,000 income option.',
+ },
+ 'account.settings.field.demographics.income.options.10k-25k': {
+ id: 'account.settings.field.demographics.income.options.10k-25k',
+ defaultMessage: 'US $10,000 - $25,000',
+ description: 'The label for the US $10,000 - $25,000 income option.',
+ },
+ 'account.settings.field.demographics.income.options.25k-50k': {
+ id: 'account.settings.field.demographics.income.options.25k-50k',
+ defaultMessage: 'US $25,000 - $50,000',
+ description: 'The label for the US $25,000 - $50,000 income option.',
+ },
+ 'account.settings.field.demographics.income.options.50k-75k': {
+ id: 'account.settings.field.demographics.income.options.50k-75k',
+ defaultMessage: 'US $50,000 - $75,000',
+ description: 'The label for the US $50,000 - $75,000 income option.',
+ },
+ 'account.settings.field.demographics.income.options.over-100k': {
+ id: 'account.settings.field.demographics.income.options.over-100k',
+ defaultMessage: 'Over US $100,000',
+ description: 'The label for the over US $100,000 income option.',
+ },
+ 'account.settings.field.demographics.income.options.unsure': {
+ id: 'account.settings.field.demographics.income.options.unsure',
+ defaultMessage: 'I don\'t know',
+ description: 'The label for the I don\'t know income option.',
+ },
+ 'account.settings.field.demographics.military_history': {
+ id: 'account.settings.field.demographics.military_history',
+ defaultMessage: 'U.S. Military status',
+ description: 'Label for account settings military history field.',
+ },
+ 'account.settings.field.demographics.military_history.empty': {
+ id: 'account.settings.field.demographics.military_history.empty',
+ defaultMessage: 'Add military status',
+ description: 'Placeholder for empty account settings military history field.',
+ },
+ 'account.settings.field.demographics.military_history.options.empty': {
+ id: 'account.settings.field.demographics.military_history.options.empty',
+ defaultMessage: 'Select military status',
+ description: 'Placeholder for the military history dropdown.',
+ },
+ 'account.settings.field.demographics.military_history.options.never-served': {
+ id: 'account.settings.field.demographics.income.options.never-served',
+ defaultMessage: 'Never served in the military',
+ description: 'The label for the never served in the military military history option.',
+ },
+ 'account.settings.field.demographics.military_history.options.training': {
+ id: 'account.settings.field.demographics.income.options.training',
+ defaultMessage: 'Only on active duty for training',
+ description: 'The label for the only on active duty for training military history option.',
+ },
+ 'account.settings.field.demographics.military_history.options.active': {
+ id: 'account.settings.field.demographics.income.options.active',
+ defaultMessage: 'Now on active duty',
+ description: 'The label for the now on active duty military history option.',
+ },
+ 'account.settings.field.demographics.military_history.options.previously-active': {
+ id: 'account.settings.field.demographics.income.options.previously-active',
+ defaultMessage: 'On active duty in the past, but not now',
+ description: 'The label for the on active duty in the past, but not now military history option.',
+ },
+ 'account.settings.field.demographics.learner_education_level': {
+ id: 'account.settings.field.demographics.learner_education_level',
+ defaultMessage: 'Your education level',
+ description: 'Label for account settings learner education level field.',
+ },
+ 'account.settings.field.demographics.learner_education_level.empty': {
+ id: 'account.settings.field.demographics.learner_education_level.empty',
+ defaultMessage: 'Add education level',
+ description: 'Placeholder for empty account settings learner education level field.',
+ },
+ 'account.settings.field.demographics.parent_education_level': {
+ id: 'account.settings.field.demographics.parent_education_level',
+ defaultMessage: 'Parents/Guardians education level',
+ description: 'Label for account settings parent education level field.',
+ },
+ 'account.settings.field.demographics.parent_education_level.empty': {
+ id: 'account.settings.field.demographics.parent_education_level.empty',
+ defaultMessage: 'Add education level',
+ description: 'Placeholder for empty account settings parent education level field.',
+ },
+ 'account.settings.field.demographics.education_level.options.empty': {
+ id: 'account.settings.field.demographics.education_level.options.empty',
+ defaultMessage: 'Select education level',
+ description: 'Placeholder for the education level options dropdown.',
+ },
+ 'account.settings.field.demographics.education_level.options.no-high-school': {
+ id: 'account.settings.field.demographics.education_level.options.no-high-school',
+ defaultMessage: 'No High School',
+ description: 'The label for the no high school education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.some-high-school': {
+ id: 'account.settings.field.demographics.education_level.options.some-high-school',
+ defaultMessage: 'Some High School',
+ description: 'The label for the some high school education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.high-school-ged-equivalent': {
+ id: 'account.settings.field.demographics.education_level.options.high-school-ged-equivalent',
+ defaultMessage: 'High School diploma, GED, or equivalent',
+ description: 'The label for the high school diploma, GED, or equivalent education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.some-college': {
+ id: 'account.settings.field.demographics.education_level.options.some-college',
+ defaultMessage: 'Some college, but no degree',
+ description: 'The label for the some college, but no degree education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.some-college': {
+ id: 'account.settings.field.demographics.education_level.options.some-college',
+ defaultMessage: 'Some college, but no degree',
+ description: 'The label for the some college, but no degree education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.associates': {
+ id: 'account.settings.field.demographics.education_level.options.associates',
+ defaultMessage: 'Associates degree',
+ description: 'The label for the Associates degree education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.bachelors': {
+ id: 'account.settings.field.demographics.education_level.options.bachelors',
+ defaultMessage: 'Bachelors degree',
+ description: 'The label for the Bachelors degree education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.masters': {
+ id: 'account.settings.field.demographics.education_level.options.masters',
+ defaultMessage: 'Masters degree',
+ description: 'The label for the Masters degree education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.professional': {
+ id: 'account.settings.field.demographics.education_level.options.professional',
+ defaultMessage: 'Professional degree',
+ description: 'The label for the Professional degree education level option.',
+ },
+ 'account.settings.field.demographics.education_level.options.doctorate': {
+ id: 'account.settings.field.demographics.education_level.options.doctorate',
+ defaultMessage: 'Doctorate degree',
+ description: 'The label for the Doctorate degree education level option.',
+ },
+ 'account.settings.field.demographics.work_status': {
+ id: 'account.settings.field.demographics.work_status',
+ defaultMessage: 'Employment status',
+ description: 'Label for account settings work status field.',
+ },
+ 'account.settings.field.demographics.work_status.empty': {
+ id: 'account.settings.field.demographics.work_status.empty',
+ defaultMessage: 'Add employment status',
+ description: 'Placeholder for empty account settings work status field.',
+ },
+ 'account.settings.field.demographics.work_status.options.empty': {
+ id: 'account.settings.field.demographics.work_status.options.empty',
+ defaultMessage: 'Select employment status',
+ description: 'Placeholder for the work status options dropdown.',
+ },
+ 'account.settings.field.demographics.work_status.options.full-time': {
+ id: 'account.settings.field.demographics.work_status.options.full-time',
+ defaultMessage: 'Employed, working full-time',
+ description: 'The label for the employed, working full-time work status option.',
+ },
+ 'account.settings.field.demographics.work_status.options.part-time': {
+ id: 'account.settings.field.demographics.work_status.options.part-time',
+ defaultMessage: 'Employed, working part-time',
+ description: 'The label for the employed, working part-time work status option.',
+ },
+ 'account.settings.field.demographics.work_status.options.not-employed-looking': {
+ id: 'account.settings.field.demographics.work_status.options.not-employed-looking',
+ defaultMessage: 'Not employed, looking for work',
+ description: 'The label for the not employed, looking for work work status option.',
+ },
+ 'account.settings.field.demographics.work_status.options.not-employed-not-looking': {
+ id: 'account.settings.field.demographics.work_status.options.not-employed-not-looking',
+ defaultMessage: 'Not employed, not looking for work',
+ description: 'The label for the not employed, not looking for work work status option.',
+ },
+ 'account.settings.field.demographics.work_status.options.unable': {
+ id: 'account.settings.field.demographics.work_status.options.unable',
+ defaultMessage: 'Unable to work',
+ description: 'The label for the unable to work work status option.',
+ },
+ 'account.settings.field.demographics.work_status.options.retired': {
+ id: 'account.settings.field.demographics.work_status.options.retired',
+ defaultMessage: 'Retired',
+ description: 'The label for the retired work status option.',
+ },
+ 'account.settings.field.demographics.work_status.options.other': {
+ id: 'account.settings.field.demographics.work_status.options.other',
+ defaultMessage: 'Other',
+ description: 'The label for the other work status option.',
+ },
+ 'account.settings.field.demographics.work_status_description': {
+ id: 'account.settings.field.demographics.work_status_description',
+ defaultMessage: 'Employment status description',
+ description: 'Label for account settings work status description field.',
+ },
+ 'account.settings.field.demographics.work_status_description.empty': {
+ id: 'account.settings.field.demographics.work_status_description.empty',
+ defaultMessage: 'Enter description',
+ description: 'Placeholder for empty account settings work status description field.',
+ },
+ 'account.settings.field.demographics.current_work_sector': {
+ id: 'account.settings.field.demographics.current_work_sector',
+ defaultMessage: 'Current work industry',
+ description: 'Label for account settings current work sector field.',
+ },
+ 'account.settings.field.demographics.current_work_sector.empty': {
+ id: 'account.settings.field.demographics.current_work_sector.empty',
+ defaultMessage: 'Add work industry',
+ description: 'Placeholder for empty account settings current work sector field.',
+ },
+ 'account.settings.field.demographics.future_work_sector': {
+ id: 'account.settings.field.demographics.future_work_sector',
+ defaultMessage: 'Future work industry',
+ description: 'Label for account settings future work sector field.',
+ },
+ 'account.settings.field.demographics.future_work_sector.empty': {
+ id: 'account.settings.field.demographics.future_work_sector.empty',
+ defaultMessage: 'Add work industry',
+ description: 'Placeholder for empty account settings future work sector field.',
+ },
+ 'account.settings.field.demographics.work_sector.options.empty': {
+ id: 'account.settings.field.demographics.work_sector.options.empty',
+ defaultMessage: 'Select work industry',
+ description: 'Placeholder for the work sector options dropdown.',
+ },
+ 'account.settings.field.demographics.work_sector.options.accommodation-food': {
+ id: 'account.settings.field.demographics.work_sector.options.accommodation-food',
+ defaultMessage: 'Accommodation and Food Services',
+ description: 'The label for the Accommodation and Food Services work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.administrative-support-waste-remediation': {
+ id: 'account.settings.field.demographics.work_sector.options.administrative-support-waste-remediation',
+ defaultMessage: 'Administrative and Support and Waste Management and Remediation Services',
+ description: 'The label for the Administrative and Support and Waste Management and Remediation Services work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.agriculture-forestry-fishing-hunting': {
+ id: 'account.settings.field.demographics.work_sector.options.agriculture-forestry-fishing-hunting',
+ defaultMessage: 'Agriculture, Forestry, Fishing and Hunting',
+ description: 'The label for the Agriculture, Forestry, Fishing and Hunting work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.arts-entertainment-recreation': {
+ id: 'account.settings.field.demographics.work_sector.options.arts-entertainment-recreation',
+ defaultMessage: 'Arts, Entertainment, and Recreation',
+ description: 'The label for the Arts, Entertainment, and Recreation work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.construction': {
+ id: 'account.settings.field.demographics.work_sector.options.construction',
+ defaultMessage: 'Construction',
+ description: 'The label for the Construction work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.educational': {
+ id: 'account.settings.field.demographics.work_sector.options.educational',
+ defaultMessage: 'Education Services',
+ description: 'The label for the Education Services work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.finance-insurance': {
+ id: 'account.settings.field.demographics.work_sector.options.finance-insurance',
+ defaultMessage: 'Finance and Insurance',
+ description: 'The label for the Finance and Insurance work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.healthcare-social': {
+ id: 'account.settings.field.demographics.work_sector.options.healthcare-social',
+ defaultMessage: 'Health Care and Social Assistance',
+ description: 'The label for the Health Care and Social Assistance work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.information': {
+ id: 'account.settings.field.demographics.work_sector.options.information',
+ defaultMessage: 'Information',
+ description: 'The label for the Information work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.management': {
+ id: 'account.settings.field.demographics.work_sector.options.management',
+ defaultMessage: 'Management of Companies and Enterprises',
+ description: 'The label for the Management of Companies and Enterprises work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.manufacturing': {
+ id: 'account.settings.field.demographics.work_sector.options.manufacturing',
+ defaultMessage: 'Manufacturing',
+ description: 'The label for the Manufacturing work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.mining-quarry-oil-gas': {
+ id: 'account.settings.field.demographics.work_sector.options.mining-quarry-oil-gas',
+ defaultMessage: 'Mining, Quarrying, and Oil and Gas Extraction',
+ description: 'The label for the Mining, Quarrying, and Oil and Gas Extraction work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.professional-scientific-technical': {
+ id: 'account.settings.field.demographics.work_sector.options.professional-scientific-technical',
+ defaultMessage: 'Professional, Scientific, and Technical Services',
+ description: 'The label for the Professional, Scientific, and Technical Services work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.public-admin': {
+ id: 'account.settings.field.demographics.work_sector.options.public-admin',
+ defaultMessage: 'Public Administration',
+ description: 'The label for the Public Administration work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.real-estate': {
+ id: 'account.settings.field.demographics.work_sector.options.real-estate',
+ defaultMessage: 'Real Estate and Rental and Leasing',
+ description: 'The label for the Real Estate and Rental and Leasing work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.retail': {
+ id: 'account.settings.field.demographics.work_sector.options.retail',
+ defaultMessage: 'Retail Trade',
+ description: 'The label for the Retail Trade work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.transport-warehousing': {
+ id: 'account.settings.field.demographics.work_sector.options.transport-warehousing',
+ defaultMessage: 'Transportation and Warehousing',
+ description: 'The label for the Transportation and Warehousing work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.utilities': {
+ id: 'account.settings.field.demographics.work_sector.options.utilities',
+ defaultMessage: 'Utilities',
+ description: 'The label for the Utilities work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.trade': {
+ id: 'account.settings.field.demographics.work_sector.options.trade',
+ defaultMessage: 'Wholesale Trade',
+ description: 'The label for the Wholesale Trade work sector option.',
+ },
+ 'account.settings.field.demographics.work_sector.options.other': {
+ id: 'account.settings.field.demographics.work_sector.options.other',
+ defaultMessage: 'Other',
+ description: 'The label for the Other work sector option.',
+ },
+ 'account.settings.field.demographics.options.declined': {
+ id: 'account.settings.field.demographics.options.declined',
+ defaultMessage: 'Prefer not to respond',
+ description: 'The label for the declined option.',
+ },
+});
+
+export default messages;
diff --git a/src/account-settings/demographics/data/service.js b/src/account-settings/demographics/data/service.js
new file mode 100644
index 0000000..69ed3e0
--- /dev/null
+++ b/src/account-settings/demographics/data/service.js
@@ -0,0 +1,63 @@
+import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
+import { getConfig } from '@edx/frontend-platform';
+import { convertData, TO, FROM } from './utils';
+
+/**
+ * post all of the data related to demographics.
+ * @param {Number} userId users are identified in the api by LMS id
+ * @param {Object} commitValues { demographics }
+ */
+export async function postDemographics(userId) {
+ const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/`;
+ const commitValues = { user: userId };
+ let data = {};
+
+ ({ data } = await getAuthenticatedHttpClient()
+ .post(requestUrl, commitValues)
+ .catch((error) => {
+ const apiError = Object.create(error);
+ throw apiError;
+ }));
+
+ return convertData(data, FROM);
+}
+
+/**
+ * get all data related to the demographics.
+ * @param {Number} userId users are identified in the api by LMS id
+ */
+export async function getDemographics(userId) {
+ const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/${userId}/`;
+ let data = {};
+
+ try {
+ ({ data } = await getAuthenticatedHttpClient()
+ .get(requestUrl));
+
+ data = convertData(data, FROM);
+ } catch (error) {
+ data = await postDemographics(userId);
+ }
+
+ return data;
+}
+
+/**
+ * patch all of the data related to demographics.
+ * @param {Number} userId users are identified in the api by LMS id
+ * @param {Object} commitValues { demographics }
+ */
+export async function patchDemographics(userId, commitValues) {
+ const requestUrl = `${getConfig().DEMOGRAPHICS_BASE_URL}/demographics/api/v1/demographics/${userId}/`;
+ const convertedCommitValues = convertData(commitValues, TO);
+ let data = {};
+
+ ({ data } = await getAuthenticatedHttpClient()
+ .patch(requestUrl, convertedCommitValues)
+ .catch((error) => {
+ const apiError = Object.create(error);
+ throw apiError;
+ }));
+
+ return convertData(data, FROM);
+}
diff --git a/src/account-settings/demographics/data/utils.js b/src/account-settings/demographics/data/utils.js
new file mode 100644
index 0000000..6208352
--- /dev/null
+++ b/src/account-settings/demographics/data/utils.js
@@ -0,0 +1,50 @@
+export const TO = 'to';
+export const FROM = 'from';
+
+// Frontend wants (example):
+// demographics_user_ethnicity: ["asian", "white", "other"]
+//
+// Demographics wants (example):
+// user_ethnicity: [
+// { ethnicity: "asian" },
+// { ethnicity: "white" },
+// { ethnicity: "other" }
+// ]
+function convertEthnicity(ethnicityData, direction) {
+ if (direction === FROM) {
+ return ethnicityData.map(e => e.ethnicity);
+ }
+
+ if (direction === TO) {
+ return ethnicityData.map(e => ({ ethnicity: e }));
+ }
+
+ return ethnicityData;
+}
+
+// Handles conversion of data to/from Demographics IDA to/from format needed for
+// frontend
+// * handles ethnicity field
+// * adds/removes 'demographics' to/from key
+// * replace `null` with empty string or empty string with null
+export function convertData(dataObject, direction) {
+ const converted = {};
+
+ Object.entries(dataObject).forEach(([key, value]) => {
+ let newValue = value;
+
+ if (key.includes('ethnicity')) {
+ newValue = convertEthnicity(value, direction);
+ }
+
+ if (direction === TO) {
+ converted[key.replace('demographics_', '')] = newValue || null;
+ }
+
+ if (direction === FROM) {
+ converted[`demographics_${key}`] = newValue || '';
+ }
+ });
+
+ return converted;
+}
diff --git a/src/index.jsx b/src/index.jsx
index 42dda62..3a5dde9 100755
--- a/src/index.jsx
+++ b/src/index.jsx
@@ -64,6 +64,8 @@ initialize({
mergeConfig({
SUPPORT_URL: process.env.SUPPORT_URL,
COACHING_ENABLED: (process.env.COACHING_ENABLED || false),
+ ENABLE_DEMOGRAPHICS_COLLECTION: (process.env.ENABLE_DEMOGRAPHICS_COLLECTION || false),
+ DEMOGRAPHICS_BASE_URL: process.env.DEMOGRAPHICS_BASE_URL,
}, 'App loadConfig override handler');
},
},
diff --git a/src/index.scss b/src/index.scss
index 165d90a..1ec3387 100755
--- a/src/index.scss
+++ b/src/index.scss
@@ -51,3 +51,13 @@ $fa-font-path: "~font-awesome/fonts";
font-size: 0.75rem;
}
}
+
+.checkboxOption {
+ input:focus {
+ outline: -webkit-focus-ring-color auto 5px;
+ }
+
+ label {
+ font-weight: normal;
+ }
+}
{this.props.intl.formatMessage(messages['account.settings.section.social.media'])} diff --git a/src/account-settings/AccountSettingsPage.messages.jsx b/src/account-settings/AccountSettingsPage.messages.jsx index 20e40ba..6c5535d 100644 --- a/src/account-settings/AccountSettingsPage.messages.jsx +++ b/src/account-settings/AccountSettingsPage.messages.jsx @@ -46,6 +46,11 @@ const messages = defineMessages({ defaultMessage: 'Profile Information', description: 'The profile information section heading.', }, + 'account.settings.section.demographics.information': { + id: 'account.settings.section.demographics.information', + defaultMessage: 'Optional Information', + description: 'The optional information section heading.', + }, 'account.settings.section.site.preferences': { id: 'account.settings.section.site.preferences', defaultMessage: 'Site Preferences', @@ -287,6 +292,7 @@ const messages = defineMessages({ 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', diff --git a/src/account-settings/EditableField.jsx b/src/account-settings/EditableField.jsx index cdb57c6..a14d873 100644 --- a/src/account-settings/EditableField.jsx +++ b/src/account-settings/EditableField.jsx @@ -23,6 +23,7 @@ function EditableField(props) { emptyLabel, type, value, + userSuppliedValue, options, saveState, error, @@ -66,15 +67,22 @@ function EditableField(props) { const renderValue = (rawValue) => { if (!rawValue) return renderEmptyLabel(); + let value = 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; + if (selectedOption) { + value = selectedOption.label; + }; } - return rawValue; + if (userSuppliedValue) { + value += `: ${userSuppliedValue}`; + } + + return value; }; const renderConfirmationMessage = () => { @@ -106,6 +114,7 @@ function EditableField(props) { options={options} {...others} /> + <>{others.children}>
+ {this.props.intl.formatMessage(messages['account.settings.section.demographics.information'])} +
+ +