Merge pull request #466 from edx/mroytman/MST-800-verified-name-field

[MST-800] Add Verified Name field and success alert to the Account Settings page
This commit is contained in:
Michael Roytman
2021-07-30 11:02:57 -04:00
committed by GitHub
6 changed files with 160 additions and 13 deletions

View File

@@ -13,7 +13,8 @@ import {
getCountryList,
getLanguageList,
} from '@edx/frontend-platform/i18n';
import { Hyperlink } from '@edx/paragon';
import { Hyperlink, Icon } from '@edx/paragon';
import { CheckCircle } from '@edx/paragon/icons';
import messages from './AccountSettingsPage.messages';
import { fetchSettings, saveSettings, updateDraft } from './data/actions';
@@ -27,6 +28,7 @@ import ResetPassword from './reset-password';
import ThirdPartyAuth from './third-party-auth';
import BetaLanguageBanner from './BetaLanguageBanner';
import EmailField from './EmailField';
import OneTimeDismissibleAlert from './OneTimeDismissibleAlert';
import {
YEAR_OF_BIRTH_OPTIONS,
EDUCATION_LEVELS,
@@ -206,6 +208,22 @@ class AccountSettingsPage extends React.Component {
);
}
renderVerifiedNameSuccessMessage() {
if (this.props.formValues.verified_name_enabled && this.props.formValues.is_verified) {
return (
<OneTimeDismissibleAlert
id="dismissedVerifiedNameSuccessMessage"
variant="success"
icon={CheckCircle}
header={this.props.intl.formatMessage(messages['account.settings.field.name.verified.sucess.message.header'])}
body={this.props.intl.formatMessage(messages['account.settings.field.name.verified.sucess.message'])}
/>
);
}
return null;
}
renderEmptyStaticFieldMessage() {
if (this.isManagedProfile()) {
return this.props.intl.formatMessage(messages['account.settings.static.field.empty'], {
@@ -261,6 +279,8 @@ class AccountSettingsPage extends React.Component {
// Show State field only if the country is US (could include Canada later)
const showState = this.props.formValues.country === COUNTRY_WITH_STATES;
const showVerifiedName = this.props.formValues.verified_name_enabled && this.props.formValues.verified_name;
const timeZoneOptions = this.getLocalizedTimeZoneOptions(
this.props.timeZoneOptions,
this.props.countryTimeZoneOptions,
@@ -272,6 +292,8 @@ class AccountSettingsPage extends React.Component {
return (
<>
<div className="account-section" id="basic-information" ref={this.navLinkRefs['#basic-information']}>
{this.renderVerifiedNameSuccessMessage()}
<h2 className="section-heading">
{this.props.intl.formatMessage(messages['account.settings.section.account.information'])}
</h2>
@@ -304,6 +326,30 @@ class AccountSettingsPage extends React.Component {
isEditable={this.isEditable('name')}
{...editableFieldProps}
/>
{showVerifiedName
&& (
<EditableField
name="verifiedName"
type="text"
value={this.props.formValues.verified_name}
label={
(
<div className="d-flex">
{this.props.intl.formatMessage(messages['account.settings.field.name.verified'])}
{this.props.formValues.is_verified && <Icon src={CheckCircle} className="ml-1" style={{ height: '18px', width: '18px', color: 'green' }} />}
</div>
)
}
helpText={
this.props.formValues.is_verified
? this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.verified'])
: this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.pending'])
}
isEditable={this.isEditable('verified_name')}
{...editableFieldProps}
/>
)}
<EmailField
name="email"
label={this.props.intl.formatMessage(messages['account.settings.field.email'])}
@@ -578,6 +624,9 @@ AccountSettingsPage.propTypes = {
}),
state: PropTypes.string,
shouldDisplayDemographicsSection: PropTypes.bool,
verified_name: PropTypes.string,
is_verified: PropTypes.bool,
verified_name_enabled: PropTypes.bool,
}).isRequired,
siteLanguage: PropTypes.shape({
previousValue: PropTypes.string,

View File

@@ -91,6 +91,31 @@ const messages = defineMessages({
defaultMessage: 'The name that is used for ID verification and that appears on your certificates.',
description: 'Help text for the account settings name field.',
},
'account.settings.field.name.verified': {
id: 'account.settings.field.name.verified',
defaultMessage: 'Verified name',
description: 'Label for account settings verified name field.',
},
'account.settings.field.name.verified.help.text.verified': {
id: 'account.settings.field.name.verified.help.text.verified',
defaultMessage: 'This name has been verified by government ID.',
description: 'Help text for the account settings verified name field when the name is verified.',
},
'account.settings.field.name.verified.help.text.pending': {
id: 'account.settings.field.name.verified.help.text.pending',
defaultMessage: 'This name is pending verification.',
description: 'Help text for the account settings verified name field when the name is pending verification.',
},
'account.settings.field.name.verified.sucess.message': {
id: 'account.settings.field.name.verified.sucess.message',
defaultMessage: 'Your identity verification request has successfully completed. You now have the option of selecting which name you prefer to appear on your certificates and public-records.',
description: 'The body of the success alert indicating that a user\'s name has been verified',
},
'account.settings.field.name.verified.sucess.message.header': {
id: 'account.settings.field.name.verified.sucess.message.header',
defaultMessage: 'Your name change request is complete!',
description: 'The header of the success alert indicating that a user\'s name has been verified',
},
'account.settings.field.email': {
id: 'account.settings.field.email',
defaultMessage: 'Email address (Sign in)',

View File

@@ -172,7 +172,7 @@ function EditableField(props) {
EditableField.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
label: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.node]),
emptyLabel: PropTypes.node,
type: PropTypes.string.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

View File

@@ -0,0 +1,43 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { Alert } from '@edx/paragon';
export default function OneTimeDismissibleAlert(props) {
const [dismissed, setDismissed] = useState(localStorage.getItem(props.id) !== 'true');
const onClose = () => {
localStorage.setItem(props.id, 'true');
setDismissed(false);
};
return (
<Alert
variant={props.variant}
dismissible
icon={props.icon}
onClose={onClose}
show={dismissed}
>
<Alert.Heading>{props.header}</Alert.Heading>
<p>
{props.body}
</p>
</Alert>
);
}
OneTimeDismissibleAlert.propTypes = {
id: PropTypes.string.isRequired,
variant: PropTypes.string,
icon: PropTypes.func,
header: PropTypes.string,
body: PropTypes.string,
};
OneTimeDismissibleAlert.defaultProps = {
variant: 'success',
icon: undefined,
header: undefined,
body: undefined,
};

View File

@@ -38,7 +38,11 @@ import {
import { saga as thirdPartyAuthSaga } from '../third-party-auth';
// Services
import { getSettings, patchSettings, getTimeZones } from './service';
import {
getSettings,
patchSettings,
getTimeZones,
} from './service';
export function* handleFetchSettings() {
try {

View File

@@ -176,12 +176,36 @@ export async function shouldDisplayDemographicsQuestions() {
return false;
}
export async function getVerifiedName(username) {
let data;
const client = getAuthenticatedHttpClient();
try {
({ data } = await client
.get(`${getConfig().LMS_BASE_URL}/api/edx_name_affirmation/v1/verified_name?username=${username}`));
} catch (error) {
return {};
}
return data;
}
/**
* A single function to GET everything considered a setting.
* Currently encapsulates Account, Preferences, Coaching, ThirdPartyAuth, and Demographics
*/
export async function getSettings(username, userRoles, userId) {
const results = await Promise.all([
const [
account,
preferences,
thirdPartyAuthProviders,
profileDataManager,
timeZones,
coaching,
shouldDisplayDemographicsQuestionsResponse,
demographics,
demographicsOptions,
verifiedName,
] = await Promise.all([
getAccount(username),
getPreferences(username),
getThirdPartyAuthProviders(),
@@ -191,18 +215,20 @@ export async function getSettings(username, userRoles, userId) {
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && shouldDisplayDemographicsQuestions(),
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographics(userId),
getConfig().ENABLE_DEMOGRAPHICS_COLLECTION && getDemographicsOptions(),
getVerifiedName(username),
]);
return {
...results[0],
...results[1],
thirdPartyAuthProviders: results[2],
profileDataManager: results[3],
timeZones: results[4],
coaching: results[5],
shouldDisplayDemographicsSection: results[6],
...results[7], // demographics
demographicsOptions: results[8],
...account,
...preferences,
thirdPartyAuthProviders,
profileDataManager,
timeZones,
coaching,
shouldDisplayDemographicsQuestions: shouldDisplayDemographicsQuestionsResponse,
...demographics,
demographicsOptions,
...verifiedName,
};
}