diff --git a/src/applications/_mock-form-ae-design-patterns/app-entry.jsx b/src/applications/_mock-form-ae-design-patterns/app-entry.jsx index 91d73206f289..4de3896f2ac3 100644 --- a/src/applications/_mock-form-ae-design-patterns/app-entry.jsx +++ b/src/applications/_mock-form-ae-design-patterns/app-entry.jsx @@ -1,21 +1,31 @@ import 'platform/polyfills'; import './sass/_mock-form-ae-design-patterns.scss'; -import startApp from 'platform/startup'; import routes from './routes'; import reducer from './reducers'; import manifest from './manifest.json'; import coeReducer from './patterns/pattern2/TaskGray/shared/reducers'; +import { asyncStartApp } from './utils/asyncStartApp'; const combinedReducers = { ...reducer, certificateOfEligibility: coeReducer.certificateOfEligibility, }; -startApp({ +const createRoutes = initialRoutes => { + // here we can do some async stuff + // maybe we change the routes based on the state or other api call responses? + // this could be where we add or remove routes for the contact info that is missing for a user + // replace () with (store) to access the store and use it to determine the routes + return () => { + return initialRoutes; + }; +}; + +asyncStartApp({ entryName: manifest.entryName, url: manifest.rootUrl, reducer: combinedReducers, - routes, + createAsyncRoutesWithStore: createRoutes(routes), }); diff --git a/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/user/index.js b/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/user/index.js index 8422b0cc910c..4bfbff9fb185 100644 --- a/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/user/index.js +++ b/src/applications/_mock-form-ae-design-patterns/mocks/endpoints/user/index.js @@ -275,13 +275,73 @@ const loa3UserWithUpdatedHomePhoneTimeStamp = set( ); const loa3UserWithUpdatedMailingAddress = set( + set( + cloneDeep(loa3User), + 'data.attributes.vet360ContactInformation.mailingAddress.addressLine1', + '345 Mailing Address St.', + ), + 'data.attributes.vet360ContactInformation.mailingAddress.updatedAt', + new Date().toISOString(), +); + +const loa3UserWithNoEmail = set( + cloneDeep(loa3User), + 'data.attributes.vet360ContactInformation.email', + {}, +); + +const loa3UserWithNoContactInfo = set( cloneDeep(loa3User), - 'data.attributes.vet360ContactInformation.mailingAddress.addressLine1', - '345 Mailing Address St.', + 'data.attributes.vet360ContactInformation', + { + email: { + ...loa3User.data.attributes.vet360ContactInformation.email, + emailAddress: '', + }, + homePhone: { + ...loa3User.data.attributes.vet360ContactInformation.homePhone, + phoneNumber: '', + areaCode: '', + countryCode: '', + phoneType: '', + }, + mobilePhone: { + ...loa3User.data.attributes.vet360ContactInformation.mobilePhone, + phoneNumber: '', + areaCode: '', + countryCode: '', + phoneType: '', + }, + mailingAddress: { + ...loa3User.data.attributes.vet360ContactInformation.mailingAddress, + addressLine1: '', + addressLine2: '', + addressLine3: '', + city: '', + stateCode: '', + zipCode: '', + countryCodeIso2: '', + countryCodeIso3: '', + countryCodeFips: '', + countyCode: '', + countyName: '', + createdAt: '', + effectiveEndDate: '', + effectiveStartDate: '', + geocodeDate: '', + geocodePrecision: '', + id: '', + internationalPostalCode: '', + latitude: '', + longitude: '', + }, + }, ); module.exports = { loa3User, loa3UserWithUpdatedHomePhoneTimeStamp, loa3UserWithUpdatedMailingAddress, + loa3UserWithNoEmail, + loa3UserWithNoContactInfo, }; diff --git a/src/applications/_mock-form-ae-design-patterns/mocks/script/mem-db.js b/src/applications/_mock-form-ae-design-patterns/mocks/script/mem-db.js index 5cb8aa26b714..c7a860fc57ee 100644 --- a/src/applications/_mock-form-ae-design-patterns/mocks/script/mem-db.js +++ b/src/applications/_mock-form-ae-design-patterns/mocks/script/mem-db.js @@ -1,10 +1,15 @@ const _ = require('lodash'); -const { loa3User } = require('../endpoints/user'); +const { loa3UserWithNoContactInfo, loa3User } = require('../endpoints/user'); + +const possibleUsers = { + loa3UserWithNoContactInfo, + loa3User, +}; // in memory db const memDb = { - user: loa3User, + user: possibleUsers.loa3UserWithNoContactInfo, }; // sanitize user input @@ -28,7 +33,11 @@ const updateFields = (target, source, fields) => { } return updatedTarget; }, - { ...target, updatedAt: new Date().toISOString() }, + { + ...target, + updatedAt: new Date().toISOString(), + createdAt: new Date().toISOString(), + }, ); }; @@ -42,6 +51,12 @@ const updateConfig = { 'city', 'stateCode', 'zipCode', + 'countryCodeIso2', + 'countryCodeIso3', + 'countryCodeFips', + 'countyCode', + 'countyName', + 'addressPou', ], transactionId: 'mock-update-mailing-address-success-transaction-id', type: 'AsyncTransaction::VAProfile::AddressTransaction', @@ -55,6 +70,12 @@ const updateConfig = { 'city', 'stateCode', 'zipCode', + 'countryCodeIso2', + 'countryCodeIso3', + 'countryCodeFips', + 'countyCode', + 'countyName', + 'addressPou', ], transactionId: 'mock-update-residential-address-success-transaction-id', type: 'AsyncTransaction::VAProfile::AddressTransaction', @@ -114,7 +135,10 @@ const updateMemDb = (req, res = null) => { throw new Error('Invalid phone type sent to PUT telephones'); } - if (key === 'PUT /v0/profile/addresses') { + if ( + key === 'PUT /v0/profile/addresses' || + key === 'POST /v0/profile/addresses' + ) { const addressType = body.addressPou?.toLowerCase(); if ( addressType === 'correspondence' || diff --git a/src/applications/_mock-form-ae-design-patterns/mocks/server.js b/src/applications/_mock-form-ae-design-patterns/mocks/server.js index de7d119f863d..0362d5a358eb 100644 --- a/src/applications/_mock-form-ae-design-patterns/mocks/server.js +++ b/src/applications/_mock-form-ae-design-patterns/mocks/server.js @@ -129,7 +129,7 @@ const responses = { ); }, 'POST /v0/profile/addresses': (req, res) => { - return res.json(updateMemDb(req, address.homeAddressUpdateReceived)); + return res.json(updateMemDb(req)); }, 'DELETE /v0/profile/addresses': (_req, res) => { const secondsOfDelay = 1; diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/contactInfo.js b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/contactInfo.js new file mode 100644 index 000000000000..f310322dfec4 --- /dev/null +++ b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/contactInfo.js @@ -0,0 +1,24 @@ +import profileContactInfo from 'applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/profileContactInfo'; + +import { getContent } from 'platform/forms-system/src/js/utilities/data/profile'; + +const content = { + ...getContent('application'), + description: null, + title: 'Confirm the contact information we have on file for you', +}; + +export const contactInfo = profileContactInfo({ + content, + contactInfoPageKey: 'confirmContactInfo3', + contactPath: 'veteran-information', + contactInfoRequiredKeys: [ + 'mailingAddress', + 'email', + 'homePhone', + 'mobilePhone', + ], + included: ['homePhone', 'mailingAddress', 'email', 'mobilePhone'], + disableMockContactInfo: true, + prefillPatternEnabled: true, +}); diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/form.js b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/form.js index 1b26eaa0b20c..85b9d286c216 100644 --- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/form.js +++ b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/form.js @@ -8,8 +8,8 @@ import { taskCompletePagePattern2 } from 'applications/_mock-form-ae-design-patt // page level imports import IntroductionPage from '../IntroductionPage'; -import profileContactInfo from './profileContactInfo'; import veteranInfo from './veteranInfo'; +import { contactInfo } from './contactInfo'; const formConfig = { rootUrl: manifest.rootUrl, @@ -65,17 +65,7 @@ const formConfig = { uiSchema: veteranInfo.uiSchema, schema: veteranInfo.schema, }, - ...profileContactInfo({ - contactInfoPageKey: 'confirmContactInfo3', - contactPath: 'veteran-information', - contactInfoRequiredKeys: [ - 'mailingAddress', - 'email', - 'homePhone', - 'mobilePhone', - ], - included: ['homePhone', 'mailingAddress', 'email', 'mobilePhone'], - }), + ...contactInfo, taskCompletePagePattern2, }, }, diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfo.jsx b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfo.jsx new file mode 100644 index 000000000000..523096f3ba7f --- /dev/null +++ b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfo.jsx @@ -0,0 +1,532 @@ +import React, { useEffect, useState, useRef } from 'react'; +import { useSelector } from 'react-redux'; +import PropTypes from 'prop-types'; +import { withRouter } from 'react-router'; + +import { + focusElement, + scrollTo, + scrollAndFocus, +} from '@department-of-veterans-affairs/platform-utilities/ui'; +import environment from '@department-of-veterans-affairs/platform-utilities/environment'; + +import { + selectProfile, + isLoggedIn, +} from '@department-of-veterans-affairs/platform-user/selectors'; + +import { useFeatureToggle } from 'platform/utilities/feature-toggles'; +import { Element } from 'platform/utilities/scroll'; + +import { generateMockUser } from 'platform/site-wide/user-nav/tests/mocks/user'; + +// import { AddressView } from '@department-of-veterans-affairs/platform-user/exports'; +// import AddressView from '@@vap-svc/components/AddressField/AddressView'; +import AddressView from 'platform/user/profile/vap-svc/components/AddressField/AddressView'; + +// import FormNavButtons from '@department-of-veterans-affairs/platform-forms-system/FormNavButtons'; +// import FormNavButtons from 'platform/forms-system/src/js/components/FormNavButtons'; +import FormNavButtons from 'platform/forms-system/src/js/components/FormNavButtons'; + +import readableList from 'platform/forms-system/src/js/utilities/data/readableList'; +import { + setReturnState, + getReturnState, + clearReturnState, + renderTelephone, + getMissingInfo, + REVIEW_CONTACT, + convertNullishObjectValuesToEmptyString, + contactInfoPropTypes, +} from 'platform/forms-system/src/js/utilities/data/profile'; +import { getValidationErrors } from 'platform/forms-system/src/js/utilities/validations'; +import { VaLink } from '@department-of-veterans-affairs/component-library/dist/react-bindings'; +import { ContactInfoLoader } from './ContactInfoLoader'; +import { ContactInfoSuccessAlerts } from './ContactInfoSuccessAlerts'; + +/** + * Render contact info page + * @param {Object} data - full form data + * @param {Function} goBack - CustomPage param + * @param {Function} goForward - CustomPage param + * @param {Boolean} onReviewPage - CustomPage param + * @param {Function} updatePage - CustomPage param + * @param {Element} contentBeforeButtons - CustomPage param + * @param {Element} contentAfterButtons - CustomPage param + * @param {Function} setFormData - CustomPage param + * @param {Object} content - Contact info page content + * @param {String} contactPath - Contact info path; used in edit page path + * @parma {import('../utilities/data/profile').ContactInfoKeys} keys - contact info data key + * @param {String[]} requiredKeys - list of keys of required fields + * @returns + */ +const ContactInfoBase = ({ + data, + goBack, + goForward, + onReviewPage, + updatePage, + contentBeforeButtons, + contentAfterButtons, + setFormData, + content, + contactPath, + keys, + requiredKeys, + uiSchema, + testContinueAlert = false, + contactInfoPageKey, + disableMockContactInfo = false, + contactSectionHeadingLevel, + prefillPatternEnabled, + ...rest +}) => { + const { TOGGLE_NAMES, useToggleValue } = useFeatureToggle(); + const aedpPrefillToggleEnabled = useToggleValue(TOGGLE_NAMES.aedpPrefill); + + const { router } = rest; + + const { pathname } = router.location; + + const wrapRef = useRef(null); + window.sessionStorage.setItem(REVIEW_CONTACT, onReviewPage || false); + const [hasInitialized, setHasInitialized] = useState(false); + const [hadError, setHadError] = useState(false); + const [submitted, setSubmitted] = useState(false); + const [editState] = useState(getReturnState()); + + // vapContactInfo is an empty object locally, so mock it + const profile = useSelector(selectProfile) || {}; + const loggedIn = useSelector(isLoggedIn) || false; + const contactInfo = + loggedIn && environment.isLocalhost() && !disableMockContactInfo + ? generateMockUser({ authBroker: 'iam' }).data.attributes + .vet360ContactInformation + : profile.vapContactInfo || {}; + + const dataWrap = data[keys.wrapper] || {}; + const email = dataWrap[keys.email] || ''; + const homePhone = dataWrap[keys.homePhone] || {}; + const mobilePhone = dataWrap[keys.mobilePhone] || {}; + const address = dataWrap[keys.address] || {}; + + const missingInfo = getMissingInfo({ + data: dataWrap, + keys, + content, + requiredKeys, + }); + + const list = readableList(missingInfo); + const plural = missingInfo.length > 1; + + const validationErrors = uiSchema?.['ui:required']?.(data) + ? getValidationErrors(uiSchema?.['ui:validations'] || [], {}, data) + : []; + + const handlers = { + onSubmit: event => { + // This prevents this nested form submit event from passing to the + // outer form and causing a page advance + event.stopPropagation(); + }, + onGoBack: () => { + clearReturnState(); + goBack(); + }, + onGoForward: () => { + setSubmitted(true); + if (missingInfo.length || validationErrors.length) { + scrollAndFocus(wrapRef.current); + } else { + clearReturnState(); + goForward(data); + } + }, + updatePage: () => { + setSubmitted(true); + if (missingInfo.length || validationErrors.length) { + scrollAndFocus(wrapRef.current); + } else { + setReturnState('true'); + updatePage(); + } + }, + }; + + useEffect( + () => { + if ( + (keys.email && (contactInfo.email?.emailAddress || '') !== email) || + (keys.homePhone && + contactInfo.homePhone?.updatedAt !== homePhone?.updatedAt) || + (keys.mobilePhone && + contactInfo.mobilePhone?.updatedAt !== mobilePhone?.updatedAt) || + (keys.address && + contactInfo.mailingAddress?.updatedAt !== address?.updatedAt) + ) { + const wrapper = { ...data[keys.wrapper] }; + if (keys.address) { + wrapper[keys.address] = convertNullishObjectValuesToEmptyString( + contactInfo.mailingAddress, + ); + } + if (keys.homePhone) { + wrapper[keys.homePhone] = convertNullishObjectValuesToEmptyString( + contactInfo.homePhone, + ); + } + if (keys.mobilePhone) { + wrapper[keys.mobilePhone] = convertNullishObjectValuesToEmptyString( + contactInfo.mobilePhone, + ); + } + if (keys.email) { + wrapper[keys.email] = contactInfo.email?.emailAddress; + } + setFormData({ ...data, [keys.wrapper]: wrapper }); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [contactInfo, setFormData, data, keys], + ); + + useEffect( + () => { + if (editState) { + const [lastEdited, returnState] = editState.split(','); + setTimeout(() => { + const target = + returnState === 'canceled' + ? `#edit-${lastEdited}` + : `#updated-${lastEdited}`; + scrollTo( + onReviewPage + ? `${contactInfoPageKey}ScrollElement` + : `header-${lastEdited}`, + ); + focusElement(onReviewPage ? `#${contactInfoPageKey}Header` : target); + }); + } + }, + [contactInfoPageKey, editState, onReviewPage], + ); + + useEffect( + () => { + if ((hasInitialized && missingInfo.length) || testContinueAlert) { + // page had an error flag, so we know when to show a success alert + setHadError(true); + } + setTimeout(() => { + setHasInitialized(true); + }); + }, + [missingInfo, hasInitialized, testContinueAlert], + ); + + const MainHeader = onReviewPage ? 'h4' : 'h3'; + const Headers = contactSectionHeadingLevel || (onReviewPage ? 'h5' : 'h4'); + const headerClassNames = [ + 'vads-u-font-size--h4', + 'vads-u-width--auto', + 'vads-u-margin-top--0', + ].join(' '); + + // keep alerts in DOM, so we don't have to delay focus; but keep the 100ms + // delay to move focus away from the h3 + const showSuccessAlertInField = (id, text) => { + if (prefillPatternEnabled && aedpPrefillToggleEnabled) { + return null; + } + return ( + + {`${text} ${content.updated}`} + + ); + }; + + // Loop to separate pages when editing + // Each Link includes an ID for focus management on the review & submit page + + const contactSection = [ + keys.address ? ( + + + + {content.mailingAddress} + + {showSuccessAlertInField('address', content.mailingAddress)} + + {loggedIn && ( +

+ { + e.preventDefault(); + router.push(`${pathname}/edit-mailing-address`); + }} + active + /> +

+ )} +
+
+ ) : null, + + keys.homePhone ? ( + + + + {content.homePhone} + + {showSuccessAlertInField('home-phone', content.homePhone)} + + {renderTelephone(dataWrap[keys.homePhone])} + + {loggedIn && ( +

+ { + e.preventDefault(); + router.push(`${pathname}/edit-home-phone`); + }} + active + /> +

+ )} +
+
+ ) : null, + + keys.mobilePhone ? ( + + + + {content.mobilePhone} + + {showSuccessAlertInField('mobile-phone', content.mobilePhone)} + + {renderTelephone(dataWrap[keys.mobilePhone])} + + {loggedIn && ( +

+ { + e.preventDefault(); + router.push(`${pathname}/edit-mobile-phone`); + }} + active + /> +

+ )} +
+
+ ) : null, + + keys.email ? ( + + + + {content.email} + + {showSuccessAlertInField('email', content.email)} + + {dataWrap[keys.email] || ''} + + {loggedIn && ( +

+ { + e.preventDefault(); + router.push(`${pathname}/edit-email-address`); + }} + active + /> +

+ )} +
+
+ ) : null, + ]; + + const navButtons = onReviewPage ? ( + + ) : ( + <> + {contentBeforeButtons} + + {contentAfterButtons} + + ); + + return ( + +
+ + +
+ + {content.title} + + {content.description} + {!loggedIn && ( + + You must be logged in to enable view and edit this page. + + )} +
+ {hadError && + missingInfo.length === 0 && + validationErrors.length === 0 && ( +
+ +
+ {content.alertContent} +
+
+
+ )} + {missingInfo.length > 0 && ( + <> +

+ Note: + {missingInfo[0].startsWith('e') ? ' An ' : ' A '} + {list} {plural ? 'are' : 'is'} required for this application. +

+ {submitted && ( +
+ +
+ We still don’t have your {list}. Please edit and update + the field. +
+
+
+ )} +
+ +
+ Your {list} {plural ? 'are' : 'is'} missing. Please edit + and update the {plural ? 'fields' : 'field'}. +
+
+
+ + )} + {submitted && + missingInfo.length === 0 && + validationErrors.length > 0 && ( +
+ +
+ {validationErrors[0]} +
+
+
+ )} +
+
+
+
+
+ {contactSection} +
+
+
+
+
+
{navButtons}
+
+
+ ); +}; + +ContactInfoBase.propTypes = { + contactInfoPageKey: contactInfoPropTypes.contactInfoPageKey, + contactPath: PropTypes.string, + contactSectionHeadingLevel: PropTypes.string, + content: contactInfoPropTypes.content, // content passed in from profileContactInfo + contentAfterButtons: PropTypes.element, + contentBeforeButtons: PropTypes.element, + data: contactInfoPropTypes.data, + disableMockContactInfo: PropTypes.bool, + goBack: PropTypes.func, + goForward: PropTypes.func, + immediateRedirect: PropTypes.bool, + keys: contactInfoPropTypes.keys, + requiredKeys: PropTypes.arrayOf(PropTypes.string), + setFormData: PropTypes.func, + testContinueAlert: PropTypes.bool, // for unit testing only + uiSchema: PropTypes.shape({ + 'ui:required': PropTypes.func, + 'ui:validations': PropTypes.array, + }), + updatePage: PropTypes.func, + onReviewPage: PropTypes.bool, + prefillPatternEnabled: PropTypes.bool, +}; + +const ContactInfo = withRouter(ContactInfoBase); + +export default ContactInfo; diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoLoader.jsx b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoLoader.jsx new file mode 100644 index 000000000000..fb723b360820 --- /dev/null +++ b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoLoader.jsx @@ -0,0 +1,48 @@ +import { withRouter } from 'react-router'; +import React from 'react'; +import { useFeatureToggle } from 'platform/utilities/feature-toggles'; +import PropTypes from 'prop-types'; +import { useRouteMetadata } from './useRouteMetadata'; +import useContactInfo from './useContactInfo'; + +const ContactInfoLoaderBase = ({ + router, + children, + requiredKeys, + disableMockContactInfo, + contactPath, + prefillPatternEnabled, +}) => { + const { useToggleLoadingValue } = useFeatureToggle(); + const loading = useToggleLoadingValue(); + const routeMetadata = useRouteMetadata(router); + + const { missingFields } = useContactInfo({ + requiredKeys, + disableMockContactInfo, + fullContactPath: `${routeMetadata?.urlPrefix || ''}${contactPath}`, + }); + + if (loading) { + return
Loading...
; + } + + if (missingFields.length > 0 && prefillPatternEnabled) { + router.push(missingFields[0].editPath); + } + + return
{children}
; +}; + +ContactInfoLoaderBase.propTypes = { + children: PropTypes.node.isRequired, + router: PropTypes.shape({ + push: PropTypes.func.isRequired, + }).isRequired, + contactPath: PropTypes.string, + disableMockContactInfo: PropTypes.bool, + prefillPatternEnabled: PropTypes.bool, + requiredKeys: PropTypes.arrayOf(PropTypes.string), +}; + +export const ContactInfoLoader = withRouter(ContactInfoLoaderBase); diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoReview.jsx b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoReview.jsx new file mode 100644 index 000000000000..0b657f4ba91c --- /dev/null +++ b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoReview.jsx @@ -0,0 +1,308 @@ +import React, { useEffect, useRef } from 'react'; +import PropTypes from 'prop-types'; + +import { focusElement } from '@department-of-veterans-affairs/platform-utilities/ui'; + +import { Element } from 'platform/utilities/scroll'; + +// import { ADDRESS_TYPES } from '@department-of-veterans-affairs/platform-forms/exports'; +import { ADDRESS_TYPES } from 'platform/forms/address/helpers'; + +import { + renderTelephone, + contactInfoPropTypes, + validateEmail, + validatePhone, + validateZipcode, + getReturnState, + setReturnState, + clearReturnState, +} from 'platform/forms-system/src/js/utilities/data/profile'; + +/** + * Contact info fields shown on the review & submit page + * @param {Object} data - form data + * @param {function} editPage - edit page callback + * @param {ContactInfoContent} content + * @param {Object} keys - form data keys + * @returns {Element} + */ +const ContactInfoReview = ({ + data, + editPage, + content, + keys, + contactInfoPageKey, +}) => { + const editRef = useRef(null); + useEffect( + () => { + if (getReturnState() === 'true,' && editRef?.current) { + // focus on edit button _after_ editing and returning + clearReturnState(); + setTimeout( + () => focusElement('va-button', {}, editRef.current?.shadowRoot), + 0, + ); + } + }, + [editRef], + ); + + const dataWrap = data[keys.wrapper] || {}; + const emailString = dataWrap[keys.email] || ''; + const homePhoneObj = dataWrap[keys.homePhone] || {}; + const mobilePhoneObj = dataWrap[keys.mobilePhone] || {}; + const addressObj = dataWrap[keys.address] || {}; + + const isUS = addressObj.addressType !== ADDRESS_TYPES.international; + + /** + * Renders value (if it isn't all whitespace) or an error message wrapped in + * a class that makes the text bold & red + * @param {String} value - Field value to show + * @param {String} errorMessage - Error message text + * @returns {String|JSX} - value or error message + */ + const showValueOrErrorMessage = (value, errorMessage) => + (value || '').trim() || + (errorMessage && ( + {errorMessage} + )) || + ''; + + /** + * Display field label & data (or error message) on the review & submit page + * Using an array here to maintain display order + * Entry: [ Label, Value or error message ] + * - Label = Name of field (customizable in getContent) + * - Value or error message = `getValue` function that uses `keys` which is + * the data object key that contains the value; e.g. keys.homePhone matches + * this data path: + * { [wrapperKey]: { [homePhone]: { areaCode: '', phoneNumber: '' } } } + * If the value function returns an empty string, the row isn't rendered + * */ + const display = [ + [ + content.homePhone, // label + () => { + // keys.homePhone is undefined if not in `included` option within + // `profileContactInfo` + if (!keys.homePhone) { + return ''; // Don't render row + } + // content contains the error messages, homePhoneObj is the phone object + const errorMsg = validatePhone(content, homePhoneObj); + // Pass showValueOrErrorMessage an empty string so error renders + return errorMsg + ? showValueOrErrorMessage('', errorMsg) + : renderTelephone(homePhoneObj); // va-telephone web component + }, + ], + [ + content.mobilePhone, + () => { + // keys.mobilePhone is undefined if not in `included` option within + // `profileContactInfo` + if (!keys.mobilePhone) { + return ''; // Don't render row + } + const errorMsg = validatePhone(content, mobilePhoneObj); + // Pass showValueOrErrorMessage an empty string so error renders + return errorMsg + ? showValueOrErrorMessage('', errorMsg) + : renderTelephone(mobilePhoneObj); // va-telephone web component + }, + ], + [ + content.email, + () => { + // keys.email is undefined if not in `included` option within + // `profileContactInfo` + if (!keys.email) { + return ''; // Don't render row + } + const errorMsg = validateEmail(content, emailString); + // Pass showValueOrErrorMessage an empty string so error renders + return errorMsg ? showValueOrErrorMessage('', errorMsg) : emailString; + }, + ], + [ + content.country, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo` + if (!keys.address) { + return ''; // Don't render row + } + // showValueOrErrorMessage will render the trimmed value or an error + return showValueOrErrorMessage( + addressObj.countryName, + content.missingCountryError, + ); + }, + ], + [ + content.address1, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo` + if (!keys.address) { + return ''; // Don't render row + } + // showValueOrErrorMessage will render the trimmed value or an error + return showValueOrErrorMessage( + addressObj.addressLine1, + content.missingStreetAddressError, + ); + }, + ], + [ + content.address2, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo` + if (!keys.address) { + return ''; // Don't render row + } + return addressObj.addressLine2; // No error because it's optional + }, + ], + [ + content.address3, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo` + if (!keys.address) { + return ''; // Don't render row + } + return addressObj.addressLine3; // No error because it's optional + }, + ], + [ + content.city, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo` + if (!keys.address) { + return ''; // Don't render row + } + // showValueOrErrorMessage will render the trimmed value or an error + return showValueOrErrorMessage( + addressObj.city, + content.missingCityError, + ); + }, + ], + [ + content.state, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo`, or don't render this row for non-U.S. addresses + if (!keys.address || !isUS) { + return ''; // Don't render row + } + // showValueOrErrorMessage will render the trimmed value or an error + return showValueOrErrorMessage( + addressObj.stateCode, + content.missingStateError, + ); + }, + ], + [ + content.province, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo` & don't render this row for U.S. addresses + if (!keys.address && isUS) { + return ''; // Don't render row + } + return addressObj.province; // No error because it's optional + }, + ], + [ + content.zipCode, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo`, or don't render this row for non-U.S. addresses + if (!keys.address || !isUS) { + return ''; // Don't render row + } + const { zipCode } = addressObj; + // Profile should only provide a 5-digit zipcode; evaluate for missing + // or invalid zipcode + const errorMsg = validateZipcode(content, zipCode); + // Pass showValueOrErrorMessage an empty string so error renders + return errorMsg ? showValueOrErrorMessage('', errorMsg) : zipCode; + }, + ], + [ + content.postal, + () => { + // keys.address is undefined if not in `included` option within + // `profileContactInfo` & don't render this row for U.S. addresses + if (!keys.address && isUS) { + return ''; // Don't render row + } + return addressObj.internationalPostalCode; // No error because it's optional + }, + ], + ]; + + const handlers = { + onEditPage: () => { + // maintain state using session storage + setReturnState('true'); + editPage(); + }, + }; + + // Process display list of rows to show on the review & submit page + const list = display + .map(([label, getValue], index) => { + const value = getValue() || ''; + // don't render anything if the value is falsy (getValue will always + // return a string) + return value ? ( +
+
{label}
+
+ {value} +
+
+ ) : null; + }) + .filter(Boolean); + + return ( +
+ +
+

+ {content.title} +

+ +
+ {list.length ?
{list}
: null} +
+ ); +}; + +ContactInfoReview.propTypes = { + contactInfoPageKey: contactInfoPropTypes.contactInfoPageKey, + content: contactInfoPropTypes.content, + data: contactInfoPropTypes.data, + editPage: PropTypes.func, + keys: contactInfoPropTypes.keys, +}; + +export default ContactInfoReview; diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoSuccessAlerts.jsx b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoSuccessAlerts.jsx new file mode 100644 index 000000000000..55cc6b36a57b --- /dev/null +++ b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/ContactInfoSuccessAlerts.jsx @@ -0,0 +1,33 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { clearReturnState } from 'platform/forms-system/src/js/utilities/data/profile'; + +export const ContactInfoSuccessAlerts = ({ + editState, + prefillPatternEnabled, +}) => { + const showSuccessAlert = + prefillPatternEnabled && editState && editState?.includes('updated'); + + if (showSuccessAlert) { + clearReturnState(); + } + + return showSuccessAlert ? ( +
+ +

+ We’ve updated your contact information. +

+
+ We’ve made these changes to this form and your VA.gov profile. +
+
+
+ ) : null; +}; + +ContactInfoSuccessAlerts.propTypes = { + editState: PropTypes.string, + prefillPatternEnabled: PropTypes.bool, +}; diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/EditContactInfo.jsx b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/EditContactInfo.jsx new file mode 100644 index 000000000000..3958b2863a66 --- /dev/null +++ b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/EditContactInfo.jsx @@ -0,0 +1,132 @@ +import React, { useRef, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import { useDispatch, useSelector } from 'react-redux'; + +import InitializeVAPServiceID from 'platform/user/profile/vap-svc/containers/InitializeVAPServiceID'; +import ProfileInformationFieldController from 'platform/user/profile/vap-svc/components/ProfileInformationFieldController'; +import { FIELD_NAMES } from 'platform/user/profile/vap-svc/constants'; + +import { focusElement } from '@department-of-veterans-affairs/platform-utilities/ui'; + +import { + REVIEW_CONTACT, + setReturnState, +} from 'platform/forms-system/src/js/utilities/data/profile'; +import { usePrevious } from 'platform/utilities/react-hooks'; +import { withRouter } from 'react-router'; +import { refreshProfile } from 'platform/user/exportsFile'; +import { useRouteMetadata } from './useRouteMetadata'; + +export const BuildPageBase = ({ + title, + field, + id, + goToPath, + contactPath, + editContactInfoHeadingLevel, + router, +}) => { + const dispatch = useDispatch(); + const Heading = editContactInfoHeadingLevel || 'h3'; + const headerRef = useRef(null); + + const modalState = useSelector(state => state?.vapService.modal); + const prevModalState = usePrevious(modalState); + + const routeMetadata = useRouteMetadata(router); + + const fullContactPath = `${routeMetadata?.urlPrefix || ''}${contactPath}`; + + useEffect( + () => { + if (headerRef?.current) { + focusElement(headerRef?.current); + } + }, + [headerRef], + ); + + useEffect( + () => { + const shouldFocusOnHeaderRef = + prevModalState === 'addressValidation' && + modalState === 'mailingAddress'; + + // we do this to make sure focus is set when cancelling out of address validation UI + if (shouldFocusOnHeaderRef) { + setTimeout(() => { + focusElement(headerRef?.current); + }, 250); + } + }, + [modalState, prevModalState], + ); + + const onReviewPage = window.sessionStorage.getItem(REVIEW_CONTACT) === 'true'; + const returnPath = onReviewPage ? '/review-and-submit' : fullContactPath; + + const handlers = { + onSubmit: event => { + // This prevents this nested form submit event from passing to the + // outer form and causing a page advance + event.stopPropagation(); + }, + cancel: () => { + setReturnState(id, 'canceled'); + goToPath(returnPath); + }, + success: async () => { + setReturnState(id, 'updated'); + await dispatch(refreshProfile); + goToPath(returnPath); + }, + }; + + return ( +
+ + + {title} + + + +
+ ); +}; + +BuildPageBase.propTypes = { + router: PropTypes.shape({ + location: PropTypes.object, + }).isRequired, + contactPath: PropTypes.string, + editContactInfoHeadingLevel: PropTypes.string, + field: PropTypes.string, + goToPath: PropTypes.func, + id: PropTypes.string, + title: PropTypes.string, +}; + +const BuildPage = withRouter(BuildPageBase); + +export const EditHomePhone = props => ( + +); + +export const EditMobilePhone = props => ( + +); + +export const EditEmail = props => ( + +); + +export const EditAddress = props => ( + +); diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/index.jsx b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/index.jsx new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/profileContactInfo.js b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/profileContactInfo.js similarity index 80% rename from src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/profileContactInfo.js rename to src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/profileContactInfo.js index 03189472570a..afe9a069164a 100644 --- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskBlue/config/profileContactInfo.js +++ b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/profileContactInfo.js @@ -1,4 +1,3 @@ -import ContactInfoReview from 'platform/forms-system/src/js/components/ContactInfoReview'; import React from 'react'; import { getContent, @@ -7,15 +6,20 @@ import { standardEmailSchema, profileAddressSchema, blankSchema, + getReturnState, clearReturnState, -} from '../../../../utils/data/task-blue/profile'; +} from 'platform/forms-system/src/js/utilities/data/profile'; + +import { scrollTo, focusElement } from 'platform/utilities/ui'; + import { EditAddress, EditEmail, EditHomePhone, EditMobilePhone, -} from '../EditContactInfo'; -import ContactInfo from '../ContactInfo'; +} from './EditContactInfo'; +import ContactInfo from './ContactInfo'; +import ContactInfoReview from './ContactInfoReview'; /** * Profile settings @@ -87,6 +91,10 @@ const profileContactInfo = ({ // depends callback for contact info page depends = null, contactInfoUiSchema = {}, + disableMockContactInfo = false, + contactSectionHeadingLevel = null, + editContactInfoHeadingLevel = null, + prefillPatternEnabled = false, } = {}) => { const config = {}; const wrapperProperties = {}; @@ -99,7 +107,13 @@ const profileContactInfo = ({ config[`${contactInfoPageKey}EditMailingAddress`] = { title: content.editMailingAddress, path: `${contactPath}/edit-mailing-address`, - CustomPage: props => EditAddress({ ...props, content, contactPath }), + CustomPage: props => + EditAddress({ + ...props, + content, + contactPath, + editContactInfoHeadingLevel, + }), CustomPageReview: null, // not shown on review & submit depends: () => false, // accessed from contact info page uiSchema: {}, @@ -119,6 +133,10 @@ const profileContactInfo = ({ ...props, content, contactPath, + editContactInfoHeadingLevel, + requiredKeys: contactInfoRequiredKeys, + contactInfoPageKey, + disableMockContactInfo, }), CustomPageReview: null, // not shown on review & submit depends: () => false, // accessed from contact info page @@ -134,7 +152,16 @@ const profileContactInfo = ({ config[`${contactInfoPageKey}EditMobilePhone`] = { title: content.editMobilePhone, path: `${contactPath}/edit-mobile-phone`, - CustomPage: props => EditMobilePhone({ ...props, content, contactPath }), + CustomPage: props => + EditMobilePhone({ + ...props, + content, + contactPath, + editContactInfoHeadingLevel, + requiredKeys: contactInfoRequiredKeys, + contactInfoPageKey, + disableMockContactInfo, + }), CustomPageReview: null, // not shown on review & submit depends: () => false, // accessed from contact info page uiSchema: {}, @@ -147,7 +174,16 @@ const profileContactInfo = ({ config[`${contactInfoPageKey}EditEmailAddress`] = { title: content.editEmail, path: `${contactPath}/edit-email-address`, - CustomPage: props => EditEmail({ ...props, content, contactPath }), + CustomPage: props => + EditEmail({ + ...props, + content, + contactPath, + editContactInfoHeadingLevel, + requiredKeys: contactInfoRequiredKeys, + contactInfoPageKey, + disableMockContactInfo, + }), CustomPageReview: null, // not shown on review & submit depends: () => false, // accessed from contact info page uiSchema: {}, @@ -167,6 +203,10 @@ const profileContactInfo = ({ keys={keys} requiredKeys={contactInfoRequiredKeys} contactInfoPageKey={contactInfoPageKey} + disableMockContactInfo={disableMockContactInfo} + contactSectionHeadingLevel={contactSectionHeadingLevel} + editContactInfoHeadingLevel={editContactInfoHeadingLevel} + prefillPatternEnabled={prefillPatternEnabled} /> ), CustomPageReview: props => @@ -194,6 +234,13 @@ const profileContactInfo = ({ clearReturnState(); return formData; }, + // overide scroll & focus header + scrollAndFocusTarget: () => { + if (!getReturnState()) { + scrollTo('topContentElement'); + focusElement('h3'); + } + }, }, // edit pages; only accessible via ContactInfo component links ...config, diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/useContactInfo.js b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/useContactInfo.js new file mode 100644 index 000000000000..ffd809123b01 --- /dev/null +++ b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/useContactInfo.js @@ -0,0 +1,123 @@ +import { useMemo } from 'react'; +import { useSelector } from 'react-redux'; +import { isLoggedIn } from '@department-of-veterans-affairs/platform-user/selectors'; +import { generateMockUser } from 'platform/site-wide/user-nav/tests/mocks/user'; +import environment from '@department-of-veterans-affairs/platform-utilities/environment'; + +const useContactInfo = ({ + disableMockContactInfo = false, + requiredKeys = [], + fullContactPath, +} = {}) => { + const loggedIn = useSelector(isLoggedIn); + const profile = useSelector(state => state?.user?.profile); + + const editPagePathMap = { + email: 'edit-email-address', + homePhone: 'edit-home-phone', + mobilePhone: 'edit-mobile-phone', + mailingAddress: 'edit-mailing-address', + }; + + return useMemo( + () => { + // Use mock data locally if logged in and not disabled + const vapContactInfo = + loggedIn && environment.isLocalhost() && !disableMockContactInfo + ? generateMockUser({ authBroker: 'iam' }).data.attributes + .vet360ContactInformation + : profile?.vapContactInfo || {}; + + const email = vapContactInfo?.email?.emailAddress || ''; + const homePhone = vapContactInfo?.homePhone || {}; + const mobilePhone = vapContactInfo?.mobilePhone || {}; + const mailingAddress = vapContactInfo?.mailingAddress || {}; + + // Get missing info + const missingFields = []; + if (!email && requiredKeys.includes('email')) + missingFields.push({ + field: 'email', + editPath: `${fullContactPath}/${editPagePathMap.email}`, + label: 'email address', + }); + if (!homePhone?.phoneNumber && requiredKeys.includes('homePhone')) + missingFields.push({ + field: 'homePhone', + editPath: `${fullContactPath}/${editPagePathMap.homePhone}`, + label: 'home phone', + }); + if (!mobilePhone?.phoneNumber && requiredKeys.includes('mobilePhone')) + missingFields.push({ + field: 'mobilePhone', + editPath: `${fullContactPath}/${editPagePathMap.mobilePhone}`, + label: 'mobile phone', + }); + if ( + !mailingAddress?.addressLine1 && + requiredKeys.includes('mailingAddress') + ) + missingFields.push({ + field: 'mailingAddress', + editPath: `${fullContactPath}/${editPagePathMap.mailingAddress}`, + label: 'mailing address', + }); + + return { + email: { + emailAddress: email, + id: vapContactInfo?.email?.id, + status: vapContactInfo?.email?.status, + missing: missingFields.some(field => field.field === 'email'), + required: requiredKeys.includes('email'), + }, + homePhone: { + phoneNumber: homePhone?.phoneNumber || '', + extension: homePhone?.extension || '', + id: homePhone?.id, + status: homePhone?.status, + missing: missingFields.some(field => field.field === 'homePhone'), + required: requiredKeys.includes('homePhone'), + }, + mobilePhone: { + phoneNumber: mobilePhone?.phoneNumber || '', + extension: mobilePhone?.extension || '', + id: mobilePhone?.id, + status: mobilePhone?.status, + missing: missingFields.some(field => field.field === 'mobilePhone'), + required: requiredKeys.includes('mobilePhone'), + }, + mailingAddress: { + addressLine1: mailingAddress?.addressLine1 || '', + addressLine2: mailingAddress?.addressLine2 || '', + addressLine3: mailingAddress?.addressLine3 || '', + city: mailingAddress?.city || '', + stateCode: mailingAddress?.stateCode || '', + zipCode: mailingAddress?.zipCode || '', + countryName: mailingAddress?.countryName || '', + id: mailingAddress?.id, + status: mailingAddress?.status, + missing: missingFields.some( + field => field.field === 'mailingAddress', + ), + required: requiredKeys.includes('mailingAddress'), + }, + missingFields, + isLoggedIn: loggedIn, + }; + }, + [ + loggedIn, + profile, + disableMockContactInfo, + requiredKeys, + fullContactPath, + editPagePathMap.email, + editPagePathMap.homePhone, + editPagePathMap.mailingAddress, + editPagePathMap.mobilePhone, + ], + ); +}; + +export default useContactInfo; diff --git a/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/useRouteMetadata.js b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/useRouteMetadata.js new file mode 100644 index 000000000000..1c3a1d6aec1f --- /dev/null +++ b/src/applications/_mock-form-ae-design-patterns/shared/components/ContactInfo/useRouteMetadata.js @@ -0,0 +1,17 @@ +export const useRouteMetadata = router => { + const { pathname } = router.location; + + // check that there is a route with a urlPrefix and a path property, then combine them + const foundRoute = router.routes.find( + route => + route.urlPrefix && + route.path && + `${route.urlPrefix}${route.path}` === pathname, + ); + + if (!foundRoute) { + return null; + } + + return foundRoute; +}; diff --git a/src/applications/_mock-form-ae-design-patterns/shared/context/PatternConfigContext.jsx b/src/applications/_mock-form-ae-design-patterns/shared/context/PatternConfigContext.jsx index efcd03e61b5d..1db3267b2142 100644 --- a/src/applications/_mock-form-ae-design-patterns/shared/context/PatternConfigContext.jsx +++ b/src/applications/_mock-form-ae-design-patterns/shared/context/PatternConfigContext.jsx @@ -1,74 +1,47 @@ -import React, { createContext } from 'react'; +import React, { createContext, useEffect } from 'react'; import { useDispatch } from 'react-redux'; import PropTypes from 'prop-types'; -import greenFormConfig from '../../patterns/pattern1/TaskGreen/config/form'; -import yellowFormConfig from '../../patterns/pattern1/TaskYellow/config/form'; -import purpleFormConfig from '../../patterns/pattern1/TaskPurple/config/form'; -import ezrFormConfig from '../../patterns/pattern1/ezr/config/form'; -import grayFormConfig from '../../patterns/pattern2/TaskGray/form/config/form'; -import blueFormConfig from '../../patterns/pattern2/TaskBlue/config/form'; -import { formConfigForOrangeTask } from '../../patterns/pattern2/TaskOrange/config/form'; +import { withRouter } from 'react-router'; import fallbackForm from '../config/fallbackForm'; import { TaskTabs } from '../components/TaskTabs'; import { Portal } from '../components/Portal'; import { useMockedLogin } from '../../hooks/useMockedLogin'; -export const getFormConfig = location => { - if (location.pathname.includes('/1/task-green')) { - return greenFormConfig; - } - - if (location.pathname.includes('/1/task-yellow')) { - return yellowFormConfig; - } - - if (location.pathname.includes('/1/task-purple')) { - return purpleFormConfig; - } - - if (location.pathname.includes('/1/ezr')) { - return ezrFormConfig; - } - - if (location.pathname.includes('/2/task-gray')) { - return grayFormConfig; - } - - if (location.pathname.includes('/2/task-orange')) { - return formConfigForOrangeTask; - } - - if (location.pathname.includes('/2/task-blue')) { - return blueFormConfig; - } - - return fallbackForm; -}; - export const PatternConfigContext = createContext(); -export const PatternConfigProvider = ({ location, children }) => { +export const PatternConfigProviderBase = ({ children, router }) => { const { useLoggedInQuery } = useMockedLogin(); - useLoggedInQuery(location); - const formConfig = getFormConfig(location); + useLoggedInQuery(router?.location); + const formConfig = router?.routes?.[1]?.formConfig || fallbackForm; const dispatch = useDispatch(); - dispatch({ type: 'SET_NEW_FORM_CONFIG', formConfig }); + + useEffect( + () => { + dispatch({ type: 'SET_NEW_FORM_CONFIG', formConfig }); + }, + [dispatch, formConfig], + ); // we need to get the header element to append the tabs to it const header = document.getElementById('header-default'); return formConfig ? ( - + {children} ) : null; }; -PatternConfigProvider.propTypes = { +export const PatternConfigProvider = withRouter(PatternConfigProviderBase); + +PatternConfigProviderBase.propTypes = { children: PropTypes.node.isRequired, - location: PropTypes.object.isRequired, + router: PropTypes.shape({ + location: PropTypes.object, + routes: PropTypes.array, + }).isRequired, }; diff --git a/src/applications/_mock-form-ae-design-patterns/tests/e2e/pattern2/taskBlue.cypress.spec.js b/src/applications/_mock-form-ae-design-patterns/tests/e2e/pattern2/taskBlue.cypress.spec.js index cc44c2e94802..6ef527b19b25 100644 --- a/src/applications/_mock-form-ae-design-patterns/tests/e2e/pattern2/taskBlue.cypress.spec.js +++ b/src/applications/_mock-form-ae-design-patterns/tests/e2e/pattern2/taskBlue.cypress.spec.js @@ -2,6 +2,7 @@ import manifest from '../../../manifest.json'; // eslint-disable-next-line import/no-duplicates import mockUsers from '../../../mocks/endpoints/user'; import mockPrefills from '../../../mocks/endpoints/in-progress-forms/mock-form-ae-design-patterns'; +import { generateFeatureToggles } from '../../../mocks/endpoints/feature-toggles'; // eslint-disable-next-line import/no-duplicates describe('Prefill pattern - Blue Task', () => { @@ -78,6 +79,14 @@ describe('Prefill pattern - Blue Task', () => { }, }, }); + + cy.intercept( + 'GET', + '/v0/feature_toggles*', + generateFeatureToggles({ + aedpPrefill: true, + }), + ); }); it('should show user as authenticated from the start', () => { @@ -146,8 +155,9 @@ describe('Prefill pattern - Blue Task', () => { cy.injectAxeThenAxeCheck(); + cy.get('va-link[label="Edit mailing address"]').click(); // update mailing address and save form - cy.findByLabelText('Edit mailing address').click(); + // cy.findByLabelText('Edit mailing address').click(); // need this to access the input in the web component shadow dom cy.get('va-text-input[name="root_addressLine1"]') @@ -159,12 +169,12 @@ describe('Prefill pattern - Blue Task', () => { cy.get('@addressInput').type('345 Mailing Address St.'); // confirming save to profile ques is selected yes by default - cy.contains( - 'legend', - 'Do you also want to update this information in your VA.gov profile?', - ).should('exist'); + // cy.contains( + // 'legend', + // 'Do you also want to update this information in your VA.gov profile?', + // ).should('exist'); - cy.get('#saveToProfileYes').click(); + // cy.get('#saveToProfileYes').click(); cy.findByTestId('save-edit-button').click(); @@ -172,7 +182,7 @@ describe('Prefill pattern - Blue Task', () => { // redirect to previous page and show save alert cy.url().should('contain', '/veteran-information'); - cy.findByText('We’ve updated your mailing address').should('exist'); + cy.findByText('We’ve updated your contact information.').should('exist'); cy.findByText( 'We’ve made these changes to this form and your VA.gov profile.', ).should('exist'); diff --git a/src/applications/_mock-form-ae-design-patterns/utils/asyncSetUpCommonFunctionality.js b/src/applications/_mock-form-ae-design-patterns/utils/asyncSetUpCommonFunctionality.js index cf6d494493c3..878e55166c2a 100644 --- a/src/applications/_mock-form-ae-design-patterns/utils/asyncSetUpCommonFunctionality.js +++ b/src/applications/_mock-form-ae-design-patterns/utils/asyncSetUpCommonFunctionality.js @@ -2,7 +2,7 @@ import * as Sentry from '@sentry/browser'; import { connectFeatureToggle } from 'platform/utilities/feature-toggles'; import startSitewideComponents from 'platform/site-wide'; -import createRtkCommonStore from './rtkStore'; +import createCommonStore from 'platform/startup/store'; /** * Wrapper for creating a store and sitewide components, this async version is used @@ -28,7 +28,7 @@ export default async function asyncSetUpCommonFunctionality({ // Set the app name for use in the apiRequest helper window.appName = entryName; - const store = createRtkCommonStore(reducer, analyticsEvents); + const store = createCommonStore(reducer, analyticsEvents); await connectFeatureToggle(store.dispatch); if (url?.endsWith('/')) {