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 (
+
+
+
+ );
+};
+
+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 (
+
+ );
+};
+
+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('/')) {