diff --git a/src/account-settings/AccountSettingsPage.jsx b/src/account-settings/AccountSettingsPage.jsx index c82286c..579daa4 100644 --- a/src/account-settings/AccountSettingsPage.jsx +++ b/src/account-settings/AccountSettingsPage.jsx @@ -19,7 +19,12 @@ import { import { CheckCircle, Error, WarningFilled } from '@edx/paragon/icons'; import messages from './AccountSettingsPage.messages'; -import { fetchSettings, saveSettings, updateDraft } from './data/actions'; +import { + fetchSettings, + saveMultipleSettings, + saveSettings, + updateDraft, +} from './data/actions'; import { accountSettingsPageSelector } from './data/selectors'; import PageLoading from './PageLoading'; import JumpNav from './JumpNav'; @@ -162,6 +167,23 @@ class AccountSettingsPage extends React.Component { this.props.saveSettings(formId, values); } + handleSubmitName = (formId, values) => { + if (Object.keys(this.props.drafts).includes('useVerifiedNameForCerts')) { + this.props.saveMultipleSettings([ + { + formId, + commitValues: values, + }, + { + formId: 'useVerifiedNameForCerts', + commitValues: this.props.formValues.useVerifiedNameForCerts, + }, + ], formId); + } else { + this.props.saveSettings(formId, values); + } + }; + isEditable(fieldName) { return !this.props.staticFields.includes(fieldName); } @@ -224,6 +246,25 @@ class AccountSettingsPage extends React.Component { ); } + renderFullNameHelpText = (status) => { + if ( + !this.props.formValues.verifiedNameHistory + || !this.props.formValues.verifiedNameHistory.verified_name_enabled + ) { + return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text']); + } + + switch (status) { + case 'submitted': + return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.submitted']); + default: + if (this.props.committedValues.useVerifiedNameForCerts) { + return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.non.certificate']); + } + return this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.certificate']); + } + } + renderVerifiedNameSuccessMessage = () => ( { switch (status) { case 'approved': + if (this.props.committedValues.useVerifiedNameForCerts) { + return (this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.certificate'])); + } return (this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.verified'])); case 'submitted': return (this.props.intl.formatMessage(messages['account.settings.field.name.verified.help.text.submitted'])); @@ -320,15 +364,6 @@ class AccountSettingsPage extends React.Component { } } - renderFullNameHelpText = (status) => { - switch (status) { - case 'submitted': - return (this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text.submitted'])); - default: - return (this.props.intl.formatMessage(messages['account.settings.field.full.name.help.text'])); - } - } - renderEmptyStaticFieldMessage() { if (this.isManagedProfile()) { return this.props.intl.formatMessage(messages['account.settings.static.field.empty'], { @@ -440,7 +475,8 @@ class AccountSettingsPage extends React.Component { isGrayedOut={ verifiedNameEnabled && verifiedName && !this.isEditable('verifiedName') } - {...editableFieldProps} + onChange={this.handleEditableFieldChange} + onSubmit={this.handleSubmitName} /> {verifiedNameEnabled && verifiedName && ( @@ -461,7 +497,8 @@ class AccountSettingsPage extends React.Component { helpText={this.renderVerifiedNameHelpText(verifiedName.status)} isEditable={this.isEditable('verifiedName')} isGrayedOut={!this.isEditable('verifiedName')} - {...(this.isEditable('verifiedName') && editableFieldProps)} + onChange={this.handleEditableFieldChange} + onSubmit={this.handleSubmitName} /> )} @@ -739,6 +776,7 @@ AccountSettingsPage.propTypes = { }), state: PropTypes.string, shouldDisplayDemographicsSection: PropTypes.bool, + useVerifiedNameForCerts: PropTypes.bool.isRequired, verifiedNameHistory: PropTypes.shape({ verified_name_enabled: PropTypes.bool, use_verified_name_for_certs: PropTypes.bool, @@ -758,6 +796,10 @@ AccountSettingsPage.propTypes = { status: PropTypes.string, }), }).isRequired, + committedValues: PropTypes.shape({ + useVerifiedNameForCerts: PropTypes.bool, + }), + drafts: PropTypes.shape({}), siteLanguage: PropTypes.shape({ previousValue: PropTypes.string, draft: PropTypes.string, @@ -781,6 +823,7 @@ AccountSettingsPage.propTypes = { })), fetchSiteLanguages: PropTypes.func.isRequired, updateDraft: PropTypes.func.isRequired, + saveMultipleSettings: PropTypes.func.isRequired, saveSettings: PropTypes.func.isRequired, fetchSettings: PropTypes.func.isRequired, tpaProviders: PropTypes.arrayOf(PropTypes.object), @@ -790,6 +833,10 @@ AccountSettingsPage.defaultProps = { loading: false, loaded: false, loadingError: null, + committedValues: { + useVerifiedNameForCerts: false, + }, + drafts: {}, siteLanguage: null, siteLanguageOptions: [], timeZoneOptions: [], @@ -804,6 +851,7 @@ AccountSettingsPage.defaultProps = { export default connect(accountSettingsPageSelector, { fetchSettings, saveSettings, + saveMultipleSettings, updateDraft, fetchSiteLanguages, })(injectIntl(AccountSettingsPage)); diff --git a/src/account-settings/AccountSettingsPage.messages.jsx b/src/account-settings/AccountSettingsPage.messages.jsx index 2caa2d4..92bf3ea 100644 --- a/src/account-settings/AccountSettingsPage.messages.jsx +++ b/src/account-settings/AccountSettingsPage.messages.jsx @@ -91,6 +91,16 @@ 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.full.name.help.text.non.certificate': { + id: 'account.settings.field.full.name.help.text.non.certificate', + defaultMessage: 'The name that appears on your public profile.', + description: 'Help text for the account settings name field.', + }, + 'account.settings.field.full.name.help.text.certificate': { + id: 'account.settings.field.full.name.help.text.certificate', + defaultMessage: 'This name is selected to appear on your certificates and public-facing records.', + description: 'Help text for the account settings name field.', + }, 'account.settings.field.name.verified': { id: 'account.settings.field.name.verified', defaultMessage: 'Verified name', @@ -101,6 +111,11 @@ const messages = defineMessages({ 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.certificate': { + id: 'account.settings.field.name.verified.help.text.certificate', + defaultMessage: 'This name has been verified by government ID and selected to appear on your certificates and public-facing records.', + description: 'Help text for the account settings verified name field when the name is selected for certificates.', + }, 'account.settings.field.name.verified.help.text.submitted': { id: 'account.settings.field.name.verified.help.text.submitted', defaultMessage: 'Verification has been submitted. This usually takes 48 hours or less. Verified name cannot be changed at this time.', diff --git a/src/account-settings/EditableField.jsx b/src/account-settings/EditableField.jsx index b57a09e..d751319 100644 --- a/src/account-settings/EditableField.jsx +++ b/src/account-settings/EditableField.jsx @@ -16,6 +16,7 @@ import { closeForm, } from './data/actions'; import { editableFieldSelector } from './data/selectors'; +import CertificatePreference from './certificate-preference/CertificatePreference'; function EditableField(props) { const { @@ -103,54 +104,57 @@ function EditableField(props) { expression={isEditing ? 'editing' : 'default'} cases={{ editing: ( -
- - - - <>{others.children} - -

- { - // Swallow clicks if the state is pending. - // We do this instead of disabling the button to prevent - // it from losing focus (disabled elements cannot have focus). - // Disabling it would causes upstream issues in focus management. - // Swallowing the onSubmit event on the form would be better, but - // we would have to add that logic for every field given our - // current structure of the application. - if (saveState === 'pending') { e.preventDefault(); } - }} - disabledStates={[]} - /> - -

-
+ + + <>{others.children} + +

+ { + // Swallow clicks if the state is pending. + // We do this instead of disabling the button to prevent + // it from losing focus (disabled elements cannot have focus). + // Disabling it would causes upstream issues in focus management. + // Swallowing the onSubmit event on the form would be better, but + // we would have to add that logic for every field given our + // current structure of the application. + if (saveState === 'pending') { e.preventDefault(); } + }} + disabledStates={[]} + /> + +

+ + {['name', 'verifiedName'].includes(name) && } + ), default: (
diff --git a/src/account-settings/certificate-preference/CertificatePreference.jsx b/src/account-settings/certificate-preference/CertificatePreference.jsx new file mode 100644 index 0000000..ad4ef94 --- /dev/null +++ b/src/account-settings/certificate-preference/CertificatePreference.jsx @@ -0,0 +1,175 @@ +import React, { useState, useEffect } from 'react'; +import { connect, useDispatch } from 'react-redux'; +import PropTypes from 'prop-types'; + +import { + ActionRow, + Form, + ModalDialog, + StatefulButton, +} from '@edx/paragon'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; + +import { + closeForm, + resetDrafts, + saveSettings, + updateDraft, +} from '../data/actions'; +import { certPreferenceSelector } from '../data/selectors'; + +import commonMessages from '../AccountSettingsPage.messages'; +import messages from './messages'; + +function CertificatePreference({ + intl, + fieldName, + originalFullName, + originalVerifiedName, + saveState, + useVerifiedNameForCerts, + verifiedNameEnabled, +}) { + if (!verifiedNameEnabled || !originalVerifiedName) { + // If the user doesn't have an approved verified name, do not display this component + return null; + } + + const dispatch = useDispatch(); + const [checked, setChecked] = useState(false); + const [modalIsOpen, setModalIsOpen] = useState(false); + const formId = 'useVerifiedNameForCerts'; + + function handleCheckboxChange() { + if (!checked) { + if (fieldName === 'verifiedName') { + dispatch(updateDraft(formId, true)); + } else { + dispatch(updateDraft(formId, false)); + } + } else { + setModalIsOpen(true); + } + } + + function handleCancel() { + setModalIsOpen(false); + dispatch(resetDrafts()); + } + + function handleModalChange(e) { + if (e.target.value === 'fullName') { + dispatch(updateDraft(formId, false)); + } else { + dispatch(updateDraft(formId, true)); + } + } + + function handleSubmit(e) { + e.preventDefault(); + + if (saveState === 'pending') { + return; + } + + dispatch(saveSettings(formId, useVerifiedNameForCerts)); + } + + useEffect(() => { + if (fieldName === 'verifiedName') { + setChecked(useVerifiedNameForCerts); + } else { + setChecked(!useVerifiedNameForCerts); + } + }, [useVerifiedNameForCerts]); + + useEffect(() => { + if (modalIsOpen && saveState === 'complete') { + setModalIsOpen(false); + dispatch(closeForm(fieldName)); + } + }, [modalIsOpen, saveState]); + + return ( + <> + + {intl.formatMessage(messages['account.settings.field.name.checkbox.certificate.select'])} + + + +
+ + + {intl.formatMessage(messages['account.settings.field.name.modal.certificate.title'])} + + + + + + + {intl.formatMessage(messages['account.settings.field.name.modal.certificate.select'])} + + + + {originalFullName}{' '} + ({intl.formatMessage(messages['account.settings.field.name.modal.certificate.option.full'])}) + + + {originalVerifiedName}{' '} + ({intl.formatMessage(messages['account.settings.field.name.modal.certificate.option.verified'])}) + + + + + + + + + {intl.formatMessage(commonMessages['account.settings.editable.field.action.cancel'])} + + + + +
+
+ + ); +} + +CertificatePreference.propTypes = { + intl: intlShape.isRequired, + fieldName: PropTypes.string.isRequired, + originalFullName: PropTypes.string, + originalVerifiedName: PropTypes.string, + saveState: PropTypes.string, + useVerifiedNameForCerts: PropTypes.bool, + verifiedNameEnabled: PropTypes.bool, +}; + +CertificatePreference.defaultProps = { + originalFullName: '', + originalVerifiedName: '', + saveState: null, + useVerifiedNameForCerts: false, + verifiedNameEnabled: false, +}; + +export default connect(certPreferenceSelector)(injectIntl(CertificatePreference)); diff --git a/src/account-settings/certificate-preference/data/service.js b/src/account-settings/certificate-preference/data/service.js new file mode 100644 index 0000000..cca37fa --- /dev/null +++ b/src/account-settings/certificate-preference/data/service.js @@ -0,0 +1,22 @@ +import { getConfig } from '@edx/frontend-platform'; +import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth'; + +import { handleRequestError } from '../../data/utils'; + +// eslint-disable-next-line import/prefer-default-export +export async function postVerifiedNameConfig(username, commitValues) { + const requestConfig = { headers: { Accept: 'application/json' } }; + const requestUrl = `${getConfig().LMS_BASE_URL}/api/edx_name_affirmation/v1/verified_name/config`; + + const { useVerifiedNameForCerts } = commitValues; + const postValues = { + username, + use_verified_name_for_certs: useVerifiedNameForCerts, + }; + + const { data } = await getAuthenticatedHttpClient() + .post(requestUrl, postValues, requestConfig) + .catch(error => handleRequestError(error)); + + return data; +} diff --git a/src/account-settings/certificate-preference/messages.js b/src/account-settings/certificate-preference/messages.js new file mode 100644 index 0000000..9abde13 --- /dev/null +++ b/src/account-settings/certificate-preference/messages.js @@ -0,0 +1,36 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + 'account.settings.field.name.checkbox.certificate.select': { + id: 'account.settings.field.name.certificate.select', + defaultMessage: 'If checked, this name will appear on your certificates and public-facing records.', + description: 'Label for checkbox describing that the selected name will appear on the user‘s certificates.', + }, + 'account.settings.field.name.modal.certificate.title': { + id: 'account.settings.field.name.modal.certificate.title', + defaultMessage: 'Choose a preferred name for certificates and public-facing records', + description: 'Title instructing the user to choose a preferred name.', + }, + 'account.settings.field.name.modal.certificate.select': { + id: 'account.settings.field.name.modal.certificate.select', + defaultMessage: 'Select a name', + description: 'Label instructing the user to select a name.', + }, + 'account.settings.field.name.modal.certificate.option.full': { + id: 'account.settings.field.name.modal.certificate.option.full', + defaultMessage: 'Full Name', + description: 'Option representing the user’s full name.', + }, + 'account.settings.field.name.modal.certificate.option.verified': { + id: 'account.settings.field.name.modal.certificate.option.verified', + defaultMessage: 'Verified Name', + description: 'Option representing the user’s verified name.', + }, + 'account.settings.field.name.modal.certificate.button.choose': { + id: 'account.settings.field.name.modal.certificate.button.choose', + defaultMessage: 'Choose name', + description: 'Button to confirm the user’s name choice.', + }, +}); + +export default messages; diff --git a/src/account-settings/certificate-preference/test/CertificatePreference.test.jsx b/src/account-settings/certificate-preference/test/CertificatePreference.test.jsx new file mode 100644 index 0000000..1ff28ab --- /dev/null +++ b/src/account-settings/certificate-preference/test/CertificatePreference.test.jsx @@ -0,0 +1,161 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import { Provider } from 'react-redux'; +import { Router } from 'react-router-dom'; +import configureStore from 'redux-mock-store'; +import { + fireEvent, + render, + screen, +} from '@testing-library/react'; +import { createMemoryHistory } from 'history'; + +import * as auth from '@edx/frontend-platform/auth'; +import { IntlProvider, injectIntl } from '@edx/frontend-platform/i18n'; + +// Modal creates a portal. Overriding ReactDOM.createPortal allows portals to be tested in jest. +ReactDOM.createPortal = node => node; + +import CertificatePreference from '../CertificatePreference'; // eslint-disable-line import/first + +const mockDispatch = jest.fn(); +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useDispatch: () => mockDispatch, +})); + +jest.mock('@edx/frontend-platform/auth'); +jest.mock('../../data/selectors', () => jest.fn().mockImplementation(() => ({ certPreferenceSelector: () => ({}) }))); + +const history = createMemoryHistory(); + +const IntlCertificatePreference = injectIntl(CertificatePreference); + +const mockStore = configureStore(); + +describe('NameChange', () => { + let props = {}; + let store = {}; + const formId = 'useVerifiedNameForCerts'; + const updateDraft = 'UPDATE_DRAFT'; + const labelText = 'If checked, this name will appear on your certificates and public-facing records.'; + + const reduxWrapper = children => ( + + + {children} + + + ); + + beforeEach(() => { + store = mockStore(); + props = { + fieldName: 'name', + originalFullName: 'Ed X', + originalVerifiedName: 'edX Verified', + saveState: null, + useVerifiedNameForCerts: false, + verifiedNameEnabled: true, + intl: {}, + }; + + auth.getAuthenticatedHttpClient = jest.fn(() => ({ + patch: async () => ({ + data: { status: 200 }, + catch: () => {}, + }), + })); + auth.getAuthenticatedUser = jest.fn(() => ({ userId: 3 })); + }); + + afterEach(() => jest.clearAllMocks()); + + it('does not render if there is no verified name', () => { + props = { + ...props, + originalVerifiedName: '', + }; + + const wrapper = render(reduxWrapper()); + + expect(wrapper).toMatchSnapshot(); + }); + + it('does not trigger modal when checking empty checkbox, and updates draft immediately', () => { + props = { + ...props, + useVerifiedNameForCerts: true, + }; + + render(reduxWrapper()); + + const checkbox = screen.getByLabelText(labelText); + expect(checkbox.checked).toEqual(false); + + fireEvent.click(checkbox); + + expect(screen.queryByRole('radiogroup')).toBeNull(); + expect(mockDispatch).toHaveBeenCalledWith({ + payload: { name: formId, value: false }, + type: updateDraft, + }); + }); + + it('triggers modal when attempting to uncheck checkbox', () => { + render(reduxWrapper()); + + const checkbox = screen.getByLabelText(labelText); + expect(checkbox.checked).toEqual(true); + + fireEvent.click(checkbox); + expect(mockDispatch).not.toHaveBeenCalled(); + + screen.getByRole('radiogroup'); + }); + + it('updates draft when changing radio value', () => { + render(reduxWrapper()); + + const checkbox = screen.getByLabelText(labelText); + fireEvent.click(checkbox); + + const fullNameOption = screen.getByLabelText('Ed X (Full Name)'); + const verifiedNameOption = screen.getByLabelText('edX Verified (Verified Name)'); + expect(fullNameOption.checked).toEqual(true); + expect(verifiedNameOption.checked).toEqual(false); + + fireEvent.click(verifiedNameOption); + expect(mockDispatch).toHaveBeenCalledWith({ + payload: { name: formId, value: true }, + type: updateDraft, + }); + }); + + it('clears draft on cancel', () => { + render(reduxWrapper()); + + const checkbox = screen.getByLabelText(labelText); + fireEvent.click(checkbox); + + const cancelButton = screen.getByText('Cancel'); + fireEvent.click(cancelButton); + + expect(mockDispatch).toHaveBeenCalledWith({ type: 'RESET_DRAFTS' }); + expect(screen.queryByRole('radiogroup')).toBeNull(); + }); + + it('submits', () => { + render(reduxWrapper()); + + const checkbox = screen.getByLabelText(labelText); + fireEvent.click(checkbox); + + const submitButton = screen.getByText('Choose name'); + fireEvent.click(submitButton); + expect(mockDispatch).toHaveBeenCalledWith({ + payload: { formId, commitValues: false }, + type: 'ACCOUNT_SETTINGS__SAVE_SETTINGS', + }); + }); +}); diff --git a/src/account-settings/certificate-preference/test/__snapshots__/CertificatePreference.test.jsx.snap b/src/account-settings/certificate-preference/test/__snapshots__/CertificatePreference.test.jsx.snap new file mode 100644 index 0000000..f45f598 --- /dev/null +++ b/src/account-settings/certificate-preference/test/__snapshots__/CertificatePreference.test.jsx.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NameChange does not render if there is no verified name 1`] = ` +Object { + "asFragment": [Function], + "baseElement": +
+ , + "container":
, + "debug": [Function], + "findAllByAltText": [Function], + "findAllByDisplayValue": [Function], + "findAllByLabelText": [Function], + "findAllByPlaceholderText": [Function], + "findAllByRole": [Function], + "findAllByTestId": [Function], + "findAllByText": [Function], + "findAllByTitle": [Function], + "findByAltText": [Function], + "findByDisplayValue": [Function], + "findByLabelText": [Function], + "findByPlaceholderText": [Function], + "findByRole": [Function], + "findByTestId": [Function], + "findByText": [Function], + "findByTitle": [Function], + "getAllByAltText": [Function], + "getAllByDisplayValue": [Function], + "getAllByLabelText": [Function], + "getAllByPlaceholderText": [Function], + "getAllByRole": [Function], + "getAllByTestId": [Function], + "getAllByText": [Function], + "getAllByTitle": [Function], + "getByAltText": [Function], + "getByDisplayValue": [Function], + "getByLabelText": [Function], + "getByPlaceholderText": [Function], + "getByRole": [Function], + "getByTestId": [Function], + "getByText": [Function], + "getByTitle": [Function], + "queryAllByAltText": [Function], + "queryAllByDisplayValue": [Function], + "queryAllByLabelText": [Function], + "queryAllByPlaceholderText": [Function], + "queryAllByRole": [Function], + "queryAllByTestId": [Function], + "queryAllByText": [Function], + "queryAllByTitle": [Function], + "queryByAltText": [Function], + "queryByDisplayValue": [Function], + "queryByLabelText": [Function], + "queryByPlaceholderText": [Function], + "queryByRole": [Function], + "queryByTestId": [Function], + "queryByText": [Function], + "queryByTitle": [Function], + "rerender": [Function], + "unmount": [Function], +} +`; diff --git a/src/account-settings/data/selectors.js b/src/account-settings/data/selectors.js index b1d485b..2dd9fc0 100644 --- a/src/account-settings/data/selectors.js +++ b/src/account-settings/data/selectors.js @@ -58,12 +58,22 @@ const valuesSelector = createSelector( accountSettingsSelector, mostRecentVerifiedNameSelector, mostRecentApprovedVerifiedNameValueSelector, - (accountSettings, mostRecentVerifiedNameValue, mostRecentApprovedVerifiedNameValue) => ( - { + (accountSettings, mostRecentVerifiedNameValue, mostRecentApprovedVerifiedNameValue) => { + let useVerifiedNameForCerts = ( + accountSettings.values.verifiedNameHistory?.use_verified_name_for_certs || false + ); + + if (Object.keys(accountSettings.confirmationValues).includes('useVerifiedNameForCerts')) { + useVerifiedNameForCerts = accountSettings.confirmationValues.useVerifiedNameForCerts; + } + + return { ...accountSettings.values, verifiedName: mostRecentApprovedVerifiedNameValue, mostRecentVerifiedName: mostRecentVerifiedNameValue, - }), + useVerifiedNameForCerts, + }; + }, ); const draftsSelector = createSelector( @@ -150,7 +160,11 @@ const formValuesSelector = createSelector( (values, drafts) => { const formValues = {}; Object.entries(values).forEach(([name, value]) => { - formValues[name] = chooseFormValue(drafts[name], value) || ''; + if (typeof value === 'boolean') { + formValues[name] = chooseFormValue(drafts[name], value); + } else { + formValues[name] = chooseFormValue(drafts[name], value) || ''; + } }); return formValues; }, @@ -195,6 +209,8 @@ export const accountSettingsPageSelector = createSelector( siteLanguageOptionsSelector, siteLanguageSelector, formValuesSelector, + valuesSelector, + draftsSelector, profileDataManagerSelector, staticFieldsSelector, timeZonesSelector, @@ -205,6 +221,8 @@ export const accountSettingsPageSelector = createSelector( siteLanguageOptions, siteLanguage, formValues, + committedValues, + drafts, profileDataManager, staticFields, timeZoneOptions, @@ -220,12 +238,36 @@ export const accountSettingsPageSelector = createSelector( countryTimeZoneOptions, isActive: activeAccount, formValues, + committedValues, + drafts, profileDataManager, staticFields, tpaProviders: accountSettings.thirdPartyAuth.providers, }), ); +export const certPreferenceSelector = createSelector( + valuesSelector, + formValuesSelector, + mostRecentApprovedVerifiedNameValueSelector, + saveStateSelector, + errorSelector, + ( + committedValues, + formValues, + mostRecentApprovedVerifiedNameValue, + saveState, + errors, + ) => ({ + originalFullName: committedValues?.name || '', + originalVerifiedName: mostRecentApprovedVerifiedNameValue?.verified_name || '', + useVerifiedNameForCerts: formValues.useVerifiedNameForCerts || false, + saveState, + verifiedNameEnabled: formValues.verifiedNameHistory?.verified_name_enabled || false, + formErrors: errors, + }), +); + export const coachingConsentPageSelector = createSelector( accountSettingsSelector, formValuesSelector, diff --git a/src/account-settings/data/service.js b/src/account-settings/data/service.js index c77ceb4..be451ff 100644 --- a/src/account-settings/data/service.js +++ b/src/account-settings/data/service.js @@ -7,6 +7,7 @@ import isEmpty from 'lodash.isempty'; import { handleRequestError, unpackFieldErrors } from './utils'; import { getThirdPartyAuthProviders } from '../third-party-auth'; +import { postVerifiedNameConfig } from '../certificate-preference/data/service'; import { getCoachingPreferences, patchCoachingPreferences } from '../coaching/data/service'; import { getDemographics, getDemographicsOptions, patchDemographics } from '../demographics/data/service'; import { DEMOGRAPHICS_FIELDS } from '../demographics/data/utils'; @@ -256,11 +257,19 @@ export async function patchSettings(username, commitValues, userId) { const preferenceKeys = ['time_zone']; const coachingKeys = ['coaching']; const demographicsKeys = DEMOGRAPHICS_FIELDS; + const certificateKeys = ['useVerifiedNameForCerts']; const isDemographicsKey = (value, key) => key.includes('demographics'); - const accountCommitValues = omit(commitValues, preferenceKeys, coachingKeys, demographicsKeys); + const accountCommitValues = omit( + commitValues, + preferenceKeys, + coachingKeys, + demographicsKeys, + certificateKeys, + ); const preferenceCommitValues = pick(commitValues, preferenceKeys); const coachingCommitValues = pick(commitValues, coachingKeys); const demographicsCommitValues = pickBy(commitValues, isDemographicsKey); + const certCommitValues = pick(commitValues, certificateKeys); const patchRequests = []; if (!isEmpty(accountCommitValues)) { @@ -275,6 +284,9 @@ export async function patchSettings(username, commitValues, userId) { if (!isEmpty(demographicsCommitValues)) { patchRequests.push(patchDemographics(userId, demographicsCommitValues)); } + if (!isEmpty(certCommitValues)) { + patchRequests.push(postVerifiedNameConfig(username, certCommitValues)); + } const results = await Promise.all(patchRequests); // Assigns in order of requests. Preference keys