From c40222973c3fb94211aa5a1119e907d2448a2dd9 Mon Sep 17 00:00:00 2001 From: Thomas Tracy Date: Fri, 13 Mar 2020 14:46:25 -0400 Subject: [PATCH] Made requested changes and additional fixes --- .env.development | 2 +- package-lock.json | 5 ++ package.json | 1 + src/account-settings/AccountSettingsPage.jsx | 53 ++++--------- .../AccountSettingsPage.messages.jsx | 25 ------ .../coaching/CoachingToggle.jsx | 76 +++++++++++++++++++ .../coaching/CoachingToggle.messages.js | 31 ++++++++ src/account-settings/coaching/data/service.js | 36 +++++++++ src/account-settings/data/sagas.js | 4 +- src/account-settings/data/service.js | 33 +------- src/index.jsx | 1 + 11 files changed, 170 insertions(+), 97 deletions(-) create mode 100644 src/account-settings/coaching/CoachingToggle.jsx create mode 100644 src/account-settings/coaching/CoachingToggle.messages.js create mode 100644 src/account-settings/coaching/data/service.js diff --git a/.env.development b/.env.development index e144c5f..862832d 100644 --- a/.env.development +++ b/.env.development @@ -17,4 +17,4 @@ SITE_NAME='edX' SUPPORT_URL='http://localhost:18000/support' USER_INFO_COOKIE_NAME='edx-user-info' # Temporary, Remove this once we are ready to release the feature. -TEMP_COACHING_FEATURE_FLAG=true +COACHING_ENABLED=true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 6b0a274..894f14b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11519,6 +11519,11 @@ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" + }, "lodash.defaultsdeep": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", diff --git a/package.json b/package.json index 8052921..4089a3c 100755 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "formdata-polyfill": "3.0.19", "history": "4.10.1", "lodash.camelcase": "4.3.0", + "lodash.debounce": "^4.0.8", "lodash.findindex": "4.6.0", "lodash.get": "4.4.2", "lodash.isempty": "4.4.0", diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx index c3cadcb..3677139 100644 --- a/src/account-settings/AccountSettingsPage.jsx +++ b/src/account-settings/AccountSettingsPage.jsx @@ -13,7 +13,7 @@ import { getCountryList, getLanguageList, } from '@edx/frontend-platform/i18n'; -import { Hyperlink, Input, ValidationFormGroup } from '@edx/paragon'; +import { Hyperlink } from '@edx/paragon'; import messages from './AccountSettingsPage.messages'; import { fetchSettings, saveSettings, updateDraft } from './data/actions'; @@ -33,6 +33,7 @@ import { GENDER_OPTIONS, } from './data/constants'; import { fetchSiteLanguages } from './site-language'; +import CoachingToggle from './coaching/CoachingToggle'; class AccountSettingsPage extends React.Component { constructor(props, context) { @@ -327,44 +328,13 @@ class AccountSettingsPage extends React.Component { emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.language.proficiencies.empty'])} {...editableFieldProps} /> - {process.env.TEMP_COACHING_FEATURE_FLAG && - <> - - - { - this.handleEditableFieldChange(e.target.name, e.target.checked); - if (this.props.formValues.phone_number) { - this.handleSubmit(e.target.name, e.target.checked); - } else { - this.handleSubmit(e.target.name, false); - } - }} - /> - - - + {getConfig().COACHING_ENABLED && + this.props.formValues.coaching.eligible_for_coaching && + } @@ -519,6 +489,11 @@ AccountSettingsPage.propTypes = { social_link_facebook: PropTypes.string, social_link_twitter: PropTypes.string, time_zone: PropTypes.string, + coaching: PropTypes.objectOf(PropTypes.shape({ + coaching_consent: PropTypes.string.isRequired, + user: PropTypes.number.isRequired, + eligible_for_coaching: PropTypes.bool.isRequired, + })).isRequired, }).isRequired, siteLanguage: PropTypes.shape({ previousValue: PropTypes.string, diff --git a/src/account-settings/AccountSettingsPage.messages.jsx b/src/account-settings/AccountSettingsPage.messages.jsx index 42744d7..03c4692 100644 --- a/src/account-settings/AccountSettingsPage.messages.jsx +++ b/src/account-settings/AccountSettingsPage.messages.jsx @@ -257,31 +257,6 @@ const messages = defineMessages({ defaultMessage: 'Other', description: 'The label for catch-all gender option.', }, - 'account.settings.field.phone_number': { - id: 'account.settings.field.phone_number', - defaultMessage: 'Phone Number', - description: 'The label for a phone numbers setting in the user profile', - }, - 'account.settings.field.phone_number.empty': { - id: 'account.settings.field.phone_number.empty', - defaultMessage: 'Add a phone number', - description: 'placeholder for a profiles empty phone number field', - }, - 'account.settings.field.coaching_consent': { - id: 'account.settings.field.coaching_consent', - defaultMessage: 'Coaching consent', - description: 'The label for the coaching consent setting in the user profile', - }, - 'account.settings.field.coaching_consent.tooltip': { - id: 'account.settings.field.coaching_consent.tooltip', - defaultMessage: 'MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available in English and Spanish languages. Standard messaging rates apply. Text ‘STOP’ at anytime to opt-out of messages.', - description: 'A tooltip explaining what coaching is and who it is for', - }, - 'account.settings.field.coaching_consent.error': { - id: 'account.settings.field.coaching_consent.error', - defaultMessage: 'A phone number is required to sign up for coaching', - description: 'An error message that displays when a user attempts to consent to coaching without first providing a phone number in their profile', - }, 'account.settings.field.language.proficiencies': { id: 'account.settings.field.language.proficiencies', defaultMessage: 'Spoken languages', diff --git a/src/account-settings/coaching/CoachingToggle.jsx b/src/account-settings/coaching/CoachingToggle.jsx new file mode 100644 index 0000000..333f4c4 --- /dev/null +++ b/src/account-settings/coaching/CoachingToggle.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { ValidationFormGroup, Input } from '@edx/paragon'; +import messages from './CoachingToggle.messages'; +import { editableFieldSelector } from '../data/selectors'; +import { saveSettings, updateDraft } from '../data/actions'; +import EditableField from '../EditableField'; + + +const CoatchingToggle = props => ( + <> + + + { + const { name } = e.target; + const value = { + ...props.coaching, + phone_number: props.phone_number, + coaching_consent: e.target.checked, + }; + props.saveSettings(name, value); + }} + /> + + + +); + +CoatchingToggle.defaultProps = { + phone_number: '', + error: '', +}; + +CoatchingToggle.propTypes = { + name: PropTypes.string.isRequired, + error: PropTypes.string, + coaching: PropTypes.objectOf(PropTypes.shape({ + coaching_consent: PropTypes.string.isRequired, + user: PropTypes.number.isRequired, + eligible_for_coaching: PropTypes.bool.isRequired, + })).isRequired, + saveState: PropTypes.func.isRequired, + saveSettings: PropTypes.func.isRequired, + updateDraft: PropTypes.func.isRequired, + intl: intlShape.isRequired, + phone_number: PropTypes.string, +}; + +export default connect(editableFieldSelector, { + saveSettings, + updateDraft, +})(injectIntl(CoatchingToggle)); diff --git a/src/account-settings/coaching/CoachingToggle.messages.js b/src/account-settings/coaching/CoachingToggle.messages.js new file mode 100644 index 0000000..68b9c59 --- /dev/null +++ b/src/account-settings/coaching/CoachingToggle.messages.js @@ -0,0 +1,31 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + 'account.settings.field.phone_number': { + id: 'account.settings.field.phone_number', + defaultMessage: 'Phone Number', + description: 'The label for a phone numbers setting in the user profile', + }, + 'account.settings.field.phone_number.empty': { + id: 'account.settings.field.phone_number.empty', + defaultMessage: 'Add a phone number', + description: 'placeholder for a profiles empty phone number field', + }, + 'account.settings.field.coaching_consent': { + id: 'account.settings.field.coaching_consent', + defaultMessage: 'Coaching consent', + description: 'The label for the coaching consent setting in the user profile', + }, + 'account.settings.field.coaching_consent.tooltip': { + id: 'account.settings.field.coaching_consent.tooltip', + defaultMessage: 'MicroBachelors programs include text message based coaching that helps you pair educational experiences with your career goals through one-on-one advice. Coaching services are included at no additional cost, and are available in English and Spanish languages. Standard messaging rates apply. Text ‘STOP’ at anytime to opt-out of messages.', + description: 'A tooltip explaining what coaching is and who it is for', + }, + 'account.settings.field.coaching_consent.error': { + id: 'account.settings.field.coaching_consent.error', + defaultMessage: 'A valid US phone number is required to opt into coaching', + description: 'An error message that displays when a user attempts to consent to coaching without first providing a phone number in their profile', + }, +}); + +export default messages; diff --git a/src/account-settings/coaching/data/service.js b/src/account-settings/coaching/data/service.js new file mode 100644 index 0000000..b507622 --- /dev/null +++ b/src/account-settings/coaching/data/service.js @@ -0,0 +1,36 @@ +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; +import { getConfig } from '@edx/frontend-platform'; + +/** + * get all settings related to the coaching plugin. Settings used + * by Microbachelors students. + * @param {Number} userId users are identified in the api by LMS id + */ +export async function getCoachingPreferences(userId) { + const { data } = await getAuthenticatedHttpClient() + .get(`${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`); + return data; +} + +/** + * patch all of the settings related to coaching. + * @param {Number} userId users are identified in the api by LMS id + * @param {Object} commitValues { coaching } + */ +export async function patchCoachingPreferences(userId, commitValues) { + const requestUrl = `${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`; + const { coaching } = commitValues; + coaching.user = userId; + + await getAuthenticatedHttpClient() + .patch(requestUrl, coaching) + .catch((error) => { + const apiError = Object.create(error); + apiError.fieldErrors = JSON.parse(error.customAttributes.httpErrorResponseData); + // eslint-disable-next-line prefer-destructuring + apiError.fieldErrors.coaching = apiError.fieldErrors.phone_number[0]; + delete apiError.fieldErrors.phone_number; + throw apiError; + }); + return commitValues; +} diff --git a/src/account-settings/data/sagas.js b/src/account-settings/data/sagas.js index eb94059..3f66773 100644 --- a/src/account-settings/data/sagas.js +++ b/src/account-settings/data/sagas.js @@ -1,4 +1,4 @@ -import { call, put, delay, takeEvery, all, debounce } from 'redux-saga/effects'; +import { call, put, delay, takeEvery, all } from 'redux-saga/effects'; import { publish } from '@edx/frontend-platform'; import { getLocale, handleRtl, LOCALE_CHANGED } from '@edx/frontend-platform/i18n'; @@ -108,7 +108,7 @@ export function* handleFetchTimeZones(action) { export default function* saga() { yield takeEvery(FETCH_SETTINGS.BASE, handleFetchSettings); - yield debounce(500, SAVE_SETTINGS.BASE, handleSaveSettings); + yield takeEvery(SAVE_SETTINGS.BASE, handleSaveSettings); yield takeEvery(FETCH_TIME_ZONES.BASE, handleFetchTimeZones); yield all([ deleteAccountSaga(), diff --git a/src/account-settings/data/service.js b/src/account-settings/data/service.js index 9d83136..07a4871 100644 --- a/src/account-settings/data/service.js +++ b/src/account-settings/data/service.js @@ -6,6 +6,7 @@ import isEmpty from 'lodash.isempty'; import { handleRequestError, unpackFieldErrors } from './utils'; import { getThirdPartyAuthProviders } from '../third-party-auth'; +import { getCoachingPreferences, patchCoachingPreferences } from '../coaching/data/service'; const SOCIAL_PLATFORMS = [ { id: 'twitter', key: 'social_link_twitter' }, @@ -149,34 +150,6 @@ export async function getProfileDataManager(username, userRoles) { return null; } -/** - * get all settings related to the coaching plugin. Settings used - * by Microbachelors students. - * @param {Number} userId users are identified in the api by LMS id - */ -export async function getCoachingPreferences(userId) { - const { data } = await getAuthenticatedHttpClient() - .get(`${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`); - return data.coaching_consent; -} - -/** - * patch all of the settings related to coaching. - * @param {Number} userId users are identified in the api by LMS id - * @param {Object} commitValues { coaching_consent } - */ -export async function patchCoachingPreferences(userId, commitValues) { - const requestUrl = `${getConfig().LMS_BASE_URL}/api/coaching/v1/users/${userId}/`; - const body = { - ...commitValues, - user: userId, - }; - const options = { headers: { 'Content-Type': 'application/json' } }; - getAuthenticatedHttpClient() - .patch(requestUrl, body, options); - - return commitValues; -} /** * A single function to GET everything considered a setting. * Currently encapsulates Account, Preferences, Coaching, and ThirdPartyAuth @@ -197,7 +170,7 @@ export async function getSettings(username, userRoles, userId) { thirdPartyAuthProviders: results[2], profileDataManager: results[3], timeZones: results[4], - coaching_consent: results[5], + coaching: results[5], }; } @@ -210,7 +183,7 @@ export async function patchSettings(username, commitValues, userId) { // but it is always null and won't update. It also exists in // user/v1/preferences where it does update. This is the one we use. const preferenceKeys = ['time_zone']; - const coachingKeys = ['coaching_consent']; + const coachingKeys = ['coaching']; const accountCommitValues = omit(commitValues, preferenceKeys); const preferenceCommitValues = pick(commitValues, preferenceKeys); const coachingCommitValues = pick(commitValues, coachingKeys); diff --git a/src/index.jsx b/src/index.jsx index 87cac02..75a26d2 100755 --- a/src/index.jsx +++ b/src/index.jsx @@ -49,6 +49,7 @@ initialize({ config: () => { mergeConfig({ SUPPORT_URL: process.env.SUPPORT_URL, + COACHING_ENABLED: process.env.COACHING_ENABLED, }, 'App loadConfig override handler'); }, },