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 && ( + + )} +

+ {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 ( +
+
+ VA logo +
+ + +

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

+ +

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) => {