diff --git a/.env.development b/.env.development index 5bbfc73..e144c5f 100644 --- a/.env.development +++ b/.env.development @@ -16,3 +16,5 @@ SEGMENT_KEY=null 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 diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx index ab5b48f..c3cadcb 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 } from '@edx/paragon'; +import { Hyperlink, Input, ValidationFormGroup } from '@edx/paragon'; import messages from './AccountSettingsPage.messages'; import { fetchSettings, saveSettings, updateDraft } from './data/actions'; @@ -327,6 +327,45 @@ 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); + } + }} + /> + + + + }
@@ -474,6 +513,8 @@ AccountSettingsPage.propTypes = { level_of_education: PropTypes.string, gender: PropTypes.string, language_proficiencies: PropTypes.string, + phone_number: PropTypes.string, + coaching_consent: PropTypes.oneOfType([PropTypes.string, PropTypes.bool]), social_link_linkedin: PropTypes.string, social_link_facebook: PropTypes.string, social_link_twitter: PropTypes.string, diff --git a/src/account-settings/AccountSettingsPage.messages.jsx b/src/account-settings/AccountSettingsPage.messages.jsx index 03c4692..42744d7 100644 --- a/src/account-settings/AccountSettingsPage.messages.jsx +++ b/src/account-settings/AccountSettingsPage.messages.jsx @@ -257,6 +257,31 @@ 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/_style.scss b/src/account-settings/_style.scss index 1b3013d..a869a9e 100644 --- a/src/account-settings/_style.scss +++ b/src/account-settings/_style.scss @@ -35,4 +35,13 @@ margin-bottom: map-get($spacers, 5); padding-top: 1rem; } + + .custom-switch { + padding: 0; + max-width: 500px; + .custom-control-label { + left: 2.25rem; + line-height: 1.6rem; + } + } } diff --git a/src/account-settings/data/sagas.js b/src/account-settings/data/sagas.js index fe9c8b6..eb94059 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 } from 'redux-saga/effects'; +import { call, put, delay, takeEvery, all, debounce } from 'redux-saga/effects'; import { publish } from '@edx/frontend-platform'; import { getLocale, handleRtl, LOCALE_CHANGED } from '@edx/frontend-platform/i18n'; @@ -37,7 +37,7 @@ import { getSettings, patchSettings, getTimeZones } from './service'; export function* handleFetchSettings() { try { yield put(fetchSettingsBegin()); - const { username, roles: userRoles } = getAuthenticatedUser(); + const { username, userId, roles: userRoles } = getAuthenticatedUser(); const { thirdPartyAuthProviders, profileDataManager, timeZones, ...values @@ -45,6 +45,7 @@ export function* handleFetchSettings() { getSettings, username, userRoles, + userId, ); if (values.country) yield put(fetchTimeZones(values.country)); @@ -65,7 +66,7 @@ export function* handleSaveSettings(action) { try { yield put(saveSettingsBegin()); - const { username } = getAuthenticatedUser(); + const { username, userId } = getAuthenticatedUser(); const { commitValues, formId } = action.payload; const commitData = { [formId]: commitValues }; let savedValues = null; @@ -83,7 +84,7 @@ export function* handleSaveSettings(action) { handleRtl(); savedValues = commitData; } else { - savedValues = yield call(patchSettings, username, commitData); + savedValues = yield call(patchSettings, username, commitData, userId); } yield put(saveSettingsSuccess(savedValues, commitData)); if (savedValues.country) yield put(fetchTimeZones(savedValues.country)); @@ -107,7 +108,7 @@ export function* handleFetchTimeZones(action) { export default function* saga() { yield takeEvery(FETCH_SETTINGS.BASE, handleFetchSettings); - yield takeEvery(SAVE_SETTINGS.BASE, handleSaveSettings); + yield debounce(500, 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 4345d60..9d83136 100644 --- a/src/account-settings/data/service.js +++ b/src/account-settings/data/service.js @@ -150,16 +150,45 @@ export async function getProfileDataManager(username, userRoles) { } /** - * A single function to GET everything considered a setting. - * Currently encapsulates Account, Preferences, and ThirdPartyAuth + * 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 getSettings(username, userRoles) { +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 + */ +export async function getSettings(username, userRoles, userId) { const results = await Promise.all([ getAccount(username), getPreferences(username), getThirdPartyAuthProviders(), getProfileDataManager(username, userRoles), getTimeZones(), + getCoachingPreferences(userId), ]); return { @@ -168,20 +197,23 @@ export async function getSettings(username, userRoles) { thirdPartyAuthProviders: results[2], profileDataManager: results[3], timeZones: results[4], + coaching_consent: results[5], }; } /** * A single function to PATCH everything considered a setting. - * Currently encapsulates Account, Preferences, and ThirdPartyAuth + * Currently encapsulates Account, Preferences, coaching and ThirdPartyAuth */ -export async function patchSettings(username, commitValues) { +export async function patchSettings(username, commitValues, userId) { // Note: time_zone exists in the return value from user/v1/accounts // 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 accountCommitValues = omit(commitValues, preferenceKeys); const preferenceCommitValues = pick(commitValues, preferenceKeys); + const coachingCommitValues = pick(commitValues, coachingKeys); const patchRequests = []; if (!isEmpty(accountCommitValues)) { @@ -190,6 +222,9 @@ export async function patchSettings(username, commitValues) { if (!isEmpty(preferenceCommitValues)) { patchRequests.push(patchPreferences(username, preferenceCommitValues)); } + if (!isEmpty(coachingCommitValues)) { + patchRequests.push(patchCoachingPreferences(userId, coachingCommitValues)); + } const results = await Promise.all(patchRequests); // Assigns in order of requests. Preference keys