Add phone number field and coaching consent toggle
MB-196 Adds phone number and coaching consent toggle to the profile. Currently, toggled off in production, until we are ready for MB learners to start recieving coaching.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 &&
|
||||
<>
|
||||
<EditableField
|
||||
name="phone_number"
|
||||
type="text"
|
||||
value={this.props.formValues.phone_number}
|
||||
label={this.props.intl.formatMessage(messages['account.settings.field.phone_number'])}
|
||||
emptyLabel={this.props.intl.formatMessage(messages['account.settings.field.phone_number.empty'])}
|
||||
{...editableFieldProps}
|
||||
/>
|
||||
<ValidationFormGroup
|
||||
for="coachingConsent"
|
||||
helpText={this.props.intl.formatMessage(messages['account.settings.field.coaching_consent.tooltip'])}
|
||||
invalid={
|
||||
!this.props.formValues.phone_number && this.props.formValues.coaching_consent
|
||||
}
|
||||
invalidMessage={this.props.intl.formatMessage(messages['account.settings.field.coaching_consent.error'])}
|
||||
className="custom-control custom-switch"
|
||||
>
|
||||
<Input
|
||||
name="coaching_consent"
|
||||
className="custom-control-input"
|
||||
type="checkbox"
|
||||
id="coachingConsent"
|
||||
checked={this.props.formValues.coaching_consent}
|
||||
value={this.props.formValues.coaching_consent}
|
||||
onChange={(e) => {
|
||||
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);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<label className="custom-control-label" htmlFor="coachingConsent">{this.props.intl.formatMessage(messages['account.settings.field.coaching_consent'])}</label>
|
||||
</ValidationFormGroup>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="account-section" id="social-media">
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user