It can take up to 10 days for us to receive your form. Your
- confirmation number is {formSubmissionId}.
+ confirmation number is {confirmationNumber}.
- {
pageList={props.route.pageList}
startText="Add or remove a dependent"
headingLevel={2}
- />
+ >
+
+ You should also know that we updated our online form.{' '}
+
+ If you started applying online before {V2_LAUNCH_DATE},
+ {' '}
+ you’ll need to review the information in your application.Select
+ Continue your application to use our updated form.
+
+
+
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/hooks/useLocalStorage.js b/src/applications/_mock-form-ae-design-patterns/hooks/useLocalStorage.js
index fd86dbbfa505..2ecc282895b3 100644
--- a/src/applications/_mock-form-ae-design-patterns/hooks/useLocalStorage.js
+++ b/src/applications/_mock-form-ae-design-patterns/hooks/useLocalStorage.js
@@ -3,9 +3,10 @@ import { useState, useEffect } from 'react';
* useLocalStorage is a hook that provides a way to store and retrieve values from localStorage
* @param {string} key - The key to store the value under
* @param {any} defaultValue - The default value to use if the key does not exist
+ * @param {boolean} json - Whether to parse the value as JSON, this way a stringified object can be stored and retrieved as an object
* @returns {array} An array with [value, setValue, clearValue]
*/
-export const useLocalStorage = (key, defaultValue) => {
+export const useLocalStorage = (key, defaultValue, json = false) => {
const [value, setValue] = useState(() => {
let currentValue;
@@ -13,7 +14,7 @@ export const useLocalStorage = (key, defaultValue) => {
const item = localStorage.getItem(key);
if (item === null) {
currentValue = defaultValue;
- } else if (item.startsWith('{') || item.startsWith('[')) {
+ } else if (json && (item.startsWith('{') || item.startsWith('['))) {
currentValue = JSON.parse(item);
} else {
currentValue = item;
@@ -29,14 +30,21 @@ export const useLocalStorage = (key, defaultValue) => {
useEffect(
() => {
- localStorage.setItem(key, JSON.stringify(value));
+ if (value === null) {
+ localStorage.removeItem(key);
+ return;
+ }
+ if (json) {
+ localStorage.setItem(key, JSON.stringify(value));
+ return;
+ }
+ localStorage.setItem(key, value);
},
- [value, key],
+ [value, key, json],
);
const clearValue = () => {
- localStorage.removeItem(key);
- setValue(defaultValue);
+ setValue(null);
};
return [value, setValue, clearValue];
diff --git a/src/applications/_mock-form-ae-design-patterns/hooks/useMockedLogin.js b/src/applications/_mock-form-ae-design-patterns/hooks/useMockedLogin.js
index ec6162de8a80..07614b965724 100644
--- a/src/applications/_mock-form-ae-design-patterns/hooks/useMockedLogin.js
+++ b/src/applications/_mock-form-ae-design-patterns/hooks/useMockedLogin.js
@@ -2,23 +2,31 @@ import { useEffect, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { teardownProfileSession } from 'platform/user/profile/utilities';
import { updateLoggedInStatus } from 'platform/user/authentication/actions';
+import { refreshProfile } from 'platform/user/exportsFile';
import { initializeProfile } from 'platform/user/profile/actions';
import { useLocalStorage } from './useLocalStorage';
+// useMockedLogin is a hook that provides a way to log in and out of the application
+// it also provides a way to check if the user is logged in
+// used for local development
export const useMockedLogin = () => {
const [
localHasSession,
setLocalHasSession,
clearLocalHasSession,
- ] = useLocalStorage('hasSession', '');
+ ] = useLocalStorage('hasSession', null);
const loggedInFromState = useSelector(
state => state?.user?.login?.currentlyLoggedIn,
);
+ /**
+ * memoized value that is true if the local storage has a session or the redux store has a logged in status
+ * @returns {boolean}
+ */
const loggedIn = useMemo(
- () => localHasSession === 'true' || loggedInFromState,
+ () => localHasSession === 'true' && loggedInFromState,
[localHasSession, loggedInFromState],
);
@@ -26,7 +34,9 @@ export const useMockedLogin = () => {
const logIn = () => {
setLocalHasSession('true');
- dispatch(initializeProfile());
+ dispatch(updateLoggedInStatus(true));
+ // get the profile right away, so that user state is updated in the redux store
+ dispatch(refreshProfile());
};
const logOut = () => {
@@ -35,6 +45,12 @@ export const useMockedLogin = () => {
clearLocalHasSession();
};
+ /**
+ * useLoggedInQuery is a hook that checks the url query params for loggedIn=true or loggedIn=false
+ * and sets the local storage and redux store accordingly
+ * @param {*} location - the location object from react router
+ * @returns {void}
+ */
const useLoggedInQuery = location => {
useEffect(
() => {
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/drupal-vamc-data/mockLocalDSOT.js b/src/applications/_mock-form-ae-design-patterns/mocks/script/drupal-vamc-data/mockLocalDSOT.js
index c78d14e6f479..560029507a3f 100644
--- a/src/applications/_mock-form-ae-design-patterns/mocks/script/drupal-vamc-data/mockLocalDSOT.js
+++ b/src/applications/_mock-form-ae-design-patterns/mocks/script/drupal-vamc-data/mockLocalDSOT.js
@@ -4,13 +4,30 @@ const dfns = require('date-fns');
const fetch = require('node-fetch');
const { warn, error, success, debug } = require('../utils');
-const cwd = process.cwd();
+const findBuildRoot = startDir => {
+ let currentDir = startDir;
-const buildLocalhostPath = './build/localhost/data/cms/vamc-ehr.json';
+ // Walk up until we find .build or hit the root
+ while (currentDir !== path.parse(currentDir).root) {
+ const buildPath = path.join(currentDir, 'build');
-const urlForStagingVamcDSOT = 'https://staging.va.gov/data/cms/vamc-ehr.json';
-const pathToSaveData = path.join(cwd, buildLocalhostPath);
+ if (fs.existsSync(buildPath)) {
+ return currentDir;
+ }
+
+ currentDir = path.dirname(currentDir);
+ }
+ throw new Error('Could not find .build directory in parent directories');
+};
+
+const buildRoot = findBuildRoot(__dirname);
+const pathToSaveData = path.join(
+ buildRoot,
+ 'build/localhost/data/cms/vamc-ehr.json',
+);
+
+const urlForStagingVamcDSOT = 'https://staging.va.gov/data/cms/vamc-ehr.json';
const amount = 14;
const twoWeeksAgo = dfns.subDays(new Date(), amount);
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..7fb227367657 100644
--- a/src/applications/_mock-form-ae-design-patterns/mocks/server.js
+++ b/src/applications/_mock-form-ae-design-patterns/mocks/server.js
@@ -48,8 +48,8 @@ const responses = {
generateFeatureToggles({
aedpVADX: true,
aedpPrefill: true,
- coeAccess: false,
- profileUseExperimental: false,
+ coeAccess: true,
+ profileUseExperimental: true,
}),
),
secondsOfDelay,
@@ -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/nodemon.json b/src/applications/_mock-form-ae-design-patterns/nodemon.json
new file mode 100644
index 000000000000..f820c219006c
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/nodemon.json
@@ -0,0 +1,9 @@
+{
+ "watch": [
+ "src/applications/_mock-form-ae-design-patterns/vadx/server/**/*.js",
+ "src/applications/_mock-form-ae-design-patterns/mocks/**/*.js"
+ ],
+ "ignore": ["*.spec.js"],
+ "ext": "js,json",
+ "delay": "1000"
+}
\ No newline at end of file
diff --git a/src/applications/_mock-form-ae-design-patterns/package.json b/src/applications/_mock-form-ae-design-patterns/package.json
index 92d38c7c9d5b..9501a1e260b8 100644
--- a/src/applications/_mock-form-ae-design-patterns/package.json
+++ b/src/applications/_mock-form-ae-design-patterns/package.json
@@ -2,7 +2,9 @@
"name": "@department-of-veterans-affairs/applications-mock-form-ae-design-patterns",
"version": "1.0.0",
"scripts": {
- "demo": "echo DEMOING $npm_package_name v $npm_package_version with WORKSPACES"
+ "demo": "echo DEMOING $npm_package_name v $npm_package_version with WORKSPACES",
+ "vadx": "node vadx/server/index.js --entry='mock-form-ae-design-patterns'",
+ "vadx:dev": "nodemon vadx/server/index.js"
},
"licence": "MIT",
"dependencies": {},
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/patterns/pattern2/TaskGray/form/config/form.js b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/form/config/form.js
index b926c073f0a0..04c7c78f4791 100644
--- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/form/config/form.js
+++ b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/form/config/form.js
@@ -15,13 +15,12 @@ import { VIEW_FIELD_SCHEMA } from 'applications/_mock-form-ae-design-patterns/ut
import { ContactInformationInfoSection } from '../components/ContactInfo';
import VeteranProfileInformation from '../components/VeteranProfileInformation';
import IntroductionPage from '../containers/IntroductionPage';
-import manifest from '../manifest.json';
import { definitions } from './schemaImports';
import profileContactInfo from './profileContactInfo';
import ReviewPage from '../../pages/ReviewPage';
const formConfig = {
- rootUrl: manifest.rootUrl,
+ rootUrl: '/mock-form-ae-design-patterns',
urlPrefix: '/2/task-gray/',
// submitUrl: `${environment.API_URL}/v0/coe/submit_coe_claim`,
// transformForSubmit: customCOEsubmit,
diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/form/manifest.json b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/form/manifest.json
deleted file mode 100644
index 5a8bc1ff23a9..000000000000
--- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/form/manifest.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "appName": "Apply for Certificate of Eligibility",
- "entryFile": "./app-entry.jsx",
- "entryName": "coe",
- "rootUrl": "/mock-form-ae-design-patterns",
- "productId": "56491e7e-ed71-42c1-9678-f8e0b4cacb07"
-}
diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/status/app-entry.jsx b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/status/app-entry.jsx
deleted file mode 100644
index a1f92d3bdb3f..000000000000
--- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/status/app-entry.jsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import 'platform/polyfills';
-import './sass/coe-status.scss';
-
-import startApp from 'platform/startup';
-
-import routes from './routes';
-import reducer from '../shared/reducers';
-import manifest from './manifest.json';
-
-startApp({
- entryName: manifest.entryName,
- url: manifest.rootUrl,
- reducer,
- routes,
-});
diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/status/manifest.json b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/status/manifest.json
deleted file mode 100644
index 7c80196ad359..000000000000
--- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskGray/status/manifest.json
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "appName": "Your VA home loan COE",
- "entryFile": "./app-entry.jsx",
- "entryName": "coe-status",
- "rootUrl": "/housing-assistance/home-loans/check-coe-status/your-coe"
-}
diff --git a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskOrange/manifest.json b/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskOrange/manifest.json
deleted file mode 100644
index 87e3c0694ee9..000000000000
--- a/src/applications/_mock-form-ae-design-patterns/patterns/pattern2/TaskOrange/manifest.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "appName": "22-1990 Education benefits form",
- "entryFile": "./edu-benefits-entry.jsx",
- "entryName": "1990-edu-benefits",
- "rootUrl": "/mock-form-ae-design-patterns",
- "productId": "74dba175-91a7-4986-86cb-7070773e1abe"
-}
diff --git a/src/applications/_mock-form-ae-design-patterns/routes.jsx b/src/applications/_mock-form-ae-design-patterns/routes.jsx
index aeda30b19659..aa3600f6c62e 100644
--- a/src/applications/_mock-form-ae-design-patterns/routes.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/routes.jsx
@@ -28,9 +28,11 @@ const Form1990Entry = lazy(() =>
import { plugin } from './shared/components/VADXPlugin';
-const DevPanel = lazy(() => import('./vadx/app/pages/DevPanel'));
-
import { VADX } from './vadx';
+import { Debug } from './vadx/app/pages/debug/Debug';
+import { withLayout } from './vadx/app/layout/withLayout';
+import { Servers } from './vadx/app/pages/servers/Servers';
+import { FeatureToggles } from './vadx/app/pages/feature-toggles/FeatureToggles';
// Higher order component to wrap routes in the PatternConfigProvider and other common components
const routeHoc = Component => props => (
@@ -118,8 +120,16 @@ const routes = [
...pattern1Routes,
...pattern2Routes,
{
- path: '/dev',
- component: routeHoc(DevPanel),
+ path: '/vadx',
+ component: routeHoc(withLayout(Servers)),
+ },
+ {
+ path: '/vadx/debug',
+ component: routeHoc(withLayout(Debug)),
+ },
+ {
+ path: '/vadx/feature-toggles',
+ component: routeHoc(withLayout(FeatureToggles)),
},
{
path: '*',
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 && (
+
+ );
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/constants.js b/src/applications/_mock-form-ae-design-patterns/vadx/constants.js
new file mode 100644
index 000000000000..15e96f2c5906
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/constants.js
@@ -0,0 +1,7 @@
+const constants = {
+ API_BASE_URL: 'http://localhost:1337',
+ FRONTEND_PROCESS_NAME: 'frontend-server',
+ MOCK_SERVER_PROCESS_NAME: 'mock-server',
+};
+
+module.exports = constants;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/context/processManager.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/context/processManager.jsx
new file mode 100644
index 000000000000..5f9a9c87cf66
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/context/processManager.jsx
@@ -0,0 +1,239 @@
+import React, {
+ createContext,
+ useContext,
+ useState,
+ useRef,
+ useCallback,
+ useMemo,
+} from 'react';
+import PropTypes from 'prop-types';
+import { formatDate } from '../utils/dates';
+import { API_BASE_URL } from '../constants';
+
+const ProcessManagerContext = createContext(null);
+
+export const ProcessManagerProvider = ({ children }) => {
+ const [processes, setProcesses] = useState({});
+ const [activeApps, setActiveApps] = useState({});
+ const [output, setOutput] = useState({});
+ const eventSourcesRef = useRef({});
+ const [manifests, setManifests] = useState([]);
+
+ const handleSSEMessage = useCallback((processName, event) => {
+ const parsedEvent = JSON.parse(event.data);
+
+ if (parsedEvent.type === 'status') {
+ if (parsedEvent.data === 'stopped') {
+ // remove the process from the output
+ setOutput(prev => {
+ // eslint-disable-next-line no-unused-vars
+ const { [processName]: _, ...rest } = prev;
+ return rest;
+ });
+ return;
+ }
+ setProcesses(prev => ({
+ ...prev,
+ [processName]: {
+ ...prev[processName],
+ status: parsedEvent.data.status,
+ lastUpdate: parsedEvent.data.timestamp,
+ metadata: parsedEvent.data.metadata,
+ },
+ }));
+ return;
+ }
+
+ if (parsedEvent.type === 'stdout' || parsedEvent.type === 'stderr') {
+ setOutput(prev => ({
+ ...prev,
+ [processName]: [
+ {
+ id: Date.now(),
+ friendlyDate: formatDate(new Date().toISOString()),
+ ...parsedEvent,
+ },
+ ...(prev[processName] || []),
+ ],
+ }));
+ }
+
+ if (parsedEvent.type === 'cache') {
+ setOutput(prev => ({
+ ...prev,
+ [processName]: [
+ ...(prev[processName] || []),
+ ...parsedEvent.data.map((line, index) => ({
+ id: `${processName}-cache-${index}`,
+ friendlyDate: formatDate(new Date().toISOString()),
+ data: line,
+ })),
+ ],
+ }));
+ }
+ }, []);
+
+ // Move all the event source and process management logic here
+ const setupEventSource = useCallback(
+ processName => {
+ const eventSource = new EventSource(
+ `${API_BASE_URL}/events/${processName}`,
+ );
+
+ eventSource.onmessage = event => {
+ handleSSEMessage(processName, event);
+ };
+
+ eventSource.onerror = () => {
+ eventSource.close();
+ delete eventSourcesRef.current[processName];
+ };
+
+ return eventSource;
+ },
+ [handleSSEMessage],
+ );
+
+ const fetchStatus = useCallback(
+ async () => {
+ try {
+ const response = await fetch(`${API_BASE_URL}/status`);
+ const { processes: processStatus, apps } = await response.json();
+ setProcesses(processStatus);
+ setActiveApps(apps);
+
+ // Setup or tear down event sources based on process status
+ Object.keys(processStatus).forEach(processName => {
+ if (
+ processStatus[processName] &&
+ !eventSourcesRef.current[processName]
+ ) {
+ eventSourcesRef.current[processName] = setupEventSource(
+ processName,
+ );
+ } else if (
+ !processStatus[processName] &&
+ eventSourcesRef.current[processName]
+ ) {
+ eventSourcesRef.current[processName].close();
+ delete eventSourcesRef.current[processName];
+ }
+ });
+ } catch (error) {
+ setProcesses({});
+ setActiveApps([]);
+ }
+ },
+ [setupEventSource],
+ ); // Empty dependency array since it only uses stable references
+
+ const fetchManifests = useCallback(async () => {
+ try {
+ const response = await fetch(`${API_BASE_URL}/manifests`);
+ const data = await response.json();
+ setManifests(data.manifests);
+ return data.manifests;
+ } catch (error) {
+ return [];
+ }
+ }, []); // Empty dependency array since it only uses stable references
+
+ const startProcess = async (processName, processConfig) => {
+ try {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout
+
+ const response = await fetch(`${API_BASE_URL}/start-${processName}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify(processConfig),
+ signal: controller.signal,
+ });
+
+ clearTimeout(timeoutId);
+
+ if (response.ok) {
+ delete eventSourcesRef.current[processName];
+ fetchStatus();
+ return true;
+ }
+
+ throw new Error('Failed to start process');
+ } catch (error) {
+ if (error.name === 'AbortError') {
+ // remove process from event sources
+ delete eventSourcesRef.current[processName];
+ fetchStatus();
+ return true;
+ }
+ return false;
+ }
+ };
+
+ const stopProcess = async (processName, port) => {
+ try {
+ const controller = new AbortController();
+ const timeoutId = setTimeout(() => controller.abort(), 5000); // 5s timeout
+
+ const response = await fetch(`${API_BASE_URL}/stop`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ port }),
+ signal: controller.signal,
+ });
+
+ clearTimeout(timeoutId);
+
+ if (response.ok) {
+ if (eventSourcesRef.current[processName]) {
+ eventSourcesRef.current[processName].close();
+ delete eventSourcesRef.current[processName];
+ }
+ fetchStatus();
+ return true;
+ }
+
+ throw new Error('Failed to stop process');
+ } catch (error) {
+ if (error.name === 'AbortError') {
+ // This is expected when server restarts
+ fetchStatus();
+ return true;
+ }
+ return false;
+ }
+ };
+
+ const value = {
+ processes,
+ output,
+ startProcess,
+ stopProcess,
+ fetchStatus,
+ setupEventSource,
+ manifests,
+ fetchManifests,
+ activeApps: useMemo(() => activeApps, [activeApps]),
+ setOutput,
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+ProcessManagerProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+};
+
+export const useProcessManager = () => {
+ const context = useContext(ProcessManagerContext);
+ if (!context) {
+ throw new Error(
+ 'useProcessManager must be used within a ProcessManagerProvider',
+ );
+ }
+ return context;
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/context/vadx.js b/src/applications/_mock-form-ae-design-patterns/vadx/context/vadx.js
index 98bffcc69624..18b824fd7c37 100644
--- a/src/applications/_mock-form-ae-design-patterns/vadx/context/vadx.js
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/context/vadx.js
@@ -94,6 +94,12 @@ export const VADXProvider = ({ children }) => {
[broadcastChannel],
);
+ const createUpdateHandlerByKey = key => {
+ return update => {
+ setSyncedData({ ...preferences, [key]: update });
+ };
+ };
+
// update the loading state for the dev tools
const updateDevLoading = isLoading => {
setSyncedData({ ...preferences, isDevLoading: isLoading });
@@ -117,6 +123,9 @@ export const VADXProvider = ({ children }) => {
setSyncedData({ ...preferences, showVADX: show });
};
+ const updateFeApi = createUpdateHandlerByKey('feApiUrl');
+ const updateBeApi = createUpdateHandlerByKey('beApiUrl');
+
// update local toggles
const updateLocalToggles = useCallback(
async toggles => {
@@ -148,10 +157,12 @@ export const VADXProvider = ({ children }) => {
return (
{
updateShowVADX,
updateLocalToggles,
updateClearLocalToggles,
+ updateFeApi,
+ updateBeApi,
debouncedSetSearchQuery,
- togglesLoading,
- togglesState,
}}
>
{children}
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/index.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/index.jsx
index 6efb6c00ecd3..cf765d8cd241 100644
--- a/src/applications/_mock-form-ae-design-patterns/vadx/index.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/index.jsx
@@ -1,5 +1,6 @@
import React, { Suspense } from 'react';
import PropTypes from 'prop-types';
+import { Servers } from './app/pages/servers/Servers';
import { VADXProvider } from './context/vadx';
import { VADXPanelLoader } from './panel/VADXPanelLoader';
@@ -33,3 +34,5 @@ VADX.propTypes = {
featureToggleName: PropTypes.string,
plugin: PropTypes.object,
};
+
+export { Servers as VADXServersRoute };
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormTab.jsx b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormTab.jsx
index 9830ee426e71..4634aff3f3ec 100644
--- a/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormTab.jsx
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/panel/tabs/form/FormTab.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import { Link, withRouter } from 'react-router';
import { useSelector } from 'react-redux';
+import PropTypes from 'prop-types';
import ChapterAnalyzer from './ChapterAnalyzer';
import { FormDataViewer } from './FormDataViewer';
@@ -69,4 +70,8 @@ const FormTabBase = props => {
);
};
+FormTabBase.propTypes = {
+ router: PropTypes.object.isRequired,
+};
+
export const FormTab = withRouter(FormTabBase);
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/constants/mockServerPaths.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/constants/mockServerPaths.js
new file mode 100644
index 000000000000..82bfed18a4c2
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/constants/mockServerPaths.js
@@ -0,0 +1,44 @@
+const MOCK_SERVER_PATHS = [
+ 'src/applications/_mock-form-ae-design-patterns/mocks/server.js',
+ 'src/applications/appeals/shared/tests/mock-api.js',
+ 'src/applications/avs/api/mocks/index.js',
+ 'src/applications/check-in/api/local-mock-api/index.js',
+ 'src/applications/combined-debt-portal/combined/utils/mocks/mockServer.js',
+ 'src/applications/disability-benefits/2346/mocks/index.js',
+ 'src/applications/disability-benefits/all-claims/local-dev-mock-api/index.js',
+ 'src/applications/education-letters/testing/response.js',
+ 'src/applications/financial-status-report/mocks/responses.js',
+ 'src/applications/health-care-supply-reordering/mocks/index.js',
+ 'src/applications/ivc-champva/10-10D/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/ivc-champva/10-7959C/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/ivc-champva/10-7959f-1/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/mhv-landing-page/mocks/api/index.js',
+ 'src/applications/mhv-medications/mocks/api/index.js',
+ 'src/applications/mhv-secure-messaging/api/mocks/index.js',
+ 'src/applications/mhv-supply-reordering/mocks/index.js',
+ 'src/applications/my-education-benefits/testing/responses.js',
+ 'src/applications/personalization/dashboard/mocks/server.js',
+ 'src/applications/personalization/notification-center/mocks/server.js',
+ 'src/applications/personalization/profile/mocks/server.js',
+ 'src/applications/personalization/review-information/tests/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/post-911-gib-status/mocks/server.js',
+ 'src/applications/representative-appoint/mocks/server.js',
+ 'src/applications/simple-forms/20-10206/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/simple-forms/20-10207/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/simple-forms/21-0845/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/simple-forms/21-0966/tests/e2e/fixtures/mocks/local-mock-api-responses.js',
+ 'src/applications/simple-forms/21-0972/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/simple-forms/21-10210/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/simple-forms/21-4138/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/simple-forms/21-4142/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/simple-forms/40-0247/tests/e2e/fixtures/mocks/local-mock-api-reponses.js',
+ 'src/applications/simple-forms/form-upload/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/simple-forms/mock-simple-forms-patterns/tests/e2e/fixtures/mocks/local-mock-responses.js',
+ 'src/applications/travel-pay/services/mocks/index.js',
+ 'src/applications/vaos/services/mocks/index.js',
+ 'src/platform/mhv/api/mocks/index.js',
+ 'src/platform/mhv/downtime/mocks/api/index.js',
+ 'src/platform/testing/local-dev-mock-api/common.js',
+];
+
+module.exports = { MOCK_SERVER_PATHS };
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/index.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/index.js
new file mode 100644
index 000000000000..1c7ccebc8b5c
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/index.js
@@ -0,0 +1,34 @@
+/* eslint-disable no-console */
+const express = require('express');
+
+const cors = require('./utils/cors');
+const { initializeManifests } = require('./utils/manifests');
+const { autoStartServers } = require('./utils/processes');
+const parseArgs = require('./utils/parseArgs');
+
+const app = express();
+const port = 1337;
+const args = parseArgs();
+
+app.use(express.json());
+
+// Allow CORS
+app.use(cors);
+
+const router = express.Router();
+
+router.use(require('./routes/events'));
+router.use(require('./routes/manifests'));
+router.use(require('./routes/output'));
+router.use(require('./routes/start-mock-server'));
+router.use(require('./routes/start-frontend-server'));
+router.use(require('./routes/status'));
+router.use(require('./routes/stop-on-port'));
+
+app.use(router);
+
+app.listen(port, async () => {
+ await initializeManifests();
+ await autoStartServers(args);
+ console.log(`Process manager server listening at http://localhost:${port}`);
+});
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/events.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/events.js
new file mode 100644
index 000000000000..3dc223a0ea15
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/events.js
@@ -0,0 +1,36 @@
+const express = require('express');
+const { outputCache, clients, sendSSE } = require('../utils/processes');
+
+const router = express.Router();
+
+router.get('/events/:name', (req, res) => {
+ const { name } = req.params;
+
+ res.writeHead(200, {
+ 'Content-Type': 'text/event-stream',
+ 'Cache-Control': 'no-cache',
+ Connection: 'keep-alive',
+ });
+
+ // Send the current cache immediately
+ if (outputCache[name]) {
+ sendSSE(res, { type: 'cache', data: outputCache[name] });
+ }
+
+ // Add this client to the list of clients for this process
+ if (!clients.has(name)) {
+ clients.set(name, []);
+ }
+ clients.get(name).push(res);
+
+ // Remove the client when the connection is closed
+ req.on('close', () => {
+ const clientsForProcess = clients.get(name) || [];
+ const index = clientsForProcess.indexOf(res);
+ if (index !== -1) {
+ clientsForProcess.splice(index, 1);
+ }
+ });
+});
+
+module.exports = router;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/manifests.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/manifests.js
new file mode 100644
index 000000000000..529a411380fe
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/manifests.js
@@ -0,0 +1,15 @@
+const express = require('express');
+const { getCachedManifests } = require('../utils/manifests');
+
+const router = express.Router();
+
+router.get('/manifests', (req, res) => {
+ const manifests = getCachedManifests();
+ res.json({
+ success: true,
+ count: manifests.length,
+ manifests,
+ });
+});
+
+module.exports = router;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/output.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/output.js
new file mode 100644
index 000000000000..d267b03066c5
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/output.js
@@ -0,0 +1,21 @@
+const express = require('express');
+const { outputCache } = require('../utils/processes');
+
+const router = express.Router();
+
+router.get('/output/:name', (req, res) => {
+ const { name } = req.params;
+ if (outputCache[name]) {
+ res.json(outputCache[name]);
+ } else {
+ res
+ .status(404)
+ .json({ error: `No output cache found for process ${name}` });
+ }
+});
+
+router.get('/output', (req, res) => {
+ res.json({ all: outputCache });
+});
+
+module.exports = router;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/start-frontend-server.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/start-frontend-server.js
new file mode 100644
index 000000000000..b4e3f4a042fd
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/start-frontend-server.js
@@ -0,0 +1,85 @@
+const express = require('express');
+const { killProcessOnPort } = require('../utils/processes');
+const { startProcess } = require('../utils/processes');
+const paths = require('../utils/paths');
+const logger = require('../utils/logger');
+const { getCachedManifests } = require('../utils/manifests');
+const { FRONTEND_PROCESS_NAME } = require('../../constants');
+
+const router = express.Router();
+
+router.post('/start-frontend-server', async (req, res) => {
+ const { entries = [] } = req.body;
+
+ if (!Array.isArray(entries) || entries.length === 0) {
+ return res.status(400).json({
+ success: false,
+ message: 'Request body must include an array of entry strings',
+ });
+ }
+
+ try {
+ const manifests = getCachedManifests();
+
+ // Find manifests that match the requested entries
+ const validManifests = entries
+ .map(entry => manifests.find(manifest => manifest.entryName === entry))
+ .filter(Boolean);
+
+ // check if we found all requested entries
+ if (validManifests.length !== entries.length) {
+ const foundEntries = validManifests.map(m => m.entryName);
+ const invalidEntries = entries.filter(
+ entry => !foundEntries.includes(entry),
+ );
+
+ // provides the invalid entries and the available entries
+ // might remove the available entries in the future just to be more consistent
+ // but this way we can provide the user with the available entries
+ return res.status(400).json({
+ success: false,
+ message: 'Invalid entry names provided',
+ invalidEntries,
+ availableEntries: manifests.map(m => m.entryName).filter(Boolean),
+ });
+ }
+
+ // Use only the validated entry names from the manifests
+ // this way user input is not used to start the server
+ const validatedEntries = validManifests.map(m => m.entryName);
+
+ await killProcessOnPort('3001');
+
+ const result = await startProcess(
+ FRONTEND_PROCESS_NAME,
+ 'yarn',
+ [
+ '--cwd',
+ paths.root,
+ 'watch',
+ '--env',
+ `entry=${validatedEntries.join(',')}`,
+ 'api=http://localhost:3000',
+ ],
+ {
+ forceRestart: true,
+ metadata: {
+ entries: validatedEntries,
+ },
+ },
+ );
+
+ logger.debug('result', result);
+
+ return res.status(200).json(result);
+ } catch (error) {
+ logger.error('Error in /start-frontend-server:', error);
+ return res.status(500).json({
+ success: false,
+ message: 'Failed to validate entries',
+ error: error.message,
+ });
+ }
+});
+
+module.exports = router;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/start-mock-server.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/start-mock-server.js
new file mode 100644
index 000000000000..e2db13f5c1d0
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/start-mock-server.js
@@ -0,0 +1,57 @@
+const express = require('express');
+const path = require('path');
+const { killProcessOnPort, startProcess } = require('../utils/processes');
+const paths = require('../utils/paths');
+const { MOCK_SERVER_PATHS } = require('../constants/mockServerPaths');
+const { MOCK_SERVER_PROCESS_NAME } = require('../../constants');
+
+const router = express.Router();
+
+router.post('/start-mock-server', async (req, res) => {
+ const { responsesPath } = req.body;
+
+ if (!responsesPath) {
+ return res.status(400).json({
+ success: false,
+ message: 'responsesPath is required',
+ });
+ }
+
+ // Normalize the path for comparison just in case there are any issues with the path string
+ const normalizedPath = path.normalize(responsesPath).replace(/\\/g, '/');
+
+ const matchingPathIndex = MOCK_SERVER_PATHS.findIndex(
+ allowedPath =>
+ path.normalize(allowedPath).replace(/\\/g, '/') === normalizedPath,
+ );
+
+ if (matchingPathIndex === -1) {
+ return res.status(403).json({
+ success: false,
+ message: 'Invalid responses path',
+ allowedPaths: MOCK_SERVER_PATHS, // I might remove this in the future
+ });
+ }
+
+ // Use the validated path from our array
+ // this way we are not using user input to start the server
+ const validatedPath = path.join(
+ paths.root,
+ MOCK_SERVER_PATHS[matchingPathIndex],
+ );
+
+ await killProcessOnPort('3000');
+
+ const result = await startProcess(
+ MOCK_SERVER_PROCESS_NAME,
+ 'node',
+ [paths.mockApi, '--responses', validatedPath],
+ {
+ forceRestart: true,
+ },
+ );
+
+ return res.json(result);
+});
+
+module.exports = router;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/status.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/status.js
new file mode 100644
index 000000000000..a4f896d1df7a
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/status.js
@@ -0,0 +1,82 @@
+// server/routes/status.js
+const express = require('express');
+const { processes } = require('../utils/processes');
+const { getCachedManifests } = require('../utils/manifests');
+const logger = require('../utils/logger');
+const { FRONTEND_PROCESS_NAME } = require('../../constants');
+
+const router = express.Router();
+
+/**
+ * Extracts entry names from frontend process args
+ * @param {string[]} args - Process spawn arguments
+ * @returns {string[]} Array of entry names
+ */
+const getEntryNamesFromArgs = args => {
+ const entryArg = args.find(arg => arg.startsWith('entry='));
+ if (!entryArg) return [];
+
+ // Split on comma to handle multiple entries
+ return entryArg.replace('entry=', '').split(',');
+};
+
+/**
+ * Gets app information from manifests for given entry names
+ * @param {string[]} entryNames - Array of entry names to look up
+ * @returns {Object[]} Array of app information objects
+ */
+const getAppInfo = async entryNames => {
+ const apps = [];
+ const manifestFiles = getCachedManifests();
+
+ entryNames.forEach(entryName => {
+ const manifest = manifestFiles.find(m => m.entryName === entryName);
+ if (manifest) {
+ apps.push({
+ entryName,
+ rootUrl: manifest.rootUrl,
+ appName: manifest.appName || '',
+ });
+ }
+ });
+
+ return apps;
+};
+
+router.get('/status', async (req, res) => {
+ try {
+ // Get basic process status info
+ const processStatus = Object.keys(processes).reduce((acc, name) => {
+ acc[name] = {
+ pid: processes[name].pid,
+ killed: processes[name].killed,
+ exitCode: processes[name].exitCode,
+ signalCode: processes[name].signalCode,
+ args: processes[name].spawnargs,
+ };
+ return acc;
+ }, {});
+
+ // Get route information for frontend process if running
+ let apps = [];
+ if (processStatus[FRONTEND_PROCESS_NAME]) {
+ const entryNames = getEntryNamesFromArgs(
+ processStatus[FRONTEND_PROCESS_NAME].args,
+ );
+ apps = await getAppInfo(entryNames);
+ }
+
+ res.json({
+ processes: processStatus,
+ apps,
+ });
+ } catch (error) {
+ logger.error('Error getting status:', error);
+ res.status(500).json({
+ error: 'Failed to get server status',
+ message: error.message,
+ });
+ }
+});
+
+module.exports = router;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/stop-on-port.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/stop-on-port.js
new file mode 100644
index 000000000000..836dbe6a98ca
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/routes/stop-on-port.js
@@ -0,0 +1,45 @@
+const express = require('express');
+const { killProcessOnPort } = require('../utils/processes');
+const logger = require('../utils/logger');
+
+const router = express.Router();
+
+router.post('/stop', async (req, res) => {
+ const { port: portToStop } = req.body;
+
+ // Validate port is a number and within allowed range
+ // adds a bit of protection from invalid port numbers
+ const port = parseInt(portToStop, 10);
+ if (Number.isNaN(port) || port < 1024 || port > 65535) {
+ return res.status(400).json({
+ success: false,
+ message: 'Invalid port number. Must be between 1024 and 65535',
+ });
+ }
+
+ // Only allow stopping known development ports
+ // if 1337 is in the list, we are stopping the whole server manager aka this server
+ const allowedPorts = [3000, 3001, 3002, 1337];
+ if (!allowedPorts.includes(port)) {
+ return res.status(403).json({
+ success: false,
+ message: 'Not allowed to stop processes on this port',
+ });
+ }
+
+ try {
+ await killProcessOnPort(port);
+ return res.json({
+ success: true,
+ message: `Process on port ${port} stopped`,
+ });
+ } catch (error) {
+ logger.error(`Error stopping process on port ${port}:`, error);
+ return res.status(500).json({
+ success: false,
+ message: `Error stopping process on port ${port}: ${error.message}`,
+ });
+ }
+});
+
+module.exports = router;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/cors.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/cors.js
new file mode 100644
index 000000000000..8eb7f45e6b30
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/cors.js
@@ -0,0 +1,42 @@
+/**
+ * _LOCAL USE ONLY_ - middleware to enable cross-origin requests (CORS).
+ * Sets necessary CORS headers and handles preflight requests.
+ *
+ * @middleware
+ * @param {import('express').Request} req - Express request object
+ * @param {import('express').Response} res - Express response object
+ * @param {import('express').NextFunction} next - Express next middleware function
+ * @returns {void|Response} Returns 200 for preflight requests, otherwise calls next()
+ *
+ * @example
+ * // Apply as middleware to Express app or router
+ * app.use(cors);
+ *
+ * @security
+ * - Only for use in local server
+ * - Allows all origins ('*')
+ * - Allows all common request methods for REST
+ * - Allows X-Requested-With and content-type headers
+ * - Enables credentials
+ */
+const cors = (req, res, next) => {
+ res.setHeader('Access-Control-Allow-Origin', '*');
+ res.setHeader(
+ 'Access-Control-Allow-Methods',
+ 'GET, POST, OPTIONS, PUT, PATCH, DELETE',
+ );
+ res.setHeader(
+ 'Access-Control-Allow-Headers',
+ 'X-Requested-With,content-type',
+ );
+ res.setHeader('Access-Control-Allow-Credentials', true);
+
+ // Handle preflight requests by immediately responding with 200
+ if (req.method === 'OPTIONS') {
+ return res.sendStatus(200);
+ }
+
+ return next();
+};
+
+module.exports = cors;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/logger.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/logger.js
new file mode 100644
index 000000000000..184256ed6a1e
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/logger.js
@@ -0,0 +1,73 @@
+/* eslint-disable no-console */
+const chalk = require('chalk');
+
+/**
+ * @typedef {(name: string, type: string, message: string|Buffer) => void} ProcessFn
+ * Process-specific logging function
+ */
+
+/**
+ * @typedef {(...args: any[]) => void} LoggerFn
+ * Generic logger function
+ */
+
+/**
+ * @typedef {Object} Logger
+ * @property {LoggerFn} info - log blue message with `[INFO]` prefix
+ * @property {LoggerFn} success - log green message with `[SUCCESS]` prefix
+ * @property {LoggerFn} warn - log yellow message with `[WARN]` prefix
+ * @property {LoggerFn} error - log red message with `[ERROR]` prefix
+ * @property {LoggerFn} debug - log gray message with `[DEBUG]` prefix
+ * @property {ProcessFn} process - log process-specific messages with name and type
+ */
+
+/**
+ * Logger for server-side logging with colored output.
+ * Only creates this logger when running in Node.js environment.
+ * Returns a no-op logger in non-Node environments to prevent accidental logging.
+ *
+ * @type {Logger}
+ *
+ * @example
+ * logger.info('Server started on port 3000');
+ * logger.success('Database connected successfully');
+ * logger.warn('Rate limit approaching');
+ * logger.error('Failed to connect to database');
+ * logger.debug(data);
+ *
+ * // Log process-specific messages, red for stderr type, blue for stdout or other types
+ * logger.process('Server', 'stdout', 'Server initialized');
+ * logger.process('Worker', 'stderr', 'Memory limit exceeded');
+ *
+ * // Handling Buffer messages
+ * const buf = Buffer.from('Process output');
+ * logger.process('Process', 'stdout', buf);
+ */
+const logger =
+ typeof process !== 'undefined' && process.versions?.node
+ ? {
+ info: (...args) => console.log(chalk.blue('[INFO]'), ...args),
+ success: (...args) => console.log(chalk.green('[SUCCESS]'), ...args),
+ warn: (...args) => console.log(chalk.yellow('[WARN]'), ...args),
+ error: (...args) => console.log(chalk.red('[ERROR]'), ...args),
+ debug: (...args) => console.log(chalk.gray('[DEBUG]'), ...args),
+ /**
+ * Log process-specific messages
+ * @type {ProcessFn}
+ */
+ process: (name, type, message) => {
+ const color = type === 'stderr' ? 'red' : 'blue';
+ const text = Buffer.isBuffer(message) ? message.toString() : message;
+ console.log(chalk[color](`[${name}] ${type}:`), text);
+ },
+ }
+ : {
+ info: () => {},
+ success: () => {},
+ warn: () => {},
+ error: () => {},
+ debug: () => {},
+ process: () => {},
+ };
+
+module.exports = logger;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/manifests.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/manifests.js
new file mode 100644
index 000000000000..1ecfbb7dce02
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/manifests.js
@@ -0,0 +1,105 @@
+const fs = require('fs').promises;
+const path = require('path');
+const logger = require('./logger');
+const paths = require('./paths');
+
+/**
+ * @typedef {Object} ManifestFile
+ * @property {string} path - file path to the manifest.json
+ * @property {string} entryName - name of the entry file
+ * @property {string} rootUrl - root url of the app
+ * @property {string} appName - name of the app
+ * @property {string} [productId] - product id of the app
+ */
+
+/** @type {ManifestFile[]} */
+let _cachedManifests = [];
+
+/**
+ * Searches a directory for manifest.json files
+ * @param {string} dir - Dir path to search
+ * @returns {Promise} Array of manifest objects
+ */
+async function findManifestFiles(dir) {
+ const manifests = [];
+
+ /**
+ * Recursively searches directories for manifest files
+ * @param {string} currentDir - Current directory being searched
+ * @returns {Promise}
+ */
+ async function searchDir(currentDir) {
+ try {
+ const files = await fs.readdir(currentDir);
+ const fileStats = await Promise.all(
+ files.map(file => {
+ const filePath = path.join(currentDir, file);
+ return fs.stat(filePath).then(stat => ({ file, filePath, stat }));
+ }),
+ );
+
+ const dirPromises = [];
+ const manifestPromises = [];
+
+ for (const { file, filePath, stat } of fileStats) {
+ // there were a few files called manifest.json in the node_modules folder
+ // so we need to filter them out
+ if (file !== 'node_modules') {
+ if (stat.isDirectory()) {
+ dirPromises.push(searchDir(filePath));
+ } else if (file === 'manifest.json') {
+ try {
+ manifestPromises.push(
+ fs.readFile(filePath, 'utf8').then(content => ({
+ path: filePath,
+ ...JSON.parse(content),
+ })),
+ );
+ } catch (err) {
+ logger.error(`Error reading manifest at ${filePath}:`, err);
+ }
+ }
+ }
+ }
+
+ // using Promise.all to run all promises in parallel and not wait for each one
+ await Promise.all(dirPromises);
+ manifests.push(...(await Promise.all(manifestPromises)));
+ } catch (err) {
+ logger.error(`Error reading directory ${currentDir}:`, err);
+ }
+ }
+
+ await searchDir(dir);
+ return manifests;
+}
+
+/**
+ * Returns the currently cached manifests.
+ * Used because just accessing the `_cachedManifests` variable directly
+ * will only return the initial value `[]`
+ * @returns {ManifestFile[]} Array of cached manifest objects
+ */
+const getCachedManifests = () => _cachedManifests;
+
+/**
+ * Creates the manifest cache.
+ * Used during vadx startup
+ * @returns {Promise}
+ * @throws {Error} If there is an error reading the manifests
+ */
+async function initializeManifests() {
+ try {
+ logger.debug('Scanning for manifests in:', paths.applications);
+ _cachedManifests = await findManifestFiles(paths.applications);
+ logger.info(`Loaded ${_cachedManifests.length} manifests at startup`);
+ } catch (error) {
+ logger.error('Error loading manifests at startup:', error);
+ _cachedManifests = [];
+ }
+}
+
+module.exports = {
+ initializeManifests,
+ getCachedManifests,
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/parseArgs.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/parseArgs.js
new file mode 100644
index 000000000000..8a76055ca1b0
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/parseArgs.js
@@ -0,0 +1,20 @@
+const commandLineArgs = require('command-line-args');
+
+const optionDefinitions = [
+ { name: 'entry', type: String, defaultValue: 'mock-form-ae-design-patterns' },
+ { name: 'api', type: String, defaultValue: 'http://localhost:3000' },
+ { name: 'responses', type: String },
+];
+
+function parseArgs() {
+ const options = commandLineArgs(optionDefinitions);
+ return {
+ entry: options.entry,
+ api: options.api,
+ responses:
+ options.responses ||
+ `src/applications/_mock-form-ae-design-patterns/mocks/server.js`,
+ };
+}
+
+module.exports = parseArgs;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/paths.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/paths.js
new file mode 100644
index 000000000000..6a8ec44f452e
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/paths.js
@@ -0,0 +1,40 @@
+const path = require('path');
+const fs = require('fs');
+
+const findRoot = startDir => {
+ let currentDir = startDir;
+
+ // Walk up the directory tree until we find package.json or hit the root
+ while (currentDir !== path.parse(currentDir).root) {
+ const pkgPath = path.join(currentDir, 'package.json');
+
+ if (fs.existsSync(pkgPath)) {
+ try {
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
+ // Verify this is the right package.json
+ if (pkg.name === 'vets-website') {
+ return currentDir;
+ }
+ } catch (e) {
+ // Continue if package.json is invalid
+ }
+ }
+
+ currentDir = path.dirname(currentDir);
+ }
+
+ throw new Error('Could not find vets-website root directory');
+};
+
+// Get absolute paths that can be used anywhere
+const paths = {
+ root: findRoot(__dirname),
+ get applications() {
+ return path.join(this.root, 'src/applications');
+ },
+ get mockApi() {
+ return path.join(this.root, 'src/platform/testing/e2e/mockapi.js');
+ },
+};
+
+module.exports = paths;
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/processes.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/processes.js
new file mode 100644
index 000000000000..ee2c9750b6a0
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/processes.js
@@ -0,0 +1,201 @@
+const { spawn, exec } = require('child_process');
+const { stripAnsi } = require('./strings');
+const logger = require('./logger');
+const paths = require('./paths');
+const { FRONTEND_PROCESS_NAME } = require('../../constants');
+
+const processes = {};
+const outputCache = {};
+const MAX_CACHE_LINES = 100;
+const clients = new Map();
+
+function killProcessOnPort(portToKill) {
+ return new Promise((resolve, reject) => {
+ const isWin = process.platform === 'win32';
+
+ let command;
+ if (isWin) {
+ command = `FOR /F "tokens=5" %a in ('netstat -aon ^| find ":${portToKill}" ^| find "LISTENING"') do taskkill /F /PID %a`;
+ } else {
+ command = `lsof -ti :${portToKill} | xargs kill -9`;
+ }
+
+ exec(command, error => {
+ if (error) {
+ logger.error(`Error killing process on port ${portToKill}: ${error}`);
+ reject(error);
+ } else {
+ logger.debug(`Process on port ${portToKill} killed`);
+ resolve();
+ }
+ });
+ });
+}
+
+function sendSSE(res, data) {
+ res.write(`data: ${JSON.stringify(data)}\n\n`);
+}
+
+function addToCache(name, type, data) {
+ if (!outputCache[name]) {
+ outputCache[name] = [];
+ }
+
+ const strippedData = stripAnsi(data.toString().trim());
+
+ // Send SSE to all connected clients for this process
+ const clientsForProcess = clients.get(name) || [];
+ clientsForProcess.forEach(client => {
+ sendSSE(client, { type, data: strippedData });
+ });
+
+ if (type === 'status') {
+ return;
+ }
+
+ outputCache[name].unshift(strippedData);
+ if (outputCache[name].length > MAX_CACHE_LINES) {
+ outputCache[name].pop();
+ }
+}
+
+// Modify setupProcessHandlers to include status events
+function setupProcessHandlers(childProcess, procName, metadata) {
+ addToCache(procName, 'status', {
+ status: 'started',
+ timestamp: Date.now(),
+ metadata,
+ });
+
+ const statusInterval = setInterval(() => {
+ if (processes[procName]) {
+ const clientsForProcess = clients.get(procName) || [];
+ clientsForProcess.forEach(client => {
+ sendSSE(client, {
+ type: 'status',
+ data: { status: 'running', metadata },
+ });
+ });
+ } else {
+ clearInterval(statusInterval);
+ }
+ }, 5000);
+
+ childProcess.stdout.on('data', data => {
+ logger.process(procName, 'stdout', data);
+ addToCache(procName, 'stdout', data);
+ });
+
+ childProcess.stderr.on('data', data => {
+ logger.process(procName, 'stderr', data);
+ addToCache(procName, 'stderr', data);
+ });
+
+ childProcess.on('close', code => {
+ logger.process(procName, 'close', code);
+ addToCache(procName, 'status', 'stopped');
+ clearInterval(statusInterval);
+ delete processes[procName];
+ });
+}
+
+function startProcess(procName, command, args, options = {}) {
+ const { forceRestart = false } = options;
+
+ const { metadata } = options;
+
+ logger.debug(`Starting process: ${procName}`);
+ logger.debug(`Command: ${command}`);
+ logger.debug(`Args: ${args}`);
+ logger.debug({ metadata });
+ return new Promise(resolve => {
+ if (processes[procName]) {
+ if (!forceRestart) {
+ return resolve({
+ success: false,
+ message: `Process ${procName} is already running`,
+ });
+ }
+
+ logger.debug(`Force stopping existing process: ${procName}`);
+ const oldProcess = processes[procName];
+
+ // Clean up the old process
+ oldProcess.on('close', () => {
+ delete processes[procName];
+ if (outputCache[procName]) {
+ outputCache[procName] = [];
+ }
+
+ // Start new process after old one is fully cleaned up
+ const childProcess = spawn(command, args, {
+ env: process.env,
+ });
+ processes[procName] = childProcess;
+
+ setupProcessHandlers(childProcess, procName, metadata);
+
+ resolve({
+ success: true,
+ message: `Process ${procName} restarted`,
+ });
+ });
+
+ return oldProcess.kill();
+ }
+ // No existing process, just start a new one
+ const childProcess = spawn(command, args, {
+ env: process.env,
+ });
+ processes[procName] = childProcess;
+
+ setupProcessHandlers(childProcess, procName, metadata);
+
+ return resolve({
+ success: true,
+ message: `Process ${procName} started`,
+ });
+ });
+}
+
+async function autoStartServers(options = {}) {
+ const { entry, api, responses } = options;
+
+ await killProcessOnPort('3000');
+ await killProcessOnPort('3001');
+
+ await startProcess(
+ FRONTEND_PROCESS_NAME,
+ 'yarn',
+ ['--cwd', paths.root, 'watch', '--env', `entry=${entry}`, `api=${api}`],
+ {
+ forceRestart: true,
+ metadata: {
+ entries: [entry],
+ },
+ },
+ );
+
+ await startProcess(
+ 'mock-server',
+ 'yarn',
+ ['--cwd', paths.root, 'mock-api', '--responses', responses],
+ {
+ forceRestart: true,
+ metadata: {
+ responses,
+ },
+ },
+ );
+}
+
+module.exports = {
+ processes,
+ outputCache,
+ clients,
+ sendSSE,
+ addToCache,
+ startProcess,
+ autoStartServers,
+ killProcessOnPort,
+};
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/strings.js b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/strings.js
new file mode 100644
index 000000000000..74634c574925
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/server/utils/strings.js
@@ -0,0 +1,19 @@
+/**
+ * Strip ANSI escape codes from a string
+ * this makes the output more readable in the UI for terminal output
+ * @param {string} str - The string to strip ANSI escape codes from
+ * @returns {string} - The string with ANSI escape codes removed
+ */
+function stripAnsi(str) {
+ // couldn't figure out a good way to strip ansi codes
+ // from the output without using a regex that contained control characters
+ // this eslint rule also is created to avoid mistakes as these control characters are 'rarely used'
+ // but in our case we need to strip them
+ return str.replace(
+ // eslint-disable-next-line no-control-regex
+ /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
+ '',
+ );
+}
+
+module.exports = { stripAnsi };
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/utils/HeadingHierarchyAnalyzer.js b/src/applications/_mock-form-ae-design-patterns/vadx/utils/HeadingHierarchyAnalyzer.js
index d403e248f375..da5381aae8a2 100644
--- a/src/applications/_mock-form-ae-design-patterns/vadx/utils/HeadingHierarchyAnalyzer.js
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/utils/HeadingHierarchyAnalyzer.js
@@ -123,7 +123,7 @@ class HeadingHierarchyAnalyzer {
if (h1Count > 1) {
issues.push({
type: 'multiple-h1',
- message: `Page has ${h1Count} h1 headings (should have exactly 1)`,
+ message: `Page has multiple (${h1Count}) top level H1 headings and should have exactly 1`,
});
}
diff --git a/src/applications/_mock-form-ae-design-patterns/vadx/utils/dates.js b/src/applications/_mock-form-ae-design-patterns/vadx/utils/dates.js
new file mode 100644
index 000000000000..15787e517808
--- /dev/null
+++ b/src/applications/_mock-form-ae-design-patterns/vadx/utils/dates.js
@@ -0,0 +1,10 @@
+import { format, parseISO } from 'date-fns';
+
+export const formatDate = dateString => {
+ try {
+ const date = parseISO(dateString);
+ return format(date, 'HH:mm:ss:aaaaa');
+ } catch (error) {
+ return dateString; // Return original string if parsing fails
+ }
+};
diff --git a/src/applications/accredited-representative-portal/actions/user.js b/src/applications/accredited-representative-portal/actions/user.js
deleted file mode 100644
index 35805b2dbde2..000000000000
--- a/src/applications/accredited-representative-portal/actions/user.js
+++ /dev/null
@@ -1,46 +0,0 @@
-import { apiRequest } from '@department-of-veterans-affairs/platform-utilities/api';
-import environment from '~/platform/utilities/environment';
-
-export const FETCH_USER = 'FETCH_USER';
-export const FETCH_USER_SUCCESS = 'FETCH_USER_SUCCESS';
-export const FETCH_USER_FAILURE = 'FETCH_USER_FAILURE';
-
-export function fetchUser() {
- return async dispatch => {
- dispatch({
- type: FETCH_USER,
- });
-
- try {
- const path = '/accredited_representative_portal/v0/user';
- const user = await apiRequest(`${environment.API_URL}${path}`);
-
- /**
- * This is an even stricter success condition than having a user. We
- * additionally require what is needed for access token refreshing to
- * function.
- */
- const serviceName = user?.profile?.signIn?.serviceName;
- if (!serviceName)
- throw new Error('Missing user with sign in service name.');
-
- // Needed for access token refreshing to function.
- sessionStorage.setItem('serviceName', serviceName);
-
- dispatch({
- type: FETCH_USER_SUCCESS,
- payload: user,
- });
- } catch (e) {
- const error =
- e.errors?.[0]?.detail ||
- e.message ||
- 'Unknown error while fetching user';
-
- dispatch({
- type: FETCH_USER_FAILURE,
- error,
- });
- }
- };
-}
diff --git a/src/applications/accredited-representative-portal/app-entry.jsx b/src/applications/accredited-representative-portal/app-entry.jsx
index f0b459e6b464..897324feef1f 100644
--- a/src/applications/accredited-representative-portal/app-entry.jsx
+++ b/src/applications/accredited-representative-portal/app-entry.jsx
@@ -2,26 +2,27 @@ import '@department-of-veterans-affairs/platform-polyfills';
import React from 'react';
import { Provider } from 'react-redux';
-import { RouterProvider } from 'react-router-dom';
+import { RouterProvider, createBrowserRouter } from 'react-router-dom';
import startReactApp from '@department-of-veterans-affairs/platform-startup/react';
-import { connectFeatureToggle } from 'platform/utilities/feature-toggles';
import './sass/accredited-representative-portal.scss';
import './sass/POARequestCard.scss';
import './sass/POARequestDetails.scss';
+import './sass/Header.scss';
import manifest from './manifest.json';
-import router from './routes';
-import createReduxStore from './store';
-import rootReducer from './reducers';
+import routes from './routes';
+import createReduxStore from './utilities/store';
window.appName = manifest.entryName;
-const store = createReduxStore(rootReducer);
-connectFeatureToggle(store.dispatch);
+
+const router = createBrowserRouter(routes, {
+ basename: '/representative',
+});
startReactApp(
-
+ ,
);
diff --git a/src/applications/accredited-representative-portal/auth.js b/src/applications/accredited-representative-portal/auth.js
deleted file mode 100644
index 7390ad8fe493..000000000000
--- a/src/applications/accredited-representative-portal/auth.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import { apiRequest } from '@department-of-veterans-affairs/platform-utilities/api';
-
-let user = null;
-
-export async function fetchUser() {
- try {
- user = await apiRequest('/accredited_representative_portal/v0/user');
- return user;
- } catch (error) {
- user = null;
- throw error;
- }
-}
-
-export function getUser() {
- return user;
-}
-
-export function isAuthenticated() {
- return user !== null;
-}
diff --git a/src/applications/accredited-representative-portal/components/DigitalSubmissionAlert/DigitalSubmissionAlert.jsx b/src/applications/accredited-representative-portal/components/DigitalSubmissionAlert/DigitalSubmissionAlert.jsx
deleted file mode 100644
index e7dc7a516afa..000000000000
--- a/src/applications/accredited-representative-portal/components/DigitalSubmissionAlert/DigitalSubmissionAlert.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-
-const DigitalSubmissionAlert = () => (
-
-
- Veterans can now digitally submit form 21-22 from VA.gov
-
-
- Veterans can now{' '}
-
- find a VSO
- {' '}
- and{' '}
-
- sign and submit
- {' '}
- a digital version of form 21-22. Digital submissions will immediately
- populate in the table below.
-
- The .gov means it’s official.
-
- Federal government websites often end in .gov or .mil. Before
- sharing sensitive information, make sure you’re on a federal
- government site.
-
-
-
-
-
-
-
- The site is secure.
- The https:// ensures that you’re
- connecting to the official website and that any information you
- provide is encrypted and sent securely.
-
);
};
diff --git a/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx b/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
index 271a434fc8d2..7d4f3b42392b 100644
--- a/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
+++ b/src/applications/accredited-representative-portal/containers/POARequestDetailsPage.jsx
@@ -1,12 +1,52 @@
import React, { useState } from 'react';
-import { apiRequest } from '@department-of-veterans-affairs/platform-utilities/api';
-import { Link, useLoaderData } from 'react-router-dom';
+import { Link, useLoaderData, Form, redirect } from 'react-router-dom';
import { formatDateShort } from 'platform/utilities/date/index';
import {
VaRadio,
VaRadioOption,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
-import mockPOARequestsResponse from '../mocks/mockPOARequestsResponse.json';
+
+import api from '../utilities/api';
+
+const DECISION_TYPES = {
+ ACCEPTANCE: 'acceptance',
+ DECLINATION: 'declination',
+};
+
+const DECLINATION_OPTIONS = {
+ DECLINATION_HEALTH_RECORDS_WITHHELD: {
+ type: DECISION_TYPES.DECLINATION,
+ reason:
+ "I decline the request, because the claimant didn't provide access to health records",
+ },
+ DECLINATION_ADDRESS_CHANGE_WITHHELD: {
+ type: DECISION_TYPES.DECLINATION,
+ reason:
+ "I decline the request, because the claimant didn't allow me to change their address",
+ },
+ DECLINATION_BOTH_WITHHELD: {
+ type: DECISION_TYPES.DECLINATION,
+ reason:
+ 'I decline the request, because the claimant did not provide access to change address and to health records',
+ },
+ DECLINATION_NOT_ACCEPTING_CLIENTS: {
+ type: DECISION_TYPES.DECLINATION,
+ reason:
+ 'I decline the request, because the VSO is not currently accepting new clients',
+ },
+ DECLINATION_OTHER: {
+ type: DECISION_TYPES.DECLINATION,
+ reason: 'I decline for another reason',
+ },
+};
+
+const DECISION_OPTIONS = {
+ ACCEPTANCE: {
+ type: DECISION_TYPES.ACCEPTANCE,
+ reason: null,
+ },
+ ...DECLINATION_OPTIONS,
+};
const checkAuthorizations = (
isTreatmentDisclosureAuthorized,
@@ -25,7 +65,7 @@ const checkAuthorizations = (
};
const POARequestDetailsPage = () => {
- const poaRequest = useLoaderData();
+ const poaRequest = useLoaderData().attributes;
const [error, setError] = useState(false);
const handleChange = e => {
e.preventDefault();
@@ -171,14 +211,14 @@ const POARequestDetailsPage = () => {
Back to power of attorney list
-
+
);
};
-export default POARequestDetailsPage;
+POARequestDetailsPage.loader = ({ params, request }) => {
+ return api.getPOARequest(params.id, {
+ signal: request.signal,
+ });
+};
-export async function poaRequestLoader({ params }) {
- const { id } = params;
+POARequestDetailsPage.createDecisionAction = async ({ request, params }) => {
+ const key = (await request.formData()).get('decision');
+ const decision = DECISION_OPTIONS[key];
- try {
- const response = await apiRequest(`/power_of_attorney_requests/${id}`, {
- apiVersion: 'accredited_representative_portal/v0',
- });
- return response.data;
- } catch (error) {
- // Return mock data if API fails (TODO: remove this before pilot and replace with commented throw below)
- // throwing the error will cause the app to show the error message configured in routes.jsx
- return mockPOARequestsResponse.data.find(r => r.id === Number(id))
- ?.attributes;
- // throw error;
- }
-}
+ await api.createPOARequestDecision(params.id, decision);
+
+ return redirect('..');
+};
+
+export default POARequestDetailsPage;
diff --git a/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx b/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
index 0eaafeb464cf..fb1c67653a7e 100644
--- a/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
+++ b/src/applications/accredited-representative-portal/containers/POARequestSearchPage.jsx
@@ -6,11 +6,8 @@ import {
Link,
} from 'react-router-dom';
-import { apiRequest } from '@department-of-veterans-affairs/platform-utilities/api';
-
-import mockPOARequestsResponse from '../mocks/mockPOARequestsResponse.json';
-import POARequestCard from '../components/POARequestCard/POARequestCard';
-import DigitalSubmissionAlert from '../components/DigitalSubmissionAlert/DigitalSubmissionAlert';
+import api from '../utilities/api';
+import POARequestCard from '../components/POARequestCard';
const STATUSES = {
PENDING: 'pending',
@@ -39,18 +36,45 @@ const SearchResults = ({ poaRequests }) => {
);
};
-const StatusTabLink = ({ status, searchStatus, children }) => {
- const active = status === searchStatus;
+const StatusTabLink = ({ tabStatus, searchStatus, children }) => {
+ const active = tabStatus === searchStatus;
const classNames = ['poa-request__tab-link'];
if (active) classNames.push('active');
return (
-
+
{children}
);
};
+const DigitalSubmissionAlert = () => (
+
+
+ Veterans can now digitally submit form 21-22 from VA.gov
+
+
+ Veterans can now{' '}
+
+ find a VSO
+ {' '}
+ and{' '}
+
+ sign and submit
+ {' '}
+ a digital version of form 21-22. Digital submissions will immediately
+ populate in the table below.
+
If an issue is missing,{' '}
- go back and add it
+ Go back to add more issues
.
diff --git a/src/applications/appeals/10182/tests/pages/issueSummary.unit.spec.jsx b/src/applications/appeals/10182/tests/pages/issueSummary.unit.spec.jsx
index 441fd7591ea1..8b40551c5f63 100644
--- a/src/applications/appeals/10182/tests/pages/issueSummary.unit.spec.jsx
+++ b/src/applications/appeals/10182/tests/pages/issueSummary.unit.spec.jsx
@@ -46,7 +46,7 @@ describe('NOD selected issues summary page', () => {
const link = form.find('Link');
expect(link.length).to.equal(1);
- expect(link.text()).to.contain('go back and add');
+ expect(link.text()).to.contain('Go back to add more issues');
expect(link.props().to.pathname).to.equal(CONTESTABLE_ISSUES_PATH);
expect(link.props().to.search).to.equal('?redirect');
form.unmount();
diff --git a/src/applications/appeals/995/components/EvidenceSummaryLists.jsx b/src/applications/appeals/995/components/EvidenceSummaryLists.jsx
index 8887b28e4511..70fdabd01394 100644
--- a/src/applications/appeals/995/components/EvidenceSummaryLists.jsx
+++ b/src/applications/appeals/995/components/EvidenceSummaryLists.jsx
@@ -63,7 +63,7 @@ const confirmationPageLabel = showListOnly =>
'vads-u-color--gray',
'vads-u-font-size--sm',
].join(' ')
- : '';
+ : 'vads-u-font-weight--bold';
const formatDate = (date = '', format = FORMAT_COMPACT_DATE_FNS) =>
// Use `parse` from date-fns because it is a non-ISO8061 formatted date string
diff --git a/src/applications/appeals/995/content/evidencePrivateRecordsAuthorization.jsx b/src/applications/appeals/995/content/evidencePrivateRecordsAuthorization.jsx
index c5e8698f56c2..ec77d5ee56e9 100644
--- a/src/applications/appeals/995/content/evidencePrivateRecordsAuthorization.jsx
+++ b/src/applications/appeals/995/content/evidencePrivateRecordsAuthorization.jsx
@@ -22,7 +22,7 @@ export const authorizationAlertContent = onAnchorClick => (
Or, go back a page and select No where we ask about
non-VA medical records. Then you can upload your records or submit a
- 21-4142 and 21-4142a after submitting this form.Go back to upload records
+ 21-4142 and 21-4142a after submitting this form.
Go back to upload records
>
diff --git a/src/applications/appeals/995/tests/utils/submit.unit.spec.js b/src/applications/appeals/995/tests/utils/submit.unit.spec.js
index 2c98d3a54e84..3baf35b199e0 100644
--- a/src/applications/appeals/995/tests/utils/submit.unit.spec.js
+++ b/src/applications/appeals/995/tests/utils/submit.unit.spec.js
@@ -10,13 +10,15 @@ import {
} from '../../constants';
import {
getAddress,
+ getClaimantData,
+ getEmail,
getEvidence,
getForm4142,
getPhone,
- getEmail,
- getClaimantData,
+ getTreatmentDate,
hasDuplicateFacility,
hasDuplicateLocation,
+ TEMP_DATE,
} from '../../utils/submit';
const text =
@@ -211,6 +213,24 @@ describe('getClaimantData', () => {
});
});
+describe('getTreatmentDate', () => {
+ const wrap = (date, noDate) => ({ treatmentDate: date, noDate });
+ it('should return treatment date', () => {
+ expect(getTreatmentDate(wrap('2020-02', false))).to.eq('2020-02-01');
+ });
+ // A default treatment date is only needed until LH finalizes the new API
+ it('should return default treatment date', () => {
+ expect(getTreatmentDate()).to.eq(TEMP_DATE);
+ expect(getTreatmentDate({})).to.eq(TEMP_DATE);
+ expect(getTreatmentDate(wrap())).to.eq(TEMP_DATE);
+ expect(getTreatmentDate(wrap('12'))).to.eq(TEMP_DATE);
+ expect(getTreatmentDate(wrap('1234'))).to.eq(TEMP_DATE);
+ expect(getTreatmentDate(wrap('1234-1'))).to.eq(TEMP_DATE);
+ expect(getTreatmentDate(wrap('', true))).to.eq(TEMP_DATE);
+ expect(getTreatmentDate(wrap('2020-03-03', true))).to.eq(TEMP_DATE);
+ });
+});
+
describe('hasDuplicateLocation', () => {
const getLocation = ({
wrap = false,
@@ -222,6 +242,7 @@ describe('hasDuplicateLocation', () => {
locationAndName: name,
issues: ['1', '2'],
evidenceDates: wrap ? [{ startDate: from, endDate: to }] : { from, to },
+ noDate: false,
treatmentDate: from.substring(0, from.lastIndexOf('-')),
};
return wrap ? { attributes: location } : location;
@@ -263,8 +284,7 @@ describe('hasDuplicateLocation', () => {
expect(hasDuplicateLocation(list, second, true)).to.be.true;
// check date format without leading zeros
- const first2 = getLocation({ from: '2022-1-1' });
- expect(hasDuplicateLocation(list, first2, true)).to.be.true;
+ expect(hasDuplicateLocation(list, first, true)).to.be.true;
});
});
@@ -288,6 +308,7 @@ describe('getEmail', () => {
expect(result.slice(-10)).to.eq('12345@test');
});
});
+
describe('getEvidence', () => {
const getData = ({ hasVa = true, showScNewForm = false } = {}) => ({
data: {
@@ -307,6 +328,13 @@ describe('getEvidence', () => {
evidenceDates: { from: '2022-03-03', to: '2022-04-04' },
treatmentDate: '2002-07',
},
+ {
+ locationAndName: 'test 3',
+ issues: ['2'],
+ evidenceDates: { from: '2022-05-05', to: '2022-06-06' },
+ treatmentDate: '2002', // incomplete date
+ noDate: true,
+ },
],
},
result: (newForm = false) => ({
@@ -338,6 +366,18 @@ describe('getEvidence', () => {
],
},
},
+ {
+ type: 'retrievalEvidence',
+ attributes: {
+ locationAndName: 'test 3',
+ evidenceDates: [
+ {
+ startDate: newForm ? TEMP_DATE : '2022-05-05',
+ endDate: newForm ? TEMP_DATE : '2022-06-06',
+ },
+ ],
+ },
+ },
],
},
}),
@@ -383,7 +423,7 @@ describe('getEvidence', () => {
evidence.data.locations.push(evidence.data.locations[0]);
evidence.data.locations.push(evidence.data.locations[1]);
- expect(evidence.data.locations.length).to.eq(4);
+ expect(evidence.data.locations.length).to.eq(5);
expect(getEvidence(evidence.data)).to.deep.equal(evidence.result());
});
diff --git a/src/applications/appeals/995/tests/validations/date.unit.spec.js b/src/applications/appeals/995/tests/validations/date.unit.spec.js
index d978a515b38a..7e275f00fb0b 100644
--- a/src/applications/appeals/995/tests/validations/date.unit.spec.js
+++ b/src/applications/appeals/995/tests/validations/date.unit.spec.js
@@ -160,7 +160,7 @@ describe('validateYMDate', () => {
});
it('should throw a missing date error', () => {
validateYMDate(errors, '200', fullData);
- expect(errorMessage[0]).to.eq(scErrors.evidence.blankDate);
+ expect(errorMessage.length).to.eq(0);
});
it('should throw a invalid date error', () => {
validateYMDate(errors, '2023-13', fullData);
@@ -180,9 +180,9 @@ describe('validateYMDate', () => {
validateYMDate(errors, date, fullData);
expect(errorMessage[0]).to.contain(scErrors.evidence.newerDate);
});
- it('should throw a invalid date for truncated dates', () => {
+ it('should not throw an error for truncated dates', () => {
// Testing 'YYYY-'
validateYMDate(errors, '2000-', fullData);
- expect(errorMessage[0]).to.eq(scErrors.evidence.blankDate);
+ expect(errorMessage.length).to.eq(0);
});
});
diff --git a/src/applications/appeals/995/tests/validations/evidence.unit.spec.js b/src/applications/appeals/995/tests/validations/evidence.unit.spec.js
index e7765ec25f8d..424e37818046 100644
--- a/src/applications/appeals/995/tests/validations/evidence.unit.spec.js
+++ b/src/applications/appeals/995/tests/validations/evidence.unit.spec.js
@@ -165,17 +165,15 @@ describe('VA evidence', () => {
validateVaDate(errors, { treatmentDate: '2022-01' }, fullData);
expect(errors.addError.called).to.be.false;
});
- it('should show an error for an invalid treatment date', () => {
+ it('should not show an error for an invalid treatment date', () => {
const errors = { addError: sinon.spy() };
validateVaDate(errors, { treatmentDate: '2000' }, fullData);
- expect(errors.addError.calledWith(errorMessages.evidence.blankDate)).to
- .true;
+ expect(errors.addError.notCalled).to.be.true;
});
- it('should show an error for a missing treatment date', () => {
+ it('should not show an error for a missing treatment date', () => {
const errors = { addError: sinon.spy() };
validateVaDate(errors, { treatmentDate: '' }, fullData);
- expect(errors.addError.calledWith(errorMessages.evidence.blankDate)).to
- .true;
+ expect(errors.addError.notCalled).to.be.true;
});
it('should show an error for a to date in the future', () => {
const errors = { addError: sinon.spy() };
diff --git a/src/applications/appeals/995/utils/submit.js b/src/applications/appeals/995/utils/submit.js
index b3d44452c83b..4d7d88750c9e 100644
--- a/src/applications/appeals/995/utils/submit.js
+++ b/src/applications/appeals/995/utils/submit.js
@@ -132,14 +132,36 @@ export const getPhone = formData => {
: {};
};
+export const TEMP_DATE = '2006-06-06';
+/**
+ * @typedef VALocation
+ * @type {Object}
+ * @property {String} locationAndName - VA or private medical records name
+ * @property {Array} issues - list of selected issues
+ * @property {String} treatmentDate - YYYY-MM (new form)
+ * @property {Boolean} noDate - no date provided (new form)
+ * @property {Object} evidenceDates - date range (current form)
+ * @property {String} evidenceDates.from - YYYY-MM-DD
+ * @property {String} evidenceDates.to - YYYY-MM-DD
+ */
+/**
+ * Get treatment date and noData boolean, then return a full date (YYYY-MM-DD)
+ * with DD set to 01 for date comparisons; Currently, if the treatment date
+ * appears to be invalid, or noDate is set, we return a made up date until we
+ * know what Lighthouse's final API looks like
+ * @param {VALocation} location
+ * @returns {String} YYYY-MM-DD (including day for date comparisons)
+ */
+export const getTreatmentDate = ({ treatmentDate = '', noDate } = {}) => {
+ // return a made up date until we know what the final API looks like
+ return !noDate && treatmentDate.length === 7
+ ? fixDateFormat(`${treatmentDate}-01`)
+ : TEMP_DATE; // change this once we know the final API
+};
+
export const hasDuplicateLocation = (list, currentLocation, newForm = false) =>
!!list.find(location => {
- const {
- locationAndName,
- evidenceDates,
- treatmentDate,
- } = location.attributes;
-
+ const { locationAndName, evidenceDates } = location.attributes;
return (
buildVaLocationString(
{
@@ -150,7 +172,7 @@ export const hasDuplicateLocation = (list, currentLocation, newForm = false) =>
from: evidenceDates?.[0]?.startDate,
to: evidenceDates?.[0]?.endDate,
},
- treatmentDate: newForm ? treatmentDate : '',
+ treatmentDate: newForm ? getTreatmentDate(location.attributes) : '',
},
',',
{ includeIssues: false },
@@ -164,7 +186,7 @@ export const hasDuplicateLocation = (list, currentLocation, newForm = false) =>
from: currentLocation.evidenceDates?.from,
to: currentLocation.evidenceDates?.to,
},
- treatmentDate: newForm ? currentLocation.treatmentDate : '',
+ treatmentDate: newForm ? getTreatmentDate(currentLocation) : '',
},
',',
{ includeIssues: false },
@@ -254,19 +276,20 @@ export const getEvidence = formData => {
// Temporary transformation of `treatmentDate` (YYYY-MM) to
// `evidenceDates` range { from: 'YYYY-MM-DD', to: 'YYYY-MM-DD' }
const from = showNewFormContent
- ? `${location.treatmentDate}-01`
+ ? getTreatmentDate(location)
: location.evidenceDates?.from;
const to = showNewFormContent
- ? `${location.treatmentDate}-01`
+ ? getTreatmentDate(location)
: location.evidenceDates?.to;
list.push({
type: 'retrievalEvidence',
attributes: {
- // we're not including the issues here - it's only in the form to make
- // the UX consistent with the private records location pages
+ // We're not including the issues here - it's only in the form to
+ // make the UX consistent with the private records location pages
locationAndName: location.locationAndName,
// Lighthouse wants between 1 and 4 evidenceDates, but we're only
- // providing one
+ // providing one; with the new form, these dates will not be
+ // required. Leaving this as is until LH provides the new API
evidenceDates: [
{
startDate: fixDateFormat(from),
diff --git a/src/applications/appeals/995/validations/date.js b/src/applications/appeals/995/validations/date.js
index 8cbd978bfaed..a6e939e71e46 100644
--- a/src/applications/appeals/995/validations/date.js
+++ b/src/applications/appeals/995/validations/date.js
@@ -55,6 +55,10 @@ export const isValidDate = dateString => {
};
export const validateYMDate = (errors, rawDateString = '') => {
+ if (!rawDateString || rawDateString.length < 7) {
+ return; // VA date is not required
+ }
+
// Add "-01" (day) to the YYYY-MM date for processing
const date = createDateObject(`${rawDateString}-01`);
const error = errorMessages.evidence;
diff --git a/src/applications/appeals/996/content/issueSummary.jsx b/src/applications/appeals/996/content/issueSummary.jsx
index 93133dd8a338..6c5a5f83592b 100644
--- a/src/applications/appeals/996/content/issueSummary.jsx
+++ b/src/applications/appeals/996/content/issueSummary.jsx
@@ -17,15 +17,13 @@ export const SummaryTitle = ({ formData }) => {
{ShowIssuesList({ issues })}
- If an issue is missing, please{' '}
- go back and add it
+ Go back to add more issues
.
- If you don’t think this is the right form for you, find out about
- other decision review options.
+ If you don’t think this is the right form for you, there other
+ decision review options.
@@ -16,37 +16,37 @@ const OtherVADebts = ({ module, subHeading }) => {
Our records show you have{' '}
{module === APP_TYPES.DEBT && (
- VA benefit debt. You can{' '}
-
- check the details of your current debt
-
-
- , find out how to pay your debt, and learn how to request
- financial assistance.
-
+ VA benefit debt. You can check the details of your current debt,
+ find out how to pay your debt, and learn how to request financial
+ assistance.
)}{' '}
{module === APP_TYPES.COPAY && (
- a VA health care copay bill. You can{' '}
-
- check the details of your copay balance
-
-
- , find out how to pay your balance, and learn how to request
- financial assistance.
-
+ VA health care copay bills. You can check the details of your copay
+ balance, find out how to pay your balance, and learn how to request
+ financial assistance.
)}
-
-
+ {module === APP_TYPES.DEBT && (
+
+ )}
+ {module === APP_TYPES.COPAY && (
+
+ )}
>
);
};
diff --git a/src/applications/combined-debt-portal/combined/components/ZeroBalanceCard.jsx b/src/applications/combined-debt-portal/combined/components/ZeroBalanceCard.jsx
index b69877df15d6..6d392e4392e6 100644
--- a/src/applications/combined-debt-portal/combined/components/ZeroBalanceCard.jsx
+++ b/src/applications/combined-debt-portal/combined/components/ZeroBalanceCard.jsx
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
import { APP_TYPES } from '../utils/helpers';
const ZeroBalanceCard = ({ appType }) => {
@@ -12,7 +13,7 @@ const ZeroBalanceCard = ({ appType }) => {
appType === APP_TYPES.DEBT ? (
If you think this is incorrect, call the Debt Management Center (DMC) at{' '}
- (
+ (
). We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
diff --git a/src/applications/combined-debt-portal/combined/containers/OverviewPage.jsx b/src/applications/combined-debt-portal/combined/containers/OverviewPage.jsx
index b071bdded5a4..e572d45c207a 100644
--- a/src/applications/combined-debt-portal/combined/containers/OverviewPage.jsx
+++ b/src/applications/combined-debt-portal/combined/containers/OverviewPage.jsx
@@ -1,6 +1,7 @@
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { VaBreadcrumbs } from '@department-of-veterans-affairs/web-components/react-bindings';
+import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
import Balances from '../components/Balances';
import ComboAlerts from '../components/ComboAlerts';
import { ALERT_TYPES, setPageFocus } from '../utils/helpers';
@@ -74,7 +75,7 @@ const OverviewPage = () => {
Questions about benefit debt
Call the Debt Management Center (DMC) at{' '}
- (
+ (
). We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
Our records show you don’t have any current debt related to VA
benefits. If you think this is incorrect, call the Debt Management
- Center (DMC) at (
+ Center (DMC) at (
). We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
diff --git a/src/applications/combined-debt-portal/debt-letters/components/Alerts.jsx b/src/applications/combined-debt-portal/debt-letters/components/Alerts.jsx
index 54195857fb60..6db6f7480fa8 100644
--- a/src/applications/combined-debt-portal/debt-letters/components/Alerts.jsx
+++ b/src/applications/combined-debt-portal/debt-letters/components/Alerts.jsx
@@ -53,7 +53,7 @@ export const DowntimeMessage = () => {
If you need help resolving a debt, or you would like to get information
about a debt that has been resolved, call the Debt Management Center at{' '}
- .
+ .
);
@@ -91,7 +91,7 @@ export const ErrorAlert = () => (
What you can do
You can check back later or call the Debt Management Center at{' '}
- to find out more information about
+ to find out more information about
how to resolve your debt.
(
What you can do
If you need to access debt letters that were mailed to you, call the Debt
- Management Center at .
+ Management Center at .
(
Our records show you don’t have any debt letters related to VA benefits.
If you think this is an error, please contact the Debt Management Center
- at .
+ at .
If you have VA health care copay debt, go to our
diff --git a/src/applications/combined-debt-portal/debt-letters/components/HowDoIPay.jsx b/src/applications/combined-debt-portal/debt-letters/components/HowDoIPay.jsx
index 92b5049002f5..5ee2a119e458 100644
--- a/src/applications/combined-debt-portal/debt-letters/components/HowDoIPay.jsx
+++ b/src/applications/combined-debt-portal/debt-letters/components/HowDoIPay.jsx
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
import { deductionCodes } from '../const/deduction-codes';
export const getDeductionDescription = code => {
@@ -75,9 +76,9 @@ const HowDoIPay = ({ userData }) => {
Pay by phone
- Call us at (
- from overseas) (
-
+ Call us at (
+ from
+ overseas) (
). We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
diff --git a/src/applications/combined-debt-portal/debt-letters/components/NeedHelp.jsx b/src/applications/combined-debt-portal/debt-letters/components/NeedHelp.jsx
index 92f20ba14d93..f90edbad73da 100644
--- a/src/applications/combined-debt-portal/debt-letters/components/NeedHelp.jsx
+++ b/src/applications/combined-debt-portal/debt-letters/components/NeedHelp.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
const NeedHelp = () => (
<>
@@ -47,11 +48,11 @@ const NeedHelp = () => (
If you think a debt was created in error, you can dispute it. Get
information about disputing a debt by contacting us online through{' '}
Ask VA or calling the Debt
- Management Center at (
+ Management Center at (
). For international callers, use{' '}
- . We’re here Monday through
- Friday, 7:30 a.m. to 7:00 p.m. ET.
+ . We’re
+ here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
@@ -61,11 +62,11 @@ const NeedHelp = () => (
If you have any questions about your benefit overpayment. Contact us
online through Ask VA or call the
- Debt Management Center at (
+ Debt Management Center at (
). For international callers, use{' '}
- . We’re here Monday through
- Friday, 7:30 a.m. to 7:00 p.m. ET.
+ . We’re
+ here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
If you have questions about your VA debt, call us at{' '}
- (
+ (
). If you’re outside the U.S., call{' '}
- . We’re here Monday
- through Friday, 7:30 a.m. to 7:00 p.m. ET.
+ . We’re here
+ Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
);
diff --git a/src/applications/combined-debt-portal/debt-letters/const/diary-codes/debtSummaryCardContent.js b/src/applications/combined-debt-portal/debt-letters/const/diary-codes/debtSummaryCardContent.js
index 597449830512..1ebcecafd6dd 100644
--- a/src/applications/combined-debt-portal/debt-letters/const/diary-codes/debtSummaryCardContent.js
+++ b/src/applications/combined-debt-portal/debt-letters/const/diary-codes/debtSummaryCardContent.js
@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
+import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
import { endDate } from '../../utils/helpers';
const WarningIcon = () => (
@@ -164,7 +165,7 @@ export const debtSummaryText = (diaryCode, dateOfLetter, balance) => {
return (
Contact the U.S. Department of the Treasury’s Debt Management Services
- at , 8:30 a.m. to 6:30 p.m. ET.
+ at , 8:30 a.m. to 6:30 p.m. ET.
to pay this debt.
);
diff --git a/src/applications/combined-debt-portal/debt-letters/containers/DebtLettersDownload.jsx b/src/applications/combined-debt-portal/debt-letters/containers/DebtLettersDownload.jsx
index 3ba39e2a89f5..efbb2ed7c6a1 100644
--- a/src/applications/combined-debt-portal/debt-letters/containers/DebtLettersDownload.jsx
+++ b/src/applications/combined-debt-portal/debt-letters/containers/DebtLettersDownload.jsx
@@ -1,5 +1,6 @@
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
+import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
import { VaBreadcrumbs } from '@department-of-veterans-affairs/web-components/react-bindings';
import {
setPageFocus,
@@ -76,11 +77,11 @@ const DebtLettersDownload = () => {
If you’ve received a letter about a VA debt that isn’t listed here,
call us at{' '}
-
+ {' '}
(or{' '}
-
+ {' '}
from overseas). You can also call us to get information about your
resolved debts.
diff --git a/src/applications/combined-debt-portal/debt-letters/containers/DebtLettersSummary.jsx b/src/applications/combined-debt-portal/debt-letters/containers/DebtLettersSummary.jsx
index 35f572681f21..0cdfe8753a28 100644
--- a/src/applications/combined-debt-portal/debt-letters/containers/DebtLettersSummary.jsx
+++ b/src/applications/combined-debt-portal/debt-letters/containers/DebtLettersSummary.jsx
@@ -1,6 +1,7 @@
import React, { useEffect } from 'react';
import { useSelector } from 'react-redux';
import { Link } from 'react-router-dom';
+import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
import { VaBreadcrumbs } from '@department-of-veterans-affairs/web-components/react-bindings';
import {
setPageFocus,
@@ -53,7 +54,7 @@ const renderOtherVA = (mcpLength, mcpError) => {
if (mcpError) {
return (
<>
-
Your other VA bills
+
Your VA copay bills
{alertInfo.header}
@@ -146,11 +147,11 @@ const DebtLettersSummary = () => {
think your debt was created in an error, you can dispute it.
Contact us online through Ask VA{' '}
or call the Debt Management Center at{' '}
- (
+ (
). For international callers, use{' '}
- . We’re here Monday through
- Friday, 7:30 a.m. to 7:00 p.m. ET.
+ .
+ We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
It can take up to 10 days for us to receive your form. Your
- confirmation number is {formSubmissionId}.
+ confirmation number is {confirmationNumber}.
= this.props.longWaitTime) {
- this.setState({ longWait: true });
- }
setTimeout(this.poll, waitTime);
}
})
- .catch(response => {
+ .catch(() => {
// Don't process the request once it comes back if the component is no longer mounted
if (!this.__isMounted) {
return;
@@ -85,8 +77,6 @@ export class ConfirmationPoll extends React.Component {
} else {
this.setState({
submissionStatus: submissionStatuses.apiFailure,
- // NOTE: I don't know that it'll always take this shape.
- failureCode: get('errors[0].status', response),
});
}
});
@@ -95,21 +85,11 @@ export class ConfirmationPoll extends React.Component {
render() {
const { submissionStatus, claimId } = this.state;
if (submissionStatus === submissionStatuses.pending) {
- setTimeout(() => focusElement('.loading-indicator-container'));
return (
-
-
-
-
-
- {pendingMessage(this.state.longWait)}
-
-
+
);
}
@@ -167,7 +147,6 @@ ConfirmationPoll.propTypes = {
}),
isSubmittingBDD: PropTypes.bool,
jobId: PropTypes.string,
- longWaitTime: PropTypes.number,
pollRate: PropTypes.number,
route: PropTypes.shape({
formConfig: PropTypes.object,
@@ -179,7 +158,6 @@ ConfirmationPoll.propTypes = {
ConfirmationPoll.defaultProps = {
pollRate: 5000,
delayFailure: 6000, // larger than pollRate
- longWaitTime: 30000,
};
export default connect(mapStateToProps)(ConfirmationPoll);
diff --git a/src/applications/disability-benefits/all-claims/config/form0781/index.js b/src/applications/disability-benefits/all-claims/config/form0781/index.js
index 81c0d60137af..1e5fc48dcd51 100644
--- a/src/applications/disability-benefits/all-claims/config/form0781/index.js
+++ b/src/applications/disability-benefits/all-claims/config/form0781/index.js
@@ -50,12 +50,6 @@ export const form0781PagesConfig = {
uiSchema: eventType.uiSchema,
schema: eventType.schema,
},
- additionalInformationPage: {
- path: 'additional-forms/mental-health-statement/additional-information',
- depends: formData => isCompletingForm0781(formData),
- uiSchema: additionalInformationPage.uiSchema,
- schema: additionalInformationPage.schema,
- },
// Behavioral Changes Pages
behaviorIntroPage: {
path: 'additional-forms/mental-health-statement/behavior-changes',
@@ -75,10 +69,17 @@ export const form0781PagesConfig = {
uiSchema: behaviorListPage.uiSchema,
schema: behaviorListPage.schema,
},
+ // Conclusion Pages
consentPage: {
path: 'additional-forms/mental-health-statement/consent',
depends: formData => isRelatedToMST(formData),
uiSchema: consentPage.uiSchema,
schema: consentPage.schema,
},
+ additionalInformationPage: {
+ path: 'additional-forms/mental-health-statement/additional-information',
+ depends: formData => isCompletingForm0781(formData),
+ uiSchema: additionalInformationPage.uiSchema,
+ schema: additionalInformationPage.schema,
+ },
};
diff --git a/src/applications/disability-benefits/all-claims/constants.js b/src/applications/disability-benefits/all-claims/constants.js
index 9c9c713ef6ea..721f5a510bee 100644
--- a/src/applications/disability-benefits/all-claims/constants.js
+++ b/src/applications/disability-benefits/all-claims/constants.js
@@ -1,7 +1,7 @@
import constants from 'vets-json-schema/dist/constants.json';
import fullSchema from 'vets-json-schema/dist/21-526EZ-ALLCLAIMS-schema.json';
-const { pciuStates: PCIU_STATES } = constants;
+const { formProfileStates: FORM_PROFILE_STATES } = constants;
import {
VA_FORM_IDS,
@@ -38,10 +38,10 @@ export const RESERVE_GUARD_TYPES = {
reserve: 'Reserve',
};
-export { PCIU_STATES };
+export { FORM_PROFILE_STATES };
-export const STATE_LABELS = PCIU_STATES.map(state => state.label);
-export const STATE_VALUES = PCIU_STATES.map(state => state.value);
+export const STATE_LABELS = FORM_PROFILE_STATES.map(state => state.label);
+export const STATE_VALUES = FORM_PROFILE_STATES.map(state => state.value);
export const MILITARY_STATE_VALUES = ['AA', 'AE', 'AP'];
export const MILITARY_STATE_LABELS = [
diff --git a/src/applications/disability-benefits/all-claims/containers/ConfirmationPage.jsx b/src/applications/disability-benefits/all-claims/containers/ConfirmationPage.jsx
index 21a0b5f26e05..a33b16ec13f8 100644
--- a/src/applications/disability-benefits/all-claims/containers/ConfirmationPage.jsx
+++ b/src/applications/disability-benefits/all-claims/containers/ConfirmationPage.jsx
@@ -1,11 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
-import { Toggler } from 'platform/utilities/feature-toggles';
-import {
- focusElement,
- scrollToTop,
-} from '@department-of-veterans-affairs/platform-utilities/ui';
+import { focusElement } from '@department-of-veterans-affairs/platform-utilities/ui';
import { ConfirmationView } from 'platform/forms-system/src/js/components/ConfirmationView';
import {
submissionStatuses,
@@ -14,9 +10,6 @@ import {
SAVED_SEPARATION_DATE,
} from '../constants';
import {
- retryableErrorContent,
- successfulSubmitContent,
- submitErrorContent,
howLongForDecision,
dependentsAdditionalBenefits,
} from '../content/confirmation-page';
@@ -25,67 +18,38 @@ import { ClaimConfirmationInfo } from '../components/ClaimConfirmationInfo';
import { BddConfirmationAlert } from '../content/bddConfirmationAlert';
export default class ConfirmationPage extends React.Component {
- constructor(props) {
- super(props);
- this.state = { isExpanded: false };
- }
-
componentDidMount() {
- scrollToTop();
setTimeout(() => focusElement('va-alert h2'), 100);
}
- // the legacy 526 confirmation page that has 3 states
- LegacyConfirmationPage = props => {
- switch (props.submissionStatus) {
- case submissionStatuses.succeeded:
- return successfulSubmitContent(props);
- case submissionStatuses.retry:
- case submissionStatuses.exhausted:
- case submissionStatuses.apiFailure:
- return retryableErrorContent(props);
- default:
- return submitErrorContent(props);
- }
- };
-
- // the new 526 submission confirmation that has one state
ConfirmationPageContent = props => (
-
-
-
- >}
- content={alertBody}
- />
- {props.isSubmittingBDD && }
-
-
- >}
- item1Actions={<>>}
- item2Header="Next we’ll send you a letter to let you know we have your claim"
- item2Content="You should get this letter in about 1 week, plus mailing time, after we receive your claim."
- />
-
- {howLongForDecision}
- {dependentsAdditionalBenefits}
-
-
-
-
- {this.LegacyConfirmationPage(props)}
-
+
+ >} content={alertBody} />
+ {props.isSubmittingBDD && }
+
+
+ >}
+ item1Actions={<>>}
+ item2Header="Next we’ll send you a letter to let you know we have your claim"
+ item2Content="You should get this letter in about 1 week, plus mailing time, after we receive your claim."
+ />
+
+ {howLongForDecision}
+ {dependentsAdditionalBenefits}
+
+
+
);
render() {
diff --git a/src/applications/disability-benefits/all-claims/content/confirmation-page.jsx b/src/applications/disability-benefits/all-claims/content/confirmation-page.jsx
index 1ee47853e94e..d95a5c0a04a1 100644
--- a/src/applications/disability-benefits/all-claims/content/confirmation-page.jsx
+++ b/src/applications/disability-benefits/all-claims/content/confirmation-page.jsx
@@ -1,286 +1,5 @@
import React from 'react';
-import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
-
-import DownloadPDF from '../components/DownloadPDF';
-import { capitalizeEachWord, formatDate } from '../utils';
-import {
- successMessage,
- checkLaterMessage,
- errorMessage,
-} from './confirmation-poll';
-
-import { NULL_CONDITION_STRING } from '../constants';
-import { BddConfirmationAlert } from './bddConfirmationAlert';
-
-const template = (props, title, content, submissionMessage, messageType) => {
- const { fullName, disabilities, submittedAt, isSubmittingBDD } = props;
- const { first, last, middle, suffix } = fullName;
- // This is easier than passing down props and checking if the form type
- const pageTitle = document.title.includes('Benefits')
- ? 'Benefits Delivery at Discharge Claim'
- : 'Disability Compensation Claim';
-
- const backButtonContent = (
-
- How long will it take VA to make a decision on my claim?
-
-
- We process applications in the order we receive them. The amount of
- time it takes us to review you claim depends on:
-
-
-
The type of claim you filed
-
- How many injuries or conditions you claimed and how complex they are
-
-
- How long it takes us to collect the evidence needed to decide your
- claim We may contact you if we have questions or need more
- information
-
-
-
-
- How can I check the status of my claim?
-
-
- You can check the status of your claim online. Please allow 24 hours
- for your disability claim to show up there. If you don’t see your
- disability claim online after 24 hours, please call Veterans Benefits
- Assistance at , Monday
- through Friday, 8:00 a.m. to 9:00 p.m. ET.
-
- Or you can fill out and submit an Application Request to Add
- and/or Remove Dependents (VA Form 21-686c)
-
-
-
-
-
- Note: If you’re claiming your child who became
- permanently disabled before they turned 18, you’ll need to submit
- all military and private medical records relating to the child’s
- disabilities with your application.
-
-
-
- If you’re claiming a child who’s between 18 and 23 years old and
- attending school full time
-
- , you’ll need to fill out and submit a Request for Approval of
- School Attendance (VA Form 21-674) so we can verify their
- attendance.
-
-
-
-
-
- If you have dependent parents, you may be
- entitled to additional payments. Fill out and submit a Statement
- of Dependency of Parent(s) (VA Form 21P-509).
-
-
-
-
- >
- )}
-
-
- What happens after I file a claim for disability compensation?
-
- );
-};
-
-export const retryableErrorContent = props =>
- template(
- props,
- "It's taking us longer than expected to submit your claim.",
-
-
- This delay should be resolved within a few hours. We’ll keep trying to
- submit your claim. You can check the status of your claim online after
- 24 hours.
-
-
- If you don’t see your disability claim online after 24 hours,
- {' '}
- please call Veterans Benefits Assistance at{' '}
- , Monday through Friday,
- 8:00 a.m. to 9:00 p.m. ET and provide this reference number{' '}
- {props.jobId}.
-
-
,
- checkLaterMessage(props.jobId),
- 'warning',
- );
-
-export const successfulSubmitContent = props =>
- template(
- props,
- 'Your claim has successfully been submitted.',
- <>>,
- successMessage(props.claimId),
- 'success',
- );
-
-export const submitErrorContent = props => {
- const submissionIdContent = props.submissionId
- ? ` and provide this reference number ${props.submissionId}`
- : '';
-
- return template(
- props,
- 'We’re sorry. Something went wrong when we tried to submit your claim.',
-
-
For help submitting your claim:
-
-
- Please call Veterans Benefits Assistance at{' '}
- , Monday through
- Friday, 8:00 a.m. to 9:00 p.m. ET
- {submissionIdContent}, or
-
How long will it take VA to make a decision on my claim?
diff --git a/src/applications/disability-benefits/all-claims/content/confirmation-poll.jsx b/src/applications/disability-benefits/all-claims/content/confirmation-poll.jsx
index db36696194be..64fd609f35ca 100644
--- a/src/applications/disability-benefits/all-claims/content/confirmation-poll.jsx
+++ b/src/applications/disability-benefits/all-claims/content/confirmation-poll.jsx
@@ -11,16 +11,6 @@ export const checkLaterMessage = () => ;
export const errorMessage = () => ;
-export const pendingMessage = longWait => {
- const message = !longWait
- ? 'Please wait while we submit your application and give you a confirmation number.'
- : 'We’re sorry. It’s taking us longer than expected to submit your application. Thank you for your patience.';
- const label = longWait
- ? 'we’re still trying to submit your application'
- : 'submitting your application';
- return ;
-};
-
export const alertBody = (
<>
Your submission is in progress.
diff --git a/src/applications/disability-benefits/all-claims/migrations/02-convert-country-code.js b/src/applications/disability-benefits/all-claims/migrations/02-convert-country-code.js
index 9b87a70282e4..b2040efb55a6 100644
--- a/src/applications/disability-benefits/all-claims/migrations/02-convert-country-code.js
+++ b/src/applications/disability-benefits/all-claims/migrations/02-convert-country-code.js
@@ -33,7 +33,7 @@ export default function convertCountryCode(savedData) {
},
];
- // If they've got a 3-letter country code, try to match it to the list of PCIU countries
+ // If they've got a 3-letter country code, try to match it to the list of form data countries
let earliestReturnUrl = '';
let { formData: newData } = savedData;
addressPaths.forEach(({ path, returnUrl }) => {
diff --git a/src/applications/disability-benefits/all-claims/tests/components/ConfirmationPoll.unit.spec.jsx b/src/applications/disability-benefits/all-claims/tests/components/ConfirmationPoll.unit.spec.jsx
index 77f917383f90..d878f6902cba 100644
--- a/src/applications/disability-benefits/all-claims/tests/components/ConfirmationPoll.unit.spec.jsx
+++ b/src/applications/disability-benefits/all-claims/tests/components/ConfirmationPoll.unit.spec.jsx
@@ -1,12 +1,10 @@
import React from 'react';
import { shallow, mount } from 'enzyme';
import { expect } from 'chai';
-
import {
mockApiRequest,
mockMultipleApiRequests,
} from 'platform/testing/unit/helpers';
-import { Toggler } from 'platform/utilities/feature-toggles';
import thunk from 'redux-thunk';
import configureStore from 'redux-mock-store';
import { Provider } from 'react-redux';
@@ -20,11 +18,7 @@ import formConfig from '../../config/form';
const middleware = [thunk];
const mockStore = configureStore(middleware);
-const getData = ({
- renderName = true,
- suffix = 'Esq.',
- disability526NewConfirmationPage = false,
-} = {}) => ({
+const getData = ({ renderName = true, suffix = 'Esq.' } = {}) => ({
user: {
profile: {
userFullName: renderName
@@ -32,11 +26,6 @@ const getData = ({
: {},
},
},
- featureToggles: {
- loading: false,
- [Toggler.TOGGLE_NAMES
- .disability526NewConfirmationPage]: disability526NewConfirmationPage,
- },
});
const pendingResponse = {
@@ -173,28 +162,6 @@ describe('ConfirmationPoll', () => {
}, 50);
});
- it('should render long wait alert', done => {
- mockMultipleApiRequests([
- pendingResponse,
- pendingResponse,
- pendingResponse,
- successResponse,
- ]);
-
- const form = mount(
-
- ,
- ,
- );
- setTimeout(() => {
- expect(global.fetch.callCount).to.equal(4);
- const alert = form.find('va-loading-indicator');
- expect(alert.html()).to.contain('longer than expected');
- form.unmount();
- done();
- }, 50);
- });
-
it('should ignore immediate api failures', done => {
mockMultipleApiRequests([
errorResponse,
@@ -209,10 +176,9 @@ describe('ConfirmationPoll', () => {
,
);
setTimeout(() => {
- form.update();
expect(global.fetch.callCount).to.equal(4);
- const confirmationPage = form.find('ConfirmationPage');
- expect(confirmationPage.length).to.equal(1);
+ const loadingIndicator = form.find('va-loading-indicator');
+ expect(loadingIndicator.length).to.equal(1);
form.unmount();
done();
}, 50);
diff --git a/src/applications/disability-benefits/all-claims/tests/containers/ConfirmationPage.unit.spec.jsx b/src/applications/disability-benefits/all-claims/tests/containers/ConfirmationPage.unit.spec.jsx
index 3ddea2e84b65..cdfe3315d0ea 100644
--- a/src/applications/disability-benefits/all-claims/tests/containers/ConfirmationPage.unit.spec.jsx
+++ b/src/applications/disability-benefits/all-claims/tests/containers/ConfirmationPage.unit.spec.jsx
@@ -1,28 +1,15 @@
import React from 'react';
import { expect } from 'chai';
import { render } from '@testing-library/react';
-import { Toggler } from 'platform/utilities/feature-toggles';
import configureStore from 'redux-mock-store';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
import ConfirmationPage from '../../containers/ConfirmationPage';
-import {
- submissionStatuses,
- WIZARD_STATUS,
- FORM_STATUS_BDD,
- SAVED_SEPARATION_DATE,
-} from '../../constants';
+import { submissionStatuses } from '../../constants';
import { bddConfirmationHeadline } from '../../content/bddConfirmationAlert';
import formConfig from '../../config/form';
-const retryableErrorTitle =
- "It's taking us longer than expected to submit your claim.";
-
-const getData = ({
- renderName = true,
- suffix = 'Esq.',
- disability526NewConfirmationPage = false,
-} = {}) => ({
+const getData = ({ renderName = true, suffix = 'Esq.' } = {}) => ({
user: {
profile: {
userFullName: renderName
@@ -33,11 +20,6 @@ const getData = ({
form: {
data: {},
},
- featureToggles: {
- loading: false,
- [Toggler.TOGGLE_NAMES
- .disability526NewConfirmationPage]: disability526NewConfirmationPage,
- },
});
describe('ConfirmationPage', () => {
@@ -59,259 +41,103 @@ describe('ConfirmationPage', () => {
const middleware = [thunk];
const mockStore = configureStore(middleware);
- const testPage = (status, otherProps) =>
- render(
-
-
- ,
- ,
- );
-
- it('should render success status', () => {
- const tree = testPage(submissionStatuses.succeeded);
- tree.getByText('Claim ID number');
- tree.getByText('Your claim has successfully been submitted.');
- tree.getByText('Date submitted');
-
- tree.getByText('Conditions claimed');
- tree.getByText('Something Something');
- tree.getByText('Unknown Condition');
- });
-
- it('should not render success with BDD SHA alert when not submitting BDD claim', () => {
- const { queryByText } = render(
-
-
- ,
- ,
+ /**
+ * Utility to verify confirmation page
+ * @param {string} claimId - if claimId has a value, verify the label and value are on the page
+ * @param {boolean} isBdd - if true, verify BDD alert is present, otherwise verify it is not present
+ * @param {string} submissionStatus - used to verify logic based on success or non success status
+ */
+ const verifyConfirmationPage = (claimId, isBdd = false, submissionStatus) => {
+ const store = mockStore(
+ getData({
+ disability526NewConfirmationPage: true,
+ }),
);
-
- expect(queryByText(bddConfirmationHeadline)).to.not.exist;
- });
-
- it('should render success with BDD SHA alert', () => {
- const tree = render(
-
-
- ,
- ,
- );
- tree.getByText('Claim ID number');
- tree.getByText(bddConfirmationHeadline);
- });
-
- it('should render retry status', () => {
- const tree = testPage(submissionStatuses.retry);
- tree.getByText(retryableErrorTitle);
- });
-
- it('should render exhausted status', () => {
- const tree = testPage(submissionStatuses.exhausted);
- tree.getByText(retryableErrorTitle);
- });
-
- it('should render apiFailure status', () => {
- const tree = testPage(submissionStatuses.apiFailure);
- tree.getByText(retryableErrorTitle);
- });
-
- it('should render retryable failure with BDD SHA alert', () => {
- const tree = render(
-
-
- ,
- ,
- );
-
- tree.getByText(
- 'Submit your Separation Health Assessment - Part A Self-Assessment now if you haven’t already',
- );
- tree.getByText(
- 'Separation Health Assessment - Part A Self-Assessment (opens in new tab)',
- );
- });
-
- it('should render other status', () => {
- const tree = testPage(submissionStatuses.failed, { submissionId: '123' });
- tree.getByText(
- 'We’re sorry. Something went wrong when we tried to submit your claim.',
- );
- tree.getByText('and provide this reference number 123', { exact: false });
- });
-
- it('should render note about email', () => {
const props = {
...defaultProps,
+ submissionStatus,
};
-
- const tree = render(
-
-
- ,
+ if (claimId) {
+ props.claimId = claimId;
+ }
+ if (isBdd) {
+ props.isSubmittingBDD = true;
+ }
+
+ const { container, getByText, queryByText } = render(
+
+ ,
);
- tree.getByText(
- 'We’ll send you an email to confirm that we received your claim.',
- );
- });
+ if (isBdd) {
+ getByText(bddConfirmationHeadline);
+ } else {
+ expect(queryByText(bddConfirmationHeadline)).to.not.exist;
+ }
- it('should not render email message when there is an error', () => {
- const props = {
- ...defaultProps,
- submissionStatus: submissionStatuses.failed,
- };
+ // success alert
+ getByText('Form submission started on', { exact: false });
+ getByText('Your submission is in progress.', {
+ exact: false,
+ });
- const tree = render(
-
-
- ,
+ // summary box with claim info
+ getByText('Disability Compensation Claim');
+ getByText('For Hector Lee Brooks Sr.');
+ getByText('Date submitted');
+ getByText('November 7, 2024');
+ getByText('Conditions claimed');
+ getByText('Something Something');
+ getByText('Unknown Condition');
+
+ if (claimId) {
+ getByText('Claim ID number');
+ getByText(claimId);
+ } else {
+ expect(queryByText('Claim ID number')).to.not.exist;
+ }
+
+ // rest of sections are present
+ getByText('Print this confirmation page');
+ getByText('What to expect');
+ getByText('How to contact us if you have questions');
+ getByText('How long will it take VA to make a decision on my claim?');
+ getByText('If I have dependents, how can I receive additional benefits?');
+ getByText('Need help?');
+
+ // links
+ expect(container.querySelectorAll('va-link')).to.have.lengthOf(5);
+ const link = container.querySelectorAll('va-link')[1];
+ expect(link.getAttribute('download')).to.exist;
+ expect(link.getAttribute('filetype')).to.equal('PDF');
+ expect(link.getAttribute('href')).to.equal(
+ 'https://www.vba.va.gov/pubs/forms/VBA-21-686c-ARE.pdf',
+ );
+ expect(link.getAttribute('pages')).to.equal('15');
+ expect(link.getAttribute('text')).to.equal(
+ 'Download VA Form 21-686c (opens in new tab)',
);
+ };
- expect(
- tree.queryByText(
- 'We’ll send you an email to confirm that we received your claim.',
- ),
- ).to.be.null;
+ it('should render confirmation page when submission succeeded with claim id', () => {
+ verifyConfirmationPage('12345678', false, submissionStatuses.succeeded);
});
- it('should reset wizard state & values', () => {
- sessionStorage.setItem(WIZARD_STATUS, 'a');
- sessionStorage.setItem(FORM_STATUS_BDD, 'b');
- sessionStorage.setItem(SAVED_SEPARATION_DATE, 'c');
-
- const tree = testPage(submissionStatuses.succeeded);
- tree.getByText('Claim ID number');
- expect(sessionStorage.getItem(WIZARD_STATUS)).to.be.null;
- expect(sessionStorage.getItem(FORM_STATUS_BDD)).to.be.null;
- expect(sessionStorage.getItem(SAVED_SEPARATION_DATE)).to.be.null;
- tree.unmount();
+ it('should render confirmation page when submission succeeded with no claim id', () => {
+ verifyConfirmationPage(undefined, false, submissionStatuses.succeeded);
});
- describe('new confirmation page (toggle enabled)', () => {
- // new confirmation page toggle on
-
- /**
- * Utility to verify confirmation page
- * @param {string} claimId - if claimId has a value, verify the label and value are on the page
- * @param {boolean} isBdd - if true, verify BDD alert is present, otherwise verify it is not present
- * @param {string} submissionStatus - used to verify logic based on success or non success status
- */
- const verifyConfirmationPage = (
- claimId,
- isBdd = false,
- submissionStatus,
- ) => {
- const store = mockStore(
- getData({
- disability526NewConfirmationPage: true,
- }),
- );
- const props = {
- ...defaultProps,
- submissionStatus,
- };
- if (claimId) {
- props.claimId = claimId;
- }
- if (isBdd) {
- props.isSubmittingBDD = true;
- }
-
- const { container, getByText, queryByText } = render(
-
-
- ,
- );
-
- if (isBdd) {
- getByText(bddConfirmationHeadline);
- } else {
- expect(queryByText(bddConfirmationHeadline)).to.not.exist;
- }
-
- // success alert
- getByText('Form submission started on', { exact: false });
- getByText('Your submission is in progress.', {
- exact: false,
- });
-
- // summary box with claim info
- getByText('Disability Compensation Claim');
- getByText('For Hector Lee Brooks Sr.');
- getByText('Date submitted');
- getByText('November 7, 2024');
- getByText('Conditions claimed');
- getByText('Something Something');
- getByText('Unknown Condition');
-
- if (claimId) {
- getByText('Claim ID number');
- getByText(claimId);
- } else {
- expect(queryByText('Claim ID number')).to.not.exist;
- }
-
- // rest of sections are present
- getByText('Print this confirmation page');
- getByText('What to expect');
- getByText('How to contact us if you have questions');
- getByText('How long will it take VA to make a decision on my claim?');
- getByText('If I have dependents, how can I receive additional benefits?');
- getByText('Need help?');
-
- // links
- expect(container.querySelectorAll('va-link')).to.have.lengthOf(5);
- const link = container.querySelectorAll('va-link')[1];
- expect(link.getAttribute('download')).to.exist;
- expect(link.getAttribute('filetype')).to.equal('PDF');
- expect(link.getAttribute('href')).to.equal(
- 'https://www.vba.va.gov/pubs/forms/VBA-21-686c-ARE.pdf',
- );
- expect(link.getAttribute('pages')).to.equal('15');
- expect(link.getAttribute('text')).to.equal(
- 'Download VA Form 21-686c (opens in new tab)',
- );
- };
-
- it('should render confirmation page when submission succeeded with claim id', () => {
- verifyConfirmationPage('12345678', false, submissionStatuses.succeeded);
- });
-
- it('should render confirmation page when submission succeeded with no claim id', () => {
- verifyConfirmationPage(undefined, false, submissionStatuses.succeeded);
- });
-
- it('should render success with BDD SHA alert when submission succeeded with claim id for BDD', () => {
- verifyConfirmationPage('12345678', true, submissionStatuses.succeeded);
- });
+ it('should render success with BDD SHA alert when submission succeeded with claim id for BDD', () => {
+ verifyConfirmationPage('12345678', true, submissionStatuses.succeeded);
+ });
- it('should render success when form submitted successfully but submission status has api failure', () => {
- verifyConfirmationPage('', false, submissionStatuses.apiFailure); // 500
- });
+ it('should render success when form submitted successfully but submission status has api failure', () => {
+ verifyConfirmationPage('', false, submissionStatuses.apiFailure); // 500
+ });
- it('should render success when form submitted successfully but submission status has non retryable error', () => {
- // status code 200, but response has "status: non_retryable_error"
- verifyConfirmationPage('', false, submissionStatuses.failed);
- });
+ it('should render success when form submitted successfully but submission status has non retryable error', () => {
+ // status code 200, but response has "status: non_retryable_error"
+ verifyConfirmationPage('', false, submissionStatuses.failed);
});
});
diff --git a/src/applications/disability-benefits/all-claims/tests/utils/form0781.unit.spec.js b/src/applications/disability-benefits/all-claims/tests/utils/form0781.unit.spec.js
index e2983f71d6df..cf0245864732 100644
--- a/src/applications/disability-benefits/all-claims/tests/utils/form0781.unit.spec.js
+++ b/src/applications/disability-benefits/all-claims/tests/utils/form0781.unit.spec.js
@@ -5,11 +5,14 @@ import {
showBehaviorIntroPage,
showBehaviorIntroCombatPage,
showBehaviorListPage,
+ isCompletingForm0781,
+ isRelatedToMST,
} from '../../utils/form0781';
+import { form0781WorkflowChoices } from '../../content/form0781';
describe('showForm0781Pages', () => {
describe('when the flipper is on and a user is claiming a new condition', () => {
- it('should should return true', () => {
+ it('should return true', () => {
const formData = {
syncModern0781Flow: true,
mentalHealth: {
@@ -23,7 +26,7 @@ describe('showForm0781Pages', () => {
});
describe('when the flipper is off and a user is claiming a new condition', () => {
- it('should should return false', () => {
+ it('should return false', () => {
const formData = {
syncModern0781Flow: false,
mentalHealth: {
@@ -37,7 +40,7 @@ describe('showForm0781Pages', () => {
});
describe('when the flipper is on and a user is not claiming a new condition', () => {
- it('should should return false', () => {
+ it('should return false', () => {
const formData = {
syncModern0781Flow: true,
mentalHealth: {
@@ -49,7 +52,7 @@ describe('showForm0781Pages', () => {
expect(showForm0781Pages(formData)).to.eq(false);
});
- it('should should return false', () => {
+ it('should return false', () => {
const formData = {
syncModern0781Flow: true,
mentalHealth: {
@@ -63,11 +66,106 @@ describe('showForm0781Pages', () => {
});
});
+// Flipper is on AND user is claiming a new condition
+describe('isCompletingForm0781', () => {
+ describe('when the user selects to optIn to completing the form online', () => {
+ it('should return true', () => {
+ const formData = {
+ syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
+ mentalHealth: {
+ conditions: {
+ someCondition: true,
+ },
+ },
+ };
+ expect(isCompletingForm0781(formData)).to.eq(true);
+ });
+ });
+ describe('when the user selects to submit a paper form', () => {
+ it('should return false', () => {
+ const formData = {
+ syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.SUBMIT_PAPER_FORM,
+ mentalHealth: {
+ conditions: {
+ someCondition: true,
+ },
+ },
+ };
+ expect(isCompletingForm0781(formData)).to.eq(false);
+ });
+ });
+ describe('when the user selects to opt out', () => {
+ it('should return false', () => {
+ const formData = {
+ syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.OPT_OUT_OF_FORM0781,
+ mentalHealth: {
+ conditions: {
+ someCondition: true,
+ },
+ },
+ };
+ expect(isCompletingForm0781(formData)).to.eq(false);
+ });
+ });
+});
+
+// Flipper is on AND user is claiming a new condition AND user opts into completing online form
+describe('isRelatedToMST', () => {
+ describe('when a user has selected MST', () => {
+ it('should return true', () => {
+ const formData = {
+ syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
+ mentalHealth: {
+ conditions: {
+ someCondition: true,
+ },
+ eventTypes: {
+ combat: true,
+ mst: true,
+ },
+ },
+ };
+
+ expect(isRelatedToMST(formData)).to.eq(true);
+ });
+ });
+ describe('when a user has NOT selected MST', () => {
+ it('should return false', () => {
+ const formData = {
+ syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
+ mentalHealth: {
+ conditions: {
+ someCondition: true,
+ },
+ eventTypes: {
+ combat: true,
+ mst: false,
+ },
+ },
+ };
+ expect(isRelatedToMST(formData)).to.eq(false);
+ });
+ });
+});
+
+// Flipper is on AND user is claiming a new condition AND user opts into completing online form
describe('showBehaviorIntroCombatPage', () => {
describe('when a user has selected ONLY combat related events', () => {
- it('should should return true', () => {
+ it('should return true', () => {
const formData = {
syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
mentalHealth: {
conditions: {
someCondition: true,
@@ -84,9 +182,11 @@ describe('showBehaviorIntroCombatPage', () => {
});
describe('when a user has selected combat related AND non-combat related events', () => {
- it('should should return false', () => {
+ it('should return false', () => {
const formData = {
syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
mentalHealth: {
conditions: {
someCondition: true,
@@ -103,9 +203,11 @@ describe('showBehaviorIntroCombatPage', () => {
});
describe('when a user has not selected combat related events', () => {
- it('should should return false', () => {
+ it('should return false', () => {
const formData = {
syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
mentalHealth: {
conditions: {
someCondition: true,
@@ -124,9 +226,11 @@ describe('showBehaviorIntroCombatPage', () => {
describe('showBehaviorIntroPage', () => {
describe('when a user has not selected ONLY combat related events', () => {
- it('should should return true', () => {
+ it('should return true', () => {
const formData = {
syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
mentalHealth: {
conditions: {
someCondition: true,
@@ -142,9 +246,11 @@ describe('showBehaviorIntroPage', () => {
});
describe('when a user has selected ONLY combat related events', () => {
- it('should should return false', () => {
+ it('should return false', () => {
const formData = {
syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
mentalHealth: {
conditions: {
someCondition: true,
@@ -161,10 +267,12 @@ describe('showBehaviorIntroPage', () => {
});
describe('showBehaviorListPage', () => {
- describe('when a user has selected ONLY combat related events and opted in', () => {
- it('should should return true', () => {
+ describe('when a user has selected ONLY combat related events', () => {
+ it('should return true', () => {
const formData = {
syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
'view:answerCombatBehaviorQuestions': 'true',
mentalHealth: {
conditions: {
@@ -180,29 +288,12 @@ describe('showBehaviorListPage', () => {
});
});
- describe('when a user has selected ONLY combat related events and opted OUT', () => {
- it('should should return false', () => {
- const formData = {
- syncModern0781Flow: true,
- 'view:answerCombatBehaviorQuestions': 'false',
- mentalHealth: {
- conditions: {
- someCondition: true,
- },
- eventTypes: {
- combat: true,
- nonMst: false,
- },
- },
- };
- expect(showBehaviorListPage(formData)).to.eq(false);
- });
- });
-
describe('when a user has not selected ONLY combat related events', () => {
- it('should should return true', () => {
+ it('should return true', () => {
const formData = {
syncModern0781Flow: true,
+ 'view:mentalHealthWorkflowChoice':
+ form0781WorkflowChoices.COMPLETE_ONLINE_FORM,
mentalHealth: {
conditions: {
someCondition: true,
diff --git a/src/applications/disability-benefits/all-claims/utils/form0781.js b/src/applications/disability-benefits/all-claims/utils/form0781.js
index b3f5bf7c0a22..8ec9f0b9896b 100644
--- a/src/applications/disability-benefits/all-claims/utils/form0781.js
+++ b/src/applications/disability-benefits/all-claims/utils/form0781.js
@@ -78,7 +78,7 @@ export function isRelatedToMST(formData) {
* - AND is not seeing the 'Combat Only' version of this page
*/
export function showBehaviorIntroPage(formData) {
- return showForm0781Pages(formData) && !combatOnlySelection(formData);
+ return isCompletingForm0781(formData) && !combatOnlySelection(formData);
}
/*
@@ -91,7 +91,7 @@ export function showBehaviorIntroPage(formData) {
* - in all other cases
*/
export function showBehaviorIntroCombatPage(formData) {
- return showForm0781Pages(formData) && combatOnlySelection(formData);
+ return isCompletingForm0781(formData) && combatOnlySelection(formData);
}
/*
@@ -108,7 +108,7 @@ export function showBehaviorListPage(formData) {
_.get('view:answerCombatBehaviorQuestions', formData, 'false') === 'true';
return (
- showForm0781Pages(formData) &&
+ isCompletingForm0781(formData) &&
((showBehaviorIntroCombatPage(formData) && answerQuestions) ||
!combatOnlySelection(formData))
);
diff --git a/src/applications/discharge-wizard/components/questions/Reason.jsx b/src/applications/discharge-wizard/components/questions/Reason.jsx
index fc79b2d92772..e6bdac2aae59 100644
--- a/src/applications/discharge-wizard/components/questions/Reason.jsx
+++ b/src/applications/discharge-wizard/components/questions/Reason.jsx
@@ -16,8 +16,15 @@ const Reason = ({ formResponses, setReason, router, viewedIntroPage }) => {
const shortName = SHORT_NAME_MAP.REASON;
const H1 = QUESTION_MAP[shortName];
const reason = formResponses[shortName];
- const hint =
- 'Note: If more than one of these descriptions matches your situation, choose the one that started the events that led to your discharge. For example, if you sustained a traumatic brain injury (TBI), which led to posttraumatic stress disorder (PTSD), choose the option associated with TBI.';
+ const hint = (
+ <>
+ Note: If more than one of these descriptions matches
+ your situation, choose the one that started the events that led to your
+ discharge. For example, if you sustained a traumatic brain injury (TBI),
+ which led to posttraumatic stress disorder (PTSD), choose the option
+ associated with TBI.
+ >
+ );
const {
REASON_PTSD,
REASON_TBI,
diff --git a/src/applications/discover-your-benefits/config/form.js b/src/applications/discover-your-benefits/config/form.js
index aedc827981df..91f0880feebb 100644
--- a/src/applications/discover-your-benefits/config/form.js
+++ b/src/applications/discover-your-benefits/config/form.js
@@ -33,9 +33,6 @@ export const isOnConfirmationPage = currentLocation => {
};
export const formConfig = {
- formOptions: {
- fullWidth: true,
- },
rootUrl: manifest.rootUrl,
urlPrefix: '/',
// submitUrl: '/v0/api',
diff --git a/src/applications/discover-your-benefits/constants/benefits.js b/src/applications/discover-your-benefits/constants/benefits.js
index ed005ef8c361..40ebff704aea 100644
--- a/src/applications/discover-your-benefits/constants/benefits.js
+++ b/src/applications/discover-your-benefits/constants/benefits.js
@@ -404,10 +404,7 @@ export const BENEFITS_LIST = [
characterOfDischargeTypes.UNCHARACTERIZED,
characterOfDischargeTypes.NOT_SURE,
],
- [mappingTypes.DISABILITY_RATING]: [
- disabilityTypes.APPLIED_AND_RECEIVED,
- disabilityTypes.STARTED,
- ],
+ [mappingTypes.DISABILITY_RATING]: [anyType.ANY],
[mappingTypes.GI_BILL]: [anyType.ANY],
},
learnMoreURL:
@@ -596,10 +593,7 @@ export const BENEFITS_LIST = [
characterOfDischargeTypes.BAD_CONDUCT,
characterOfDischargeTypes.NOT_SURE,
],
- [mappingTypes.DISABILITY_RATING]: [
- disabilityTypes.STARTED,
- disabilityTypes.NOT_APPLIED,
- ],
+ [mappingTypes.DISABILITY_RATING]: [anyType.ANY],
[mappingTypes.GI_BILL]: [anyType.ANY],
},
learnMoreURL: 'https://www.va.gov/disability/',
diff --git a/src/applications/discover-your-benefits/sass/discover-your-benefits.scss b/src/applications/discover-your-benefits/sass/discover-your-benefits.scss
index 0670ccbe900a..ffcf762c96e3 100644
--- a/src/applications/discover-your-benefits/sass/discover-your-benefits.scss
+++ b/src/applications/discover-your-benefits/sass/discover-your-benefits.scss
@@ -31,6 +31,10 @@
margin-right: 0;
}
+.discover-your-benefits .usa-width-two-thirds {
+ width: auto !important;
+}
+
#filters-section-desktop {
@media (min-width: $medium-screen) {
border-right: 2px solid var(--vads-color-gray-medium);
diff --git a/src/applications/dispute-debt/actions/index.js b/src/applications/dispute-debt/actions/index.js
new file mode 100644
index 000000000000..ba595d220b05
--- /dev/null
+++ b/src/applications/dispute-debt/actions/index.js
@@ -0,0 +1,88 @@
+import * as Sentry from '@sentry/browser';
+import { isValid } from 'date-fns';
+import { head } from 'lodash';
+import { formatDateShort } from 'platform/utilities/date';
+import { apiRequest } from 'platform/utilities/api';
+import environment from 'platform/utilities/environment';
+import {
+ deductionCodes,
+ DEBT_TYPES,
+ DEBTS_FETCH_INITIATED,
+ DEBTS_FETCH_SUCCESS,
+ DEBTS_FETCH_FAILURE,
+} from '../constants';
+import { currency, endDate } from '../utils';
+
+// helper functions to get debt and copay labels and descriptions
+const getDebtLabel = debt =>
+ `${currency(debt?.currentAr)} overpayment for ${deductionCodes[
+ debt.deductionCode
+ ] || debt.benefitType}`;
+
+const getDebtDescription = debt => {
+ // most recent debt history entry
+ const dates = debt?.debtHistory?.map(m => new Date(m.date)) ?? [];
+ const sortedHistory = dates.sort((a, b) => Date.parse(b) - Date.parse(a));
+ const mostRecentDate = isValid(head(sortedHistory))
+ ? formatDateShort(head(sortedHistory))
+ : '';
+ const dateby = endDate(mostRecentDate, 30);
+ return dateby ? `Pay or request help by ${dateby}` : '';
+};
+
+export const fetchDebts = async dispatch => {
+ dispatch({
+ type: DEBTS_FETCH_INITIATED,
+ pending: true,
+ });
+ const getDebts = () => {
+ const options = {
+ method: 'GET',
+ credentials: 'include',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Key-Inflection': 'camel',
+ 'Source-App-Name': window.appName,
+ },
+ };
+
+ return apiRequest(`${environment.API_URL}/v0/debts`, options);
+ };
+
+ try {
+ const response = await getDebts();
+ const approvedDeductionCodes = Object.keys(deductionCodes);
+ // filter approved deductionCodes &&
+ // remove debts that have a current amount owed of 0
+ const filteredResponse = response.debts
+ .filter(debt => approvedDeductionCodes.includes(debt.deductionCode))
+ .filter(debt => debt.currentAr > 0)
+ .map((debt, index) => ({
+ ...debt,
+ id: index,
+ debtType: DEBT_TYPES.DEBT,
+ }));
+
+ const simplifiedResponse = filteredResponse.map(debt => ({
+ compositeDebtId: debt.compositeDebtId,
+ label: getDebtLabel(debt),
+ description: getDebtDescription(debt),
+ debtType: DEBT_TYPES.DEBT,
+ }));
+
+ return dispatch({
+ type: DEBTS_FETCH_SUCCESS,
+ debts: simplifiedResponse,
+ });
+ } catch (error) {
+ Sentry.withScope(scope => {
+ scope.setExtra('error', error);
+ Sentry.captureMessage(`FSR fetchDebts failed: ${error.detail}`);
+ });
+ dispatch({
+ type: DEBTS_FETCH_FAILURE,
+ error,
+ });
+ throw new Error(error);
+ }
+};
diff --git a/src/applications/dispute-debt/app-entry.jsx b/src/applications/dispute-debt/app-entry.jsx
new file mode 100644
index 000000000000..b22da2fc3702
--- /dev/null
+++ b/src/applications/dispute-debt/app-entry.jsx
@@ -0,0 +1,15 @@
+import '@department-of-veterans-affairs/platform-polyfills';
+import './sass/dispute-debt.scss';
+
+import { startAppFromIndex } from '@department-of-veterans-affairs/platform-startup/exports';
+
+import routes from './routes';
+import reducer from './reducers';
+import manifest from './manifest.json';
+
+startAppFromIndex({
+ entryName: manifest.entryName,
+ url: manifest.rootUrl,
+ reducer,
+ routes,
+});
diff --git a/src/applications/dispute-debt/components/AlertCard.jsx b/src/applications/dispute-debt/components/AlertCard.jsx
new file mode 100644
index 000000000000..afb33e0a7066
--- /dev/null
+++ b/src/applications/dispute-debt/components/AlertCard.jsx
@@ -0,0 +1,46 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import environment from 'platform/utilities/environment';
+import { DEBT_TYPES } from '../constants';
+
+const AlertCard = ({ debtType }) => {
+ return (
+ <>
+
+
+ We can’t access your{' '}
+ {`${debtType === DEBT_TYPES.DEBT ? 'debt' : 'copay'}`} records right
+ now
+
+
+ We’re sorry. Information about{' '}
+ {`${debtType === DEBT_TYPES.DEBT ? 'debts' : 'copays'}`} you might
+ have is unavailable because something went wrong on our end. Please
+ check back soon.
+
+
+ If you continue having trouble viewing information about your{' '}
+ {`${debtType === DEBT_TYPES.DEBT ? 'debts' : 'copays'}`}, contact us
+ online through Ask VA.
+
+
+
+ Go back to VA.gov
+
+ >
+ );
+};
+AlertCard.propTypes = {
+ debtType: PropTypes.string,
+};
+
+export default AlertCard;
diff --git a/src/applications/dispute-debt/components/DebtSelection.jsx b/src/applications/dispute-debt/components/DebtSelection.jsx
new file mode 100644
index 000000000000..62b5013510f5
--- /dev/null
+++ b/src/applications/dispute-debt/components/DebtSelection.jsx
@@ -0,0 +1,121 @@
+import React, { useEffect, useState } from 'react';
+import { useSelector, useDispatch } from 'react-redux';
+import PropTypes from 'prop-types';
+import { setData } from 'platform/forms-system/src/js/actions';
+import { VaCheckboxGroup } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import { setFocus } from '../utils';
+
+import AlertCard from './AlertCard';
+import { DEBT_TYPES } from '../constants';
+
+const DebtSelection = ({ formContext }) => {
+ const { availableDebts, isDebtError } = useSelector(
+ state => state.availableDebts,
+ );
+ const { data } = useSelector(state => state.form);
+ const { selectedDebts = [] } = data;
+ const dispatch = useDispatch();
+
+ const [selectionError, setSelectionError] = useState(null);
+
+ useEffect(
+ () => {
+ if (formContext.submitted && !selectedDebts?.length) {
+ setSelectionError('Choose at least one debt');
+ setFocus('va-checkbox-group');
+ return;
+ }
+ setSelectionError(null);
+ },
+ [dispatch, formContext.submitted, selectedDebts?.length],
+ );
+
+ // nothing to actually display so we short circuit and return just the error (no question info)
+ if (isDebtError || !availableDebts.length) {
+ return ;
+ }
+
+ const onGroupChange = ({ detail, target }) => {
+ // adding new prop selectedDebtId to selectedDebts so it's easier to filter on uncheck
+ if (detail.checked) {
+ // debts and copays use different unique identifier props, so we need to check the data-debt-type to pull the correct one
+ let selectedDebt;
+ if (target.dataset.debtType === DEBT_TYPES.DEBT) {
+ selectedDebt = availableDebts.find(
+ debt => debt.compositeDebtId === target.dataset.index,
+ );
+ }
+
+ // including new selectedDebtId prop
+ const newlySelectedDebts = [
+ ...selectedDebts,
+ { ...selectedDebt, selectedDebtId: target.dataset.index },
+ ];
+
+ return dispatch(
+ setData({
+ ...data,
+ selectedDebts: newlySelectedDebts,
+ }),
+ );
+ }
+
+ // uncheck by new selectedDebtId prop
+ const combinedChecked = selectedDebts?.filter(
+ selection => selection.selectedDebtId !== target.dataset.index,
+ );
+
+ return dispatch(
+ setData({
+ ...data,
+ selectedDebts: combinedChecked,
+ }),
+ );
+ };
+
+ return (
+
+
+ {availableDebts.map(debt => (
+ currDebt.selectedDebtId === debt.compositeDebtId,
+ )}
+ checkbox-description={debt.description}
+ data-debt-type={DEBT_TYPES.DEBT}
+ data-index={debt.compositeDebtId}
+ data-testid="debt-selection-checkbox"
+ key={debt.compositeDebtId}
+ label={debt.label}
+ />
+ ))}
+
+
+ If you received a letter about a VA benefit debt that isn’t listed here,
+ call us at (or{' '}
+ from overseas).
+ We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
+
+
+ );
+};
+
+DebtSelection.propTypes = {
+ availableDebts: PropTypes.array,
+ data: PropTypes.shape({
+ selectedDebts: PropTypes.array,
+ }),
+ formContext: PropTypes.shape({
+ submitted: PropTypes.bool,
+ }),
+ isDebtError: PropTypes.bool,
+};
+
+export default DebtSelection;
diff --git a/src/applications/dispute-debt/components/VeteranInformation.jsx b/src/applications/dispute-debt/components/VeteranInformation.jsx
new file mode 100644
index 000000000000..31359a839406
--- /dev/null
+++ b/src/applications/dispute-debt/components/VeteranInformation.jsx
@@ -0,0 +1,105 @@
+import React from 'react';
+import { useSelector } from 'react-redux';
+import PropTypes from 'prop-types';
+import { isValid, format } from 'date-fns';
+
+import { CONTACTS } from '@department-of-veterans-affairs/component-library/contacts';
+
+import { selectProfile } from '~/platform/user/selectors';
+
+import { srSubstitute } from '~/platform/forms-system/src/js/utilities/ui/mask-string';
+
+import { FORMAT_YMD_DATE_FNS, FORMAT_READABLE_DATE_FNS } from '../constants';
+
+import { parseDateToDateObj } from '../utils';
+
+// separate each number so the screenreader reads "number ending with 1 2 3 4"
+// instead of "number ending with 1,234"
+const mask = value => {
+ const number = (value || '').toString().slice(-4);
+ return srSubstitute(
+ `●●●–●●–${number}`,
+ `ending with ${number.split('').join(' ')}`,
+ );
+};
+
+const VeteranInformation = ({ formData }) => {
+ const { ssnLastFour, vaFileLastFour } = formData?.veteranInformation || {};
+ const { dob, userFullName = {} } = useSelector(selectProfile);
+ const { first, middle, last, suffix } = userFullName;
+
+ const dobDateObj = parseDateToDateObj(dob || null, FORMAT_YMD_DATE_FNS);
+
+ return (
+ <>
+
+ Confirm the personal information we have on file for you.
+
+ Social Security number:{' '}
+
+ {mask(ssnLastFour)}
+
+
+ ) : null}
+ {vaFileLastFour ? (
+
+ VA file number:{' '}
+
+ {mask(vaFileLastFour)}
+
+
+ ) : null}
+
+ Date of birth:{' '}
+ {isValid(dobDateObj) ? (
+
+ {format(dobDateObj, FORMAT_READABLE_DATE_FNS)}
+
+ ) : null}
+
+
+
+
+
+
+ Note: If you need to update your personal information,
+ you can call us at .
+ We’re here Monday through Friday, 8:00 a.m. to 9:00 p.m.{' '}
+
+ ET
+
+ .
+
+ >
+ );
+};
+
+VeteranInformation.propTypes = {
+ formData: PropTypes.shape({
+ veteranInformation: PropTypes.shape({
+ ssnLastFour: PropTypes.string,
+ vaFileLastFour: PropTypes.string,
+ }),
+ }),
+};
+
+export default VeteranInformation;
diff --git a/src/applications/dispute-debt/config/form.js b/src/applications/dispute-debt/config/form.js
new file mode 100644
index 000000000000..a92e2b7ec678
--- /dev/null
+++ b/src/applications/dispute-debt/config/form.js
@@ -0,0 +1,116 @@
+import footerContent from 'platform/forms/components/FormFooter';
+import { VA_FORM_IDS } from 'platform/forms/constants';
+
+import profileContactInfo from 'platform/forms-system/src/js/definitions/profileContactInfo';
+import {
+ veteranInformation,
+ debtSelection,
+ disputeReason,
+ supportStatement,
+} from '../pages';
+
+import ConfirmationPage from '../containers/ConfirmationPage';
+import IntroductionPage from '../containers/IntroductionPage';
+
+import prefillTransformer from './prefill-transformer';
+import { TITLE, SUBTITLE } from '../constants';
+import manifest from '../manifest.json';
+
+/** @type {FormConfig} */
+const formConfig = {
+ rootUrl: manifest.rootUrl,
+ urlPrefix: '/',
+ submitUrl: '/debts_api/v0/digital_disputes',
+ submit: () =>
+ Promise.resolve({ attributes: { confirmationNumber: '123123123' } }),
+ trackingPrefix: 'dispute-debt',
+ introduction: IntroductionPage,
+ confirmation: ConfirmationPage,
+ dev: {
+ showNavLinks: true,
+ collapsibleNavLinks: true,
+ },
+ formId: VA_FORM_IDS.FORM_DISPUTE_DEBT,
+ saveInProgress: {
+ // messages: {
+ // inProgress: 'Your digital dispute for debts application (DISPUTE-DEBT) is in progress.',
+ // expired: 'Your saved digital dispute for debts application (DISPUTE-DEBT) has expired. If you want to apply for digital dispute for debts, please start a new application.',
+ // saved: 'Your digital dispute for debts application has been saved.',
+ // },
+ },
+ version: 0,
+ prefillEnabled: true,
+ prefillTransformer,
+ savedFormMessages: {
+ notFound: 'Please start over to apply for digital dispute for debts.',
+ noAuth:
+ 'Please sign in again to continue your application for digital dispute for debts.',
+ },
+ title: TITLE,
+ subTitle: SUBTITLE,
+ defaultDefinitions: {},
+ chapters: {
+ personalInformationChapter: {
+ title: 'Veteran information',
+ pages: {
+ veteranInformation: {
+ title: 'Your personal information',
+ path: 'personal-information',
+ uiSchema: veteranInformation.uiSchema,
+ schema: veteranInformation.schema,
+ },
+ ...profileContactInfo({
+ contactInfoPageKey: 'confirmContactInfo2',
+ contactPath: 'contact-information',
+ contactInfoRequiredKeys: [
+ 'mobilePhone',
+ 'homePhone',
+ 'mailingAddress',
+ 'email',
+ ],
+ included: ['mobilePhone', 'homePhone', 'mailingAddress', 'email'], // default
+ wrapperKey: 'veteranInformation',
+ }),
+ },
+ },
+ debtSelectionChapter: {
+ title: 'Debt Selection',
+ pages: {
+ selectDebt: {
+ path: 'select-debt',
+ title: 'Which debt are you disputing?',
+ uiSchema: debtSelection.uiSchema,
+ schema: debtSelection.schema,
+ initialData: {
+ selectedDebts: [],
+ },
+ },
+ },
+ },
+ reasonForDisputeChapter: {
+ title: 'Reason for dispute',
+ pages: {
+ disputeReason: {
+ path: 'existence-or-amount/:index',
+ title: 'Debt X of Y: Name of debt',
+ uiSchema: disputeReason.uiSchema,
+ schema: disputeReason.schema,
+ showPagePerItem: true,
+ arrayPath: 'selectedDebts',
+ },
+ supportStatement: {
+ path: 'dispute-reason/:index',
+ title: 'Debt X of Y: Name of debt',
+ uiSchema: supportStatement.uiSchema,
+ schema: supportStatement.schema,
+ showPagePerItem: true,
+ arrayPath: 'selectedDebts',
+ },
+ },
+ },
+ },
+ // getHelp,
+ footerContent,
+};
+
+export default formConfig;
diff --git a/src/applications/dispute-debt/config/prefill-transformer.js b/src/applications/dispute-debt/config/prefill-transformer.js
new file mode 100644
index 000000000000..4d440ed06549
--- /dev/null
+++ b/src/applications/dispute-debt/config/prefill-transformer.js
@@ -0,0 +1,16 @@
+/* vets-api/config/form_profile_mappings/DISPUTE-DEBT.yml */
+
+export default function prefillTransformer(pages, formData, metadata) {
+ const { fileNumber = '', ssn = '' } = formData?.veteranInformation || {};
+
+ return {
+ pages,
+ formData: {
+ veteranInformation: {
+ ssnLastFour: ssn,
+ vaFileLastFour: fileNumber,
+ },
+ },
+ metadata,
+ };
+}
diff --git a/src/applications/dispute-debt/constants/index.js b/src/applications/dispute-debt/constants/index.js
new file mode 100644
index 000000000000..0505b86b0837
--- /dev/null
+++ b/src/applications/dispute-debt/constants/index.js
@@ -0,0 +1,26 @@
+export const TITLE = 'Dispute your VA debt';
+export const SUBTITLE = 'Process to dispute debt';
+
+// Date formats
+export const FORMAT_YMD_DATE_FNS = 'yyyy-MM-dd';
+export const FORMAT_READABLE_DATE_FNS = 'MMMM d, yyyy';
+
+// Debt deduction codes
+export const deductionCodes = Object.freeze({
+ '30': 'Disability compensation and pension debt',
+ '41': 'Chapter 34 education debt',
+ '44': 'Chapter 35 education debt',
+ '71': 'Post-9/11 GI Bill debt for books and supplies',
+ '72': 'Post-9/11 GI Bill debt for housing',
+ '74': 'Post-9/11 GI Bill debt for tuition',
+ '75': 'Post-9/11 GI Bill debt for tuition (school liable)',
+});
+
+export const DEBTS_FETCH_INITIATED = 'DEBTS_FETCH_INITIATED';
+export const DEBTS_FETCH_SUCCESS = 'DEBTS_FETCH_SUCCESS';
+export const DEBTS_FETCH_FAILURE = 'DEBTS_FETCH_FAILURE';
+
+export const DEBT_TYPES = Object.freeze({
+ DEBT: 'DEBT',
+ COPAY: 'COPAY',
+});
diff --git a/src/applications/dispute-debt/containers/App.jsx b/src/applications/dispute-debt/containers/App.jsx
new file mode 100644
index 000000000000..bcc224264e9d
--- /dev/null
+++ b/src/applications/dispute-debt/containers/App.jsx
@@ -0,0 +1,45 @@
+import React, { useEffect } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import PropTypes from 'prop-types';
+import { isLoggedIn } from 'platform/user/selectors';
+
+import RoutedSavableApp from 'platform/forms/save-in-progress/RoutedSavableApp';
+import formConfig from '../config/form';
+import { fetchDebts } from '../actions';
+
+export default function App({ children, location }) {
+ const dispatch = useDispatch();
+ const { isDebtPending } = useSelector(state => state.availableDebts);
+ const userLoggedIn = useSelector(state => isLoggedIn(state));
+
+ useEffect(
+ () => {
+ if (userLoggedIn) {
+ fetchDebts(dispatch);
+ }
+ },
+ [dispatch, userLoggedIn],
+ );
+
+ if (isDebtPending) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {children}
+
+ );
+}
+
+App.propTypes = {
+ children: PropTypes.element,
+ isDebtPending: PropTypes.bool,
+ location: PropTypes.object,
+};
diff --git a/src/applications/dispute-debt/containers/ConfirmationPage.jsx b/src/applications/dispute-debt/containers/ConfirmationPage.jsx
new file mode 100644
index 000000000000..f878a4a4cc6b
--- /dev/null
+++ b/src/applications/dispute-debt/containers/ConfirmationPage.jsx
@@ -0,0 +1,100 @@
+import React, { useEffect, useRef } from 'react';
+import PropTypes from 'prop-types';
+import { format, isValid } from 'date-fns';
+import { useSelector } from 'react-redux';
+import { scrollTo, waitForRenderThenFocus } from 'platform/utilities/ui';
+
+export const ConfirmationPage = () => {
+ const alertRef = useRef(null);
+ const form = useSelector(state => state.form || {});
+ const { submission, formId, data = {} } = form;
+ const { fullName } = data;
+ const submitDate = submission?.timestamp;
+ const confirmationNumber = submission?.response?.confirmationNumber;
+
+ useEffect(
+ () => {
+ if (alertRef?.current) {
+ scrollTo('topScrollElement');
+ waitForRenderThenFocus('h2', alertRef.current);
+ }
+ },
+ [alertRef],
+ );
+
+ return (
+
+
+
+
+
+
+
Your application has been submitted
+
+
+
We may contact you for more information or documents.
+
Please print this page for your records.
+
+
+ Dispute your VA debt Claim{' '}
+ (Form {formId})
+
+ What if I need help filling out my application? An
+ accredited representative, like a Veterans Service Officer (VSO), can
+ help you fill out your claim.{' '}
+
+ Get help filing your claim
+
+
+
+
+
Complete this benefits form.
+
+ After submitting the form, you’ll get a confirmation message. You can
+ print this for your records.
+
+
+
+
+ We process claims within a week. If more than a week has passed since
+ you submitted your application and you haven’t heard back, please
+ don’t apply again. Call us at.
+
+
+
+
+ Once we’ve processed your claim, you’ll get a notice in the mail with
+ our decision.
+
+
+
+ );
+};
+
+export const IntroductionPage = props => {
+ const { route } = props;
+ const { formConfig, pageList } = route;
+ const { useToggleValue, TOGGLE_NAMES } = useFeatureToggle();
+ const disputeDebtActive = useToggleValue(TOGGLE_NAMES.disputeDebt);
+
+ /* Dev Note:
+ The following are variables for confirming LOA3 and logged in status
+ we can use these to display alert to direct users to complete their profile
+ before proceeding with the application
+ */
+ const userLoggedIn = useSelector(state => isLoggedIn(state));
+ const userIdVerified = useSelector(state => isLOA3(state));
+ const showVerifyIdentify = userLoggedIn && !userIdVerified;
+
+ useEffect(() => {
+ scrollToTop();
+ focusElement('h1');
+ }, []);
+
+ return (
+
+
+ {disputeDebtActive ? (
+ <>
+
+ Follow the steps below to apply for digital dispute for debts.
+
If you have trouble using this online form, call our MyVA411 main
- information line at (
+ information line at (
).
@@ -20,13 +20,9 @@ const GetFormHelp = () => {
If you have questions about your benefit overpayments, call us at{' '}
- (or{' '}
- {' '}
- from overseas). We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m.
- ET.
+ (or{' '}
+ from
+ overseas). We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
If you have questions about your copay bills, call us at{' '}
diff --git a/src/applications/financial-status-report/constants/diary-codes/index.js b/src/applications/financial-status-report/constants/diary-codes/index.js
index 506546927320..df6957b47d6c 100644
--- a/src/applications/financial-status-report/constants/diary-codes/index.js
+++ b/src/applications/financial-status-report/constants/diary-codes/index.js
@@ -5,12 +5,9 @@ import { endDate } from '../../utils/helpers';
const ContactDMC = () => (
- (or{' '}
- {' '}
- from overseas)
+ (or{' '}
+ from
+ overseas)
);
diff --git a/src/applications/financial-status-report/constants/index.js b/src/applications/financial-status-report/constants/index.js
index 978d9807688d..898d86d4111f 100644
--- a/src/applications/financial-status-report/constants/index.js
+++ b/src/applications/financial-status-report/constants/index.js
@@ -3,8 +3,12 @@ import constants from 'vets-json-schema/dist/constants.json';
export const COUNTRY_LABELS = constants.countries.map(country => country.label);
export const COUNTRY_VALUES = constants.countries.map(country => country.value);
-export const STATE_LABELS = constants.pciuStates.map(state => state.label);
-export const STATE_VALUES = constants.pciuStates.map(state => state.value);
+export const STATE_LABELS = constants.formProfileStates.map(
+ state => state.label,
+);
+export const STATE_VALUES = constants.formProfileStates.map(
+ state => state.value,
+);
export const MILITARY_CITY_CODES = ['APO', 'DPO', 'FPO'];
export const MILITARY_STATE_CODES = ['AA', 'AE', 'AP'];
diff --git a/src/applications/financial-status-report/wizard/components/Contacts.jsx b/src/applications/financial-status-report/wizard/components/Contacts.jsx
index c6fa85973909..a2792e0cf317 100644
--- a/src/applications/financial-status-report/wizard/components/Contacts.jsx
+++ b/src/applications/financial-status-report/wizard/components/Contacts.jsx
@@ -4,14 +4,10 @@ import { VaTelephone } from '@department-of-veterans-affairs/component-library/d
const ContactDMC = () => (
<>
- Call us at (or{' '}
- {' '}
- from overseas). We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET.
- If you have hearing loss, call (
+ Call us at (or{' '}
+ from
+ overseas). We’re here Monday through Friday, 7:30 a.m. to 7:00 p.m. ET. If
+ you have hearing loss, call (
).
>
);
diff --git a/src/applications/gi/components/LicenseCertificationAdminInfo.jsx b/src/applications/gi/components/LicenseCertificationAdminInfo.jsx
index b03a50aa8fb3..5f3bfe86708f 100644
--- a/src/applications/gi/components/LicenseCertificationAdminInfo.jsx
+++ b/src/applications/gi/components/LicenseCertificationAdminInfo.jsx
@@ -1,24 +1,39 @@
+import {
+ VaIcon,
+ VaLink,
+} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import React from 'react';
function LicenseCertificationAdminInfo({ institution }) {
const { name, mailingAddress } = institution;
return (
-
- Fill out the form Request for Reimbursement of Licensing or
- Certification Test Fees.
-
- {' '}
+
+ Print and fill out form Request for Reimbursement of Licensing or
+ Certification Test Fees. Send the completed application to the Regional
+ Processing Office for your region listed in the form.
+
+
);
}
diff --git a/src/applications/gi/components/LicenseCertificationAlert.jsx b/src/applications/gi/components/LicenseCertificationAlert.jsx
index 1d03bbd0ba91..f92c1391506e 100644
--- a/src/applications/gi/components/LicenseCertificationAlert.jsx
+++ b/src/applications/gi/components/LicenseCertificationAlert.jsx
@@ -1,3 +1,4 @@
+import { VaAlert } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import React from 'react';
function LicenseCertificationAlert({
@@ -10,8 +11,8 @@ function LicenseCertificationAlert({
type,
}) {
return (
-
{changeStateAlert &&
- `The state field has been updated to ${state} becuase
+ `The state field has been updated to ${state} because
the ${name} ${
type === 'prep' ? 'prep course' : type
} is specific to that state.`}
@@ -29,7 +30,7 @@ function LicenseCertificationAlert({
`State options have been changed to reflect only those states where ${name} is available`}
{changeStateToAllAlert &&
`Certifications are nationwide. State does not apply`}
-
+
);
}
diff --git a/src/applications/gi/components/LicenseCertificationKeywordSearch.jsx b/src/applications/gi/components/LicenseCertificationKeywordSearch.jsx
index f31ce80c7c4a..636007a08a10 100644
--- a/src/applications/gi/components/LicenseCertificationKeywordSearch.jsx
+++ b/src/applications/gi/components/LicenseCertificationKeywordSearch.jsx
@@ -3,6 +3,7 @@ import Downshift from 'downshift';
import classNames from 'classnames';
import PropTypes from 'prop-types';
+import { VaAdditionalInfo } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
export default function LicenseCertificationKeywordSearch({
inputValue,
@@ -56,6 +57,16 @@ export default function LicenseCertificationKeywordSearch({
>
License/Certification Name
+
+
+ Using more specific keywords can help narrow down your search
+ results. For example, searching for "Microsoft Azure" will give
+ you more targeted results than searching for only "Microsoft."
+
+
+ >
);
}
diff --git a/src/applications/gi/constants.js b/src/applications/gi/constants.js
index 6d3aae053778..578e1795b75b 100644
--- a/src/applications/gi/constants.js
+++ b/src/applications/gi/constants.js
@@ -444,6 +444,7 @@ export const yellowRibbonDegreeLevelTypeHash = {
'AAS in Accounting': ['Associates'],
'AAS/BS - Undergraduate': ['Associates', 'Undergraduate'],
'AOS - Undergraduate': ['Undergraduate'],
+ ALL: ['All'],
All: ['All'],
'All (Arts & Science)': ['All'],
'All (Bachelor of Arts)': ['All'],
diff --git a/src/applications/gi/containers/LicenseCertificationSearchPage.jsx b/src/applications/gi/containers/LicenseCertificationSearchPage.jsx
index 64a07bf3c4e5..d02ff59eebab 100644
--- a/src/applications/gi/containers/LicenseCertificationSearchPage.jsx
+++ b/src/applications/gi/containers/LicenseCertificationSearchPage.jsx
@@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import {
VaAccordion,
VaAccordionItem,
+ VaLink,
VaLoadingIndicator,
VaModal,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
@@ -15,29 +16,67 @@ import { fetchLicenseCertificationResults } from '../actions';
const faqs = [
{
question: 'What is the difference between a license and certification?',
- answer:
- 'A license is granted by the state or a governing authority; whereas, a certification is granted by professional organizations or associations.',
+ answer: (
+
+ A license is granted by the state or a governing authority; whereas, a
+ certification is granted by professional organizations or associations.
+
+ ),
},
{
question: 'What will my benefits cover?',
- answer:
- "Part of your entitlement can be used to cover the costs of tests, up to $2000, for a job that requires a license or certification—even if you're already receiving other education benefits. Your benefits will only cover tests approved for the GI Bill.",
+ answer: (
+
+ Part of your entitlement can be used to cover the costs of tests, up to
+ \$2000, for a job that requires a license or certification—even if
+ you’re already receiving other education benefits. Your benefits will
+ only cover tests approved for the GI Bill.
+
+ ),
},
{
question:
'How do I get reimbursed for the licenses, certifications, and prep courses?',
- answer:
- 'Print and fill out form Request for Reimbursement of Licensing or Certification Test Fees. Send the completed application to the Regional Processing Office for your region listed in the form. Get VA Form 22-0803 to print.',
+ answer: (
+ <>
+
+
+
+
+ Print and fill out form Request for Reimbursement of Licensing or
+ Certification Test Fees. Send the completed application to the
+ Regional Processing Office for your region listed in the form.
+
+
+
+
+ >
+ ),
},
{
question: 'What is a prep course?',
- answer:
- 'A preparatory course (prep course) is a course that prepares students for success tied to a specific license or certification.',
+ answer: (
+
+ A preparatory course (prep course) is a course that prepares students
+ for success tied to a specific license or certification.
+
+ ),
},
{
question: 'Can I get paid to take a test more than once?',
- answer:
- "If you fail a license or certification test, we will pay again. If the license or certification expires, you can take it again and we'll pay for the renewal.",
+ answer: (
+
+ If you fail a license or certification test, we will pay again. If the
+ license or certification expires, you can take it again and we’ll pay
+ for the renewal.
+
Use the search tool to find out which tests or related prep
courses are reimbursable. If you don’t see a test or prep course
listed, it may be a valid test that’s not yet approved. We
@@ -125,7 +164,7 @@ function LicenseCertificationSearchPage({
you’re trying to obtain and whether or not it is state-specific.
@@ -115,9 +115,9 @@ export class ConfirmationPage extends React.Component {
ATTN: CHAMPVA Claims
- PO Box 460948
+ PO Box 500
- Denver, CO 80246-0948
+ Spring City, PA 19475
You can also contact us online through Ask VA.
diff --git a/src/applications/login/containers/MhvAccess.jsx b/src/applications/login/containers/MhvAccess.jsx
index ef7c7b8db2d9..3155b5377177 100644
--- a/src/applications/login/containers/MhvAccess.jsx
+++ b/src/applications/login/containers/MhvAccess.jsx
@@ -6,18 +6,18 @@ export default function MhvAccess() {
- Get temporary access to My HealtheVet
+ Access the My HealtheVet sign-in option
- Some groups are approved to access the My HealtheVet sign-in option
- until they create a new modern account. This sign-in process may
- change in the future.
+ Get temporary access to the My HealtheVet sign-in option. This sign-in
+ process may change in the future.
+
Sign in
login({ policy: 'mhv' })}
- text="Sign in with My HealtheVet"
+ text="My HealtheVet"
data-testid="accessMhvBtn"
/>
diff --git a/src/applications/login/tests/containers/MhvAccess.unit.spec.js b/src/applications/login/tests/containers/MhvAccess.unit.spec.js
index ba0dbaa1d43f..07aa069706fe 100644
--- a/src/applications/login/tests/containers/MhvAccess.unit.spec.js
+++ b/src/applications/login/tests/containers/MhvAccess.unit.spec.js
@@ -7,22 +7,24 @@ import MhvAccess from '../../containers/MhvAccess';
describe('MhvAccess', () => {
it('renders main title', () => {
const screen = renderInReduxProvider();
- const mainTitle = screen.getByRole('heading', {
- name: /get temporary access to my healthevet/i,
- });
+ const mainTitle = screen.getByText(
+ /access the my healthevet sign-in option/i,
+ );
expect(mainTitle).to.exist;
});
it('renders information paragraph', () => {
const screen = renderInReduxProvider();
const description = screen.getByText(
- /Some groups are approved to access the My HealtheVet sign-in option/i,
+ /get temporary access to the my healthevet sign-in option/i,
);
expect(description).to.exist;
});
it('renders button', () => {
const screen = renderInReduxProvider();
+ const signInHeading = screen.getByText(/sign in/i);
+ expect(signInHeading).to.exist;
const accessButton = screen.getByTestId('accessMhvBtn');
expect(accessButton).to.exist;
fireEvent.click(accessButton);
diff --git a/src/applications/login/tests/mhv-access-page.cypress.spec.js b/src/applications/login/tests/mhv-access-page.cypress.spec.js
new file mode 100644
index 000000000000..ad50826821fb
--- /dev/null
+++ b/src/applications/login/tests/mhv-access-page.cypress.spec.js
@@ -0,0 +1,33 @@
+describe('My HealtheVet Access Page', () => {
+ beforeEach(() => {
+ cy.visit('/sign-in/mhv');
+ });
+
+ it('displays the page title and description', () => {
+ cy.injectAxeThenAxeCheck();
+ cy.get('#signin-signup-modal-title')
+ .should('exist')
+ .and('contain', 'Access the My HealtheVet sign-in option');
+ cy.get('p.vads-u-measure--5')
+ .should('exist')
+ .and(
+ 'contain',
+ 'Get temporary access to the My HealtheVet sign-in option. This sign-in process may change in the future.',
+ );
+ });
+
+ it('displays a sign-in button and respond to clicks', () => {
+ cy.injectAxeThenAxeCheck();
+ cy.get('va-button[text="My HealtheVet"]').should('exist');
+ cy.get('[data-testid="accessMhvBtn"]').click();
+ });
+
+ it('should display the "Having trouble signing in?" section', () => {
+ cy.injectAxeThenAxeCheck();
+ cy.contains('h2', 'Having trouble signing in?').should('exist');
+ cy.contains(
+ 'p',
+ 'Contact the administrator who gave you access to this page.',
+ ).should('exist');
+ });
+});
diff --git a/src/applications/mhv-landing-page/components/alerts/AlertAccountApiAlert.jsx b/src/applications/mhv-landing-page/components/alerts/AlertAccountApiAlert.jsx
index a2d85198441f..fd0c2c01558b 100644
--- a/src/applications/mhv-landing-page/components/alerts/AlertAccountApiAlert.jsx
+++ b/src/applications/mhv-landing-page/components/alerts/AlertAccountApiAlert.jsx
@@ -66,7 +66,7 @@ const AlertAccountApiAlert = ({
page. Or check back later.
- {errorCode.length > 0 ? (
+ {errorCode > 0 ? (
If the problem persists, call the My HealtheVet helpdesk at
877-327-0022 (TTY: 711). We’re here Monday through Friday, 8:00
@@ -96,13 +96,13 @@ const AlertAccountApiAlert = ({
AlertAccountApiAlert.defaultProps = {
title: 'Error code 000: Contact the My HealtheVet help desk',
- errorCode: '',
+ errorCode: 0,
recordEvent: recordEventFn,
testId: 'mhv-alert--mhv-registration',
};
AlertAccountApiAlert.propTypes = {
- errorCode: PropTypes.string,
+ errorCode: PropTypes.number,
title: PropTypes.string,
headline: PropTypes.string,
recordEvent: PropTypes.func,
diff --git a/src/applications/mhv-landing-page/mocks/api/user/mhvAccountStatus.js b/src/applications/mhv-landing-page/mocks/api/user/mhvAccountStatus.js
index 96f12cbf3df3..87d00072dee2 100644
--- a/src/applications/mhv-landing-page/mocks/api/user/mhvAccountStatus.js
+++ b/src/applications/mhv-landing-page/mocks/api/user/mhvAccountStatus.js
@@ -21,7 +21,7 @@ const eightZeroOne = (req, res) => {
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '801',
+ code: 801,
},
],
});
@@ -33,7 +33,7 @@ const eightZeroFive = (req, res) => {
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '805',
+ code: 805,
},
],
});
@@ -45,7 +45,7 @@ const eightZeroSix = (req, res) => {
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '802',
+ code: 802,
},
],
});
@@ -57,7 +57,7 @@ const fiveZeroZero = (req, res) => {
{
title: 'The server responded with status 500',
detail: 'things fall apart',
- code: '500',
+ code: 500,
},
],
});
@@ -69,22 +69,22 @@ const multiError = (req, res) => {
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '802',
+ code: 802,
},
{
title: 'The server responded with status 500',
detail: 'things fall apart',
- code: '500',
+ code: 500,
},
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '805',
+ code: 805,
},
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '801',
+ code: 801,
},
],
});
@@ -110,7 +110,7 @@ const accountStatusEightZeroOne = {
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '801',
+ code: 801,
},
],
};
@@ -120,7 +120,7 @@ const accountStatusFiveZeroZero = {
{
title: 'The server responded with status 500',
detail: 'things fall apart',
- code: '500',
+ code: 500,
},
],
};
@@ -139,22 +139,22 @@ const accountStatusMultiError = {
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '802',
+ code: 802,
},
{
title: 'The server responded with status 500',
detail: 'things fall apart',
- code: '500',
+ code: 500,
},
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '805',
+ code: 805,
},
{
title: 'The server responded with status 422',
detail: 'things fall apart',
- code: '801',
+ code: 801,
},
],
};
diff --git a/src/applications/mhv-landing-page/selectors/mhvAccountStatus.js b/src/applications/mhv-landing-page/selectors/mhvAccountStatus.js
index af581754760c..8ff290ff8139 100644
--- a/src/applications/mhv-landing-page/selectors/mhvAccountStatus.js
+++ b/src/applications/mhv-landing-page/selectors/mhvAccountStatus.js
@@ -1,4 +1,4 @@
-const userActionErrorCodes = ['801', '805', '806', '807'];
+const userActionErrorCodes = [801, 805, 806, 807];
export const mhvAccountStatusLoading = state => {
return state?.myHealth?.accountStatus?.loading;
diff --git a/src/applications/mhv-landing-page/tests/e2e/alerts/no-mhv-account.cypress.spec.js b/src/applications/mhv-landing-page/tests/e2e/alerts/no-mhv-account.cypress.spec.js
index 6abbb12e21d9..77a87213d291 100644
--- a/src/applications/mhv-landing-page/tests/e2e/alerts/no-mhv-account.cypress.spec.js
+++ b/src/applications/mhv-landing-page/tests/e2e/alerts/no-mhv-account.cypress.spec.js
@@ -64,8 +64,6 @@ describe(`${appName} - MHV Registration Alert - `, () => {
});
it('should reference a specific error', () => {
- 'Tell the representative that you received';
-
cy.injectAxeThenAxeCheck();
cy.findByText('Tell the representative that you received', {
exact: false,
diff --git a/src/applications/mhv-landing-page/tests/selectors/mhvAccountStatus.unit.spec.js b/src/applications/mhv-landing-page/tests/selectors/mhvAccountStatus.unit.spec.js
new file mode 100644
index 000000000000..e1effba8d50d
--- /dev/null
+++ b/src/applications/mhv-landing-page/tests/selectors/mhvAccountStatus.unit.spec.js
@@ -0,0 +1,81 @@
+import { expect } from 'chai';
+import {
+ mhvAccountStatusUsersuccess,
+ mhvAccountStatusErrorsSorted,
+ mhvAccountStatusUserError,
+} from '../../selectors';
+import { accountStatusMultiError } from '../../mocks/api/user/mhvAccountStatus';
+
+describe('mhvAccountStatusUsersuccess', () => {
+ it('returns false when loading', () => {
+ const loadingState = {
+ myHealth: {
+ accountStatus: {
+ loading: true,
+ },
+ },
+ };
+ const result = mhvAccountStatusUsersuccess(loadingState);
+ expect(result).to.be.false;
+ });
+
+ it('returns false when errors', () => {
+ const errorState = {
+ myHealth: {
+ accountStatus: {
+ data: {
+ errors: [
+ {
+ title: 'The server responded with status 422',
+ detail: 'things fall apart',
+ code: 801,
+ },
+ ],
+ },
+ },
+ },
+ };
+ const result = mhvAccountStatusUsersuccess(errorState);
+ expect(result).to.be.false;
+ });
+
+ it('returns true not loading and no errors', () => {
+ const state = {
+ myHealth: {
+ accountStatus: {
+ data: {},
+ },
+ },
+ };
+ const result = mhvAccountStatusUsersuccess(state);
+ expect(result).to.be.true;
+ });
+});
+
+describe('mhvAccountStatusErrorsSorted', () => {
+ it('prioritizes user errors', () => {
+ const state = {
+ myHealth: {
+ accountStatus: {
+ data: accountStatusMultiError,
+ },
+ },
+ };
+ const result = mhvAccountStatusErrorsSorted(state);
+ expect(result[0].code).to.eq(805);
+ });
+});
+
+describe('mhvAccountStatusUserError', () => {
+ it('returns user error code', () => {
+ const state = {
+ myHealth: {
+ accountStatus: {
+ data: accountStatusMultiError,
+ },
+ },
+ };
+ const result = mhvAccountStatusUserError(state);
+ expect(result[0].code).to.eq(805);
+ });
+});
diff --git a/src/applications/mhv-medical-records/containers/App.jsx b/src/applications/mhv-medical-records/containers/App.jsx
index b5ed2f83e654..7c375733db5f 100644
--- a/src/applications/mhv-medical-records/containers/App.jsx
+++ b/src/applications/mhv-medical-records/containers/App.jsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState, useRef, useMemo } from 'react';
+import React, { useEffect, useMemo } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom/cjs/react-router-dom.min';
import PropTypes from 'prop-types';
@@ -9,6 +9,7 @@ import {
renderMHVDowntime,
useDatadogRum,
MhvSecondaryNav,
+ useBackToTop,
} from '@department-of-veterans-affairs/mhv/exports';
import {
DowntimeNotification,
@@ -38,11 +39,8 @@ const App = ({ children }) => {
);
const dispatch = useDispatch();
-
- const [isHidden, setIsHidden] = useState(true);
- const [height, setHeight] = useState(0);
const location = useLocation();
- const measuredRef = useRef();
+ const { measuredRef, isHidden } = useBackToTop(location);
const atLandingPage = location.pathname === '/';
const scheduledDowntimes = useSelector(
@@ -117,52 +115,6 @@ const App = ({ children }) => {
};
useDatadogRum(datadogRumConfig);
- useEffect(
- () => {
- if (height) {
- // small screen (mobile)
- if (window.innerWidth <= 481 && height > window.innerHeight * 4) {
- setIsHidden(false);
- }
- // medium screen (desktop/tablet)
- else if (window.innerWidth > 481 && height > window.innerHeight * 2) {
- setIsHidden(false);
- }
- // default to hidden
- else {
- setIsHidden(true);
- }
- }
- },
- [height, location],
- );
-
- const { current } = measuredRef;
-
- useEffect(
- () => {
- if (!current) return () => {};
- let isMounted = true; // Flag to prevent React state update on an unmounted component
-
- const resizeObserver = new ResizeObserver(() => {
- requestAnimationFrame(() => {
- if (isMounted && height !== current.offsetHeight) {
- setHeight(current.offsetHeight);
- }
- });
- });
- resizeObserver.observe(current);
- return () => {
- isMounted = false;
- if (current) {
- resizeObserver.unobserve(current);
- }
- resizeObserver.disconnect();
- };
- },
- [current, height],
- );
-
useEffect(
() => {
// If the user is not whitelisted or feature flag is disabled, redirect them.
@@ -242,6 +194,7 @@ const App = ({ children }) => {
)}
{
@@ -131,6 +133,11 @@ const MedicationsList = props => {
)}
+ If you change your mind and want us to stop releasing your personal
+ information, you can contact us online through{' '}
+
+ Ask VA
+
+
+ or call us at{' '}
+
+ 1-800-827-1000
+
+ . We’re here Monday through Friday, 8:00 a.m. to 9:00 p.m. ET.
+
+);
-const content = {
- headlineText: 'Thank you for submitting your authorization',
- nextStepsText: (
- <>
-
- If you change your mind and do not want VA to give out your personal
- benefit or claim information, you may notify us in writing, or by
- telephone at{' '}
-
- 1-800-827-1000
- {' '}
- or contact VA online at{' '}
-
- Ask VA
-
-
- .
-
-
- Upon notification from you VA will no longer give out benefit or claim
- information (except for the information VA has already given out based
- on your permission).
-
- ),
+ ...currentOrPastDateUI({
+ title: 'When should we stop releasing your information?',
+ description: 'Enter a valid date',
+ errorMessages: {
+ required: 'Please provide an end date.',
+ pattern: 'Please provide a valid end date.',
+ },
+ }),
'ui:validations': [releaseEndDateValidation],
- 'ui:errorMessages': {
- required: 'Please provide an end date.',
- pattern: 'Please provide a valid end date.',
- },
},
},
schema: {
type: 'object',
required: ['releaseEndDate'],
properties: {
- releaseEndDate: definitions.date,
+ releaseEndDate: currentOrPastDateSchema,
},
},
};
diff --git a/src/applications/simple-forms/21-0845/pages/securityQuestion.js b/src/applications/simple-forms/21-0845/pages/securityQuestion.js
index a6bbf7244466..0c7d676204e7 100644
--- a/src/applications/simple-forms/21-0845/pages/securityQuestion.js
+++ b/src/applications/simple-forms/21-0845/pages/securityQuestion.js
@@ -1,5 +1,8 @@
import React from 'react';
-
+import {
+ radioUI,
+ radioSchema,
+} from 'platform/forms-system/src/js/web-component-patterns';
import {
THIRD_PARTY_TYPES,
SECURITY_QUESTIONS,
@@ -13,77 +16,53 @@ import {
/** @type {PageSchema} */
export default {
uiSchema: {
- securityQuestion: {
- 'ui:widget': 'radio',
- 'ui:errorMessages': {
+ securityQuestion: radioUI({
+ description: (
+ <>
+
+ Select a security question. We’ll ask you to enter the answer on the
+ next screen. You’ll then need to give the answer to your designated
+ third-party source.
+
+
+ We’ll ask this question each time your designated third-party source
+ contacts us.
+
-
- Select a security question. We’ll ask you to enter the answer
- on the next screen. You’ll then need to give the answer to
- your designated third-party source.
-
-
- We’ll ask this question each time your designated third-party
- source contacts us.
-
- >
- ),
- uiSchema,
- };
- },
- labels: getLabelsFromConstants(SECURITY_QUESTIONS),
+ return {
+ title: `What security question should we ask ${thirdPartyName} to verify their identity?`,
+ };
},
- },
+ }),
},
schema: {
type: 'object',
required: ['securityQuestion'],
properties: {
- securityQuestion: {
- type: 'string',
- enum: getEnumsFromConstants(SECURITY_QUESTIONS),
- },
+ securityQuestion: radioSchema(getEnumsFromConstants(SECURITY_QUESTIONS)),
},
},
};
diff --git a/src/applications/simple-forms/21-0845/pages/thirdPartyType.js b/src/applications/simple-forms/21-0845/pages/thirdPartyType.js
index 2087bbac0d17..da4bedc7ed49 100644
--- a/src/applications/simple-forms/21-0845/pages/thirdPartyType.js
+++ b/src/applications/simple-forms/21-0845/pages/thirdPartyType.js
@@ -1,44 +1,31 @@
-import React from 'react';
-
+import {
+ radioUI,
+ radioSchema,
+} from 'platform/forms-system/src/js/web-component-patterns';
import { THIRD_PARTY_TYPES } from '../definitions/constants';
-const labelString =
- 'Do you authorize us to release your information to a specific person or to an organization?';
-
/** @type {PageSchema} */
export default {
uiSchema: {
- thirdPartyType: {
- 'ui:title':
- You’ll need to scan your document onto the device you’re using to submit
- this application, such as your computer, tablet, or mobile phone. You can
- upload your document from there.
+
Your file
+
+ Note: After you upload
+ your file, you’ll need to continue to the next screen to submit it. If you
+ leave before you submit it, you’ll need to upload it again.
- ) : (
- UPLOAD_GUIDELINES
- ),
- };
- },
- },
},
uploadedFile: {
...fileInputUI({
@@ -41,6 +30,8 @@ export const uploadPage = {
name: 'form-upload-file-input',
fileUploadUrl,
title,
+ hint:
+ 'You can upload a .pdf, .jpeg, or .png file. Your file should be no larger than 25MB',
formNumber,
required: () => true,
// Disallow uploads greater than 25 MB
diff --git a/src/applications/simple-forms/form-upload/tests/unit/pages/UploadPage.unit.spec.jsx b/src/applications/simple-forms/form-upload/tests/unit/pages/UploadPage.unit.spec.jsx
index 54c323bb015e..969cb59dfcd8 100644
--- a/src/applications/simple-forms/form-upload/tests/unit/pages/UploadPage.unit.spec.jsx
+++ b/src/applications/simple-forms/form-upload/tests/unit/pages/UploadPage.unit.spec.jsx
@@ -3,7 +3,6 @@ import { render } from '@testing-library/react';
import { expect } from 'chai';
import { UploadPage } from '../../../pages/upload';
import formConfig from '../../../config/form';
-import { UPLOAD_GUIDELINES } from '../../../config/constants';
describe('UploadPage', () => {
const { uiSchema } = formConfig().chapters.uploadChapter.pages.uploadPage;
@@ -15,31 +14,6 @@ describe('UploadPage', () => {
expect(container).to.exist;
});
- it('updates the description when there are no warnings', () => {
- const result = uiSchema['view:uploadGuidelines'][
- 'ui:options'
- ].updateUiSchema({});
-
- expect(result).to.deep.equal({
- 'ui:description': UPLOAD_GUIDELINES,
- });
- });
-
- it('updates the description when there are warnings', () => {
- const formData = {
- uploadedFile: {
- warnings: ['bad news'],
- },
- };
- const result = uiSchema['view:uploadGuidelines'][
- 'ui:options'
- ].updateUiSchema(formData);
-
- expect(result).to.deep.equal({
- 'ui:description':
Your file
,
- });
- });
-
it('updates the title when there are no warnings', () => {
const result = uiSchema.uploadedFile['ui:options'].updateUiSchema({});
diff --git a/src/applications/find-forms/README.md b/src/applications/static-pages/find-forms/README.md
similarity index 100%
rename from src/applications/find-forms/README.md
rename to src/applications/static-pages/find-forms/README.md
diff --git a/src/applications/find-forms/actions/index.js b/src/applications/static-pages/find-forms/actions/index.js
similarity index 96%
rename from src/applications/find-forms/actions/index.js
rename to src/applications/static-pages/find-forms/actions/index.js
index 683d83e83e21..aceef1db87c6 100644
--- a/src/applications/find-forms/actions/index.js
+++ b/src/applications/static-pages/find-forms/actions/index.js
@@ -1,11 +1,11 @@
import URLSearchParams from 'url-search-params';
-import recordEvent from '~/platform/monitoring/record-event';
+import recordEvent from 'platform/monitoring/record-event';
import { fetchFormsApi } from '../api';
-import { MAX_PAGE_LIST_LENGTH } from '../containers/SearchResults';
import {
FETCH_FORMS,
FETCH_FORMS_FAILURE,
FETCH_FORMS_SUCCESS,
+ MAX_PAGE_LIST_LENGTH,
UPDATE_HOW_TO_SORT,
UPDATE_PAGINATION,
UPDATE_RESULTS,
diff --git a/src/applications/find-forms/api/index.js b/src/applications/static-pages/find-forms/api/index.js
similarity index 94%
rename from src/applications/find-forms/api/index.js
rename to src/applications/static-pages/find-forms/api/index.js
index 19b799f89ed4..3c5223bc6afd 100644
--- a/src/applications/find-forms/api/index.js
+++ b/src/applications/static-pages/find-forms/api/index.js
@@ -1,5 +1,5 @@
import appendQuery from 'append-query';
-import { apiRequest } from '~/platform/utilities/api';
+import { apiRequest } from 'platform/utilities/api';
import STUBBED_RESPONSE from '../constants/stub.json';
export const fetchFormsApi = async (query, options = {}) => {
diff --git a/src/applications/find-forms/components/FindVaForms.jsx b/src/applications/static-pages/find-forms/components/FindVaForms.jsx
similarity index 96%
rename from src/applications/find-forms/components/FindVaForms.jsx
rename to src/applications/static-pages/find-forms/components/FindVaForms.jsx
index b3bed4eab249..a8cafad29b4b 100644
--- a/src/applications/find-forms/components/FindVaForms.jsx
+++ b/src/applications/static-pages/find-forms/components/FindVaForms.jsx
@@ -2,8 +2,8 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
-import { toggleValues } from '~/platform/site-wide/feature-toggles/selectors';
-import FEATURE_FLAG_NAMES from '~/platform/utilities/feature-toggles/featureFlagNames';
+import { toggleValues } from 'platform/site-wide/feature-toggles/selectors';
+import FEATURE_FLAG_NAMES from 'platform/utilities/feature-toggles/featureFlagNames';
import SearchForm from '../containers/SearchForm';
import SearchResults from '../containers/SearchResults';
diff --git a/src/applications/find-forms/components/FormTitle.jsx b/src/applications/static-pages/find-forms/components/FormTitle.jsx
similarity index 100%
rename from src/applications/find-forms/components/FormTitle.jsx
rename to src/applications/static-pages/find-forms/components/FormTitle.jsx
diff --git a/src/applications/find-forms/components/PdfModal.jsx b/src/applications/static-pages/find-forms/components/PdfModal.jsx
similarity index 100%
rename from src/applications/find-forms/components/PdfModal.jsx
rename to src/applications/static-pages/find-forms/components/PdfModal.jsx
diff --git a/src/applications/find-forms/components/SearchResult.jsx b/src/applications/static-pages/find-forms/components/SearchResult.jsx
similarity index 98%
rename from src/applications/find-forms/components/SearchResult.jsx
rename to src/applications/static-pages/find-forms/components/SearchResult.jsx
index e7d4e57848bd..356d4e96f145 100644
--- a/src/applications/find-forms/components/SearchResult.jsx
+++ b/src/applications/static-pages/find-forms/components/SearchResult.jsx
@@ -2,8 +2,8 @@ import React from 'react';
import { format, parseISO, isAfter } from 'date-fns';
import PropTypes from 'prop-types';
import { replaceWithStagingDomain } from 'platform/utilities/environment/stagingDomains';
-import environment from '~/platform/utilities/environment';
-import recordEvent from '~/platform/monitoring/record-event';
+import environment from 'platform/utilities/environment';
+import recordEvent from 'platform/monitoring/record-event';
import * as customPropTypes from '../prop-types';
import { FORM_MOMENT_PRESENTATION_DATE_FORMAT } from '../constants';
import FormTitle from './FormTitle';
diff --git a/src/applications/find-forms/constants/index.js b/src/applications/static-pages/find-forms/constants/index.js
similarity index 95%
rename from src/applications/find-forms/constants/index.js
rename to src/applications/static-pages/find-forms/constants/index.js
index bfef52530aae..b21aca4ed594 100644
--- a/src/applications/find-forms/constants/index.js
+++ b/src/applications/static-pages/find-forms/constants/index.js
@@ -15,3 +15,4 @@ export const FORM_MOMENT_CONSTRUCTOR_DATE_FORMAT = 'yyyy-mm-dd';
export const FORM_MOMENT_PRESENTATION_DATE_FORMAT = 'MMMM yyyy';
export const UPDATE_HOW_TO_SORT = 'findVAForms/UPDATE_HOW_TO_SORT';
export const UPDATE_RESULTS = 'findVAForms/UPDATE_RESULTS';
+export const MAX_PAGE_LIST_LENGTH = 10;
diff --git a/src/applications/find-forms/constants/stub.json b/src/applications/static-pages/find-forms/constants/stub.json
similarity index 100%
rename from src/applications/find-forms/constants/stub.json
rename to src/applications/static-pages/find-forms/constants/stub.json
diff --git a/src/applications/find-forms/containers/SearchForm.jsx b/src/applications/static-pages/find-forms/containers/SearchForm.jsx
similarity index 100%
rename from src/applications/find-forms/containers/SearchForm.jsx
rename to src/applications/static-pages/find-forms/containers/SearchForm.jsx
diff --git a/src/applications/find-forms/containers/SearchResults.jsx b/src/applications/static-pages/find-forms/containers/SearchResults.jsx
similarity index 97%
rename from src/applications/find-forms/containers/SearchResults.jsx
rename to src/applications/static-pages/find-forms/containers/SearchResults.jsx
index c18ac2096f6d..ac9840569630 100644
--- a/src/applications/find-forms/containers/SearchResults.jsx
+++ b/src/applications/static-pages/find-forms/containers/SearchResults.jsx
@@ -1,12 +1,12 @@
import React, { useEffect, useRef, useState } from 'react';
+import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
VaPagination,
VaSelect,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
-import { connect } from 'react-redux';
-import recordEvent from '~/platform/monitoring/record-event';
-import { focusElement } from '~/platform/utilities/ui';
+import recordEvent from 'platform/monitoring/record-event';
+import { focusElement } from 'platform/utilities/ui';
import * as customPropTypes from '../prop-types';
import {
updateSortByPropertyNameThunk,
@@ -14,11 +14,10 @@ import {
} from '../actions';
import { deriveDefaultModalState } from '../helpers';
import { getFindFormsAppState } from '../helpers/selectors';
-import { FAF_SORT_OPTIONS } from '../constants';
+import { FAF_SORT_OPTIONS, MAX_PAGE_LIST_LENGTH } from '../constants';
import SearchResult from '../components/SearchResult';
import PdfModal from '../components/PdfModal';
-export const MAX_PAGE_LIST_LENGTH = 10;
const usePreviousProps = value => {
// This is a mirror to storing and assessing prevProps vs current props
const ref = useRef(); // Refs are like a class instance var, in react their values don't change unless that same ref is redefined.
@@ -208,7 +207,7 @@ export const SearchResults = ({
<>
Showing {startLabel} – {lastLabel}{' '}
- of {results.length} results for "{' '}
+ of {results.length} results for "
>
{query}"
diff --git a/src/applications/find-forms/createFindVaForms.js b/src/applications/static-pages/find-forms/createFindVaForms.js
similarity index 100%
rename from src/applications/find-forms/createFindVaForms.js
rename to src/applications/static-pages/find-forms/createFindVaForms.js
diff --git a/src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/DownloadHandler.js b/src/applications/static-pages/find-forms/download-widget/DownloadHandler.js
similarity index 96%
rename from src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/DownloadHandler.js
rename to src/applications/static-pages/find-forms/download-widget/DownloadHandler.js
index 10c154989170..cb5a3cb66ed7 100644
--- a/src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/DownloadHandler.js
+++ b/src/applications/static-pages/find-forms/download-widget/DownloadHandler.js
@@ -4,10 +4,10 @@ import PropTypes from 'prop-types';
import { Provider } from 'react-redux';
import DownloadPDFModal from './DownloadPDFModal';
import InvalidFormDownload from './InvalidFormAlert';
-import { sentryLogger } from './index';
+import { sentryLogger } from './sentryLogger';
const removeReactRoot = () => {
- const pdf = document.querySelector('.faf-pdf-alert-modal-root');
+ const pdf = document.querySelector('.faf-pdf-alert-modal');
pdf.remove();
};
diff --git a/src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/DownloadPDFModal.jsx b/src/applications/static-pages/find-forms/download-widget/DownloadPDFModal.jsx
similarity index 93%
rename from src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/DownloadPDFModal.jsx
rename to src/applications/static-pages/find-forms/download-widget/DownloadPDFModal.jsx
index bb4c9bf36a3a..0d175a17fb47 100644
--- a/src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/DownloadPDFModal.jsx
+++ b/src/applications/static-pages/find-forms/download-widget/DownloadPDFModal.jsx
@@ -1,6 +1,6 @@
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
-import PdfModal from '../../components/PdfModal';
+import PdfModal from '../components/PdfModal';
// DownloadPDFModal is state wrapper + modal for PDF guidance upon PDf being valid
const DownloadPDFModal = ({ clickedId, formNumber, removeNode, url }) => {
@@ -44,6 +44,7 @@ const DownloadPDFModal = ({ clickedId, formNumber, removeNode, url }) => {
};
DownloadPDFModal.propTypes = {
+ clickedId: PropTypes.string,
formNumber: PropTypes.string,
removeNode: PropTypes.func,
url: PropTypes.string,
diff --git a/src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/InvalidFormAlert.jsx b/src/applications/static-pages/find-forms/download-widget/InvalidFormAlert.jsx
similarity index 100%
rename from src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/InvalidFormAlert.jsx
rename to src/applications/static-pages/find-forms/download-widget/InvalidFormAlert.jsx
diff --git a/src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/index.js b/src/applications/static-pages/find-forms/download-widget/index.js
similarity index 82%
rename from src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/index.js
rename to src/applications/static-pages/find-forms/download-widget/index.js
index c0482bca2df1..7ca4a0213f7d 100644
--- a/src/applications/find-forms/widgets/createFindVaFormsPDFDownloadHelper/index.js
+++ b/src/applications/static-pages/find-forms/download-widget/index.js
@@ -1,17 +1,7 @@
-import * as Sentry from '@sentry/browser';
-import { fetchFormsApi } from '../../api';
+import { fetchFormsApi } from '../api';
+import { sentryLogger } from './sentryLogger';
import DownloadHandler from './DownloadHandler';
-// HOF for reusable situations in Component.
-export function sentryLogger(form, formNumber, downloadUrl, message) {
- return Sentry.withScope(scope => {
- scope.setExtra('form API response', form);
- scope.setExtra('form number', formNumber);
- scope.setExtra('download link (invalid)', downloadUrl);
- Sentry.captureMessage(message);
- });
-}
-
export async function onDownloadLinkClick(event, reduxStore) {
// This function purpose is to determine if the PDF is valid on click.
// Once it's done, it passes information to DownloadHandler() which determines what to render.
diff --git a/src/applications/static-pages/find-forms/download-widget/sentryLogger.js b/src/applications/static-pages/find-forms/download-widget/sentryLogger.js
new file mode 100644
index 000000000000..ce7ae2466692
--- /dev/null
+++ b/src/applications/static-pages/find-forms/download-widget/sentryLogger.js
@@ -0,0 +1,11 @@
+import * as Sentry from '@sentry/browser';
+
+// HOF for reusable situations in Component.
+export function sentryLogger(form, formNumber, downloadUrl, message) {
+ return Sentry.withScope(scope => {
+ scope.setExtra('form API response', form);
+ scope.setExtra('form number', formNumber);
+ scope.setExtra('download link (invalid)', downloadUrl);
+ Sentry.captureMessage(message);
+ });
+}
diff --git a/src/applications/find-forms/find-va-forms-entry.js b/src/applications/static-pages/find-forms/find-va-forms-entry.js
similarity index 100%
rename from src/applications/find-forms/find-va-forms-entry.js
rename to src/applications/static-pages/find-forms/find-va-forms-entry.js
diff --git a/src/applications/find-forms/helpers/index.js b/src/applications/static-pages/find-forms/helpers/index.js
similarity index 100%
rename from src/applications/find-forms/helpers/index.js
rename to src/applications/static-pages/find-forms/helpers/index.js
diff --git a/src/applications/find-forms/helpers/selectors.js b/src/applications/static-pages/find-forms/helpers/selectors.js
similarity index 100%
rename from src/applications/find-forms/helpers/selectors.js
rename to src/applications/static-pages/find-forms/helpers/selectors.js
diff --git a/src/applications/find-forms/prop-types.js b/src/applications/static-pages/find-forms/prop-types.js
similarity index 100%
rename from src/applications/find-forms/prop-types.js
rename to src/applications/static-pages/find-forms/prop-types.js
diff --git a/src/applications/find-forms/reducers/findVAFormsReducer.js b/src/applications/static-pages/find-forms/reducers/findVAFormsReducer.js
similarity index 100%
rename from src/applications/find-forms/reducers/findVAFormsReducer.js
rename to src/applications/static-pages/find-forms/reducers/findVAFormsReducer.js
diff --git a/src/applications/find-forms/reducers/index.js b/src/applications/static-pages/find-forms/reducers/index.js
similarity index 100%
rename from src/applications/find-forms/reducers/index.js
rename to src/applications/static-pages/find-forms/reducers/index.js
diff --git a/src/applications/find-forms/sass/find-va-forms.scss b/src/applications/static-pages/find-forms/sass/find-va-forms.scss
similarity index 100%
rename from src/applications/find-forms/sass/find-va-forms.scss
rename to src/applications/static-pages/find-forms/sass/find-va-forms.scss
diff --git a/src/applications/find-forms/tests/actions/index.unit.spec.js b/src/applications/static-pages/find-forms/tests/actions/index.unit.spec.js
similarity index 100%
rename from src/applications/find-forms/tests/actions/index.unit.spec.js
rename to src/applications/static-pages/find-forms/tests/actions/index.unit.spec.js
diff --git a/src/applications/find-forms/tests/components/FindVaForms.unit.spec.jsx b/src/applications/static-pages/find-forms/tests/components/FindVaForms.unit.spec.jsx
similarity index 100%
rename from src/applications/find-forms/tests/components/FindVaForms.unit.spec.jsx
rename to src/applications/static-pages/find-forms/tests/components/FindVaForms.unit.spec.jsx
diff --git a/src/applications/find-forms/tests/components/FormTitle.unit.spec.jsx b/src/applications/static-pages/find-forms/tests/components/FormTitle.unit.spec.jsx
similarity index 100%
rename from src/applications/find-forms/tests/components/FormTitle.unit.spec.jsx
rename to src/applications/static-pages/find-forms/tests/components/FormTitle.unit.spec.jsx
diff --git a/src/applications/find-forms/tests/components/PdfModal.unit.spec.jsx b/src/applications/static-pages/find-forms/tests/components/PdfModal.unit.spec.jsx
similarity index 100%
rename from src/applications/find-forms/tests/components/PdfModal.unit.spec.jsx
rename to src/applications/static-pages/find-forms/tests/components/PdfModal.unit.spec.jsx
diff --git a/src/applications/find-forms/tests/components/SearchResult.unit.spec.jsx b/src/applications/static-pages/find-forms/tests/components/SearchResult.unit.spec.jsx
similarity index 100%
rename from src/applications/find-forms/tests/components/SearchResult.unit.spec.jsx
rename to src/applications/static-pages/find-forms/tests/components/SearchResult.unit.spec.jsx
diff --git a/src/applications/find-forms/tests/containers/SearchForm.unit.spec.jsx b/src/applications/static-pages/find-forms/tests/containers/SearchForm.unit.spec.jsx
similarity index 100%
rename from src/applications/find-forms/tests/containers/SearchForm.unit.spec.jsx
rename to src/applications/static-pages/find-forms/tests/containers/SearchForm.unit.spec.jsx
diff --git a/src/applications/find-forms/tests/containers/SearchResults.unit.spec.jsx b/src/applications/static-pages/find-forms/tests/containers/SearchResults.unit.spec.jsx
similarity index 93%
rename from src/applications/find-forms/tests/containers/SearchResults.unit.spec.jsx
rename to src/applications/static-pages/find-forms/tests/containers/SearchResults.unit.spec.jsx
index f78f3214c8f0..f10810388951 100644
--- a/src/applications/find-forms/tests/containers/SearchResults.unit.spec.jsx
+++ b/src/applications/static-pages/find-forms/tests/containers/SearchResults.unit.spec.jsx
@@ -3,11 +3,8 @@ import sinon from 'sinon';
import { expect } from 'chai';
import { mount, shallow } from 'enzyme';
import times from 'lodash/times';
-import { INITIAL_SORT_STATE } from '../../constants';
-import {
- SearchResults,
- MAX_PAGE_LIST_LENGTH,
-} from '../../containers/SearchResults';
+import { INITIAL_SORT_STATE, MAX_PAGE_LIST_LENGTH } from '../../constants';
+import { SearchResults } from '../../containers/SearchResults';
describe('Find VA Forms ', () => {
const results = times(MAX_PAGE_LIST_LENGTH + 1, () => ({
diff --git a/src/applications/find-forms/tests/cypress/SearchForm.cypress.spec.js b/src/applications/static-pages/find-forms/tests/cypress/SearchForm.cypress.spec.js
similarity index 100%
rename from src/applications/find-forms/tests/cypress/SearchForm.cypress.spec.js
rename to src/applications/static-pages/find-forms/tests/cypress/SearchForm.cypress.spec.js
diff --git a/src/applications/find-forms/tests/cypress/find-forms-results.cypress.spec.js b/src/applications/static-pages/find-forms/tests/cypress/find-forms-results.cypress.spec.js
similarity index 99%
rename from src/applications/find-forms/tests/cypress/find-forms-results.cypress.spec.js
rename to src/applications/static-pages/find-forms/tests/cypress/find-forms-results.cypress.spec.js
index fb67975e983e..78f224f01bae 100644
--- a/src/applications/find-forms/tests/cypress/find-forms-results.cypress.spec.js
+++ b/src/applications/static-pages/find-forms/tests/cypress/find-forms-results.cypress.spec.js
@@ -1,6 +1,6 @@
import chunk from 'lodash/chunk';
-import { FAF_SORT_OPTIONS } from '../../constants';
import stub from '../../constants/stub.json';
+import { FAF_SORT_OPTIONS } from '../../constants';
import { SELECTORS as s } from './helpers';
describe('functionality of Find Forms', () => {
@@ -20,7 +20,7 @@ describe('functionality of Find Forms', () => {
cy.get(s.FINDFORM_INPUT_ROOT)
.shadow()
- .find('button')
+ .find(s.FINDFORM_SEARCH)
.should('exist')
.click();
diff --git a/src/applications/find-forms/tests/cypress/helpers.js b/src/applications/static-pages/find-forms/tests/cypress/helpers.js
similarity index 94%
rename from src/applications/find-forms/tests/cypress/helpers.js
rename to src/applications/static-pages/find-forms/tests/cypress/helpers.js
index dcaf52859dab..68325e2b8358 100644
--- a/src/applications/find-forms/tests/cypress/helpers.js
+++ b/src/applications/static-pages/find-forms/tests/cypress/helpers.js
@@ -3,7 +3,7 @@ export const SELECTORS = {
WIDGET: '[data-widget-type="find-va-forms"]',
FINDFORM_INPUT_ROOT: 'va-search-input',
FINDFORM_INPUT: 'input',
- FINDFORM_SEARCH: 'button',
+ FINDFORM_SEARCH: 'button[type="submit"]',
FINDFORM_ERROR_BODY: '[data-e2e-id="find-form-error-body"]',
FINDFORM_REQUIRED: '[data-e2e-id="find-form-required"]',
FINDFORM_ERROR_MSG: '[data-e2e-id="find-form-error-message"]',
diff --git a/src/applications/find-forms/tests/widgets/DownloadHandler.unit.spec.js b/src/applications/static-pages/find-forms/tests/download-widget/DownloadHandler.unit.spec.js
similarity index 89%
rename from src/applications/find-forms/tests/widgets/DownloadHandler.unit.spec.js
rename to src/applications/static-pages/find-forms/tests/download-widget/DownloadHandler.unit.spec.js
index d61ea344ddf3..314c8b8d508c 100644
--- a/src/applications/find-forms/tests/widgets/DownloadHandler.unit.spec.js
+++ b/src/applications/static-pages/find-forms/tests/download-widget/DownloadHandler.unit.spec.js
@@ -2,7 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom';
import sinon from 'sinon';
import { expect } from 'chai';
-import DownloadHandler from '../../widgets/createFindVaFormsPDFDownloadHelper/DownloadHandler';
+import DownloadHandler from '../../download-widget/DownloadHandler';
describe('DownloadHandler', () => {
const insertSpy = sinon.spy();
diff --git a/src/applications/find-forms/tests/helpers/index.unit.spec.js b/src/applications/static-pages/find-forms/tests/helpers/index.unit.spec.js
similarity index 100%
rename from src/applications/find-forms/tests/helpers/index.unit.spec.js
rename to src/applications/static-pages/find-forms/tests/helpers/index.unit.spec.js
diff --git a/src/applications/find-forms/tests/reducers/findVAFormsReducer.unit.spec.js b/src/applications/static-pages/find-forms/tests/reducers/findVAFormsReducer.unit.spec.js
similarity index 100%
rename from src/applications/find-forms/tests/reducers/findVAFormsReducer.unit.spec.js
rename to src/applications/static-pages/find-forms/tests/reducers/findVAFormsReducer.unit.spec.js
diff --git a/src/applications/static-pages/static-pages-entry.js b/src/applications/static-pages/static-pages-entry.js
index 2cf2c5889995..633e563f8c28 100644
--- a/src/applications/static-pages/static-pages-entry.js
+++ b/src/applications/static-pages/static-pages-entry.js
@@ -49,8 +49,8 @@ import createAppointARepLandingContent from './representative-appoint';
import createRepresentativeStatus from './representative-status';
import createFindVaForms, {
findVaFormsWidgetReducer,
-} from '../find-forms/createFindVaForms';
-import createFindVaFormsPDFDownloadHelper from '../find-forms/widgets/createFindVaFormsPDFDownloadHelper';
+} from './find-forms/createFindVaForms';
+import createFindVaFormsPDFDownloadHelper from './find-forms/download-widget';
import createHCAPerformanceWarning from './hca-performance-warning';
import createHomepageEmailSignup from './homepage-email-signup';
import createManageVADebtCTA from './manage-va-debt/createManageVADebtCTA';
diff --git a/src/applications/vaos/.eslintrc b/src/applications/vaos/.eslintrc
index 30ea67eb4921..2a37b4afe3ed 100644
--- a/src/applications/vaos/.eslintrc
+++ b/src/applications/vaos/.eslintrc
@@ -26,6 +26,10 @@
{
"target": "./src/applications/vaos/appointment-list",
"from": "./src/applications/vaos/express-care"
+ },
+ {
+ "target": "./src/applications/vaos/referral-appointments",
+ "from": "./src/applications/vaos/appointment-list"
}
]
}
diff --git a/src/applications/vaos/components/FacilityPhone.jsx b/src/applications/vaos/components/FacilityPhone.jsx
index 4da1a596f8a0..1dddf5109147 100644
--- a/src/applications/vaos/components/FacilityPhone.jsx
+++ b/src/applications/vaos/components/FacilityPhone.jsx
@@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
import { VaTelephone } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
export default function FacilityPhone({
- contact,
+ contact = '800-698-2411',
extension,
- className = 'vads-u-font-weight--bold',
+ className = '',
level,
icon,
- heading = 'Main phone:',
+ heading = 'Phone: ',
}) {
if (!contact) {
return null;
@@ -26,6 +26,13 @@ export default function FacilityPhone({
const isClinic = !!heading.includes('Clinic');
const Heading = `h${level}`;
+ let dataTestId = 'facility-telephone';
+ if (number === '800-698-2411') {
+ dataTestId = 'main-telephone';
+ } else if (isClinic) {
+ dataTestId = 'clinic-telephone';
+ }
+
return (
<>
{!!icon === false &&
@@ -37,12 +44,13 @@ export default function FacilityPhone({
>
)}
{typeof icon === 'undefined' &&
- typeof level === 'undefined' &&
- `${heading} `}
+ typeof level === 'undefined' && (
+ {heading}
+ )}
{!isClinic && (
diff --git a/src/applications/vaos/components/FacilityPhone.unit.spec.js b/src/applications/vaos/components/FacilityPhone.unit.spec.js
index b3de89076c1b..d0d34d94bc7b 100644
--- a/src/applications/vaos/components/FacilityPhone.unit.spec.js
+++ b/src/applications/vaos/components/FacilityPhone.unit.spec.js
@@ -14,7 +14,7 @@ describe('VAOS Component: FacilityPhone', () => {
},
);
- expect(screen.getByText(new RegExp(`Main phone:`))).to.exist;
+ expect(screen.getByText(new RegExp(`Phone:`))).to.exist;
const vaPhone = screen.getByTestId('facility-telephone');
expect(vaPhone).to.exist;
@@ -32,7 +32,7 @@ describe('VAOS Component: FacilityPhone', () => {
},
);
- expect(screen.getByText(new RegExp(`Main phone:`))).to.exist;
+ expect(screen.getByText(new RegExp(`Phone:`))).to.exist;
const vaPhone = screen.getByTestId('facility-telephone');
expect(vaPhone).to.exist;
@@ -50,7 +50,7 @@ describe('VAOS Component: FacilityPhone', () => {
},
);
- expect(screen.getByText(new RegExp(`Main phone:`))).to.exist;
+ expect(screen.getByText(new RegExp(`Phone:`))).to.exist;
const vaPhone = screen.getByTestId('facility-telephone');
expect(vaPhone).to.exist;
diff --git a/src/applications/vaos/components/layouts/CCLayout.jsx b/src/applications/vaos/components/layouts/CCLayout.jsx
index 3d1007dcecec..fd05ed6befec 100644
--- a/src/applications/vaos/components/layouts/CCLayout.jsx
+++ b/src/applications/vaos/components/layouts/CCLayout.jsx
@@ -92,7 +92,7 @@ export default function CCLayout({ data: appointment }) {
{!!ccProvider && (
<>
-
+
>
)}
diff --git a/src/applications/vaos/components/layouts/ClaimExamLayout.jsx b/src/applications/vaos/components/layouts/ClaimExamLayout.jsx
index cfa8f273c57f..01c314c59d40 100644
--- a/src/applications/vaos/components/layouts/ClaimExamLayout.jsx
+++ b/src/applications/vaos/components/layouts/ClaimExamLayout.jsx
@@ -153,9 +153,7 @@ export default function ClaimExamLayout({ data: appointment }) {
<>
{facility.name}
- {facilityPhone && (
-
- )}
+ {facilityPhone && }
{!facilityPhone && <>Not available>}
>
)}
diff --git a/src/applications/vaos/components/layouts/ClaimExamLayout.unit.spec.js b/src/applications/vaos/components/layouts/ClaimExamLayout.unit.spec.js
index 13d1116af678..8d6902b995bd 100644
--- a/src/applications/vaos/components/layouts/ClaimExamLayout.unit.spec.js
+++ b/src/applications/vaos/components/layouts/ClaimExamLayout.unit.spec.js
@@ -343,13 +343,12 @@ describe('VAOS Component: ClaimExamLayout', () => {
expect(screen.container.querySelector('va-icon[icon="directions"]')).to.be
.ok;
-
expect(screen.getByText(/Location:/i));
expect(screen.getByText(/CHEYENNE/));
expect(screen.getByText(/Clinic:/i));
expect(screen.getByText(/Clinic 1/i));
- expect(screen.getByText(/Phone:/i));
+ expect(screen.getAllByText(/Clinic phone:/i));
expect(
screen.container.querySelector('va-telephone[contact="500-500-5000"]'),
).to.be.ok;
@@ -478,7 +477,7 @@ describe('VAOS Component: ClaimExamLayout', () => {
expect(screen.getByText(/Clinic:/i));
expect(screen.getByText(/Clinic 1/i));
- expect(screen.getByText(/Phone/i));
+ expect(screen.getByText(/Clinic phone/i));
expect(
screen.container.querySelector('va-telephone[contact="500-500-5000"]'),
).to.be.ok;
@@ -583,7 +582,7 @@ describe('VAOS Component: ClaimExamLayout', () => {
expect(screen.getByText(/Clinic:/i));
expect(screen.getByText(/Clinic 1/i));
- expect(screen.getByText(/Phone:/i));
+ expect(screen.getByText(/Clinic phone:/i));
expect(
screen.container.querySelector('va-telephone[contact="500-500-5000"]'),
).to.be.ok;
diff --git a/src/applications/vaos/components/layouts/DetailPageLayout.jsx b/src/applications/vaos/components/layouts/DetailPageLayout.jsx
index 94f5a318bc31..f175e7cbddd1 100644
--- a/src/applications/vaos/components/layouts/DetailPageLayout.jsx
+++ b/src/applications/vaos/components/layouts/DetailPageLayout.jsx
@@ -1,9 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
-import {
- VaButton,
- VaTelephone,
-} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import { VaButton } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { useDispatch, useSelector } from 'react-redux';
import recordEvent from '@department-of-veterans-affairs/platform-monitoring/record-event';
import { useParams } from 'react-router-dom';
@@ -138,21 +135,17 @@ export function ClinicOrFacilityPhone({
if (clinicPhone) {
return (
);
}
if (facilityPhone) {
- return ;
+ return ;
}
- return (
-
- Phone:
-
-
- );
+ // if no clinic or facility phone number, it will default to VA main phone number
+ return ;
}
ClinicOrFacilityPhone.propTypes = {
clinicPhone: PropTypes.string,
diff --git a/src/applications/vaos/components/layouts/VARequestLayout.jsx b/src/applications/vaos/components/layouts/VARequestLayout.jsx
index 4193857dd91f..49b51222e74d 100644
--- a/src/applications/vaos/components/layouts/VARequestLayout.jsx
+++ b/src/applications/vaos/components/layouts/VARequestLayout.jsx
@@ -107,9 +107,7 @@ export default function VARequestLayout({ data: appointment }) {
- {facilityPhone && (
-
- )}
+ {facilityPhone && }
{!facilityPhone && <>Not available>}
{
'href',
'https://maps.google.com?saddr=Current+Location&daddr=2360 East Pershing Boulevard, Cheyenne, WY 82001-5356',
);
- expect(screen.baseElement).to.contain.text('Main phone:');
+ expect(screen.baseElement).to.contain.text('Phone:');
expect(screen.getByTestId('facility-telephone')).to.exist;
expect(screen.getByTestId('add-to-calendar-link')).to.exist;
diff --git a/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/ProviderSelect.jsx b/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/ProviderSelect.jsx
index 997ff14913f9..5e7fa9856a48 100644
--- a/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/ProviderSelect.jsx
+++ b/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/ProviderSelect.jsx
@@ -1,9 +1,9 @@
-import React, { useState } from 'react';
+import recordEvent from '@department-of-veterans-affairs/platform-monitoring/record-event';
import PropTypes from 'prop-types';
+import React, { useState } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
-import recordEvent from '@department-of-veterans-affairs/platform-monitoring/record-event';
-import { selectProviderSelectionInfo } from '../../redux/selectors';
import { GA_PREFIX } from '../../../utils/constants';
+import { selectProviderSelectionInfo } from '../../redux/selectors';
import RemoveProviderModal from './RemoveProviderModal';
export default function SelectedProvider({
@@ -24,18 +24,16 @@ export default function SelectedProvider({
return (
)}
diff --git a/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/ProviderSortVariant.unit.spec.js b/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/ProviderSortVariant.unit.spec.js
index 274e4fe14f22..c6625abe1e42 100644
--- a/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/ProviderSortVariant.unit.spec.js
+++ b/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/ProviderSortVariant.unit.spec.js
@@ -96,12 +96,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// When the user clicks the choose a provider button
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
// Then providers should be displayed
expect(await screen.findByTestId('providersSelect')).to.exist;
@@ -141,12 +142,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// When the user selects to sort providers by distance from current location
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
const providersSelect = await screen.findByTestId('providersSelect');
@@ -201,12 +203,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// Choose Provider based on home address
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
// When the user selects to sort providers by distance from current location
@@ -264,12 +267,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// Choose Provider
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
await waitFor(() =>
expect(screen.getAllByRole('radio').length).to.equal(5),
@@ -360,12 +364,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// Choose Provider based on home address
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
// When the user selects to sort providers by distance from a specific facility
@@ -440,12 +445,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// When the user tries to choose a provider
// Trigger provider list loading
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
expect(await screen.findByTestId('providersSelect')).to.exist;
@@ -514,13 +520,14 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// When the user tries to choose a provider
// Trigger provider list loading
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
expect(await screen.findByTestId('providersSelect')).to.exist;
@@ -553,12 +560,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
CC_PROVIDERS_DATA,
true,
);
+ await screen.findByText(/Continue/i);
// When the user clicks the choose a provider button
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
// Then they should see an error message
expect(await screen.findByText(/We can’t load provider information/i)).to
diff --git a/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/index.unit.spec.js b/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/index.unit.spec.js
index 2ea3ede5430d..946b9ca71170 100644
--- a/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/index.unit.spec.js
+++ b/src/applications/vaos/new-appointment/components/CommunityCareProviderSelectionPage/index.unit.spec.js
@@ -106,7 +106,9 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
// Trigger provider list loading
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
expect(await screen.findByText(/Displaying 5 of 16 providers/i)).to.be.ok;
expect(screen.getAllByRole('radio').length).to.equal(5);
@@ -168,7 +170,9 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
// Trigger provider list loading
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
expect(await screen.findByText(/Displaying 5 of 16 providers/i)).to.be.ok;
@@ -211,7 +215,7 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
// Change Provider
userEvent.click(
- await screen.findByRole('button', { name: /change provider/i }),
+ await screen.container.querySelector('va-button[text="Change provider"]'),
);
userEvent.click(await screen.findByText(/OH, JANICE/i));
userEvent.click(
@@ -222,7 +226,7 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
// Cancel Selection (not clearing of a selected provider)
userEvent.click(
- await screen.findByRole('button', { name: /change provider/i }),
+ await screen.container.querySelector('va-button[text="Change provider"]'),
);
expect(await screen.findByText(/displaying 5 of 16 providers/i)).to.exist;
userEvent.click(await screen.findByRole('button', { name: /cancel/i }));
@@ -242,7 +246,9 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
// Choose Provider that is buried 2 clicks deep
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
userEvent.click(await screen.findByText(/more providers$/i));
userEvent.click(await screen.findByText(/more providers$/i));
@@ -252,12 +258,18 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
);
// Remove Provider
- userEvent.click(await screen.findByRole('button', { name: /remove/i }));
+ userEvent.click(
+ await screen.container.querySelector('va-button[text="Remove"]'),
+ );
expect(await screen.findByTestId('removeProviderModal')).to.exist;
userEvent.click(
await screen.findByRole('button', { name: /Remove provider/i }),
);
- expect(await screen.findByRole('button', { name: /Find a provider/i }));
+ expect(
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
+ );
expect(screen.baseElement).not.to.contain.text(
'AJADI, ADEDIWURAWASHINGTON, DC',
);
@@ -311,9 +323,9 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
// When the user tries to choose a provider
// Trigger provider list loading
userEvent.click(
- await screen.findByText(/Find a provider/i, {
- selector: 'button',
- }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
expect(await screen.findByTestId('providersSelect')).to.exist;
@@ -329,12 +341,18 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
expect(screen.baseElement).to.contain.text('OH, JANICEANNANDALE, VA');
// Remove Provider
- userEvent.click(await screen.findByRole('button', { name: /remove/i }));
+ userEvent.click(
+ await screen.container.querySelector('va-button[text="Remove"]'),
+ );
expect(await screen.findByTestId('removeProviderModal')).to.exist;
userEvent.click(
await screen.findByRole('button', { name: /Remove provider/i }),
);
- expect(await screen.findByRole('button', { name: /Find a provider/i }));
+ expect(
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
+ );
});
it('should display an error when choose a provider clicked and provider fetch error', async () => {
@@ -361,7 +379,9 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
// Trigger provider list loading
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
expect(
await screen.findByRole('heading', {
@@ -398,7 +418,9 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
// Trigger provider list loading
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
expect(await screen.findByText(/To request this appointment, you can/i)).to
.exist;
@@ -441,10 +463,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// Choose Provider
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
// await waitFor(async () => {
expect(await screen.findByText(/Displaying 5 of/i)).to.be.ok;
@@ -494,10 +519,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// Choose Provider based on home address
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
userEvent.click(await screen.findByText(/Show 5 more providers$/i));
userEvent.click(await screen.findByText(/Show 5 more providers$/i));
@@ -547,10 +575,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// Choose Provider based on home address
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
// Choose Provider based on current location
@@ -610,10 +641,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// Choose Provider based on home address
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
userEvent.click(await screen.findByLabelText(/OH, JANICE/i));
@@ -622,7 +656,7 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
);
// make sure it saves successfully
- await screen.findByRole('button', { name: /remove/i });
+ await screen.container.querySelector('va-button[text="Remove"]');
// remove the page and change the type of care
await cleanup();
@@ -642,10 +676,14 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
screen = renderWithStoreAndRouter(, {
store,
});
+ await screen.findByText(/Continue/i);
// the provider should no longer be set
- expect(await screen.findByRole('button', { name: /Find a provider/i })).to
- .exist;
+ expect(
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
+ ).to.exist;
expect(screen.queryByText(/OH, JANICE/i)).to.not.exist;
});
@@ -674,10 +712,13 @@ describe('VAOS Page: CommunityCareProviderSelectionPage', () => {
store,
},
);
+ await screen.findByText(/Continue/i);
// Choose Provider
userEvent.click(
- await screen.findByRole('button', { name: /Find a provider/i }),
+ await screen.container.querySelector(
+ 'va-button[text="Choose a provider"]',
+ ),
);
await waitFor(() =>
expect(screen.getAllByRole('radio').length).to.equal(5),
diff --git a/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js b/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js
index f998c93ba07f..ebd725a35a8f 100644
--- a/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js
+++ b/src/applications/vaos/new-appointment/components/ContactInfoPage.unit.spec.js
@@ -12,7 +12,7 @@ import { FACILITY_TYPES, FLOW_TYPES } from '../../utils/constants';
describe('VAOS Page: ContactInfoPage', () => {
// Flaky test: https://github.com/department-of-veterans-affairs/va.gov-team/issues/82968
- it('should accept email, phone, and preferred time and continue', async () => {
+ it.skip('should accept email, phone, and preferred time and continue', async () => {
const store = createTestStore({
user: {
profile: {
diff --git a/src/applications/vaos/new-appointment/components/VAFacilityPage/FacilitiesNotShown.jsx b/src/applications/vaos/new-appointment/components/VAFacilityPage/FacilitiesNotShown.jsx
index 258f47748556..f9c587d38f5f 100644
--- a/src/applications/vaos/new-appointment/components/VAFacilityPage/FacilitiesNotShown.jsx
+++ b/src/applications/vaos/new-appointment/components/VAFacilityPage/FacilitiesNotShown.jsx
@@ -78,15 +78,15 @@ export default function FacilitiesNotShown({
contact={
facility.telecom.find(t => t.system === 'phone')?.value
}
- level={3}
+ className="vads-u-font-weight--bold"
/>
))}
-
+
What you can do
-
+
Call the facility directly to schedule your appointment,{' '}
or
diff --git a/src/applications/vaos/new-appointment/components/VAFacilityPage/getEligibilityMessage.js b/src/applications/vaos/new-appointment/components/VAFacilityPage/getEligibilityMessage.js
index 5f8ed230f2e0..8e2450834320 100644
--- a/src/applications/vaos/new-appointment/components/VAFacilityPage/getEligibilityMessage.js
+++ b/src/applications/vaos/new-appointment/components/VAFacilityPage/getEligibilityMessage.js
@@ -72,8 +72,11 @@ export default function getEligibilityMessage({
Or you can go back and choose a different facility.
>
);
- } else if (requestReason === ELIGIBILITY_REASONS.error) {
- title = 'You can’t schedule this appointment online right now';
+ } else if (
+ directReason === ELIGIBILITY_REASONS.error ||
+ requestReason === ELIGIBILITY_REASONS.error
+ ) {
+ title = 'You can’t schedule an appointment online right now';
content =
'We’re sorry. There’s a problem with our system. Try again later.';
status = 'error';
diff --git a/src/applications/vaos/referral-appointments/CompleteReferral.jsx b/src/applications/vaos/referral-appointments/CompleteReferral.jsx
new file mode 100644
index 000000000000..9f54f98a4b60
--- /dev/null
+++ b/src/applications/vaos/referral-appointments/CompleteReferral.jsx
@@ -0,0 +1,246 @@
+import PropTypes from 'prop-types';
+import React, { useEffect } from 'react';
+import {
+ useLocation,
+ useRouteMatch,
+ Redirect,
+ useHistory,
+} from 'react-router-dom';
+import { useDispatch } from 'react-redux';
+import { format, intervalToDuration } from 'date-fns';
+import { formatInTimeZone } from 'date-fns-tz';
+
+import ReferralLayout from './components/ReferralLayout';
+import AddToCalendarButton from '../components/AddToCalendarButton';
+import { setFormCurrentPage } from './redux/actions';
+import { useGetProviderById } from './hooks/useGetProviderById';
+import { getReferralSlotKey } from './utils/referrals';
+import {
+ getTimezoneAbbrByFacilityId,
+ getTimezoneByFacilityId,
+} from '../utils/timezone';
+import { getSlotById } from './utils/provider';
+import FacilityDirectionsLink from '../components/FacilityDirectionsLink';
+import State from '../components/State';
+import { routeToCCPage } from './flow';
+import CCAppointmentCard from './components/CCAppointmentCard';
+
+export default function CompleteReferral(props) {
+ const { currentReferral } = props;
+ const { search } = useLocation();
+ const history = useHistory();
+ const dispatch = useDispatch();
+ useEffect(
+ () => {
+ dispatch(setFormCurrentPage('complete'));
+ },
+ [dispatch],
+ );
+
+ const basePath = useRouteMatch();
+ const { provider, loading, failed } = useGetProviderById(
+ currentReferral?.providerId,
+ );
+
+ if (failed) {
+ return (
+
+
+
+
+ We’re sorry. There was a problem with our system. We couldn’t
+ process this appointment. Call us at 877-470-5947. Monday through
+ Friday, 8:00 a.m. to 8:00 p.m. ET.
+
- You’ll need to sign in with a different account after{' '}
- {deprecationDates}. After this date, we’ll remove the{' '}
- {label} sign-in option. You’ll need to sign in using a{' '}
- Login.gov or ID.me account.
-
- We need you to verify your identity for your{' '}
- {label} account. This step helps us protect
- all Veterans’ information and prevent scammers from stealing
- your benefits.
-
-
- This one-time process often takes about 10 minutes. You’ll
- need to provide certain personal information and
- identification.
-
- We need you to verify your identity for your{' '}
- Login.gov or ID.me account.
- This step helps us protect all Veterans’ information and prevent
- scammers from stealing your benefits.
-
-
- This one-time process often takes about 10 minutes. You’ll need
- to provide certain personal information and identification.
-
-
- );
-}
diff --git a/src/applications/verify/components/UnifiedVerify.jsx b/src/applications/verify/components/UnifiedVerify.jsx
new file mode 100644
index 000000000000..19c590dff3e4
--- /dev/null
+++ b/src/applications/verify/components/UnifiedVerify.jsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { useSelector } from 'react-redux';
+import {
+ isAuthenticatedWithOAuth,
+ signInServiceName,
+} from 'platform/user/authentication/selectors';
+import {
+ VerifyIdmeButton,
+ VerifyLogingovButton,
+} from 'platform/user/authentication/components/VerifyButton';
+import { hasSession } from 'platform/user/profile/utilities';
+
+const Verify = () => {
+ const isAuthenticated = hasSession();
+ const isAuthenticatedOAuth = useSelector(isAuthenticatedWithOAuth);
+ const loginServiceName = useSelector(signInServiceName); // Get the current SIS (e.g., idme or logingov)
+
+ let buttonContent;
+
+ if (isAuthenticated) {
+ <>
+
+
+ >;
+ } else if (isAuthenticatedOAuth) {
+ // Use the loginServiceName to determine which button to show
+ if (loginServiceName === 'idme') {
+ buttonContent = ;
+ } else if (loginServiceName === 'logingov') {
+ buttonContent = ;
+ }
+ } else {
+ buttonContent = (
+ <>
+
+
+ >
+ );
+ }
+
+ const renderServiceNames = () => {
+ if (isAuthenticated) {
+ return (
+ {loginServiceName === 'idme' ? 'ID.me' : 'Login.gov'}
+ );
+ }
+ return (
+ <>
+ Login.gov or ID.me
+ >
+ );
+ };
+
+ return (
+
+
+
+
+
Verify your identity
+
+ We need you to verify your identity for your{' '}
+ {renderServiceNames()} account. This step helps us protect all
+ Veterans’ information and prevent scammers from stealing your
+ benefits.
+
+
+ This one-time process often takes about 10 minutes. You’ll need to
+ provide certain personal information and identification.
+