diff --git a/src/applications/user-testing/new-conditions/app-entry.jsx b/src/applications/user-testing/new-conditions/app-entry.jsx
new file mode 100644
index 000000000000..245e2a7d7155
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/app-entry.jsx
@@ -0,0 +1,15 @@
+import '@department-of-veterans-affairs/platform-polyfills';
+import './sass/new-conditions.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/user-testing/new-conditions/components/Autocomplete.jsx b/src/applications/user-testing/new-conditions/components/Autocomplete.jsx
new file mode 100644
index 000000000000..9f085ab5ad18
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/components/Autocomplete.jsx
@@ -0,0 +1,221 @@
+import React, { useCallback, useEffect, useRef, useState } from 'react';
+import PropTypes from 'prop-types';
+import { VaTextInput } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
+import debounce from 'lodash/debounce';
+import { fullStringSimilaritySearch } from 'platform/forms-system/src/js/utilities/addDisabilitiesStringSearch';
+
+const INSTRUCTIONS =
+ 'When autocomplete results are available use up and down arrows to review and enter to select. Touch device users, explore by touch or with swipe gestures.';
+
+const createFreeTextItem = val => `Enter your condition as "${val}"`;
+
+const Autocomplete = ({
+ availableResults,
+ debounceDelay,
+ formData,
+ id,
+ label,
+ onChange,
+}) => {
+ const [value, setValue] = useState(formData);
+ const [results, setResults] = useState([]);
+ const [activeIndex, setActiveIndex] = useState(null);
+ const [ariaLiveText, setAriaLiveText] = useState('');
+
+ const containerRef = useRef(null);
+ const inputRef = useRef(null);
+ const resultsRef = useRef([]);
+
+ // Delays screen reader result count reading to avoid interruption by input content reading
+ const debouncedSetAriaLiveText = useRef(
+ debounce((resultCount, freeTextResult) => {
+ const makePlural = resultCount > 1 ? 's' : '';
+
+ setAriaLiveText(
+ `${resultCount} result${makePlural}. ${freeTextResult}, (1 of ${resultCount})`,
+ );
+ }, 700),
+ ).current;
+
+ const debouncedSearch = useRef(
+ debounce(async inputValue => {
+ const freeTextResult = createFreeTextItem(inputValue);
+ const searchResults = fullStringSimilaritySearch(
+ inputValue,
+ availableResults,
+ );
+ const updatedResults = [freeTextResult, ...searchResults];
+ setResults(updatedResults);
+ setActiveIndex(0);
+
+ debouncedSetAriaLiveText(updatedResults.length, freeTextResult);
+ }, debounceDelay),
+ ).current;
+
+ const closeList = useCallback(
+ () => {
+ debouncedSearch.cancel();
+ debouncedSetAriaLiveText.cancel();
+ setResults([]);
+ setActiveIndex(null);
+ },
+ [debouncedSearch, debouncedSetAriaLiveText],
+ );
+
+ const handleInputChange = inputValue => {
+ setValue(inputValue);
+ onChange(inputValue);
+
+ if (!inputValue) {
+ closeList();
+ setAriaLiveText('Input is empty. Please enter a condition.');
+ return;
+ }
+
+ debouncedSearch(inputValue);
+ };
+
+ const activateScrollToAndFocus = index => {
+ setActiveIndex(index);
+
+ const activeResult = resultsRef.current[index];
+ activeResult?.scrollIntoView({
+ block: 'nearest',
+ });
+ activeResult?.focus();
+ };
+
+ const focusOnInput = () =>
+ inputRef.current.shadowRoot.querySelector('input').focus();
+
+ const navigateList = (e, adjustment) => {
+ e.preventDefault();
+ const newIndex = activeIndex + adjustment;
+ if (newIndex > results.length - 1) {
+ return;
+ }
+
+ if (newIndex < 1) {
+ activateScrollToAndFocus(0);
+
+ focusOnInput();
+ } else {
+ activateScrollToAndFocus(newIndex);
+ }
+ };
+
+ const selectResult = result => {
+ const newValue = result === createFreeTextItem(value) ? value : result;
+ setValue(newValue);
+ onChange(newValue);
+ setAriaLiveText(`${newValue} is selected`);
+ closeList();
+
+ focusOnInput();
+ };
+
+ const handleKeyDown = e => {
+ if (results.length > 0) {
+ if (e.key === 'ArrowDown') {
+ navigateList(e, 1);
+ } else if (e.key === 'ArrowUp') {
+ navigateList(e, -1);
+ } else if (e.key === 'Enter') {
+ selectResult(results[activeIndex]);
+ } else if (e.key === 'Escape') {
+ closeList();
+ focusOnInput();
+ } else if (e.key === 'Tab') {
+ closeList();
+ } else {
+ focusOnInput();
+ }
+ }
+ };
+
+ useEffect(
+ () => {
+ const handleClickOutside = event => {
+ if (
+ containerRef.current &&
+ !containerRef.current.contains(event.target)
+ ) {
+ closeList();
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ },
+ [closeList],
+ );
+
+ const handleFocus = () => {
+ if (value && results.length === 0) {
+ debouncedSearch(value);
+ }
+ };
+
+ return (
+
+
handleInputChange(e.target.value)}
+ onKeyDown={handleKeyDown}
+ />
+ {results.length > 0 && (
+
+ {results.map((result, index) => (
+ - {
+ resultsRef.current[index] = el;
+ }}
+ role="option"
+ tabIndex={-1}
+ onClick={() => selectResult(result)}
+ onKeyDown={handleKeyDown} // Keydown is handled on the input; this is never fired and prevents eslint error
+ onMouseEnter={() => activateScrollToAndFocus(index)}
+ >
+ {result}
+
+ ))}
+
+ )}
+
+ {ariaLiveText}
+
+
+ );
+};
+
+Autocomplete.propTypes = {
+ availableResults: PropTypes.array,
+ debounceDelay: PropTypes.number,
+ formData: PropTypes.string,
+ id: PropTypes.string,
+ label: PropTypes.string,
+ onChange: PropTypes.func,
+};
+
+export default Autocomplete;
diff --git a/src/applications/user-testing/new-conditions/config/form.js b/src/applications/user-testing/new-conditions/config/form.js
new file mode 100644
index 000000000000..712c530e13d4
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/config/form.js
@@ -0,0 +1,78 @@
+import { VA_FORM_IDS } from 'platform/forms/constants';
+import CallVBACenter from 'platform/static-data/CallVBACenter';
+import React from 'react';
+import { SUBTITLE, TITLE } from '../constants';
+import ConfirmationPage from '../containers/ConfirmationPage';
+import IntroductionPage from '../containers/IntroductionPage';
+import manifest from '../manifest.json';
+
+import chooseDemo from '../pages/chooseDemo';
+import conditionByConditionPages from '../pages/conditionByConditionPages';
+import conditionsFirstPages from '../pages/conditionsFirstPages';
+
+const FormFooter = () => (
+
+
+
+
+
+
+ For help filling out this form, or if the form isn’t working
+ right, please
+
+
+
+
+
+
+);
+
+/** @type {FormConfig} */
+const formConfig = {
+ rootUrl: manifest.rootUrl,
+ urlPrefix: '/',
+ submitUrl: '/v0/api',
+ submit: () =>
+ Promise.resolve({ attributes: { confirmationNumber: '123123123' } }),
+ trackingPrefix: 'new-conditions',
+ introduction: IntroductionPage,
+ confirmation: ConfirmationPage,
+ footerContent: FormFooter,
+ // dev: {
+ // showNavLinks: true,
+ // collapsibleNavLinks: true,
+ // },
+ formId: VA_FORM_IDS.FORM_21_526EZ,
+ saveInProgress: {
+ messages: {
+ inProgress:
+ 'Your disability compensation application (21-526EZ) is in progress.',
+ expired:
+ 'Your saved disability compensation application (21-526EZ) has expired. If you want to apply for disability compensation, please start a new application.',
+ saved: 'Your disability compensation application has been saved.',
+ },
+ },
+ version: 0,
+ prefillEnabled: true,
+ savedFormMessages: {
+ notFound: 'Please start over to apply for disability compensation.',
+ noAuth:
+ 'Please sign in again to continue your application for disability compensation.',
+ },
+ title: TITLE,
+ subTitle: SUBTITLE,
+ v3SegmentedProgressBar: true,
+ defaultDefinitions: {},
+ chapters: {
+ newConditionsChapter: {
+ title: 'Conditions',
+ pages: {
+ chooseDemo,
+ ...conditionByConditionPages,
+ ...conditionsFirstPages,
+ },
+ },
+ },
+};
+
+export default formConfig;
diff --git a/src/applications/user-testing/new-conditions/constants.js b/src/applications/user-testing/new-conditions/constants.js
new file mode 100644
index 000000000000..7719548817e0
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/constants.js
@@ -0,0 +1,4 @@
+export const TITLE = 'Demo: File for disability compensation - New conditions';
+export const SUBTITLE = 'Demo of VA Form 21-526EZ';
+export const CONDITION_BY_CONDITION = 'ape';
+export const CONDITIONS_FIRST = 'elk';
diff --git a/src/applications/user-testing/new-conditions/containers/App.jsx b/src/applications/user-testing/new-conditions/containers/App.jsx
new file mode 100644
index 000000000000..797306ee1e20
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/containers/App.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+
+import RoutedSavableApp from 'platform/forms/save-in-progress/RoutedSavableApp';
+import formConfig from '../config/form';
+
+export default function App({ location, children }) {
+ return (
+
+ {children}
+
+ );
+}
diff --git a/src/applications/user-testing/new-conditions/containers/ConfirmationPage.jsx b/src/applications/user-testing/new-conditions/containers/ConfirmationPage.jsx
new file mode 100644
index 000000000000..cd485448d364
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/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.
+
+
+ File for disability compensation Claim{' '}
+ (Form {formId})
+
+ {fullName ? (
+
+ for {fullName.first} {fullName.middle} {fullName.last}
+ {fullName.suffix ? `, ${fullName.suffix}` : null}
+
+ ) : null}
+
+ {confirmationNumber ? (
+ <>
+
Confirmation number
+
{confirmationNumber}
+ >
+ ) : null}
+
+ {isValid(submitDate) ? (
+
+ Date submitted
+
+ {format(submitDate, 'MMMM d, yyyy')}
+
+ ) : null}
+
+
+
+
+ Go back to VA.gov
+
+
+ {/*
+
Need help?
+
+ */}
+
+ );
+};
+
+ConfirmationPage.propTypes = {
+ form: PropTypes.shape({
+ data: PropTypes.shape({
+ fullName: {
+ first: PropTypes.string,
+ middle: PropTypes.string,
+ last: PropTypes.string,
+ suffix: PropTypes.string,
+ },
+ }),
+ formId: PropTypes.string,
+ submission: PropTypes.shape({
+ timestamp: PropTypes.string,
+ }),
+ }),
+ name: PropTypes.string,
+};
+
+export default ConfirmationPage;
diff --git a/src/applications/user-testing/new-conditions/containers/IntroductionPage.jsx b/src/applications/user-testing/new-conditions/containers/IntroductionPage.jsx
new file mode 100644
index 000000000000..0c4fa9deb1b4
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/containers/IntroductionPage.jsx
@@ -0,0 +1,55 @@
+import React, { useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { focusElement, scrollToTop } from 'platform/utilities/ui';
+import FormTitle from 'platform/forms-system/src/js/components/FormTitle';
+import SaveInProgressIntro from 'platform/forms/save-in-progress/SaveInProgressIntro';
+
+import { TITLE, SUBTITLE } from '../constants';
+
+const OMB_RES_BURDEN = 25;
+const OMB_NUMBER = '2900-0747';
+const OMB_EXP_DATE = '11/30/2025';
+
+export const IntroductionPage = props => {
+ const { route } = props;
+ const { formConfig, pageList } = route;
+
+ useEffect(() => {
+ scrollToTop();
+ focusElement('h1');
+ }, []);
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+IntroductionPage.propTypes = {
+ route: PropTypes.shape({
+ formConfig: PropTypes.shape({
+ prefillEnabled: PropTypes.bool.isRequired,
+ savedFormMessages: PropTypes.object.isRequired,
+ }).isRequired,
+ pageList: PropTypes.arrayOf(PropTypes.object).isRequired,
+ }).isRequired,
+ location: PropTypes.shape({
+ basename: PropTypes.string,
+ }),
+};
+
+export default IntroductionPage;
diff --git a/src/applications/user-testing/new-conditions/content/conditionOptions.js b/src/applications/user-testing/new-conditions/content/conditionOptions.js
new file mode 100644
index 000000000000..83656a964e81
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/content/conditionOptions.js
@@ -0,0 +1,440 @@
+export const conditionObjects = [
+ { option: 'ACL tear (anterior cruciate ligament tear)', sideOfBody: true },
+ { option: 'acne' },
+ { option: 'adjustment disorder' },
+ { option: 'agoraphobia' },
+ { option: 'alopecia' },
+ { option: 'ALS (amyotrophic lateral sclerosis)' },
+ { option: 'anal cancer' },
+ { option: 'anal or perianal fistula (fistula-in-ano)' },
+ { option: 'ankle replacement (ankle arthroplasty)', sideOfBody: true },
+ { option: 'ankle sprain', sideOfBody: true },
+ { option: 'ankylosis in ankle', sideOfBody: true },
+ { option: 'ankylosis in elbow', sideOfBody: true },
+ { option: 'ankylosis in hand or fingers', sideOfBody: true },
+ { option: 'ankylosis in hip', sideOfBody: true },
+ { option: 'ankylosis in knee', sideOfBody: true },
+ { option: 'ankylosis in shoulder', sideOfBody: true },
+ { option: 'ankylosis in wrist', sideOfBody: true },
+ { option: 'anorexia (type of eating disorder)' },
+ { option: 'aortic insufficiency (aortic regurgitation)' },
+ { option: 'aortic valve disease' },
+ { option: 'arrhythmia (irregular heartbeat)' },
+ { option: 'asthma' },
+ {
+ option: 'astragalectomy or talectomy (removal of talus bone in ankle)',
+ sideOfBody: true,
+ },
+ { option: "athlete's foot (tinea pedis)" },
+ { option: 'atrial fibrillation (A-fib)' },
+ { option: 'basal cell carcinoma of the skin, extremities or trunk' },
+ { option: 'basal cell carcinoma of the skin, head or neck' },
+ { option: 'bipolar disorder' },
+ { option: 'biventricular hypertrophy' },
+ { option: 'bladder cancer' },
+ { option: 'bladder stones' },
+ { option: 'bone cancer, including osteosarcoma, extremities or trunk' },
+ { option: 'bone cancer, including osteosarcoma, head or neck' },
+ { option: 'Boutonniere deformity in fingers', sideOfBody: true },
+ { option: 'brain cancer, including glioblastoma' },
+ { option: 'breast cancer', sideOfBody: true },
+ { option: 'bronchiectasis' },
+ { option: 'bulimia (type of eating disorder)' },
+ { option: 'bunions (hallux valgus)', sideOfBody: true },
+ { option: 'bursitis in ankle', sideOfBody: true },
+ { option: 'bursitis in elbow', sideOfBody: true },
+ { option: 'bursitis in foot', sideOfBody: true },
+ { option: 'bursitis in hand or fingers', sideOfBody: true },
+ { option: 'bursitis in hip', sideOfBody: true },
+ { option: 'bursitis in knee', sideOfBody: true },
+ { option: 'bursitis in shoulder', sideOfBody: true },
+ { option: 'bursitis in wrist', sideOfBody: true },
+ { option: 'carpal tunnel syndrome', sideOfBody: true },
+ { option: 'cervical cancer' },
+ { option: 'cervical spinal stenosis (narrowing of spinal canal in neck)' },
+ { option: 'chloracne' },
+ { option: 'chronic bronchitis' },
+ { option: 'chronic conjunctivitis (pink eye)' },
+ { option: 'chronic fatigue syndrome' },
+ { option: 'chronic kidney disease (CKD)' },
+ { option: 'chronic laryngitis' },
+ { option: 'chronic nephritis (inflammation of kidneys)' },
+ { option: 'chronic obstructive pulmonary disease (COPD)' },
+ { option: "chronic otitis externa (swimmer's ear)", sideOfBody: true },
+ {
+ option: 'chronic otitis media (chronic middle ear infection)',
+ sideOfBody: true,
+ },
+ { option: 'chronic rhinitis, allergic or non-allergic' },
+ { option: 'chronic sinusitis' },
+ {
+ option: 'chronic suppurative otitis media (CSOM) or mastoiditis',
+ sideOfBody: true,
+ },
+ { option: 'chronic urticaria (chronic hives)' },
+ { option: 'cirrhosis (scarring of liver)' },
+ { option: 'claw foot (pes cavus)', sideOfBody: true },
+ { option: 'colorectal cancer or colon cancer' },
+ { option: 'complete loss of sense of smell (anosmia)' },
+ { option: 'complete loss of sense of taste (ageusia)' },
+ { option: 'congestive heart failure (CHF)' },
+ { option: 'constrictive bronchiolitis (obliterative bronchiolitis)' },
+ { option: 'coronary artery disease (CAD or arteriosclerotic heart disease)' },
+ { option: 'costochondritis' },
+ {
+ option:
+ 'Cranial nerve paralysis or cranial neuritis (inflammation of cranial nerves)',
+ },
+ { option: 'cranial neuralgia (cranial nerve pain)' },
+ { option: "Crohn's disease" },
+ { option: 'cyclothymic disorder (mild bipolar disorder)' },
+ {
+ option: "De Quervain's tenosynovitis (De Quervain's syndrome)",
+ sideOfBody: true,
+ },
+ { option: 'decompression illness' },
+ { option: 'deep vein thrombosis (DVT) in leg', sideOfBody: true },
+ {
+ option: 'degenerative arthritis (osteoarthritis) in ankle',
+ sideOfBody: true,
+ },
+ {
+ option: 'degenerative arthritis (osteoarthritis) in elbow',
+ sideOfBody: true,
+ },
+ {
+ option: 'degenerative arthritis (osteoarthritis) in foot',
+ sideOfBody: true,
+ },
+ {
+ option: 'degenerative arthritis (osteoarthritis) in hand or fingers',
+ sideOfBody: true,
+ },
+ {
+ option: 'degenerative arthritis (osteoarthritis) in hip',
+ sideOfBody: true,
+ },
+ {
+ option: 'degenerative arthritis (osteoarthritis) in knee',
+ sideOfBody: true,
+ },
+ {
+ option: 'degenerative arthritis (osteoarthritis) in shoulder',
+ sideOfBody: true,
+ },
+ {
+ option: 'degenerative arthritis (osteoarthritis) in wrist',
+ sideOfBody: true,
+ },
+ { option: 'depression (major depressive disorder)' },
+ { option: 'deviated septum' },
+ { option: 'diabetes insipidus' },
+ { option: 'diabetes, type 1 or type 2' },
+ { option: 'diabetic nephropathy (diabetic kidney disease)' },
+ {
+ option: 'diabetic peripheral neuropathy lower extremities',
+ sideOfBody: true,
+ },
+ {
+ option: 'diabetic peripheral neuropathy upper extremities',
+ sideOfBody: true,
+ },
+ { option: 'diabetic retinopathy' },
+ { option: 'dry eye syndrome' },
+ {
+ option:
+ "Dupuytren's contracture (abnormal thickening of tissues in palm of hand)",
+ sideOfBody: true,
+ },
+ {
+ option:
+ 'dysphagia (difficulty swallowing), associated with neurological disorder',
+ },
+ {
+ option:
+ 'dysphagia (difficulty swallowing), not associated with neurological disorder',
+ },
+ { option: 'ear cancer' },
+ { option: 'eczema (atopic dermatitis)' },
+ { option: 'elbow replacement (elbow arthroplasty)', sideOfBody: true },
+ { option: 'emphysema' },
+ { option: 'endometriosis' },
+ { option: 'enlarged prostate (benign prostatic hyperplasia or BPH)' },
+ { option: 'epididymitis' },
+ { option: 'epiphora (watery eyes)' },
+ { option: 'erectile dysfunction (ED)' },
+ { option: 'esophageal cancer' },
+ { option: 'esophageal stricture (narrowing of esophagus)' },
+ { option: 'eye cancer, including melanoma of the eye' },
+ { option: 'fecal incontinence (loss of bowel control)' },
+ { option: 'female sexual arousal disorder (FSAD)' },
+ { option: 'femoral hernia (hernia in thigh area)' },
+ { option: 'fibromyalgia' },
+ { option: 'flatfoot (pes planus)', sideOfBody: true },
+ { option: 'foot fracture (tarsal or metatarsal fracture)', sideOfBody: true },
+ { option: 'frostbite in foot', sideOfBody: true },
+ { option: 'frostbite in hand or fingers', sideOfBody: true },
+ { option: 'gallstones (cholelithiasis)' },
+ { option: "gamekeeper's thumb", sideOfBody: true },
+ { option: 'ganglion cyst in hand or fingers', sideOfBody: true },
+ { option: 'ganglion cyst in wrist', sideOfBody: true },
+ { option: 'generalized anxiety disorder (GAD)' },
+ { option: 'GERD (gastroesophageal reflux disease)' },
+ { option: 'glaucoma' },
+ { option: "golfer's elbow (medial epicondylitis)", sideOfBody: true },
+ { option: 'gonococcal arthritis' },
+ { option: 'gout in foot', sideOfBody: true },
+ { option: 'gout in wrist', sideOfBody: true },
+ { option: 'granulomatous disease' },
+ {
+ option: 'greater trochanteric pain syndrome (lateral hip pain)',
+ sideOfBody: true,
+ },
+ { option: 'hallux rigidus (big toe arthritis)', sideOfBody: true },
+ { option: 'hammer toe', sideOfBody: true },
+ { option: "Hansen's disease (leprosy)" },
+ { option: 'hearing loss' },
+ { option: 'heart attack (myocardial infarction)' },
+ { option: 'heart bypass surgery (coronary artery bypass graft)' },
+ { option: 'hemorrhoids' },
+ { option: 'hepatitis B' },
+ { option: 'hepatitis C' },
+ { option: 'herpes (herpes simplex virus or HSV)' },
+ { option: 'hiatal hernia' },
+ {
+ option: 'hip impingement (femoroacetabular impingement or FAI)',
+ sideOfBody: true,
+ },
+ { option: 'hip replacement (hip arthroplasty)', sideOfBody: true },
+ { option: 'hip sprain', sideOfBody: true },
+ { option: 'HIV-related illness' },
+ { option: 'Hodgkin lymphoma' },
+ { option: 'hyperhidrosis (excessive sweating)' },
+ { option: 'hypertension (high blood pressure)' },
+ { option: 'hyperthyroidism (overactive thyroid)' },
+ { option: 'hypopharyngeal cancer' },
+ { option: 'hypothyroidism (underactive thyroid)' },
+ { option: 'hysterectomy (removal of uterus)' },
+ {
+ option: 'hysterectomy and ovariectomy (removal of uterus and both ovaries)',
+ },
+ { option: 'illness anxiety disorder (hypochondria)' },
+ { option: 'immersion foot', sideOfBody: true },
+ { option: 'inguinal hernia (hernia in groin)' },
+ { option: 'interstitial lung disease (ILD), including asbestosis' },
+ { option: 'intervertebral disc syndrome (IVDS), back' },
+ { option: 'intervertebral disc syndrome (IVDS), neck' },
+ { option: 'iron deficiency anemia' },
+ { option: 'irritable bowel syndrome (IBS)' },
+ { option: 'jaw cancer' },
+ { option: 'keratinization skin disorders' },
+ { option: 'kidney cancer (renal cancer)', sideOfBody: true },
+ { option: 'kidney removal (nephrectomy)' },
+ { option: 'kidney stones (nephrolithiasis)' },
+ { option: 'kidney transplant' },
+ { option: 'knee hyperextension (genu recurvatum)', sideOfBody: true },
+ { option: 'knee instability', sideOfBody: true },
+ { option: 'knee or patellar dislocation', sideOfBody: true },
+ { option: 'knee or patellar fracture', sideOfBody: true },
+ { option: 'knee replacement (knee arthroplasty)', sideOfBody: true },
+ { option: 'knee strain or sprain', sideOfBody: true },
+ { option: 'labyrinthitis (inner ear inflammation)', sideOfBody: true },
+ { option: 'laryngeal cancer (cancer of larynx)' },
+ { option: 'left ventricular hypertrophy (LVH)' },
+ { option: 'leukemia' },
+ { option: 'liver cancer, including hepatocellular carcinoma (HCC)' },
+ { option: 'loss of both eyes' },
+ { option: 'loss of teeth due to bone loss' },
+ { option: 'lower back sprain (lumbosacral sprain)' },
+ { option: 'lower back strain (lumbosacral strain)' },
+ {
+ option: 'lumbar spinal stenosis (narrowing of spinal canal in lower back)',
+ },
+ { option: 'lung cancer' },
+ { option: 'lupus' },
+ { option: 'lymphatic filariasis' },
+ { option: 'malaria' },
+ { option: 'malignant tumor in spinal cord' },
+ { option: 'mallet finger', sideOfBody: true },
+ { option: 'melanoma of the skin' },
+ { option: "Meniere's disease" },
+ { option: 'meniscectomy (removal of meniscus)', sideOfBody: true },
+ { option: 'meniscus tear', sideOfBody: true },
+ { option: 'metatarsalgia (pain in ball of foot)', sideOfBody: true },
+ { option: 'middle back sprain (thoracic sprain)' },
+ { option: 'middle back strain (thoracic strain)' },
+ { option: 'migraines (headaches)' },
+ { option: 'mitral regurgitation' },
+ { option: 'mitral valve prolapse (Barlow syndrome)' },
+ { option: "Morton's neuroma", sideOfBody: true },
+ { option: 'multiple myeloma, extremities or trunk' },
+ { option: 'multiple myeloma, head or neck' },
+ { option: 'multiple sclerosis (MS)' },
+ { option: 'muscle hernia' },
+ { option: 'myasthenia gravis' },
+ { option: 'nasopharyngeal cancer' },
+ { option: 'neck sprain (cervical sprain)' },
+ { option: 'neck strain (cervical strain)' },
+ { option: 'nerve-related soft-tissue sarcoma, extremities or trunk' },
+ { option: 'nerve-related soft-tissue sarcoma, head or neck' },
+ { option: 'neurogenic bladder' },
+ {
+ option: 'non-diabetic peripheral neuropathy lower extremities',
+ sideOfBody: true,
+ },
+ {
+ option: 'non-diabetic peripheral neuropathy upper extremities',
+ sideOfBody: true,
+ },
+ { option: 'non-Hodgkin lymphoma' },
+ { option: 'obsessive compulsive disorder (OCD)' },
+ { option: 'oral cavity squamous cell carcinoma' },
+ { option: 'orchitis (inflammation of testicles)' },
+ { option: 'oropharyngeal cancer' },
+ { option: 'osteomyelitis' },
+ { option: 'ovarian adhesions' },
+ { option: 'ovarian cancer', sideOfBody: true },
+ { option: 'ovarian cysts' },
+ { option: 'ovariectomy (removal of one or both ovaries)' },
+ { option: 'pancreatic cancer' },
+ { option: 'panic disorder' },
+ { option: 'paranasal sinus or nasal cavity cancer' },
+ { option: "Parkinson's disease" },
+ { option: 'patellar or quadriceps tendon rupture', sideOfBody: true },
+ { option: 'patellofemoral pain syndrome', sideOfBody: true },
+ { option: 'PCL tear (posterior cruciate ligament tear)', sideOfBody: true },
+ { option: 'penile cancer' },
+ {
+ option: 'perforated eardrum (perforated tympanic membrane)',
+ sideOfBody: true,
+ },
+ { option: 'persistent depressive disorder (dysthymic disorder)' },
+ { option: 'pharyngeal cancer (throat cancer)' },
+ { option: 'plantar fasciitis', sideOfBody: true },
+ { option: 'plantar warts (foot warts)', sideOfBody: true },
+ { option: 'pleurisy (pleuritis)' },
+ { option: 'pneumococcal arthritis' },
+ { option: 'polycystic ovary syndrome (PCOS)' },
+ { option: 'post-traumatic arthritis in ankle', sideOfBody: true },
+ { option: 'post-traumatic arthritis in elbow', sideOfBody: true },
+ { option: 'post-traumatic arthritis in foot', sideOfBody: true },
+ { option: 'post-traumatic arthritis in hand or fingers', sideOfBody: true },
+ { option: 'post-traumatic arthritis in hip', sideOfBody: true },
+ { option: 'post-traumatic arthritis in knee', sideOfBody: true },
+ { option: 'post-traumatic arthritis in shoulder', sideOfBody: true },
+ { option: 'post-traumatic arthritis in wrist', sideOfBody: true },
+ { option: 'poststreptococcal arthritis' },
+ { option: 'premature ventricular contractions' },
+ { option: 'proctitis (inflammation of rectum)' },
+ { option: 'prostate cancer' },
+ { option: 'prostatitis (inflammation of prostate)' },
+ {
+ option:
+ 'pseudofolliculitis barbae (razor bumps, shave bumps, or ingrown hairs)',
+ },
+ { option: 'psoriasis' },
+ { option: 'PTSD (post-traumatic stress disorder)' },
+ { option: 'pulmonary fibrosis' },
+ { option: 'pulmonary granuloma (lung nodule)' },
+ { option: 'radiculopathy lower extremities', sideOfBody: true },
+ { option: 'radiculopathy upper extremities', sideOfBody: true },
+ { option: "Raynaud's disease" },
+ { option: 'rheumatoid arthritis' },
+ { option: 'rib fracture', sideOfBody: true },
+ { option: 'rib removal' },
+ { option: 'right ventricular hypertrophy (RVH)' },
+ { option: 'ringworm (dermatophytosis)' },
+ { option: 'rotator cuff tear', sideOfBody: true },
+ { option: 'salivary gland cancer' },
+ { option: 'sarcoidosis' },
+ { option: 'scars, extremities or trunk' },
+ { option: 'scars, head, face or neck' },
+ { option: 'schizophrenia' },
+ { option: 'shin splints', sideOfBody: true },
+ { option: 'shoulder dislocation', sideOfBody: true },
+ { option: 'shoulder impingement syndrome', sideOfBody: true },
+ { option: 'shoulder replacement (shoulder arthroplasty)', sideOfBody: true },
+ { option: 'shoulder strain', sideOfBody: true },
+ { option: 'sickle cell anemia' },
+ {
+ option: 'SLAP tear (superior labral anterior to posterior tear)',
+ sideOfBody: true,
+ },
+ { option: 'sleep apnea' },
+ { option: 'small intestine cancer' },
+ {
+ option:
+ 'soft-tissue sarcoma of muscle, fat, or fibrous connective tissue, extremities or trunk',
+ },
+ {
+ option:
+ 'soft-tissue sarcoma of muscle, fat, or fibrous connective tissue, head or neck',
+ },
+ { option: 'somatic symptom disorder (SSD)' },
+ { option: 'spinal arthritis, back' },
+ { option: 'spinal arthritis, neck' },
+ { option: 'spondylolisthesis, back' },
+ { option: 'spondylolisthesis, neck' },
+ { option: 'squamous cell carcinoma of the skin, extremities or trunk' },
+ { option: 'squamous cell carcinoma of the skin, head or neck' },
+ { option: 'stomach cancer' },
+ { option: 'stress fracture in leg', sideOfBody: true },
+ { option: 'syphilitic arthritis' },
+ { option: 'tachycardia' },
+ { option: 'tailbone (coccyx) removal' },
+ { option: 'temporomandibular disorder, including TMJ' },
+ { option: 'tendinosis in hand or fingers', sideOfBody: true },
+ { option: 'tendonitis (tendinitis) in ankle', sideOfBody: true },
+ { option: 'tendonitis (tendinitis) in elbow', sideOfBody: true },
+ { option: 'tendonitis (tendinitis) in foot', sideOfBody: true },
+ { option: 'tendonitis (tendinitis) in hand or fingers', sideOfBody: true },
+ { option: 'tendonitis (tendinitis) in hip', sideOfBody: true },
+ { option: 'tendonitis (tendinitis) in knee', sideOfBody: true },
+ { option: 'tendonitis (tendinitis) in shoulder', sideOfBody: true },
+ { option: 'tendonitis (tendinitis) in wrist', sideOfBody: true },
+ { option: 'tennis elbow (lateral epicondylitis)', sideOfBody: true },
+ { option: 'tenosynovitis in hand or fingers', sideOfBody: true },
+ { option: 'tenosynovitis in wrist', sideOfBody: true },
+ { option: 'testicular cancer', sideOfBody: true },
+ { option: 'thyroid cancer' },
+ { option: 'tibia or fibula fracture', sideOfBody: true },
+ { option: 'Tietze syndrome' },
+ { option: 'tinnitus (ringing or hissing in ears)' },
+ { option: 'tongue cancer' },
+ { option: 'trachea cancer (cancer of windpipe)' },
+ { option: 'traumatic brain injury (TBI)' },
+ { option: 'trigger finger', sideOfBody: true },
+ { option: 'typhoid arthritis' },
+ { option: 'ulcerative colitis' },
+ { option: 'ureteral cancer (ureter cancer)' },
+ { option: 'urethritis' },
+ { option: 'urinary incontinence (loss of bladder control)' },
+ { option: 'uterine cancer' },
+ { option: 'vaginal cancer' },
+ { option: 'varicocele', sideOfBody: true },
+ { option: 'varicose veins in leg', sideOfBody: true },
+ {
+ option:
+ 'vascular soft-tissue sarcoma, including angiosarcoma, extremities or trunk',
+ },
+ {
+ option:
+ 'vascular soft-tissue sarcoma, including angiosarcoma, head or neck',
+ },
+ { option: 'ventral hernia (hernia in abdomen)' },
+ {
+ option:
+ 'visual impairment, including blurry vision, blindness and double vision',
+ },
+ { option: 'vitiligo' },
+ { option: 'vulvar cancer' },
+ { option: 'weak foot', sideOfBody: true },
+ { option: 'Wolff-Parkinson-White syndrome' },
+ { option: 'wrist fracture', sideOfBody: true },
+ { option: 'wrist replacement (wrist arthroplasty)', sideOfBody: true },
+ { option: 'wrist sprain', sideOfBody: true },
+];
+
+export const conditionOptions = conditionObjects.map(
+ condition => condition.option,
+);
diff --git a/src/applications/user-testing/new-conditions/content/newConditions.jsx b/src/applications/user-testing/new-conditions/content/newConditions.jsx
new file mode 100644
index 000000000000..ebd8a8446f2d
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/content/newConditions.jsx
@@ -0,0 +1,36 @@
+import React from 'react';
+
+export const conditionInstructions = (
+ <>
+ Add a condition below. You can add more conditions later.
+ If your condition isn’t listed
+
+ You can claim a condition that isn’t listed. Enter your condition,
+ diagnosis or short description of your symptoms.
+
+ Examples of conditions
+
+ - Tinnitus (ringing or hissing in ears)
+ - PTSD (post-traumatic stress disorder)
+ - Hearing loss
+ - Neck strain (cervical strain)
+ - Ankylosis in knee, right
+ - Hypertension (high blood pressure)
+ - Migraines (headaches)
+
+ Add a new condition
+ >
+);
+
+export const ServiceConnectedDisabilityDescription = () => (
+
+
+ To be eligible for service-connected disability benefits, you’ll need to
+ show that your disability was caused by an event, injury, or disease
+ during your military service. You’ll need to show your condition is linked
+ to your service by submitting evidence, such as medical reports or lay
+ statements, with your claim. We may ask you to have a claim exam if you
+ don’t submit evidence or if we need more information to decide your claim.
+
+
+);
diff --git a/src/applications/user-testing/new-conditions/manifest.json b/src/applications/user-testing/new-conditions/manifest.json
new file mode 100644
index 000000000000..9f3d67d20d82
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/manifest.json
@@ -0,0 +1,7 @@
+{
+ "appName": "Demo: File for disability compensation - New conditions",
+ "entryFile": "./app-entry.jsx",
+ "entryName": "new-conditions",
+ "rootUrl": "/user-testing/new-conditions",
+ "productId": "0db1a222-9d73-4c26-9966-75283e6510a1"
+}
diff --git a/src/applications/user-testing/new-conditions/pages/chooseDemo.js b/src/applications/user-testing/new-conditions/pages/chooseDemo.js
new file mode 100644
index 000000000000..cdba25c4e1cd
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/chooseDemo.js
@@ -0,0 +1,31 @@
+import {
+ radioSchema,
+ radioUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+import capitalize from 'lodash/capitalize';
+
+import { CONDITION_BY_CONDITION, CONDITIONS_FIRST } from '../constants';
+
+const demoOptions = {
+ CONDITION_BY_CONDITION: capitalize(CONDITION_BY_CONDITION),
+ CONDITIONS_FIRST: capitalize(CONDITIONS_FIRST),
+};
+
+/** @type {PageSchema} */
+export default {
+ title: 'Choose a demo',
+ path: 'choose-demo',
+ uiSchema: {
+ demo: radioUI({
+ title: 'Choose a demo',
+ labels: demoOptions,
+ }),
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ demo: radioSchema(Object.keys(demoOptions)),
+ },
+ required: ['demo'],
+ },
+};
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/cause.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/cause.js
new file mode 100644
index 000000000000..b66491d50dc8
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/cause.js
@@ -0,0 +1,48 @@
+import {
+ arrayBuilderItemSubsequentPageTitleUI,
+ radioSchema,
+ radioUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import { ServiceConnectedDisabilityDescription } from '../../content/newConditions';
+import { createItemName } from './utils';
+
+const causeOptions = {
+ NEW:
+ 'My condition was caused by an injury or exposure during my military service.',
+ SECONDARY:
+ 'My condition was caused by another service-connected disability I already have. (For example, I have a limp that caused lower-back problems.)',
+ WORSENED:
+ 'My condition existed before I served in the military, but it got worse because of my military service.',
+ VA:
+ 'My condition was caused by an injury or event that happened when I was receiving VA care.',
+};
+
+/** @returns {PageSchema} */
+const causePage = {
+ uiSchema: {
+ ...arrayBuilderItemSubsequentPageTitleUI(
+ ({ formData }) => `Cause of ${createItemName(formData)}`,
+ ),
+ cause: radioUI({
+ title: 'What caused your condition?',
+ labels: causeOptions,
+ }),
+ 'view:serviceConnectedDisabilityDescription': {
+ 'ui:description': ServiceConnectedDisabilityDescription,
+ },
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ cause: radioSchema(Object.keys(causeOptions)),
+ 'view:serviceConnectedDisabilityDescription': {
+ type: 'object',
+ properties: {},
+ },
+ },
+ required: ['cause'],
+ },
+};
+
+export default causePage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/causeFollowUp.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/causeFollowUp.js
new file mode 100644
index 000000000000..767654927f4a
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/causeFollowUp.js
@@ -0,0 +1,158 @@
+import {
+ arrayBuilderItemSubsequentPageTitleUI,
+ selectSchema,
+ selectUI,
+ textareaUI,
+ textUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import { conditionOptions } from '../../content/conditionOptions';
+import { createItemName } from './utils';
+
+const createCauseFollowUpTitles = formData => {
+ const causeTitle = {
+ NEW: `Details of injury or exposure that caused ${createItemName(
+ formData,
+ )}`,
+ SECONDARY: `Details of the service-connected disability that caused ${createItemName(
+ formData,
+ )}`,
+ WORSENED: `Details of the injury or exposure that worsened ${createItemName(
+ formData,
+ )}`,
+ VA: `Details of the injury or event in VA care for ${createItemName(
+ formData,
+ )}`,
+ };
+
+ return causeTitle[formData.cause];
+};
+
+// TODO: Fix formData so that shape is consistent
+// formData changes shape between add and edit which results in the need for the conditional below
+const createCauseFollowUpConditional = (formData, index, causeType) => {
+ const cause = formData?.conditionByCondition
+ ? formData.conditionByCondition[index]?.cause
+ : formData.cause;
+ return cause !== causeType;
+};
+
+// TODO: Fix causedByCondition functionality on edit
+// formData on add { "conditionByCondition": [{ "condition": "migraines (headaches)"... }] }
+// formData on edit { "condition": "migraines (headaches)"... } - does not include ratedDisabilities or other new conditions
+// TODO: If causedByCondition is 'asthma' asthma is updated to 'emphysema' ensure 'asthma' is cleared as potential cause
+const getOtherConditions = (formData, currentIndex) => {
+ const ratedDisabilities =
+ formData?.ratedDisabilities?.map(disability => disability.name) || [];
+
+ const otherNewConditions =
+ formData?.conditionByCondition
+ ?.filter((_, index) => index !== currentIndex)
+ ?.map(condition => createItemName(condition)) || [];
+
+ return [...ratedDisabilities, ...otherNewConditions];
+};
+
+/** @returns {PageSchema} */
+const causeFollowUpPage = {
+ uiSchema: {
+ ...arrayBuilderItemSubsequentPageTitleUI(({ formData }) =>
+ createCauseFollowUpTitles(formData),
+ ),
+ primaryDescription: textareaUI({
+ title:
+ 'Briefly describe the injury or exposure that caused your condition. For example, I operated loud machinery while in the service, and this caused me to lose my hearing.',
+ hideIf: (formData, index) =>
+ createCauseFollowUpConditional(formData, index, 'NEW'),
+ required: (formData, index) =>
+ !createCauseFollowUpConditional(formData, index, 'NEW'),
+ charcount: true,
+ }),
+ causedByCondition: selectUI({
+ title:
+ 'Choose the service-connected disability that caused the new condition that you’re claiming here.',
+ updateSchema: (formData, _schema, _uiSchema, index) => {
+ return selectSchema(getOtherConditions(formData, index));
+ },
+ hideIf: (formData, index) =>
+ createCauseFollowUpConditional(formData, index, 'SECONDARY'),
+ required: (formData, index) =>
+ !createCauseFollowUpConditional(formData, index, 'SECONDARY'),
+ }),
+ causedByConditionDescription: textareaUI({
+ title: 'Briefly describe how this disability caused your new condition.',
+ hideIf: (formData, index) =>
+ createCauseFollowUpConditional(formData, index, 'SECONDARY'),
+ required: (formData, index) =>
+ !createCauseFollowUpConditional(formData, index, 'SECONDARY'),
+ charcount: true,
+ }),
+ worsenedDescription: textUI({
+ title:
+ 'Briefly describe the injury or exposure during your military service that caused your existing disability to get worse.',
+ hideIf: (formData, index) =>
+ createCauseFollowUpConditional(formData, index, 'WORSENED'),
+ required: (formData, index) =>
+ !createCauseFollowUpConditional(formData, index, 'WORSENED'),
+ charcount: true,
+ }),
+ worsenedEffects: textareaUI({
+ title:
+ 'Tell us how the disability affected you before your service, and how it affects you now after your service.',
+ hideIf: (formData, index) =>
+ createCauseFollowUpConditional(formData, index, 'WORSENED'),
+ required: (formData, index) =>
+ !createCauseFollowUpConditional(formData, index, 'WORSENED'),
+ charcount: true,
+ }),
+ vaMistreatmentDescription: textareaUI({
+ title:
+ 'Briefly describe the injury or event while you were under VA care that caused your disability.',
+ hideIf: (formData, index) =>
+ createCauseFollowUpConditional(formData, index, 'VA'),
+ required: (formData, index) =>
+ !createCauseFollowUpConditional(formData, index, 'VA'),
+ charcount: true,
+ }),
+ vaMistreatmentLocation: textUI({
+ title: 'Tell us where this happened.',
+ hideIf: (formData, index) =>
+ createCauseFollowUpConditional(formData, index, 'VA'),
+ required: (formData, index) =>
+ !createCauseFollowUpConditional(formData, index, 'VA'),
+ charcount: true,
+ }),
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ primaryDescription: {
+ type: 'string',
+ maxLength: 400,
+ },
+ causedByCondition: selectSchema(conditionOptions),
+ causedByConditionDescription: {
+ type: 'string',
+ maxLength: 400,
+ },
+ worsenedDescription: {
+ type: 'string',
+ maxLength: 50,
+ },
+ worsenedEffects: {
+ type: 'string',
+ maxLength: 350,
+ },
+ vaMistreatmentDescription: {
+ type: 'string',
+ maxLength: 350,
+ },
+ vaMistreatmentLocation: {
+ type: 'string',
+ maxLength: 25,
+ },
+ },
+ },
+};
+
+export default causeFollowUpPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/condition.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/condition.js
new file mode 100644
index 000000000000..c8c9623a1603
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/condition.js
@@ -0,0 +1,105 @@
+import React from 'react';
+import {
+ titleUI,
+ withAlertOrDescription,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import Autocomplete from '../../components/Autocomplete';
+import { conditionOptions } from '../../content/conditionOptions';
+import { conditionInstructions } from '../../content/newConditions';
+import { arrayBuilderOptions, createTitle } from './utils';
+
+const missingConditionMessage =
+ 'Enter a condition, diagnosis, or short description of your symptoms';
+
+const NULL_CONDITION_STRING = 'Unknown Condition';
+
+const regexNonWord = /[^\w]/g;
+const sippableId = str =>
+ (str || 'blank').replace(regexNonWord, '').toLowerCase();
+
+const validateLength = (err, fieldData) => {
+ if (fieldData.length > 255) {
+ err.addError('This needs to be less than 256 characters');
+ }
+};
+
+const validateNotMissing = (err, fieldData) => {
+ const isMissingCondition =
+ !fieldData?.trim() ||
+ fieldData.toLowerCase() === NULL_CONDITION_STRING.toLowerCase();
+
+ if (isMissingCondition) {
+ err.addError(missingConditionMessage);
+ }
+};
+
+const validateNotDuplicate = (err, fieldData, formData) => {
+ const currentList =
+ formData?.conditionByCondition?.map(condition =>
+ condition.condition?.toLowerCase(),
+ ) || [];
+ const itemLowerCased = fieldData?.toLowerCase() || '';
+ const itemSippableId = sippableId(fieldData || '');
+ const itemCount = currentList.filter(
+ item => item === itemLowerCased || sippableId(item) === itemSippableId,
+ );
+
+ if (itemCount.length > 1) {
+ err.addError('You’ve already added this condition to your claim');
+ }
+};
+
+export const validateCondition = (err, fieldData = '', formData = {}) => {
+ validateLength(err, fieldData);
+ validateNotMissing(err, fieldData);
+ validateNotDuplicate(err, fieldData, formData);
+};
+
+/** @returns {PageSchema} */
+const conditionPage = {
+ uiSchema: {
+ ...titleUI(
+ () =>
+ createTitle(
+ 'Tell us the new condition you want to claim',
+ `Edit condition`,
+ ),
+ withAlertOrDescription({
+ nounSingular: arrayBuilderOptions.nounSingular,
+ description: conditionInstructions,
+ hasMultipleItemPages: true,
+ }),
+ ),
+ condition: {
+ 'ui:title': 'Enter your condition',
+ 'ui:field': data => (
+
+ ),
+ 'ui:errorMessages': {
+ required: missingConditionMessage,
+ },
+ 'ui:validations': [validateCondition],
+ 'ui:options': {
+ useAllFormData: true,
+ },
+ },
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ condition: {
+ type: 'string',
+ },
+ },
+ required: ['condition'],
+ },
+};
+
+export default conditionPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/date.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/date.js
new file mode 100644
index 000000000000..5dfbca22c7c3
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/date.js
@@ -0,0 +1,33 @@
+import {
+ arrayBuilderItemSubsequentPageTitleUI,
+ textUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import { createItemName } from './utils';
+
+/** @returns {PageSchema} */
+const datePage = {
+ uiSchema: {
+ ...arrayBuilderItemSubsequentPageTitleUI(
+ ({ formData }) => `Date of ${createItemName(formData)}`,
+ ),
+ date: textUI({
+ title:
+ 'What is the approximate date this condition began? If you’re having trouble remembering the exact date you can provide a year.',
+ hint: 'For example: January 2004 or 2004',
+ charcount: true,
+ }),
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ date: {
+ type: 'string',
+ maxLength: 25,
+ },
+ },
+ required: ['date'],
+ },
+};
+
+export default datePage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/index.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/index.js
new file mode 100644
index 000000000000..2e23a9f7c37d
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/index.js
@@ -0,0 +1,94 @@
+import { stringifyUrlParams } from '@department-of-veterans-affairs/platform-forms-system/helpers';
+import { arrayBuilderPages } from 'platform/forms-system/src/js/patterns/array-builder';
+import { getArrayIndexFromPathName } from 'platform/forms-system/src/js/patterns/array-builder/helpers';
+
+import causePage from './cause';
+import causeFollowUpPage from './causeFollowUp';
+import conditionPage from './condition';
+import datePage from './date';
+import introPage from './intro';
+import sideOfBodyPage from './sideOfBody';
+import summaryPage from './summary';
+import { arrayBuilderOptions, hasSideOfBody } from './utils';
+import { CONDITION_BY_CONDITION } from '../../constants';
+
+const conditionByConditionPages = arrayBuilderPages(
+ arrayBuilderOptions,
+ (pageBuilder, helpers) => ({
+ conditionByConditionIntro: pageBuilder.introPage({
+ title: 'New conditions intro',
+ path: `new-conditions-${CONDITION_BY_CONDITION}-intro`,
+ depends: formData => formData.demo === 'CONDITION_BY_CONDITION',
+ uiSchema: introPage.uiSchema,
+ schema: introPage.schema,
+ }),
+ conditionByConditionSummary: pageBuilder.summaryPage({
+ title: 'Review your new conditions',
+ path: `new-conditions-${CONDITION_BY_CONDITION}-summary`,
+ depends: formData => formData.demo === 'CONDITION_BY_CONDITION',
+ uiSchema: summaryPage.uiSchema,
+ schema: summaryPage.schema,
+ }),
+ conditionByConditionCondition: pageBuilder.itemPage({
+ title: 'Claim a new condition',
+ path: `new-conditions-${CONDITION_BY_CONDITION}/:index/condition`,
+ depends: formData => formData.demo === 'CONDITION_BY_CONDITION',
+ uiSchema: conditionPage.uiSchema,
+ schema: conditionPage.schema,
+ onNavForward: props => {
+ const { formData, pathname, urlParams, goPath } = props;
+ const index = getArrayIndexFromPathName(pathname);
+ const urlParamsString = stringifyUrlParams(urlParams) || '';
+
+ // TODO: This fixed bug where side of body was not being cleared when condition was edited which could result in 'Asthma, right'
+ // However, with this fix, when user doesn't change condition, side of body is cleared which could confuse users
+ formData.sideOfBody = undefined;
+
+ return hasSideOfBody(formData, index)
+ ? helpers.navForwardKeepUrlParams(props)
+ : goPath(
+ `new-conditions-${CONDITION_BY_CONDITION}/${index}/date${urlParamsString}`,
+ );
+ },
+ }),
+ conditionByConditionSideOfBody: pageBuilder.itemPage({
+ title: 'Side of body of new condition',
+ path: `new-conditions-${CONDITION_BY_CONDITION}/:index/side-of-body`,
+ depends: formData => formData.demo === 'CONDITION_BY_CONDITION',
+ uiSchema: sideOfBodyPage.uiSchema,
+ schema: sideOfBodyPage.schema,
+ }),
+ conditionByConditionDate: pageBuilder.itemPage({
+ title: 'Date of new condition',
+ path: `new-conditions-${CONDITION_BY_CONDITION}/:index/date`,
+ depends: formData => formData.demo === 'CONDITION_BY_CONDITION',
+ uiSchema: datePage.uiSchema,
+ schema: datePage.schema,
+ onNavBack: props => {
+ const { formData, pathname, urlParams, goPath } = props;
+ const index = getArrayIndexFromPathName(pathname);
+ const urlParamsString = stringifyUrlParams(urlParams) || '';
+
+ return hasSideOfBody(formData, index)
+ ? helpers.navBackKeepUrlParams(props)
+ : goPath(`new-conditions/${index}/condition${urlParamsString}`);
+ },
+ }),
+ conditionByConditionCause: pageBuilder.itemPage({
+ title: 'Cause of new condition',
+ path: `new-conditions-${CONDITION_BY_CONDITION}/:index/cause`,
+ depends: formData => formData.demo === 'CONDITION_BY_CONDITION',
+ uiSchema: causePage.uiSchema,
+ schema: causePage.schema,
+ }),
+ conditionByConditionCauseFollowUp: pageBuilder.itemPage({
+ title: 'Cause follow up for new condition',
+ path: `new-conditions-${CONDITION_BY_CONDITION}/:index/cause-follow-up`,
+ depends: formData => formData.demo === 'CONDITION_BY_CONDITION',
+ uiSchema: causeFollowUpPage.uiSchema,
+ schema: causeFollowUpPage.schema,
+ }),
+ }),
+);
+
+export default conditionByConditionPages;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/intro.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/intro.js
new file mode 100644
index 000000000000..035320fd81b0
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/intro.js
@@ -0,0 +1,17 @@
+import { titleUI } from 'platform/forms-system/src/js/web-component-patterns';
+
+/** @returns {PageSchema} */
+const introPage = {
+ uiSchema: {
+ ...titleUI(
+ 'New conditions',
+ 'In the next few screens, we’ll ask you about the new condition or conditions you’re filing for compensation.',
+ ),
+ },
+ schema: {
+ type: 'object',
+ properties: {},
+ },
+};
+
+export default introPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/sideOfBody.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/sideOfBody.js
new file mode 100644
index 000000000000..1bb251fde7fb
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/sideOfBody.js
@@ -0,0 +1,39 @@
+import {
+ radioSchema,
+ radioUI,
+ titleUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import { createTitle } from './utils';
+
+const sideOfBodyOptions = {
+ RIGHT: 'Right',
+ LEFT: 'Left',
+ BILATERAL: 'Bilateral (both sides)',
+};
+
+/** @returns {PageSchema} */
+const sideOfBodyPage = {
+ uiSchema: {
+ ...titleUI(({ formData }) => {
+ const condition = formData.condition || 'condition';
+
+ return createTitle(
+ `Where is your ${condition}?`,
+ `Edit side of body for ${condition}`,
+ );
+ }),
+ sideOfBody: radioUI({
+ title: 'Side of body',
+ labels: sideOfBodyOptions,
+ }),
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ sideOfBody: radioSchema(Object.keys(sideOfBodyOptions)),
+ },
+ },
+};
+
+export default sideOfBodyPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/summary.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/summary.js
new file mode 100644
index 000000000000..ef8bf9a1b7ea
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/summary.js
@@ -0,0 +1,33 @@
+import {
+ arrayBuilderYesNoSchema,
+ arrayBuilderYesNoUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import { arrayBuilderOptions } from './utils';
+
+/**
+ * This page is skipped on the first loop for required flow
+ * Cards are populated on this page above the uiSchema if items are present
+ *
+ * @returns {PageSchema}
+ */
+const summaryPage = {
+ uiSchema: {
+ 'view:hasConditions': arrayBuilderYesNoUI(
+ arrayBuilderOptions,
+ {},
+ {
+ hint: ' ', // Because there is maxItems: 100 if this empty string is not present the hint will count down from 100 which is a confusing user experience
+ },
+ ),
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ 'view:hasConditions': arrayBuilderYesNoSchema,
+ },
+ required: ['view:hasConditions'],
+ },
+};
+
+export default summaryPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/utils.js b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/utils.js
new file mode 100644
index 000000000000..ed838dba8f4d
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionByConditionPages/utils.js
@@ -0,0 +1,79 @@
+import { getArrayUrlSearchParams } from 'platform/forms-system/src/js/patterns/array-builder/helpers';
+
+import { conditionObjects } from '../../content/conditionOptions';
+
+export const createTitle = (defaultTitle, editTitle) => {
+ const search = getArrayUrlSearchParams();
+ const isEdit = search.get('edit');
+
+ if (isEdit) {
+ return editTitle;
+ }
+ return defaultTitle;
+};
+
+export const hasSideOfBody = (formData, index) => {
+ const condition = formData?.conditionByCondition
+ ? formData.conditionByCondition[index]?.condition
+ : formData.condition;
+
+ const conditionObject = conditionObjects.find(
+ conditionObj => conditionObj.option === condition,
+ );
+
+ return conditionObject ? conditionObject.sideOfBody : false;
+};
+
+const createCauseDescriptions = item => {
+ return {
+ NEW: 'Caused by an injury or exposure during my service.',
+ SECONDARY: `Caused by ${item?.causedByCondition}.`,
+ WORSENED:
+ 'Existed before I served in the military, but got worse because of my military service.',
+ VA:
+ 'Caused by an injury or event that happened when I was receiving VA care.',
+ };
+};
+
+const causeFollowUpChecks = {
+ NEW: item => !item?.primaryDescription,
+ SECONDARY: item =>
+ !item?.causedByCondition || !item?.causedByConditionDescription,
+ WORSENED: item => !item?.worsenedDescription || !item?.worsenedEffects,
+ VA: item => !item?.vaMistreatmentDescription || !item?.vaMistreatmentLocation,
+};
+
+// Different than lodash _capitalize because does not make rest of string lowercase which would break acronyms
+const capitalizeFirstLetter = string => {
+ return string?.charAt(0).toUpperCase() + string?.slice(1);
+};
+
+export const createItemName = (item, capFirstLetter = false) => {
+ const condition = capFirstLetter
+ ? capitalizeFirstLetter(item?.condition)
+ : item?.condition || 'condition';
+
+ if (item?.sideOfBody) {
+ return `${condition}, ${item?.sideOfBody.toLowerCase()}`;
+ }
+
+ return condition;
+};
+
+/** @type {ArrayBuilderOptions} */
+export const arrayBuilderOptions = {
+ arrayPath: 'conditionByCondition',
+ nounSingular: 'condition',
+ nounPlural: 'conditions',
+ required: true,
+ isItemIncomplete: item =>
+ !item?.condition ||
+ !item?.date ||
+ !item?.cause ||
+ (causeFollowUpChecks[item.cause] && causeFollowUpChecks[item.cause](item)),
+ maxItems: 100,
+ text: {
+ getItemName: item => createItemName(item, true),
+ cardDescription: item => createCauseDescriptions(item)[(item?.cause)],
+ },
+};
diff --git a/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/condition.js b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/condition.js
new file mode 100644
index 000000000000..c2d030f067f3
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/condition.js
@@ -0,0 +1,105 @@
+import React from 'react';
+import {
+ titleUI,
+ withAlertOrDescription,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import Autocomplete from '../../components/Autocomplete';
+import { conditionOptions } from '../../content/conditionOptions';
+import { conditionInstructions } from '../../content/newConditions';
+import { arrayBuilderOptions, createTitle } from './utils';
+
+const missingConditionMessage =
+ 'Enter a condition, diagnosis, or short description of your symptoms';
+
+const NULL_CONDITION_STRING = 'Unknown Condition';
+
+const regexNonWord = /[^\w]/g;
+const sippableId = str =>
+ (str || 'blank').replace(regexNonWord, '').toLowerCase();
+
+const validateLength = (err, fieldData) => {
+ if (fieldData.length > 255) {
+ err.addError('This needs to be less than 256 characters');
+ }
+};
+
+const validateNotMissing = (err, fieldData) => {
+ const isMissingCondition =
+ !fieldData?.trim() ||
+ fieldData.toLowerCase() === NULL_CONDITION_STRING.toLowerCase();
+
+ if (isMissingCondition) {
+ err.addError(missingConditionMessage);
+ }
+};
+
+const validateNotDuplicate = (err, fieldData, formData) => {
+ const currentList =
+ formData?.conditionsFirst?.map(condition =>
+ condition.condition?.toLowerCase(),
+ ) || [];
+ const itemLowerCased = fieldData?.toLowerCase() || '';
+ const itemSippableId = sippableId(fieldData || '');
+ const itemCount = currentList.filter(
+ item => item === itemLowerCased || sippableId(item) === itemSippableId,
+ );
+
+ if (itemCount.length > 1) {
+ err.addError('You’ve already added this condition to your claim');
+ }
+};
+
+export const validateCondition = (err, fieldData = '', formData = {}) => {
+ validateLength(err, fieldData);
+ validateNotMissing(err, fieldData);
+ validateNotDuplicate(err, fieldData, formData);
+};
+
+/** @returns {PageSchema} */
+const conditionPage = {
+ uiSchema: {
+ ...titleUI(
+ () =>
+ createTitle(
+ 'Tell us the new condition you want to claim',
+ `Edit condition`,
+ ),
+ withAlertOrDescription({
+ nounSingular: arrayBuilderOptions.nounSingular,
+ description: conditionInstructions,
+ hasMultipleItemPages: true,
+ }),
+ ),
+ condition: {
+ 'ui:title': 'Enter your condition',
+ 'ui:field': data => (
+
+ ),
+ 'ui:errorMessages': {
+ required: missingConditionMessage,
+ },
+ 'ui:validations': [validateCondition],
+ 'ui:options': {
+ useAllFormData: true,
+ },
+ },
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ condition: {
+ type: 'string',
+ },
+ },
+ required: ['condition'],
+ },
+};
+
+export default conditionPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/date.js b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/date.js
new file mode 100644
index 000000000000..5dfbca22c7c3
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/date.js
@@ -0,0 +1,33 @@
+import {
+ arrayBuilderItemSubsequentPageTitleUI,
+ textUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import { createItemName } from './utils';
+
+/** @returns {PageSchema} */
+const datePage = {
+ uiSchema: {
+ ...arrayBuilderItemSubsequentPageTitleUI(
+ ({ formData }) => `Date of ${createItemName(formData)}`,
+ ),
+ date: textUI({
+ title:
+ 'What is the approximate date this condition began? If you’re having trouble remembering the exact date you can provide a year.',
+ hint: 'For example: January 2004 or 2004',
+ charcount: true,
+ }),
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ date: {
+ type: 'string',
+ maxLength: 25,
+ },
+ },
+ required: ['date'],
+ },
+};
+
+export default datePage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/index.js b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/index.js
new file mode 100644
index 000000000000..636238c669ed
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/index.js
@@ -0,0 +1,80 @@
+import { stringifyUrlParams } from '@department-of-veterans-affairs/platform-forms-system/helpers';
+import { arrayBuilderPages } from 'platform/forms-system/src/js/patterns/array-builder';
+import { getArrayIndexFromPathName } from 'platform/forms-system/src/js/patterns/array-builder/helpers';
+
+import conditionPage from './condition';
+import datePage from './date';
+import introPage from './intro';
+import sideOfBodyPage from './sideOfBody';
+import summaryPage from './summary';
+import { arrayBuilderOptions, hasSideOfBody } from './utils';
+import { CONDITIONS_FIRST } from '../../constants';
+
+const conditionsFirstPages = arrayBuilderPages(
+ arrayBuilderOptions,
+ (pageBuilder, helpers) => ({
+ conditionsFirstIntro: pageBuilder.introPage({
+ title: 'New conditions intro',
+ path: `new-conditions-${CONDITIONS_FIRST}-intro`,
+ depends: formData => formData.demo === 'CONDITIONS_FIRST',
+ uiSchema: introPage.uiSchema,
+ schema: introPage.schema,
+ }),
+ conditionsFirstSummary: pageBuilder.summaryPage({
+ title: 'Review your new conditions',
+ path: `new-conditions-${CONDITIONS_FIRST}-summary`,
+ depends: formData => formData.demo === 'CONDITIONS_FIRST',
+ uiSchema: summaryPage.uiSchema,
+ schema: summaryPage.schema,
+ }),
+ conditionsFirstCondition: pageBuilder.itemPage({
+ title: 'Claim a new condition',
+ path: `new-conditions-${CONDITIONS_FIRST}/:index/condition`,
+ depends: formData => formData.demo === 'CONDITIONS_FIRST',
+ uiSchema: conditionPage.uiSchema,
+ schema: conditionPage.schema,
+ onNavForward: props => {
+ const { formData, pathname, urlParams, goPath } = props;
+ const index = getArrayIndexFromPathName(pathname);
+ const urlParamsString = stringifyUrlParams(urlParams) || '';
+
+ // TODO: This fixed bug where side of body was not being cleared when condition was edited which could result in 'Asthma, right'
+ // However, with this fix, when user doesn't change condition, side of body is cleared which could confuse users
+ formData.sideOfBody = undefined;
+
+ return hasSideOfBody(formData, index)
+ ? helpers.navForwardKeepUrlParams(props)
+ : goPath(
+ `new-conditions-${CONDITIONS_FIRST}/${index}/date${urlParamsString}`,
+ );
+ },
+ }),
+ conditionsFirstSideOfBody: pageBuilder.itemPage({
+ title: 'Side of body of new condition',
+ path: `new-conditions-${CONDITIONS_FIRST}/:index/side-of-body`,
+ depends: formData => formData.demo === 'CONDITIONS_FIRST',
+ uiSchema: sideOfBodyPage.uiSchema,
+ schema: sideOfBodyPage.schema,
+ }),
+ newConditionsJustConditionsDate: pageBuilder.itemPage({
+ title: 'Date of new condition',
+ path: `new-conditions-${CONDITIONS_FIRST}/:index/date`,
+ depends: formData => formData.demo === 'CONDITIONS_FIRST',
+ uiSchema: datePage.uiSchema,
+ schema: datePage.schema,
+ onNavBack: props => {
+ const { formData, pathname, urlParams, goPath } = props;
+ const index = getArrayIndexFromPathName(pathname);
+ const urlParamsString = stringifyUrlParams(urlParams) || '';
+
+ return hasSideOfBody(formData, index)
+ ? helpers.navBackKeepUrlParams(props)
+ : goPath(
+ `new-conditions-${CONDITIONS_FIRST}/${index}/condition${urlParamsString}`,
+ );
+ },
+ }),
+ }),
+);
+
+export default conditionsFirstPages;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/intro.js b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/intro.js
new file mode 100644
index 000000000000..035320fd81b0
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/intro.js
@@ -0,0 +1,17 @@
+import { titleUI } from 'platform/forms-system/src/js/web-component-patterns';
+
+/** @returns {PageSchema} */
+const introPage = {
+ uiSchema: {
+ ...titleUI(
+ 'New conditions',
+ 'In the next few screens, we’ll ask you about the new condition or conditions you’re filing for compensation.',
+ ),
+ },
+ schema: {
+ type: 'object',
+ properties: {},
+ },
+};
+
+export default introPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/sideOfBody.js b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/sideOfBody.js
new file mode 100644
index 000000000000..1bb251fde7fb
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/sideOfBody.js
@@ -0,0 +1,39 @@
+import {
+ radioSchema,
+ radioUI,
+ titleUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import { createTitle } from './utils';
+
+const sideOfBodyOptions = {
+ RIGHT: 'Right',
+ LEFT: 'Left',
+ BILATERAL: 'Bilateral (both sides)',
+};
+
+/** @returns {PageSchema} */
+const sideOfBodyPage = {
+ uiSchema: {
+ ...titleUI(({ formData }) => {
+ const condition = formData.condition || 'condition';
+
+ return createTitle(
+ `Where is your ${condition}?`,
+ `Edit side of body for ${condition}`,
+ );
+ }),
+ sideOfBody: radioUI({
+ title: 'Side of body',
+ labels: sideOfBodyOptions,
+ }),
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ sideOfBody: radioSchema(Object.keys(sideOfBodyOptions)),
+ },
+ },
+};
+
+export default sideOfBodyPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/summary.js b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/summary.js
new file mode 100644
index 000000000000..ef8bf9a1b7ea
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/summary.js
@@ -0,0 +1,33 @@
+import {
+ arrayBuilderYesNoSchema,
+ arrayBuilderYesNoUI,
+} from 'platform/forms-system/src/js/web-component-patterns';
+
+import { arrayBuilderOptions } from './utils';
+
+/**
+ * This page is skipped on the first loop for required flow
+ * Cards are populated on this page above the uiSchema if items are present
+ *
+ * @returns {PageSchema}
+ */
+const summaryPage = {
+ uiSchema: {
+ 'view:hasConditions': arrayBuilderYesNoUI(
+ arrayBuilderOptions,
+ {},
+ {
+ hint: ' ', // Because there is maxItems: 100 if this empty string is not present the hint will count down from 100 which is a confusing user experience
+ },
+ ),
+ },
+ schema: {
+ type: 'object',
+ properties: {
+ 'view:hasConditions': arrayBuilderYesNoSchema,
+ },
+ required: ['view:hasConditions'],
+ },
+};
+
+export default summaryPage;
diff --git a/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/utils.js b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/utils.js
new file mode 100644
index 000000000000..2edfa89e3872
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/pages/conditionsFirstPages/utils.js
@@ -0,0 +1,56 @@
+import { getArrayUrlSearchParams } from 'platform/forms-system/src/js/patterns/array-builder/helpers';
+
+import { conditionObjects } from '../../content/conditionOptions';
+
+export const createTitle = (defaultTitle, editTitle) => {
+ const search = getArrayUrlSearchParams();
+ const isEdit = search.get('edit');
+
+ if (isEdit) {
+ return editTitle;
+ }
+ return defaultTitle;
+};
+
+export const hasSideOfBody = (formData, index) => {
+ const condition = formData?.conditionsFirst
+ ? formData.conditionsFirst[index]?.condition
+ : formData.condition;
+
+ const conditionObject = conditionObjects.find(
+ conditionObj => conditionObj.option === condition,
+ );
+
+ return conditionObject ? conditionObject.sideOfBody : false;
+};
+
+// Different than lodash _capitalize because does not make rest of string lowercase which would break acronyms
+const capitalizeFirstLetter = string => {
+ return string?.charAt(0).toUpperCase() + string?.slice(1);
+};
+
+export const createItemName = (item, capFirstLetter = false) => {
+ const condition = capFirstLetter
+ ? capitalizeFirstLetter(item?.condition)
+ : item?.condition || 'condition';
+
+ if (item?.sideOfBody) {
+ return `${condition}, ${item?.sideOfBody.toLowerCase()}`;
+ }
+
+ return condition;
+};
+
+/** @type {ArrayBuilderOptions} */
+export const arrayBuilderOptions = {
+ arrayPath: 'conditionsFirst',
+ nounSingular: 'condition',
+ nounPlural: 'conditions',
+ required: true,
+ isItemIncomplete: item => !item?.condition || !item?.date,
+ maxItems: 100,
+ text: {
+ getItemName: item => createItemName(item, true),
+ cardDescription: item => `Date began: ${item?.date}`,
+ },
+};
diff --git a/src/applications/user-testing/new-conditions/reducers/index.js b/src/applications/user-testing/new-conditions/reducers/index.js
new file mode 100644
index 000000000000..fa616ca0da4b
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/reducers/index.js
@@ -0,0 +1,6 @@
+import { createSaveInProgressFormReducer } from 'platform/forms/save-in-progress/reducers';
+import formConfig from '../config/form';
+
+export default {
+ form: createSaveInProgressFormReducer(formConfig),
+};
diff --git a/src/applications/user-testing/new-conditions/routes.jsx b/src/applications/user-testing/new-conditions/routes.jsx
new file mode 100644
index 000000000000..8b56e82cbf9a
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/routes.jsx
@@ -0,0 +1,12 @@
+import { createRoutesWithSaveInProgress } from 'platform/forms/save-in-progress/helpers';
+import formConfig from './config/form';
+import App from './containers/App';
+
+const route = {
+ path: '/',
+ component: App,
+ indexRoute: { onEnter: (nextState, replace) => replace('/introduction') },
+ childRoutes: createRoutesWithSaveInProgress(formConfig),
+};
+
+export default route;
diff --git a/src/applications/user-testing/new-conditions/sass/new-conditions.scss b/src/applications/user-testing/new-conditions/sass/new-conditions.scss
new file mode 100644
index 000000000000..1d4590179b05
--- /dev/null
+++ b/src/applications/user-testing/new-conditions/sass/new-conditions.scss
@@ -0,0 +1,52 @@
+@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-process-list";
+@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-form-process";
+@import "../../../../platform/forms/sass/m-schemaform";
+@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-modal";
+@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-omb-info";
+@import "../../../../platform/forms/sass/m-form-confirmation";
+
+@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/functions";
+@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-process-list";
+@import "~@department-of-veterans-affairs/css-library/dist/stylesheets/modules/m-form-process";
+@import "../../../../platform/forms/sass/m-schemaform";
+@import "../../../../platform/forms/sass/m-form-confirmation";
+
+/* addDisabilities autocomplete styles */
+.cc-autocomplete {
+ position: relative;
+
+ &__list {
+ position: absolute;
+ width: 100%;
+ max-width: 30rem;
+ background-color: var(--vads-color-white);
+ padding: 0;
+ margin: 0;
+ overflow-y: auto;
+ z-index: 100;
+ max-height: 20rem;
+ border: 1px solid var(--vads-color-black);
+ }
+
+ &__option {
+ display: block;
+ padding: units(1);
+ margin: 0;
+ cursor: pointer;
+
+ &--active {
+ background-color: var(--vads-color-primary);
+ border-color: var(--vads-color-primary);
+ color: var(--vads-color-white);
+ }
+
+ &:first-child {
+ border-bottom: 1px solid var(--vads-color-gray-warm-light);
+ }
+ }
+
+ // Adjusts spacing on label inside web component text input.
+ va-text-input::part(label) {
+ margin-top: 0;
+ }
+}
diff --git a/src/platform/forms/constants.js b/src/platform/forms/constants.js
index 797e104f6513..1c0daaae1ccf 100644
--- a/src/platform/forms/constants.js
+++ b/src/platform/forms/constants.js
@@ -155,6 +155,7 @@ export const getAllFormLinks = getAppUrlImpl => {
[VA_FORM_IDS.FORM_WELCOME_VA_SETUP_REVIEW_INFORMATION]: `${tryGetAppUrl(
'welcome-va-setup-review-information',
)}/`,
+ [VA_FORM_IDS.FORM_21_526EZ]: `${tryGetAppUrl('21-526EZ')}/`,
};
};
@@ -465,6 +466,13 @@ export const MY_VA_SIP_FORMS = [
description: 'welcome va setup review information form',
trackingPrefix: 'welcome-va-setup-review-information-',
},
+ {
+ id: VA_FORM_IDS.FORM_21_526EZ,
+ benefit: 'disability compensation',
+ title: 'File for disability compensation',
+ description: 'disability compensation',
+ trackingPrefix: 'new-conditions',
+ },
];
export const FORM_BENEFITS = MY_VA_SIP_FORMS.reduce((acc, form) => {