From cc65ffc96f99dc6e7c8218c0306ca6f3ced71e80 Mon Sep 17 00:00:00 2001 From: Bianca Severino Date: Wed, 8 Sep 2021 13:21:29 -0400 Subject: [PATCH] feat: add certificate preference to name fields Adds a checkbox under "Name" and "Verified Name" fields, signifying which name the user prefers to display on their certificates. When checking the checkbox, the user can save this choice along with their name. When unchecking the check- box, a modal appears, prompting the user to choose a name to display on their certificate. --- src/account-settings/AccountSettingsPage.jsx | 72 +++++-- .../AccountSettingsPage.messages.jsx | 15 ++ src/account-settings/EditableField.jsx | 98 +++++----- .../CertificatePreference.jsx | 175 ++++++++++++++++++ .../certificate-preference/data/service.js | 22 +++ .../certificate-preference/messages.js | 36 ++++ .../test/CertificatePreference.test.jsx | 161 ++++++++++++++++ .../CertificatePreference.test.jsx.snap | 62 +++++++ src/account-settings/data/selectors.js | 50 ++++- src/account-settings/data/service.js | 14 +- 10 files changed, 641 insertions(+), 64 deletions(-) create mode 100644 src/account-settings/certificate-preference/CertificatePreference.jsx create mode 100644 src/account-settings/certificate-preference/data/service.js create mode 100644 src/account-settings/certificate-preference/messages.js create mode 100644 src/account-settings/certificate-preference/test/CertificatePreference.test.jsx create mode 100644 src/account-settings/certificate-preference/test/__snapshots__/CertificatePreference.test.jsx.snap 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