diff --git a/src/App.tsx b/src/App.tsx index 90b5ffc7a..0d6463aa1 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,26 +8,26 @@ import QuestionComponentContainer from './Components/QuestionComponentContainer/ import Confirmation from './Components/Confirmation/Confirmation'; import Results from './Components/Results/Results'; import Disclaimer from './Components/Steps/Disclaimer/Disclaimer.tsx'; -import HouseholdDataBlock from './Components/HouseholdDataBlock/HouseholdDataBlock.js'; import ProgressBar from './Components/ProgressBar/ProgressBar'; import JeffcoLandingPage from './Components/JeffcoComponents/JeffcoLandingPage/JeffcoLandingPage'; -import SelectLanguagePage from './Components/Steps/SelectLanguagePage.tsx'; +import SelectLanguagePage from './Components/Steps/SelectLanguage.tsx'; import { updateScreen, updateUser } from './Assets/updateScreen.ts'; import { STARTING_QUESTION_NUMBER, useStepNumber, useStepDirectory } from './Assets/stepDirectory'; import Box from '@mui/material/Box'; -import { Expense, HealthInsurance, HouseholdData, IncomeStream, SignUpInfo } from './Types/FormData.js'; +import { Expense, HealthInsurance, SignUpInfo } from './Types/FormData.js'; import { BrandedFooter, BrandedHeader } from './Components/Referrer/Referrer.tsx'; import { useErrorController } from './Assets/validationFunctions.tsx'; import dataLayerPush from './Assets/analytics.ts'; import { OTHER_PAGE_TITLES } from './Assets/pageTitleTags.ts'; import { isCustomTypedLocationState } from './Types/FormData.ts'; LicenseInfo.setLicenseKey(process.env.REACT_APP_MUI_LICENSE_KEY + '='); -import './App.css'; import CcigLandingPage from './Components/CcigComponents/CcigLandingPage'; import languageRouteWrapper from './Components/RouterUtil/LanguageRouter'; import SelectStatePage from './Components/Steps/SelectStatePage'; import RedirectToWhiteLabel from './Components/RouterUtil/RedirectToWhiteLabel'; import CurrentBenefits from './Components/CurrentBenefits/CurrentBenefits'; +import HouseholdMemberForm from './Components/Steps/HouseholdMembers/HouseholdMemberForm.tsx'; +import './App.css'; const App = () => { const navigate = useNavigate(); @@ -42,6 +42,7 @@ const App = () => { setTheme: changeTheme, pageIsLoading, getReferrer, + configLoading, } = useContext(Context); const stepDirectory = useStepDirectory(); @@ -161,12 +162,6 @@ const App = () => { } }; - const handleRadioButtonChange = (event: Event) => { - const { name, value } = event.target as HTMLInputElement; - let boolValue = value === 'true'; - setFormData({ ...formData, [name]: boolValue }); - }; - const handleNoAnswerChange = (event: Event) => { const { name, value } = event.target as HTMLInputElement; setFormData({ ...formData, [name]: value }); @@ -218,33 +213,6 @@ const App = () => { } }; - const handleIncomeStreamsSubmit = (validatedIncomeStreams: IncomeStream[], stepId: number, uuid: string) => { - const updatedFormData = { ...formData, incomeStreams: validatedIncomeStreams }; - updateScreen(uuid, updatedFormData, locale); - setFormData(updatedFormData); - navigate(`/${formData.whiteLabel}/${uuid}/step-${stepId + 1}`); - }; - - const handleExpenseSourcesSubmit = (validatedExpenseSources: Expense[], stepId: number, uuid: string) => { - const isComingFromConfirmationPg = isCustomTypedLocationState(location.state) - ? location.state.routedFromConfirmationPg - : false; - const updatedFormData = { ...formData, expenses: validatedExpenseSources }; - updateScreen(uuid, updatedFormData, locale); - setFormData(updatedFormData); - isComingFromConfirmationPg - ? navigate(`/${formData.whiteLabel}/${uuid}/confirm-information`) - : navigate(`/${formData.whiteLabel}/${uuid}/step-${stepId + 1}`); - }; - - const handleHouseholdDataSubmit = (memberData: HouseholdData, stepId: number, uuid: string) => { - const updatedMembers = [...formData.householdData]; - updatedMembers[stepId] = memberData; - const updatedFormData = { ...formData, householdData: updatedMembers }; - updateScreen(uuid, updatedFormData, locale); - setFormData(updatedFormData); - }; - if (pageIsLoading) { return ( @@ -316,12 +284,7 @@ const App = () => { } /> - } + element={} /> { key={window.location.href} handleTextfieldChange={handleTextfieldChange} handleContinueSubmit={handleContinueSubmit} - handleRadioButtonChange={handleRadioButtonChange} handleNoAnswerChange={handleNoAnswerChange} - handleIncomeStreamsSubmit={handleIncomeStreamsSubmit} - handleExpenseSourcesSubmit={handleExpenseSourcesSubmit} handleCheckboxChange={handleCheckboxChange} /> } diff --git a/src/Assets/questions.tsx b/src/Assets/questions.tsx index 5e699ab8a..90555dd7b 100644 --- a/src/Assets/questions.tsx +++ b/src/Assets/questions.tsx @@ -1,71 +1,13 @@ import { - radiofieldHasError, householdAssetsHasError, - benefitsHasError, - selectHasError, - displayReferralSourceHelperText, signUpOptionsHaveError, - acuteHHConditionsHasError, - displayBenefitsHelperText, displayHouseholdAssetsHelperText, - otherReferalSourceHelperText, } from './validationFunctions.tsx'; import { FormattedMessage } from 'react-intl'; import type { QuestionName, Question } from '../Types/Questions.ts'; import HelpButton from '../Components/HelpBubbleIcon/HelpButton.tsx'; const questions: Record = { - householdData: { - name: 'householdData', - componentDetails: { - componentType: 'HouseholdDataBlock', - ariaLabel: 'questions.householdData-ariaLabel', - inputName: 'householdData', - }, - }, - hasExpenses: { - name: 'hasExpenses', - header: , - question: ( - <> -
- - -
- - ), - questionDescription: ( - - ), - componentDetails: { - componentType: 'Radiofield', - ariaLabel: 'questions.hasExpenses-ariaLabel', - inputName: 'hasExpenses', - inputError: radiofieldHasError, - }, - followUpQuestions: [ - { - question: ( - - ), - name: 'expenses', - componentDetails: { - componentType: 'ExpenseBlock', - ariaLabel: 'questions.hasExpenses-a-ariaLabel', - inputName: 'expenses', - }, - }, - ], - }, householdAssets: { name: 'householdAssets', header: , @@ -93,26 +35,6 @@ const questions: Record = { required: true, }, }, - acuteHHConditions: { - name: 'acuteHHConditions', - header: ( - - ), - question: ( - - ), - componentDetails: { - componentType: 'OptionCardGroup', - inputName: 'acuteHHConditions', - inputError: acuteHHConditionsHasError, - }, - }, signUpInfo: { name: 'signUpInfo', header: ( diff --git a/src/Assets/stepDirectory.ts b/src/Assets/stepDirectory.ts index c73d1765f..d76738657 100644 --- a/src/Assets/stepDirectory.ts +++ b/src/Assets/stepDirectory.ts @@ -13,6 +13,7 @@ export function useStepDirectory() { } export function useStepNumber(name: QuestionName, raise: boolean = true) { + // The second argument is an optional boolean that you can use if you need to access the step number before the config is loaded. const stepDirectory = useStepDirectory(); const stepNumber = stepDirectory.findIndex((question) => question === name); diff --git a/src/Assets/validationFunctions.tsx b/src/Assets/validationFunctions.tsx index a2cc80e68..2847917ad 100644 --- a/src/Assets/validationFunctions.tsx +++ b/src/Assets/validationFunctions.tsx @@ -2,7 +2,7 @@ import { useContext, useState } from 'react'; import { Context } from '../Components/Wrapper/Wrapper'; import { FormattedMessage } from 'react-intl'; import type { ErrorController, ValidationFunction, MessageFunction } from '../Types/ErrorController'; -import type { Expense, HealthInsurance, HouseholdData, IncomeStream, SignUpInfo, FormData } from '../Types/FormData'; +import type { SignUpInfo, FormData } from '../Types/FormData'; import ErrorMessageWrapper from '../Components/ErrorMessage/ErrorMessageWrapper'; function useErrorController(hasErrorFunc: ValidationFunction, messageFunc: MessageFunction): ErrorController { @@ -30,138 +30,6 @@ function useErrorController(hasErrorFunc: ValidationFunction, messageFunc: return { hasError, showError, submittedCount, incrementSubmitted, setSubmittedCount, updateError, message }; } -const ageHasError: ValidationFunction = (applicantAge) => { - // handleTextfieldChange prevents setting anything to formData that does not pass a number regex test - // so applicantAge will always be initiated as a string and converted to a number once it passes the regex test - const numberApplicantAge = Number(applicantAge); - //the numbers that we type in have to be 0-8 digits long but we want them to be within this min/max range - const minimumAge = 13; - const maximumAge = 130; - return numberApplicantAge < minimumAge || numberApplicantAge > maximumAge; -}; - -const displayAgeHelperText: MessageFunction = (applicantAge) => { - const numberApplicantAge = Number(applicantAge); - const minimumAge = 13; - const maximumAge = 130; - if (numberApplicantAge < minimumAge || numberApplicantAge > maximumAge) { - return ( - - - - ); - } -}; - -const radiofieldHasError: ValidationFunction = (radiofield) => { - return typeof radiofield !== 'boolean'; -}; - -const hoursWorkedValueHasError: ValidationFunction = (valueInput) => { - const numberUpToEightDigitsLongRegex = /^\d{0,3}$/; - return Number(valueInput) <= 0 && (numberUpToEightDigitsLongRegex.test(valueInput) || valueInput === ''); -}; - -const hoursWorkedHelperText: MessageFunction = (valueInput) => { - if (hoursWorkedValueHasError(valueInput)) { - return ( - - - - ); - } -}; - -const incomeStreamValueHasError: ValidationFunction = (valueInput) => { - const incomeAmountRegex = /^\d{0,7}(?:\d\.\d{0,2})?$/; - - return Number(valueInput) <= 0 && (incomeAmountRegex.test(valueInput) || valueInput === ''); -}; - -const displayIncomeStreamValueHelperText: MessageFunction = (valueInput) => { - if (incomeStreamValueHasError(valueInput)) { - return ( - - - - ); - } -}; - -const incomeStreamsAreValid: ValidationFunction = (incomeStreams) => { - const allIncomeStreamsAreValid = incomeStreams.every((incomeSourceData) => { - const { incomeStreamName, incomeAmount, incomeFrequency, hoursPerWeek } = incomeSourceData; - - return ( - incomeStreamName.length > 0 && - incomeAmount !== '' && - Number(incomeAmount) > 0 && - (incomeFrequency !== 'hourly' || (hoursPerWeek !== '' && Number(hoursPerWeek) > 0)) && - incomeFrequency.length > 0 - ); - }); - - //incomeStreams must have a non-zero length since this function is only called if - //the user indicated that they had income. This stmt was also added to fix the vacuously - //true stmt that allIncomeStreamsAreValid for an empty array - return incomeStreams.length > 0 && allIncomeStreamsAreValid; -}; - -const expenseSourceValueHasError: ValidationFunction = (valueInput) => { - return valueInput === '' || Number(valueInput) <= 0; -}; - -const displayExpenseSourceValueHelperText: MessageFunction = (valueInput) => { - if (expenseSourceValueHasError(valueInput)) { - return ( - - - - ); - } -}; - -const expenseSourcesHaveError: ValidationFunction = (expenses) => { - const expensesHasError = expenses.some((expenseSourceData) => { - const { expenseSourceName, expenseAmount } = expenseSourceData; - return expenseSourceName === '' || expenseAmount === '' || Number(expenseAmount) <= 0; - }); - - return expensesHasError; -}; - -const displayExpensesHelperText: MessageFunction = (expenses) => { - if (expenseSourcesHaveError(expenses)) { - return ( - - - - ); - } -}; - -const householdSizeHasError: ValidationFunction = (sizeOfHousehold) => { - const numValueInput = Number(sizeOfHousehold); - return numValueInput <= 0 || numValueInput > 8; -}; - -const displayHouseholdSizeHelperText: MessageFunction = (sizeOfHousehold) => { - const numValueInput = Number(sizeOfHousehold); - return ( - (numValueInput <= 0 || numValueInput > 8) && ( - - - - ) - ); -}; - const householdAssetsHasError: ValidationFunction = (householdAssets) => { return Number(householdAssets) < 0; }; @@ -176,157 +44,6 @@ const displayHouseholdAssetsHelperText: MessageFunction = (householdAsse } }; -const householdMemberAgeHasError: ValidationFunction = (applicantAge) => { - if (applicantAge === '') { - return true; - } - const numberApplicantAge = Number(applicantAge); - return numberApplicantAge < 0; -}; - -const displayHouseholdMemberAgeHelperText: MessageFunction = (applicantAge) => { - if (householdMemberAgeHasError(applicantAge)) { - return ( - - - - ); - } -}; - -const date = new Date(); -const CURRENT_YEAR = date.getFullYear(); -// the getMonth method returns January as 0 -const CURRENT_MONTH = date.getMonth() + 1; - -function birthMonthHasError(birthMonth: number | null) { - if (birthMonth === null) { - return true; - } - - if (birthMonth < 1 || birthMonth > 12) { - return true; - } - - return false; -} - -function birthMonthErrorMessage() { - return ( - - - - ); -} - -const MAX_AGE = 130; -function birthYearHasError(birthYear: number | null) { - if (birthYear === null) { - return true; - } - - if (birthYear > CURRENT_YEAR || birthYear < CURRENT_YEAR - MAX_AGE) { - return true; - } - - return false; -} - -function birthYearErrorMessage() { - return ( - - - - ); -} - -function birthMonthInvalidWithYearErrorMessage() { - return ( - - - - ); -} - -function birthMonthInvalidWithYearError({ birthYear, birthMonth }: { birthYear: number; birthMonth: number }) { - return birthYear === CURRENT_YEAR && birthMonth > CURRENT_MONTH; -} - -const personDataIsValid: ValidationFunction = (householdDataState) => { - const { birthYear, birthMonth, relationshipToHH, hasIncome, incomeStreams, healthInsurance } = householdDataState; - - if (birthMonth === undefined || birthYear === undefined) { - return false; - } - - const ageIsValid = - !birthYearHasError(birthYear) && - !birthMonthHasError(birthMonth) && - !birthMonthInvalidWithYearError({ birthYear, birthMonth }); - const relationshipToHHIsValid = relationshipToHH !== ''; - const incomeIsValid = (hasIncome && incomeStreamsAreValid(incomeStreams)) || !hasIncome; - const healthInsuranceIsValid = healthInsuranceDataIsValid(healthInsurance); - - return ageIsValid && relationshipToHHIsValid && incomeIsValid && healthInsuranceIsValid; -}; - -const getHealthInsuranceError: MessageFunction<{ index: number; healthInsurance: HealthInsurance }> = ({ - index, - healthInsurance, -}) => { - if (healthInsuranceDataIsValid(healthInsurance) === false) { - if (healthInsurance.none === true) { - //then they chose none and another option - return ( - - {index === 1 ? ( - - ) : ( - - )} - - ); - } else { - //they haven't selected an option - return ( - - - - ); - } - } -}; - -const healthInsuranceDataIsValid: ValidationFunction = (hhMemberHealthInsData) => { - const numOfTrueValues = Object.values(hhMemberHealthInsData).filter( - (healthInsuranceValue) => healthInsuranceValue === true, - ).length; - - if (hhMemberHealthInsData.none === true) { - //check here to ensure that that is the ONLY option that was selected via numOfTrueValues - return numOfTrueValues === 1; - } else { - const atLeastOneOptionWasSelected = numOfTrueValues > 0; - return atLeastOneOptionWasSelected; - } -}; - -const healthInsuranceDataHasError: ValidationFunction = (hhMemberHealthInsData: HealthInsurance) => { - return !healthInsuranceDataIsValid(hhMemberHealthInsData); -}; - const emailHasError: ValidationFunction = (email) => { return !/^.+@(?:[a-zA-Z0-9]+\.)+[A-Za-z]+$/.test(email); }; @@ -426,19 +143,6 @@ const signUpFormHasError: ValidationFunction { - if (!email && !phone) { - return ( - - - - ); - } -}; - const signUpServerHasError: ValidationFunction = (serverError) => { return serverError === true; }; @@ -465,71 +169,6 @@ const signUpOptionsHaveError: ValidationFunction = (signUpInfo) => { } }; -const healthInsuranceHasError: ValidationFunction<{ [key: string]: boolean }> = (healthInsuranceSelections) => { - const healthInsuranceKeys = Object.keys(healthInsuranceSelections); - const noOptionWasSelected = healthInsuranceKeys.every( - (option) => healthInsuranceSelections[option as keyof HealthInsurance] === false, - ); - return noOptionWasSelected; -}; - -const displayHealthInsuranceHelperText: MessageFunction<{ [key: string]: boolean }> = (healthInsuranceSelections) => { - if (healthInsuranceHasError(healthInsuranceSelections)) { - return ( - - - - ); - } -}; - -const acuteHHConditionsHasError = () => { - return false; -}; - -const countySelectHelperText: MessageFunction = () => { - return ( - - - - ); -}; - -const expenseTypeHelperText: MessageFunction = () => { - return ( - - - - ); -}; - -const relationTypeHelperText: MessageFunction = () => { - return ( - - - - ); -}; - -const incomeStreamHelperText: MessageFunction = () => { - return ( - - - - ); -}; - -const incomeFrequencyHelperText: MessageFunction = () => { - return ( - - - - ); -}; - const otherReferalSourceHelperText: MessageFunction = () => { return ( @@ -552,25 +191,8 @@ const displayAgreeToTermsErrorMessage: MessageFunction = () => { export { useErrorController, - ageHasError, - displayAgeHelperText, - radiofieldHasError, - hoursWorkedValueHasError, - hoursWorkedHelperText, - incomeStreamValueHasError, - displayIncomeStreamValueHelperText, - incomeStreamsAreValid, - expenseSourceValueHasError, - displayExpenseSourceValueHelperText, - expenseSourcesHaveError, - displayExpensesHelperText, - householdSizeHasError, - displayHouseholdSizeHelperText, householdAssetsHasError, displayHouseholdAssetsHelperText, - householdMemberAgeHasError, - displayHouseholdMemberAgeHelperText, - personDataIsValid, emailHasError, displayEmailHelperText, selectHasError, @@ -585,23 +207,7 @@ export { signUpServerHasError, signUpServerErrorHelperText, signUpOptionsHaveError, - healthInsuranceHasError, - displayHealthInsuranceHelperText, - acuteHHConditionsHasError, - countySelectHelperText, - expenseTypeHelperText, - relationTypeHelperText, - incomeStreamHelperText, - incomeFrequencyHelperText, otherReferalSourceHelperText, termsOfServiceHasError, displayAgreeToTermsErrorMessage, - healthInsuranceDataHasError, - getHealthInsuranceError, - birthMonthHasError, - birthMonthErrorMessage, - birthYearHasError, - birthYearErrorMessage, - birthMonthInvalidWithYearError, - birthMonthInvalidWithYearErrorMessage, }; diff --git a/src/Components/AutoComplete/AutoComplete.tsx b/src/Components/AutoComplete/AutoComplete.tsx deleted file mode 100644 index 09032bc1d..000000000 --- a/src/Components/AutoComplete/AutoComplete.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Autocomplete, FormControl, FormHelperText, TextField } from '@mui/material'; -import { useMemo } from 'react'; -import { FormattedMessageType } from '../../Types/Questions'; - -type Props = { - value: string | null; - setValue: (newValue: string | null) => void; - options: string[]; - label: FormattedMessageType; - showError: boolean; - errorMessage: FormattedMessageType; -}; - -export default function AutoComplete({ options, label, value, setValue, showError, errorMessage }: Props) { - const autoCompleteOptions = useMemo(() => { - return options.map((value) => { - return { label: value }; - }); - }, [options]); - - const autoCompleteValue = value !== null ? { label: value } : null; - - return ( - - option.label === value.label} - options={autoCompleteOptions} - value={autoCompleteValue} - onChange={(event, newValue) => { - setValue(newValue?.label ?? null); - }} - renderInput={(params) => ( - { - if (options.includes(event.target.value)) { - setValue(event.target.value); - } - }} - /> - )} - /> - {showError && {errorMessage}} - - ); -} diff --git a/src/Components/ExpenseBlock/ExpenseBlock.js b/src/Components/ExpenseBlock/ExpenseBlock.js deleted file mode 100644 index 3ee92e818..000000000 --- a/src/Components/ExpenseBlock/ExpenseBlock.js +++ /dev/null @@ -1,122 +0,0 @@ -import { Button } from '@mui/material'; -import { useState, useContext, useEffect } from 'react'; -import { Context } from '../Wrapper/Wrapper.tsx'; -import { useParams } from 'react-router-dom'; -import { FormattedMessage } from 'react-intl'; -import ExpenseQuestion from './ExpenseQuestion'; -import { - useErrorController, - expenseSourcesHaveError, - displayExpensesHelperText, -} from '../../Assets/validationFunctions.tsx'; -import PreviousButton from '../PreviousButton/PreviousButton'; -import AddIcon from '@mui/icons-material/Add'; -import Box from '@mui/material/Box'; -import Stack from '@mui/material/Stack'; -import './ExpenseBlock.css'; - -const ExpenseBlock = ({ handleExpenseSourcesSubmit }) => { - const { formData } = useContext(Context); - const { id, uuid } = useParams(); - const stepNumberId = Number(id); - const expensesErrorController = useErrorController(expenseSourcesHaveError, displayExpensesHelperText); - - useEffect(() => { - const continueOnEnter = (event) => { - if (event.key === 'Enter') { - handleSaveAndContinue(event); - } - }; - document.addEventListener('keyup', continueOnEnter); - return () => { - document.removeEventListener('keyup', continueOnEnter); // remove event listener on onmount - }; - }); - - const [selectedMenuItem, setSelectedMenuItem] = useState( - formData.expenses.length > 0 - ? formData.expenses - : [ - { - expenseSourceName: '', - expenseAmount: '', - }, - ], - ); - - useEffect(() => { - expensesErrorController.updateError(selectedMenuItem, formData); - }, [selectedMenuItem]); - - const deleteExpenseBlock = (selectedIndex) => { - const updatedSelectedMenuItems = selectedMenuItem.filter((expenseSourceData, index) => index !== selectedIndex); - setSelectedMenuItem(updatedSelectedMenuItems); - expensesErrorController.updateError(updatedSelectedMenuItems, formData); - }; - - const createExpenseBlockQuestions = () => { - return selectedMenuItem.map((expenseSourceData, index) => { - return ( - - - - - - ); - }); - }; - - const handleAddAdditionalExpenseSource = (event) => { - event.preventDefault(); - setSelectedMenuItem([ - ...selectedMenuItem, - { - expenseSourceName: '', - expenseAmount: 0, - }, - ]); - }; - - const handleSaveAndContinue = (event) => { - event.preventDefault(); - const hasError = expensesErrorController.updateError(selectedMenuItem); - expensesErrorController.incrementSubmitted(); - if (!hasError) { - handleExpenseSourcesSubmit(selectedMenuItem, stepNumberId, uuid); - } - }; - - return ( - <> - {createExpenseBlockQuestions()} - -
- - -
- - ); -}; - -export default ExpenseBlock; diff --git a/src/Components/ExpenseBlock/ExpenseQuestion.js b/src/Components/ExpenseBlock/ExpenseQuestion.js deleted file mode 100644 index 4c9e0a6a1..000000000 --- a/src/Components/ExpenseBlock/ExpenseQuestion.js +++ /dev/null @@ -1,205 +0,0 @@ -import { useConfig } from '../Config/configHook.tsx'; -import { FormattedMessage } from 'react-intl'; -import { FormControl, Select, MenuItem, InputLabel, Button, FormHelperText } from '@mui/material'; -import { styled } from '@mui/material/styles'; -import { - useErrorController, - expenseSourceValueHasError, - displayExpenseSourceValueHelperText, - selectHasError, - expenseTypeHelperText, -} from '../../Assets/validationFunctions.tsx'; -import { useEffect } from 'react'; -import Textfield from '../Textfield/Textfield'; -import QuestionQuestion from '../QuestionComponents/QuestionQuestion'; -import CloseButton from '../CloseButton/CloseButton.tsx'; -import './ExpenseBlock.css'; - -const StyledSelectfield = styled(Select)({ - minWidth: 200, - maxWidth: '100%', - backgroundColor: 'white', -}); - -const ExpenseQuestion = ({ expenseData, allExpensesData, setAllExpenses, deleteExpenseBlock, index, submitted }) => { - const expenseTypeErrorController = useErrorController(selectHasError, expenseTypeHelperText); - - const expenseOptions = useConfig('expense_options'); - - useEffect(() => { - expenseTypeErrorController.updateError(expenseSourceName); - }, []); - - useEffect(() => { - expenseTypeErrorController.setSubmittedCount(submitted); - }, [submitted]); - - const handleSelectChange = (event, index) => { - const updatedSelectedMenuItems = allExpensesData.map((expenseSourceData, i) => { - if (i === index) { - return { - ...expenseSourceData, - expenseSourceName: event.target.value, - }; - } else { - return expenseSourceData; - } - }); - - expenseTypeErrorController.updateError(event.target.value); - - setAllExpenses(updatedSelectedMenuItems); - }; - - const handleExpenseTextfieldChange = (event, index) => { - const { value } = event.target; - const numberUpToEightDigitsLongRegex = /^\d{0,8}$/; - - if (numberUpToEightDigitsLongRegex.test(value)) { - const updatedSelectedMenuItems = allExpensesData.map((expenseSourceData, i) => { - if (i === index) { - return { ...expenseSourceData, expenseAmount: Math.round(Number(value)) }; - } else { - return expenseSourceData; - } - }); - - setAllExpenses(updatedSelectedMenuItems); - } - }; - - const createExpenseMenuItems = () => { - const disabledSelectMenuItem = ( - - - - ); - - const menuItemKeys = Object.keys(expenseOptions); - const menuItemLabels = Object.values(expenseOptions); - - const menuItems = menuItemKeys.map((menuItemKey, i) => { - return ( - - {menuItemLabels[i]} - - ); - }); - - return [disabledSelectMenuItem, menuItems]; - }; - - const getExpenseSourceLabel = (expenseSourceName) => { - if (expenseSourceName) { - return ( - <> - {' ('} - {expenseOptions[expenseSourceName]} - {')'}? - - ); - } - - return '?'; - }; - - const textfieldProps = { - inputType: 'text', - inputLabel: , - inputName: 'expenseAmount', - inputError: expenseSourceValueHasError, - inputHelperText: displayExpenseSourceValueHelperText, - dollarField: true, - numericField: true, - }; - - const createExpenseAmountTextfield = (expenseSourceName, expenseAmount, index) => { - return ( -
-
- - - {getExpenseSourceLabel(allExpensesData[index].expenseSourceName)} - -
-
- -
-
- ); - }; - - const createExpenseDropdownMenu = (expenseSourceName, index) => { - return ( - - - - - - } - onChange={(event) => { - handleSelectChange(event, index); - }} - > - {createExpenseMenuItems()} - - {expenseTypeErrorController.showError && ( - {expenseTypeErrorController.message()} - )} - - ); - }; - - const { expenseSourceName, expenseAmount } = expenseData; - - const expenseSourceQuestion = ( - - - - ); - - if (index === 0) { - return ( -
- {createExpenseDropdownMenu(expenseSourceName, index)} - {createExpenseAmountTextfield(expenseSourceName, expenseAmount, index)} -
- ); - } else { - return ( -
-
- deleteExpenseBlock(index)} /> -
- {expenseSourceQuestion} - {createExpenseDropdownMenu(expenseSourceName, index)} - {createExpenseAmountTextfield(expenseSourceName, expenseAmount, index)} -
- ); - } -}; - -export default ExpenseQuestion; diff --git a/src/Components/FollowUpQuestions/FollowUpQuestions.js b/src/Components/FollowUpQuestions/FollowUpQuestions.js index b715118c0..ce41512a7 100644 --- a/src/Components/FollowUpQuestions/FollowUpQuestions.js +++ b/src/Components/FollowUpQuestions/FollowUpQuestions.js @@ -12,7 +12,6 @@ const FollowUpQuestions = ({ submitted, formData, handleCheckboxChange, - handleExpenseSourcesSubmit, handleTextfieldChange, handleRadioButtonChange, }) => { diff --git a/src/Components/HealthInsuranceError/HealthInsuranceError.tsx b/src/Components/HealthInsuranceError/HealthInsuranceError.tsx deleted file mode 100644 index f4eb2688b..000000000 --- a/src/Components/HealthInsuranceError/HealthInsuranceError.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useEffect } from 'react'; -import { - useErrorController, - healthInsuranceDataHasError, - getHealthInsuranceError, -} from '../../Assets/validationFunctions'; -import { HealthInsurance } from '../../Types/FormData'; -import { FormHelperText } from '@mui/material'; - -interface HealthInsuranceErrorProps { - hhMemberIndex: number; - hhMemberInsurance: HealthInsurance; - submitted: number; -} - -const HealthInsuranceError = ({ hhMemberIndex, hhMemberInsurance, submitted }: HealthInsuranceErrorProps) => { - const healthInsuranceErrorController = useErrorController(healthInsuranceDataHasError, getHealthInsuranceError); - - useEffect(() => { - healthInsuranceErrorController.setSubmittedCount(submitted); - }, [submitted]); - - useEffect(() => { - healthInsuranceErrorController.updateError(hhMemberInsurance); - }); - - return ( - healthInsuranceErrorController.showError && ( - - {healthInsuranceErrorController.message({ index: hhMemberIndex, healthInsurance: hhMemberInsurance })} - - ) - ); -}; - -export default HealthInsuranceError; diff --git a/src/Components/HouseholdDataBlock/AgeInput.css b/src/Components/HouseholdDataBlock/AgeInput.css deleted file mode 100644 index ea7f6b477..000000000 --- a/src/Components/HouseholdDataBlock/AgeInput.css +++ /dev/null @@ -1,5 +0,0 @@ -.age-input-container { - display: flex; - gap: 1rem; - flex-wrap: wrap; -} diff --git a/src/Components/HouseholdDataBlock/AgeInput.tsx b/src/Components/HouseholdDataBlock/AgeInput.tsx deleted file mode 100644 index 18117c1f5..000000000 --- a/src/Components/HouseholdDataBlock/AgeInput.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { FormControl, FormHelperText, InputLabel, MenuItem, Select } from '@mui/material'; -import { useEffect, useMemo } from 'react'; -import { FormattedMessage } from 'react-intl'; -import { - birthMonthErrorMessage, - birthMonthHasError, - birthMonthInvalidWithYearError, - birthMonthInvalidWithYearErrorMessage, - birthYearErrorMessage, - birthYearHasError, - useErrorController, -} from '../../Assets/validationFunctions'; -import AutoComplete from '../AutoComplete/AutoComplete'; -import './AgeInput.css'; -import { getCurrentMonthYear } from '../../Assets/age'; - -const MONTHS = { - 1: , - 2: , - 3: , - 4: , - 5: , - 6: , - 7: , - 8: , - 9: , - 10: , - 11: , - 12: , -}; - -const { CURRENT_YEAR } = getCurrentMonthYear(); -const MAX_AGE = 130; -const YEARS = Array.from({ length: MAX_AGE }, (_, i) => { - const inputYear = CURRENT_YEAR - i; - return String(inputYear); -}); - -type Props = { - birthMonth: number | null; - birthYear: number | null; - setBirthMonth: (month: number | null) => void; - setBirthYear: (year: number | null) => void; - submitted: number; -}; - -export default function AgeInput({ birthYear, birthMonth, setBirthMonth, setBirthYear, submitted }: Props) { - const errorController = useErrorController(birthMonthInvalidWithYearError, birthMonthInvalidWithYearErrorMessage); - const birthMonthErrorController = useErrorController(birthMonthHasError, birthMonthErrorMessage); - const birthYearErrorController = useErrorController(birthYearHasError, birthYearErrorMessage); - - useEffect(() => { - birthMonthErrorController.updateError(birthMonth); - }, [birthMonth]); - - useEffect(() => { - birthYearErrorController.updateError(birthYear); - }, [birthYear]); - - useEffect(() => { - if (birthMonth === null || birthYear === null) { - return; - } - - // @ts-ignore the type is broken on this one. - errorController.updateError({ birthYear, birthMonth }); - }, [birthYear, birthMonth]); - - useEffect(() => { - birthMonthErrorController.setSubmittedCount(submitted); - birthYearErrorController.setSubmittedCount(submitted); - errorController.setSubmittedCount(submitted); - }, [submitted]); - - const monthErrorMessage = useMemo(() => { - if (birthMonthErrorController.showError) { - return birthMonthErrorController.message(birthMonth); - } - - if (errorController.showError) { - return errorController.message(null); - } - - return null; - }, [birthMonthErrorController.showError, errorController.showError, birthMonth, birthYear]); - - return ( -
- - - - - - {monthErrorMessage !== null && {monthErrorMessage}} - - { - setBirthYear(newValue !== null ? Number(newValue) : null); - }} - options={YEARS} - label={} - showError={birthYearErrorController.showError} - errorMessage={birthYearErrorController.message(birthMonth)} - /> -
- ); -} diff --git a/src/Components/HouseholdDataBlock/HouseholdDataBlock.js b/src/Components/HouseholdDataBlock/HouseholdDataBlock.js deleted file mode 100644 index 681b846c3..000000000 --- a/src/Components/HouseholdDataBlock/HouseholdDataBlock.js +++ /dev/null @@ -1,528 +0,0 @@ -import { useState, useEffect, useContext } from 'react'; -import { useParams, useNavigate, useLocation } from 'react-router-dom'; -import { FormattedMessage, useIntl } from 'react-intl'; -import { useConfig } from '../Config/configHook.tsx'; -import { Box, IconButton, Stack } from '@mui/material'; -import EditIcon from '@mui/icons-material/Edit'; -import ContinueButton from '../ContinueButton/ContinueButton'; -import DropdownMenu from '../DropdownMenu/DropdownMenu'; -import HealthInsuranceError from '../HealthInsuranceError/HealthInsuranceError.tsx'; -import HHDataRadiofield from '../Radiofield/HHDataRadiofield'; -import OptionCardGroup from '../OptionCardGroup/OptionCardGroup'; -import PersonIncomeBlock from '../IncomeBlock/PersonIncomeBlock'; -import PreviousButton from '../PreviousButton/PreviousButton'; -import { personDataIsValid, selectHasError, relationTypeHelperText } from '../../Assets/validationFunctions.tsx'; -import { useStepNumber } from '../../Assets/stepDirectory'; -import { Context } from '../Wrapper/Wrapper.tsx'; -import { isCustomTypedLocationState } from '../../Types/FormData.ts'; -import HelpButton from '../HelpBubbleIcon/HelpButton.tsx'; -import './HouseholdDataBlock.css'; -import { useTranslateNumber } from '../../Assets/languageOptions'; -import QuestionHeader from '../QuestionComponents/QuestionHeader'; -import QuestionQuestion from '../QuestionComponents/QuestionQuestion'; -import QuestionDescription from '../QuestionComponents/QuestionDescription'; -import AgeInput from './AgeInput'; -import { calcAge, hasBirthMonthYear, useFormatBirthMonthYear } from '../../Assets/age.tsx'; -import { QUESTION_TITLES } from '../../Assets/pageTitleTags'; - -const HouseholdDataBlock = ({ handleHouseholdDataSubmit }) => { - const { formData } = useContext(Context); - const conditionOptions = useConfig('condition_options'); - const healthInsuranceOptions = useConfig('health_insurance_options'); - const relationshipOptions = useConfig('relationship_options'); - const { householdSize } = formData; - const hHSizeNumber = Number(householdSize); - let { whiteLabel, uuid, page } = useParams(); - page = parseInt(page); - const step = useStepNumber('householdData'); - const navigate = useNavigate(); - const location = useLocation(); - const setPage = (page) => { - navigate(`/${whiteLabel}/${uuid}/step-${step}/${page}`); - }; - const [submittedCount, setSubmittedCount] = useState(0); - const intl = useIntl(); - const editHHMemberAriaLabel = intl.formatMessage({ - id: 'editHHMember.ariaText', - defaultMessage: 'edit household member', - }); - - useEffect(() => { - document.title = QUESTION_TITLES.householdData; - }, []); - - const initialMemberData = formData.householdData[page - 1] ?? { - birthYear: undefined, - birthMonth: undefined, - relationshipToHH: page === 1 ? 'headOfHousehold' : '', - conditions: { - student: false, - pregnant: false, - blindOrVisuallyImpaired: false, - disabled: false, - longTermDisability: false, - }, - hasIncome: false, - incomeStreams: [], - healthInsurance: { - none: false, - employer: false, - private: false, - medicaid: false, - medicare: false, - chp: false, - emergency_medicaid: false, - family_planning: false, - va: false, - }, - }; - - const [memberData, setMemberData] = useState(initialMemberData); - const [wasSubmitted, setWasSubmitted] = useState(false); - - useEffect(() => { - window.scroll({ top: 0, left: 0, behavior: 'smooth' }); - }, [wasSubmitted]); - - useEffect(() => { - const updatedMemberData = { ...memberData }; - - if (updatedMemberData.hasIncome === false) { - updatedMemberData.incomeStreams = []; - } - - setMemberData(updatedMemberData); - }, [memberData.hasIncome]); - - useEffect(() => { - //this useEffect solves for the unlikely scenario that the page number is greater than the HHSize or NaN, - //it routes the user back to the last valid HHM - const lastMemberPage = Math.min(formData.householdData.length + 1, formData.householdSize); - if (isNaN(page) || page < 1 || page > lastMemberPage) { - navigate(`/${whiteLabel}/${uuid}/step-${step}/${lastMemberPage}`, { replace: true }); - return; - } - }, []); - - const createAgeQuestion = (personIndex) => { - const birthMonth = memberData.birthMonth ?? null; - const birthYear = memberData.birthYear ?? null; - const setBirthMonth = (month) => { - setMemberData({ ...memberData, birthMonth: month ?? undefined }); - }; - const setBirthYear = (year) => { - setMemberData({ ...memberData, birthYear: year ?? undefined }); - }; - - if (personIndex === 1) { - return ( - - - - - - - ); - } else { - return ( - - - - - - - ); - } - }; - - const createHOfHRelationQuestion = () => { - return ( - - - - - {createRelationshipDropdownMenu()} - - ); - }; - - const formatToUSD = (num) => { - return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(num); - }; - - const createFormDataMemberCard = (member, index) => { - let relationship = relationshipOptions[member.relationshipToHH]; - if (relationship === undefined) { - relationship = ; - } - - let age = calcAge(member); - if (Number.isNaN(age)) { - age = 0; - } - - let income = 0; - for (const { incomeFrequency, incomeAmount, hoursPerWeek } of member.incomeStreams) { - let num = 0; - switch (incomeFrequency) { - case 'weekly': - num = Number(incomeAmount) * 52; - break; - case 'biweekly': - num = Number(incomeAmount) * 26; - break; - case 'semimonthly': - num = Number(incomeAmount) * 24; - break; - case 'monthly': - num = Number(incomeAmount) * 12; - break; - case 'hourly': - num = Number(incomeAmount) * Number(hoursPerWeek) * 52; - break; - } - income += Number(num); - } - - return createMemberCard(index, relationship, member.birthYear, member.birthMonth, age, income, page); - }; - - const handleEditBtnSubmit = (memberIndex) => { - setSubmittedCount(submittedCount + 1); - - const validPersonData = personDataIsValid(memberData); - if (validPersonData) { - handleHouseholdDataSubmit(memberData, page - 1, uuid); - navigate(`/${whiteLabel}/${uuid}/step-${step}/${memberIndex + 1}`); - } - }; - - const translateNumber = useTranslateNumber(); - const formatBirthMonthYear = useFormatBirthMonthYear(); - const createMemberCard = (index, relationship, birthYear, birthMonth, age, income, page) => { - const containerClassName = `member-added-container ${index + 1 === page ? 'current-household-member' : ''}`; - - return ( -
-
-

{relationship}:

-
- { - handleEditBtnSubmit(index); - }} - aria-label={editHHMemberAriaLabel} - > - - -
-
-
- - - - {translateNumber(age)} -
- {hasBirthMonthYear({ birthMonth, birthYear }) && ( -
- - - - {formatBirthMonthYear({ birthMonth, birthYear })} -
- )} -
- - :{' '} - - {translateNumber(formatToUSD(Number(income)))} - -
-
- ); - }; - - const createQHeaderAndHHMSummaries = (personIndex) => { - let header; - const headOfHHInfoWasEntered = formData.householdData.length >= 1; - - //hHMemberSummaries will have the length of members that have already been saved to formData - const hHMemberSummaries = [ - ...formData.householdData.map((member, index) => { - return createFormDataMemberCard(member, index); - }), - ]; - - //We want the active/current member's summary card to update synchronously as we change their information - //so we swap out the current one for the one we create using the memberData in state - const summariesWActiveMemberCard = hHMemberSummaries.map((member, index) => { - if (index === page - 1) { - return createFormDataMemberCard(memberData, index); - } else { - return member; - } - }); - - if (personIndex === 1) { - header = ( - - - - ); - } else { - header = ( - - - - ); - } - - return ( - <> - {header} - {headOfHHInfoWasEntered && ( - -

- -

-
{summariesWActiveMemberCard}
-
- )} - - ); - }; - - const createDropdownCompProps = () => { - const dropdownCompProps = { - labelId: 'relation-to-hh-label', - inputLabelText: ( - - ), - id: 'relationship-select', - label: , - disabledSelectMenuItemText: ( - - ), - }; - - const details = { - componentProperties: dropdownCompProps, - inputError: selectHasError, - inputHelperText: relationTypeHelperText, - }; - - return details; - }; - - const createRelationshipDropdownMenu = () => { - return ( - - ); - }; - - const createConditionOptionCards = (index) => { - return ( - - ); - }; - - const createConditionsQuestion = (index) => { - const formattedMsgId = - index === 1 - ? 'householdDataBlock.createConditionsQuestion-do-these-apply-to-you' - : 'householdDataBlock.createConditionsQuestion-do-these-apply'; - - const formattedMsgDefaultMsg = index === 1 ? 'Do any of these apply to you?' : 'Do any of these apply to them?'; - - return ( - - - - - - - - {createConditionOptionCards(index)} - - ); - }; - - const createIncomeRadioQuestion = (index) => { - const radiofieldProps = { - ariaLabel: 'householdDataBlock.createIncomeRadioQuestion-ariaLabel', - inputName: 'hasIncome', - value: memberData.hasIncome, - }; - - const formattedMsgId = - index === 1 ? 'questions.hasIncome' : 'householdDataBlock.createIncomeRadioQuestion-questionLabel'; - - const formattedMsgDefaultMsg = - index === 1 - ? 'Do you have an income?' - : 'Does this individual in your household have significant income you have not already included?'; - - return ( - - - - - - - - - - ); - }; - - const createPersonIncomeBlock = (submitted) => { - return ( - - ); - }; - - const handleContinueSubmit = (event, validateInputFunction, inputToBeValidated, stepId, questionName, uuid) => { - const isComingFromConfirmationPg = isCustomTypedLocationState(location.state) - ? location.state.routedFromConfirmationPg - : false; - - event.preventDefault(); - setSubmittedCount(submittedCount + 1); - - const validPersonData = personDataIsValid(memberData); - const lastHouseholdMember = page >= hHSizeNumber; - - if (validPersonData && isComingFromConfirmationPg) { - handleHouseholdDataSubmit(memberData, page - 1, uuid); - navigate(`/${whiteLabel}/${uuid}/confirm-information`); - } else if (validPersonData && lastHouseholdMember) { - handleHouseholdDataSubmit(memberData, page - 1, uuid); - navigate(`/${whiteLabel}/${uuid}/step-${step + 1}`); - } else if (validPersonData) { - handleHouseholdDataSubmit(memberData, page - 1, uuid); - setPage(page + 1); - } else if (!validPersonData) { - setWasSubmitted(true); - } - }; - - const handlePreviousSubmit = () => { - if (page <= 1) { - navigate(`/${whiteLabel}/${uuid}/step-${step - 1}`); - } else { - setPage(page - 1); - } - }; - - const displayHealthInsuranceQuestion = (page, hhMemberData, setHHMemberData) => { - return ( - - - {displayHealthCareQuestion(page)} - - - - - ); - }; - - const displayHealthCareQuestion = (page) => { - if (page === 1) { - return ( - - - - ); - } else { - return ( - - - - ); - } - }; - - return ( -
- {createQHeaderAndHHMSummaries(page)} - {createAgeQuestion(page)} - {page === 1 && displayHealthInsuranceQuestion(page, memberData, setMemberData)} - {page !== 1 && createHOfHRelationQuestion()} - {page !== 1 && displayHealthInsuranceQuestion(page, memberData, setMemberData)} - {createConditionsQuestion(page)} - - - {createIncomeRadioQuestion(page)} - {memberData.hasIncome && createPersonIncomeBlock(submittedCount)} - - -
- - -
-
- ); -}; - -export default HouseholdDataBlock; diff --git a/src/Components/IncomeBlock/IncomeQuestion.js b/src/Components/IncomeBlock/IncomeQuestion.js deleted file mode 100644 index 73fea3730..000000000 --- a/src/Components/IncomeBlock/IncomeQuestion.js +++ /dev/null @@ -1,427 +0,0 @@ -import { useEffect } from 'react'; -import { useConfig } from '../Config/configHook.tsx'; -import { FormattedMessage } from 'react-intl'; -import styled from 'styled-components'; -import { FormControl, Select, MenuItem, InputLabel, FormHelperText } from '@mui/material'; -import { - useErrorController, - hoursWorkedValueHasError, - hoursWorkedHelperText, - incomeStreamValueHasError, - displayIncomeStreamValueHelperText, - selectHasError, - incomeStreamHelperText, - incomeFrequencyHelperText, -} from '../../Assets/validationFunctions.tsx'; -import Textfield from '../Textfield/Textfield'; -import HelpButton from '../HelpBubbleIcon/HelpButton.tsx'; -import QuestionQuestion from '../QuestionComponents/QuestionQuestion'; -import CloseButton from '../CloseButton/CloseButton.tsx'; -import '../IncomeBlock/PersonIncomeBlock.css'; - -const StyledSelectfield = styled(Select)({ - minWidth: 200, - maxWidth: '100%', - backgroundColor: '#FFFFFF', -}); - -const IncomeQuestion = ({ - currentIncomeSource, - allIncomeSources, - setAllIncomeSources, - memberData, - setMemberData, - index, - page, - submitted, -}) => { - const hoursErrorController = useErrorController(hoursWorkedValueHasError, displayIncomeStreamValueHelperText); - const amountErrorController = useErrorController(incomeStreamValueHasError, displayIncomeStreamValueHelperText); - const incomeStreamErrorController = useErrorController(selectHasError, incomeStreamHelperText); - const incomeFrequencyErrorController = useErrorController(selectHasError, incomeFrequencyHelperText); - - const frequencyOptions = useConfig('frequency_options'); - const incomeOptions = useConfig('income_options'); - - useEffect(() => { - hoursErrorController.setSubmittedCount(submitted); - amountErrorController.setSubmittedCount(submitted); - incomeStreamErrorController.setSubmittedCount(submitted); - incomeFrequencyErrorController.setSubmittedCount(submitted); - }, [submitted]); - - useEffect(() => { - incomeStreamErrorController.updateError(incomeStreamName); - incomeFrequencyErrorController.updateError(incomeFrequency); - }); - - const getIncomeStreamNameLabel = (incomeStreamName) => { - if (incomeStreamName) { - return ( - <> - {'('} - {incomeOptions[incomeStreamName]} - {')'}? - - ); - } - - return '?'; - }; - - const createIncomeStreamsMenuItems = () => { - const disabledSelectMenuItem = ( - - - - ); - - const menuItemKeys = Object.keys(incomeOptions); - const menuItemLabels = Object.values(incomeOptions); - - const menuItems = menuItemKeys.map((menuItemKey, i) => { - return ( - - {menuItemLabels[i]} - - ); - }); - - return [disabledSelectMenuItem, menuItems]; - }; - - const createFrequencyMenuItems = () => { - const disabledSelectMenuItem = ( - - - - ); - - const menuItemKeys = Object.keys(frequencyOptions); - const menuItemLabels = Object.values(frequencyOptions); - - const menuItems = menuItemKeys.map((menuItemKey, i) => { - return ( - - {menuItemLabels[i]} - - ); - }); - - return [disabledSelectMenuItem, menuItems]; - }; - - const handleIncomeStreamsSelectChange = (event, index) => { - const updatedSelectedMenuItems = allIncomeSources.map((incomeSourceData, i) => { - if (i === index) { - return { - ...incomeSourceData, - incomeStreamName: event.target.value, - }; - } else { - return incomeSourceData; - } - }); - - setAllIncomeSources(updatedSelectedMenuItems); - }; - - const handleIncomeTextfieldChange = (event, index) => { - const { value } = event.target; - // Income amount can be up to 8 digits long with 2 decimal places. Can not start with a decimal - const incomeAmountRegex = /^\d{0,7}(?:\d\.\d{0,2})?$/; - - if (incomeAmountRegex.test(value) || value === '') { - const updatedSelectedMenuItems = allIncomeSources.map((incomeSourceData, i) => { - if (i === index) { - return { ...incomeSourceData, incomeAmount: value }; - } else { - return incomeSourceData; - } - }); - - setAllIncomeSources(updatedSelectedMenuItems); - } - }; - - const handleFrequencySelectChange = (event, index) => { - const { value } = event.target; - const updatedSelectedMenuItems = allIncomeSources.map((incomeSourceData, i) => { - if (i === index) { - return { - ...incomeSourceData, - incomeFrequency: value, - hoursPerWeek: value === 'hourly' ? incomeSourceData.hoursPerWeek : '', - }; - } else { - return incomeSourceData; - } - }); - - setAllIncomeSources(updatedSelectedMenuItems); - }; - - const createIncomeStreamsDropdownMenu = (incomeStreamName, index) => { - return ( - - - - - - } - onChange={(event) => { - handleIncomeStreamsSelectChange(event, index); - }} - > - {createIncomeStreamsMenuItems()} - - {incomeStreamErrorController.showError && ( - {incomeStreamErrorController.message()} - )} - - ); - }; - - const createHoursWorkedTextField = (incomeStreamName, hoursWorked, index) => { - let formattedMsgId = 'personIncomeBlock.createHoursWorkedTextfield-youQLabel'; - let formattedMsgDefaultMsg = 'How many hours do you work per week '; - - if (page !== 1) { - formattedMsgId = 'personIncomeBlock.createHoursWorkedTextfield-questionLabel'; - formattedMsgDefaultMsg = 'How many hours do they work per week '; - } - - const hoursWorkedChange = (event, index) => { - const { value } = event.target; - const numberUpToEightDigitsLongRegex = /^\d{0,3}$/; - - if (numberUpToEightDigitsLongRegex.test(value)) { - const updatedSelectedMenuItems = allIncomeSources.map((incomeSourceData, i) => { - if (i === index) { - return { ...incomeSourceData, hoursPerWeek: value }; - } else { - return incomeSourceData; - } - }); - - setAllIncomeSources(updatedSelectedMenuItems); - } - }; - - const textfieldProps = { - inputType: 'text', - inputLabel: , - inputName: 'hoursPerWeek', - inputError: hoursWorkedValueHasError, - inputHelperText: hoursWorkedHelperText, - numericField: true, - }; - - return ( - <> -
- - - {getIncomeStreamNameLabel(allIncomeSources[index].incomeStreamName)} - -
-
- -
- - ); - }; - - const createIncomeAmountTextfield = (incomeStreamName, incomeAmount, index) => { - let questionHeader; - - if (allIncomeSources[index].incomeFrequency === 'hourly') { - let hourlyFormattedMsgId = 'incomeBlock.createIncomeAmountTextfield-hourly-questionLabel'; - let hourlyFormattedMsgDefaultMsg = 'What is your hourly rate '; - - if (page !== 1) { - hourlyFormattedMsgId = 'personIncomeBlock.createIncomeAmountTextfield-hourly-questionLabel'; - hourlyFormattedMsgDefaultMsg = 'What is their hourly rate '; - } - - questionHeader = ; - } else { - let payPeriodFormattedMsgId = 'incomeBlock.createIncomeAmountTextfield-questionLabel'; - let payPeriodFormattedMsgDefaultMsg = 'How much do you receive before taxes each pay period for '; - - if (page !== 1) { - payPeriodFormattedMsgId = 'personIncomeBlock.createIncomeAmountTextfield-questionLabel'; - payPeriodFormattedMsgDefaultMsg = 'How much do they receive before taxes each pay period for '; - } - - questionHeader = ( - - ); - } - - const textfieldProps = { - inputType: 'text', - inputLabel: ( - - ), - inputName: 'incomeAmount', - inputError: incomeStreamValueHasError, - inputHelperText: displayIncomeStreamValueHelperText, - dollarField: true, - numericField: true, - }; - - return ( -
-
- - {questionHeader} - {getIncomeStreamNameLabel(allIncomeSources[index].incomeStreamName)} - -
- -
- ); - }; - - const createIncomeStreamFrequencyDropdownMenu = (incomeFrequency, index) => { - let formattedMsgId = 'personIncomeBlock.createIncomeStreamFrequencyDropdownMenu-youQLabel'; - let formattedMsgDefaultMsg = 'How often are you paid this income '; - if (page !== 1) { - formattedMsgId = 'personIncomeBlock.createIncomeStreamFrequencyDropdownMenu-questionLabel'; - formattedMsgDefaultMsg = 'How often are they paid this income '; - } - - return ( -
-
- - - {getIncomeStreamNameLabel(allIncomeSources[index].incomeStreamName)} - - -
- - - - - - } - onChange={(event) => { - handleFrequencySelectChange(event, index); - }} - > - {createFrequencyMenuItems()} - - {incomeFrequencyErrorController.showError && ( - {incomeFrequencyErrorController.message()} - )} - -
- ); - }; - - const deleteIncomeBlock = (selectedIndex) => { - const updatedSelectedMenuItems = allIncomeSources.filter((incomeSourceData, index) => index !== selectedIndex); - setAllIncomeSources(updatedSelectedMenuItems); - - setMemberData({ - ...memberData, - incomeStreams: updatedSelectedMenuItems, - }); - }; - - const { incomeStreamName, incomeAmount, incomeFrequency, hoursPerWeek } = currentIncomeSource; - - let formattedMsgId = 'incomeBlock.createIncomeBlockQuestions-questionLabel'; - let formattedMsgDefaultMsg = 'If you receive another type of income, select it below.'; - - if (page !== 1) { - formattedMsgId = 'personIncomeBlock.createIncomeBlockQuestions-questionLabel'; - formattedMsgDefaultMsg = 'If they receive another type of income, select it below.'; - } - - const incomeStreamQuestion = ( -
- - - - - -
- ); - - if (index === 0) { - return ( -
-
- {createIncomeStreamsDropdownMenu(incomeStreamName, index)} - {createIncomeStreamFrequencyDropdownMenu(incomeFrequency, index)} - {incomeFrequency === 'hourly' && createHoursWorkedTextField(incomeStreamName, hoursPerWeek, index)} - {createIncomeAmountTextfield(incomeStreamName, incomeAmount, index)} -
-
- ); - } else { - return ( -
-
-
- deleteIncomeBlock(index)} /> -
-
- {incomeStreamQuestion} - {createIncomeStreamsDropdownMenu(incomeStreamName, index)} - {createIncomeStreamFrequencyDropdownMenu(incomeFrequency, index)} - {incomeFrequency === 'hourly' && createHoursWorkedTextField(incomeStreamName, hoursPerWeek, index)} - {createIncomeAmountTextfield(incomeStreamName, incomeAmount, index)} -
-
-
- ); - } -}; - -export default IncomeQuestion; diff --git a/src/Components/IncomeBlock/PersonIncomeBlock.js b/src/Components/IncomeBlock/PersonIncomeBlock.js deleted file mode 100644 index a16e29b1c..000000000 --- a/src/Components/IncomeBlock/PersonIncomeBlock.js +++ /dev/null @@ -1,104 +0,0 @@ -import { FormattedMessage } from 'react-intl'; -import { Button, Box } from '@mui/material'; -import { useEffect, useState } from 'react'; -import AddIcon from '@mui/icons-material/Add'; -import IncomeQuestion from './IncomeQuestion'; -import HelpButton from '../../Components/HelpBubbleIcon/HelpButton.tsx'; -import './PersonIncomeBlock.css'; -import QuestionQuestion from '../QuestionComponents/QuestionQuestion'; - -const PersonIncomeBlock = ({ memberData, setMemberData, page, submitted }) => { - //if there are any elements in state for incomeStreams create IncomeBlock components for those - //first by assigning them to the initial selectedMenuItem state - //if not then create the initial income block questions - const [selectedMenuItem, setSelectedMenuItem] = useState( - memberData.incomeStreams.length > 0 - ? memberData.incomeStreams - : [ - { - incomeStreamName: '', - incomeAmount: '', - incomeFrequency: '', - hoursPerWeek: '', - }, - ], - ); - - useEffect(() => { - setMemberData({ ...memberData, incomeStreams: selectedMenuItem }); - }, [selectedMenuItem]); - - const createIncomeBlockQuestions = () => { - return selectedMenuItem.map((incomeSourceData, index) => { - return ( - - ); - }); - }; - - const handleAddAdditionalIncomeSource = (event) => { - event.preventDefault(); - setSelectedMenuItem([ - ...selectedMenuItem, - { - incomeStreamName: '', - incomeAmount: '', - incomeFrequency: '', - hoursPerWeek: '', - }, - ]); - }; - - const renderFollowUpIncomeQIdAndDefaultMsg = (page) => { - let formattedMsgId = 'questions.hasIncome-a'; - let formattedMsgDefaultMsg = 'What type of income have you had most recently?'; - - if (page !== 1) { - formattedMsgId = 'personIncomeBlock.return-questionLabel'; - formattedMsgDefaultMsg = 'What type of income have they had most recently?'; - } - - return [formattedMsgId, formattedMsgDefaultMsg]; - }; - - return ( - <> -
-
- - - - -
-
- {createIncomeBlockQuestions()} -
- -
- - ); -}; - -export default PersonIncomeBlock; diff --git a/src/Components/OptionCardGroup/OptionCardGroup.js b/src/Components/OptionCardGroup/OptionCardGroup.js deleted file mode 100644 index 575b86df4..000000000 --- a/src/Components/OptionCardGroup/OptionCardGroup.js +++ /dev/null @@ -1,71 +0,0 @@ -import { useIntl } from 'react-intl'; -import Card from '@mui/material/Card'; -import CardContent from '@mui/material/CardContent'; -import { CardActionArea, Typography, Stack, Box } from '@mui/material'; -import { ReactComponent as Checkmark } from '../../Assets/OptionCardIcons/checkmark.svg'; -import './OptionCardGroup.css'; - -const OptionCardGroup = ({ options, stateVariable, memberData, setMemberData, hhMemberIndex }) => { - const intl = useIntl(); - - const handleOptionCardClick = (optionName, stateVariable, memberData, setMemberData) => { - const updatedOption = !memberData[stateVariable][optionName]; - const updatedStateVariable = { ...memberData[stateVariable], [optionName]: updatedOption }; - - setMemberData({ - ...memberData, - [stateVariable]: updatedStateVariable, - }); - }; - - const displayOptionCards = (options, stateVariable, memberData, hhMemberIndex) => { - const optionCards = Object.keys(options).map((optionKey, index) => { - let translatedAriaLabel = intl.formatMessage({ - id: options[optionKey].text.props.id, - defaultMessage: options[optionKey].text.props.defaultMessage, - }); - - const isSelected = memberData[stateVariable][optionKey]; - - return ( - { - handleOptionCardClick(optionKey, stateVariable, memberData, setMemberData); - }} - onKeyDown={(event) => { - if (event.key === 'Enter') { - event.preventDefault(); - } - }} - > - - - - {options[optionKey].icon} - {translatedAriaLabel} - - - {memberData[stateVariable][optionKey] && ( - - - - )} - - - ); - }); - - return
{optionCards}
; - }; - - return ( - - {displayOptionCards(options, stateVariable, memberData, hhMemberIndex || 0)} - - ); -}; - -export default OptionCardGroup; diff --git a/src/Components/PrevAndContinueButtons/PrevAndContinueButtons.tsx b/src/Components/PrevAndContinueButtons/PrevAndContinueButtons.tsx index b5b95a911..97c139d13 100644 --- a/src/Components/PrevAndContinueButtons/PrevAndContinueButtons.tsx +++ b/src/Components/PrevAndContinueButtons/PrevAndContinueButtons.tsx @@ -1,6 +1,5 @@ import PreviousButton from '../PreviousButton/PreviousButton'; import FormContinueButton from '../ContinueButton/FormContinueButton'; -import { FormattedMessageType } from '../../Types/Questions'; type PrevAndContinueButtonsProps = { backNavigationFunction: () => void; diff --git a/src/Components/QuestionComponentContainer/QuestionComponentContainer.js b/src/Components/QuestionComponentContainer/QuestionComponentContainer.js index d08b1881d..21981d8eb 100644 --- a/src/Components/QuestionComponentContainer/QuestionComponentContainer.js +++ b/src/Components/QuestionComponentContainer/QuestionComponentContainer.js @@ -8,10 +8,9 @@ import PreviousButton from '../PreviousButton/PreviousButton'; import ContinueButton from '../ContinueButton/ContinueButton'; import BasicSelect from '../DropdownMenu/BasicSelect'; import BasicCheckboxGroup from '../CheckboxGroup/BasicCheckboxGroup'; -import OptionCardGroup from '../OptionCardGroup/OptionCardGroup'; import FollowUpQuestions from '../FollowUpQuestions/FollowUpQuestions'; import { useErrorController } from '../../Assets/validationFunctions.tsx'; -import { ZipcodeStep } from '../Steps/ZipcodeStep'; +import { Zipcode } from '../Steps/Zipcode.tsx'; import Expenses from '../Steps/Expenses/Expenses.tsx'; import HouseholdSize from '../Steps/HouseholdSize'; import QuestionLeadText from '../QuestionComponents/QuestionLeadText'; @@ -19,20 +18,17 @@ import QuestionQuestion from '../QuestionComponents/QuestionQuestion'; import QuestionDescription from '../QuestionComponents/QuestionDescription'; import QuestionHeader from '../QuestionComponents/QuestionHeader'; import { useStepName } from '../../Assets/stepDirectory'; -import './QuestionComponentContainer.css'; import ReferralSourceStep from '../Steps/Referrer'; import questions from '../../Assets/questions'; import { QUESTION_TITLES } from '../../Assets/pageTitleTags'; import AlreadyHasBenefits from '../Steps/AlreadyHasBenefits'; import ImmediateNeeds from '../Steps/ImmediateNeeds'; +import './QuestionComponentContainer.css'; const QuestionComponentContainer = ({ handleTextfieldChange, handleContinueSubmit, - handleRadioButtonChange, handleNoAnswerChange, - handleIncomeStreamsSubmit, - handleExpenseSourcesSubmit, handleCheckboxChange, }) => { const { formData, setFormData } = useContext(Context); @@ -40,7 +36,7 @@ const QuestionComponentContainer = ({ const referralOptions = useConfig('referral_options'); const signUpOptions = useConfig('sign_up_options'); let { id } = useParams(); - const questionName = useStepName(+id, formData.immutable_referrer); + const questionName = useStepName(+id, formData.immutableReferrer); const matchingQuestion = questions[questionName]; const errorController = useErrorController( matchingQuestion?.componentDetails.inputError, @@ -58,12 +54,6 @@ const QuestionComponentContainer = ({ ); }; - const renderRadiofieldComponent = (question) => { - return ( - - ); - }; - const renderNoAnswerComponent = (question) => { return ( { - if (question.name === 'acuteHHConditions') - return ( - - ); - - return ( - - ); - }; - const renderBasicSelectComponent = (question) => { if (question.name === 'referralSource') return ( @@ -148,8 +117,6 @@ const QuestionComponentContainer = ({ submitted={errorController.submittedCount} formData={formData} handleCheckboxChange={handleCheckboxChange} - handleExpenseSourcesSubmit={handleExpenseSourcesSubmit} - handleIncomeStreamsSubmit={handleIncomeStreamsSubmit} handleTextfieldChange={handleTextfieldChange} /> )} @@ -229,7 +196,7 @@ const QuestionComponentContainer = ({ case 'zipcode': return (
- +
); case 'householdSize': @@ -268,14 +235,10 @@ const QuestionComponentContainer = ({ {renderHeaderAndSubheader()} {(matchingQuestion.componentDetails.componentType === 'Textfield' && createComponent(renderTextfieldComponent(matchingQuestion))) || - (matchingQuestion.componentDetails.componentType === 'Radiofield' && - createComponent(renderRadiofieldComponent(matchingQuestion))) || (matchingQuestion.componentDetails.componentType === 'PreferNotToAnswer' && createComponent(renderNoAnswerComponent(matchingQuestion))) || (matchingQuestion.componentDetails.componentType === 'BasicCheckboxGroup' && createComponent(renderBasicCheckboxGroup(matchingQuestion))) || - (matchingQuestion.componentDetails.componentType === 'OptionCardGroup' && - createComponent(renderOptionCardGroup(matchingQuestion))) || (matchingQuestion.componentDetails.componentType === 'BasicSelect' && createComponent(renderBasicSelectComponent(matchingQuestion)))} diff --git a/src/Components/QuestionComponents/questionHooks.ts b/src/Components/QuestionComponents/questionHooks.ts index 2fdd31099..8d6c65c8d 100644 --- a/src/Components/QuestionComponents/questionHooks.ts +++ b/src/Components/QuestionComponents/questionHooks.ts @@ -18,6 +18,7 @@ export function useGoToNextStep(questionName: QuestionName, routeEnding: string const totalStepCount = stepDirectory.length + STARTING_QUESTION_NUMBER - 1; const redirectToConfirmationPage = useShouldRedirectToConfirmation(); const navigate = useNavigate(); + const { formData } = useContext(Context); return () => { if (redirectToConfirmationPage) { diff --git a/src/Components/RHFComponents/RHFOptionCardGroup.tsx b/src/Components/RHFComponents/RHFOptionCardGroup.tsx new file mode 100644 index 000000000..49bdadb2b --- /dev/null +++ b/src/Components/RHFComponents/RHFOptionCardGroup.tsx @@ -0,0 +1,87 @@ +import { useIntl } from 'react-intl'; +import Card from '@mui/material/Card'; +import CardContent from '@mui/material/CardContent'; +import { CardActionArea, Typography, Stack, Box } from '@mui/material'; +import { ReactComponent as Checkmark } from '../../Assets/OptionCardIcons/checkmark.svg'; +import { FieldValues, UseFormTrigger } from 'react-hook-form'; +import '../OptionCardGroup/OptionCardGroup.css'; +import { ReactNode } from 'react'; +import { FormattedMessageType } from '../../Types/Questions'; + +type Option = { + value: T; + text: FormattedMessageType; + icon: ReactNode; +}; + +type RHFOptionCardGroupProps = { + fields: Record; + setValue: (name: string, value: unknown, config?: Object) => void; + name: string; + options: Option[]; + triggerValidation?: UseFormTrigger; +}; + +const RHFOptionCardGroup = ({ + fields, + setValue, + name, + options, + triggerValidation, +}: RHFOptionCardGroupProps) => { + const intl = useIntl(); + + const handleOptionCardClick = async (optionName: string) => { + const updatedValue = !fields[optionName]; + setValue(`${name}.${optionName}`, updatedValue, { shouldValidate: true, shouldDirty: true }); + + if (triggerValidation) { + await triggerValidation(name); + } + }; + + const displayOptionCards = (options: Record, name: string, values: Record) => { + const optionCards = Object.keys(options).map((optionKey, index) => { + const translatedAriaLabel = intl.formatMessage({ + id: options[optionKey].text.props.id, + defaultMessage: options[optionKey].text.props.defaultMessage, + }); + + const isSelected = values[optionKey]; + + return ( + handleOptionCardClick(optionKey)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + event.preventDefault(); + } + }} + > + + + + {options[optionKey].icon} + {translatedAriaLabel} + + + {isSelected && ( + + + + )} + + + ); + }); + + return
{optionCards}
; + }; + + return {fields && displayOptionCards(options, name, fields)}; +}; + +export default RHFOptionCardGroup; diff --git a/src/Components/Radiofield/HHDataRadiofield.js b/src/Components/Radiofield/HHDataRadiofield.js deleted file mode 100644 index 502fe4d2e..000000000 --- a/src/Components/Radiofield/HHDataRadiofield.js +++ /dev/null @@ -1,46 +0,0 @@ -import { FormControlLabel, RadioGroup, Radio } from '@mui/material'; -import { styled } from '@mui/material/styles'; -import { FormattedMessage, useIntl } from 'react-intl'; -import './Radiofield.css'; - -const StyledFormControlLabel = styled(FormControlLabel)({ - marginLeft: -5, -}); - -const HHDataRadiofield = ({ componentDetails, memberData, setMemberData }) => { - const { ariaLabel, inputName, value } = componentDetails; - const intl = useIntl(); - const translatedAriaLabel = intl.formatMessage({ id: ariaLabel, defaultMessage: inputName }); - - const handleRadioButtonChange = (event) => { - const { name, value } = event.target; - let boolValue = value === 'true'; - setMemberData({ ...memberData, [name]: boolValue }); - }; - - return ( -
- { - handleRadioButtonChange(event); - }} - > - } - label={} - /> - } - label={} - /> - -
- ); -}; - -export default HHDataRadiofield; diff --git a/src/Components/Steps/Expenses/Expenses.tsx b/src/Components/Steps/Expenses/Expenses.tsx index 6b42fd0f6..1108f9c61 100644 --- a/src/Components/Steps/Expenses/Expenses.tsx +++ b/src/Components/Steps/Expenses/Expenses.tsx @@ -24,7 +24,7 @@ import { useForm, Controller, SubmitHandler, useFieldArray } from 'react-hook-fo import { zodResolver } from '@hookform/resolvers/zod'; import * as z from 'zod'; import PrevAndContinueButtons from '../../PrevAndContinueButtons/PrevAndContinueButtons'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { updateScreen } from '../../../Assets/updateScreen'; import { useDefaultBackNavigationFunction, useGoToNextStep } from '../../QuestionComponents/questionHooks'; import { useConfig } from '../../Config/configHook'; diff --git a/src/Components/HouseholdDataBlock/HouseholdDataBlock.css b/src/Components/Steps/HouseholdMembers/HHMSummaryCards.css similarity index 96% rename from src/Components/HouseholdDataBlock/HouseholdDataBlock.css rename to src/Components/Steps/HouseholdMembers/HHMSummaryCards.css index 02672c21b..4cd7a3dcb 100644 --- a/src/Components/HouseholdDataBlock/HouseholdDataBlock.css +++ b/src/Components/Steps/HouseholdMembers/HHMSummaryCards.css @@ -5,7 +5,7 @@ outline: 1px solid black; border-radius: 10px; padding: 0.7rem; - margin: 0.5rem; + margin: 0.5rem 1rem 0.5rem 0; position: relative; } diff --git a/src/Components/Steps/HouseholdMembers/HHMSummaryCards.tsx b/src/Components/Steps/HouseholdMembers/HHMSummaryCards.tsx new file mode 100644 index 000000000..7da8c7a11 --- /dev/null +++ b/src/Components/Steps/HouseholdMembers/HHMSummaryCards.tsx @@ -0,0 +1,168 @@ +import { FormattedMessage, useIntl } from 'react-intl'; +import { Box, IconButton } from '@mui/material'; +import EditIcon from '@mui/icons-material/Edit'; +import { calcAge } from '../../../Assets/age.tsx'; +import { useConfig } from '../../Config/configHook'; +import { useTranslateNumber } from '../../../Assets/languageOptions'; +import { FormData, HouseholdData } from '../../../Types/FormData'; +import { FormattedMessageType } from '../../../Types/Questions'; +import { useNavigate } from 'react-router-dom'; +import './HHMSummaryCards.css'; + +type HHMSummariesProps = { + activeMemberData: HouseholdData; + page: number; + formData: FormData; + uuid?: string; + step: number; + triggerValidation: () => Promise; +}; + +const HHMSummaries = ({ activeMemberData, page, formData, uuid, step, triggerValidation }: HHMSummariesProps) => { + const relationshipOptions = useConfig<{ [key: string]: FormattedMessageType }>('relationship_options'); + const headOfHHInfoWasEntered = formData.householdData.length >= 1; + const translateNumber = useTranslateNumber(); + const intl = useIntl(); + const editHHMemberAriaLabel = intl.formatMessage({ + id: 'editHHMember.ariaText', + defaultMessage: 'edit household member', + }); + const navigate = useNavigate(); + + const handleEditBtnSubmit = async (memberIndex: number) => { + const isValid = await triggerValidation(); + if (isValid) { + navigate(`/${formData.whiteLabel}/${uuid}/step-${step}/${memberIndex + 1}`); + } + }; + + const formatToUSD = (num: number) => { + return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(num); + }; + + const createMemberCard = ( + memberIndex: number, + memberData: HouseholdData, + age: number, + income: number, + relationship_options: Record, + ) => { + const { relationshipToHH, birthYear, birthMonth } = memberData; + const containerClassName = `member-added-container ${memberIndex + 1 === page ? 'current-household-member' : ''}`; + + let relationship = relationship_options[relationshipToHH]; + if (memberIndex === 0) { + relationship = ; + } + + return ( +
+
+

{relationship}:

+
+ { + handleEditBtnSubmit(memberIndex); + }} + aria-label={editHHMemberAriaLabel} + > + + +
+
+
+ + + + {translateNumber(age)} +
+
+ + + + {birthMonth !== undefined && + birthYear !== undefined && + translateNumber(String(birthMonth).padStart(2, '0')) + '/' + translateNumber(birthYear)} +
+
+ + :{' '} + + {translateNumber(formatToUSD(Number(income)))} + +
+
+ ); + }; + + const createFormDataMemberCard = ( + memberIndex: number, + member: HouseholdData, + relationship_options: Record, + ) => { + if (member.birthYear && member.birthMonth) { + let age = calcAge(member); + + if (Number.isNaN(age)) { + age = 0; + } + + let income = 0; + for (const { incomeFrequency, incomeAmount, hoursPerWeek } of member.incomeStreams) { + let num = 0; + switch (incomeFrequency) { + case 'weekly': + num = Number(incomeAmount) * 52; + break; + case 'biweekly': + num = Number(incomeAmount) * 26; + break; + case 'semimonthly': + num = Number(incomeAmount) * 24; + break; + case 'monthly': + num = Number(incomeAmount) * 12; + break; + case 'hourly': + num = Number(incomeAmount) * Number(hoursPerWeek) * 52; + break; + } + income += Number(num); + } + + return createMemberCard(memberIndex, member, age, income, relationship_options); + } + }; + + //hHMemberSummaries will have the length of members that have already been saved to formData + const hHMemberSummaries = [ + ...formData.householdData.map((member, memberIndex) => { + return createFormDataMemberCard(memberIndex, member, relationshipOptions); + }), + ]; + + //We want the active/current member's summary card to update synchronously as we change their information + //so we swap out the current one for the one we create using the memberData in state + const summariesWActiveMemberCard = hHMemberSummaries.map((member, memberIndex) => { + if (memberIndex === page - 1) { + return createFormDataMemberCard(memberIndex, activeMemberData, relationshipOptions); + } else { + return member; + } + }); + + return ( +
+ {headOfHHInfoWasEntered && ( + +

+ +

+
{summariesWActiveMemberCard}
+
+ )} +
+ ); +}; + +export default HHMSummaries; diff --git a/src/Components/Steps/HouseholdMembers/HelperTextFunctions.tsx b/src/Components/Steps/HouseholdMembers/HelperTextFunctions.tsx new file mode 100644 index 000000000..fe968d40a --- /dev/null +++ b/src/Components/Steps/HouseholdMembers/HelperTextFunctions.tsx @@ -0,0 +1,62 @@ +import { FormattedMessage } from 'react-intl'; +import { MessageFunction } from '../../../Types/ErrorController'; +import ErrorMessageWrapper from '../../ErrorMessage/ErrorMessageWrapper'; + +export const renderBirthMonthHelperText = () => { + return ( + + + + ); +}; + +export const renderBirthYearHelperText = () => { + return ( + + + + ); +}; + +export const renderHealthInsuranceHelperText = () => { + return ( + + + + ); +}; + +export const renderRelationshipToHHHelperText = () => { + return ( + + + + ); +}; + +export const renderIncomeFrequencyHelperText = () => { + return ( + + + + ); +}; + +export const renderHoursWorkedHelperText = () => { + return ( + + + + ); +}; + +export const renderIncomeAmountHelperText = () => { + return ( + + + + ); +}; diff --git a/src/Components/Steps/HouseholdMembers/HouseholdMemberForm.tsx b/src/Components/Steps/HouseholdMembers/HouseholdMemberForm.tsx new file mode 100644 index 000000000..64ac99c45 --- /dev/null +++ b/src/Components/Steps/HouseholdMembers/HouseholdMemberForm.tsx @@ -0,0 +1,928 @@ +import { FormattedMessage, useIntl } from 'react-intl'; +import QuestionHeader from '../../QuestionComponents/QuestionHeader'; +import HHMSummaryCards from './HHMSummaryCards'; +import { useNavigate, useParams } from 'react-router-dom'; +import { Context } from '../../Wrapper/Wrapper'; +import { ReactNode, useContext, useEffect, useMemo } from 'react'; +import { Conditions, HealthInsurance, HouseholdData } from '../../../Types/FormData'; +import { + Autocomplete, + Box, + Button, + FormControl, + FormControlLabel, + FormHelperText, + InputAdornment, + InputLabel, + MenuItem, + Radio, + RadioGroup, + Select, + Stack, + TextField, +} from '@mui/material'; +import QuestionQuestion from '../../QuestionComponents/QuestionQuestion'; +import { useStepNumber } from '../../../Assets/stepDirectory'; +import * as z from 'zod'; +import { Controller, SubmitHandler, useFieldArray, useForm } from 'react-hook-form'; +import { updateScreen } from '../../../Assets/updateScreen'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { MONTHS } from './MONTHS'; +import PrevAndContinueButtons from '../../PrevAndContinueButtons/PrevAndContinueButtons'; +import ErrorMessageWrapper from '../../ErrorMessage/ErrorMessageWrapper'; +import RHFOptionCardGroup from '../../RHFComponents/RHFOptionCardGroup'; +import { useConfig } from '../../Config/configHook'; +import QuestionDescription from '../../QuestionComponents/QuestionDescription'; +import { FormattedMessageType } from '../../../Types/Questions'; +import HelpButton from '../../HelpBubbleIcon/HelpButton'; +import AddIcon from '@mui/icons-material/Add'; +import { createMenuItems } from '../SelectHelperFunctions/SelectHelperFunctions'; +import CloseButton from '../../CloseButton/CloseButton'; +import { + renderBirthMonthHelperText, + renderBirthYearHelperText, + renderHealthInsuranceHelperText, + renderRelationshipToHHHelperText, + renderIncomeFrequencyHelperText, + renderHoursWorkedHelperText, + renderIncomeAmountHelperText, +} from './HelperTextFunctions'; +import './PersonIncomeBlock.css'; + +const HouseholdMemberForm = () => { + const { formData, setFormData, locale } = useContext(Context); + const { uuid, page } = useParams(); + const navigate = useNavigate(); + const intl = useIntl(); + const pageNumber = Number(page); + const currentMemberIndex = pageNumber - 1; + const householdMemberFormData = formData.householdData[currentMemberIndex]; + const healthInsuranceOptions = + useConfig>( + 'health_insurance_options', + ); + const conditionOptions = + useConfig>('condition_options'); + const relationshipOptions = useConfig>('relationship_options'); + const incomeOptions = useConfig>('income_options'); + const incomeStreamsMenuItems = createMenuItems( + incomeOptions, + , + ); + const frequencyOptions = useConfig>('frequency_options'); + const frequencyMenuItems = createMenuItems( + frequencyOptions, + , + ); + + const currentStepId = useStepNumber('householdData'); + const backNavigationFunction = (uuid: string, currentStepId: number, pageNumber: number) => { + if (pageNumber <= 1) { + navigate(`/${formData.whiteLabel}/${uuid}/step-${currentStepId - 1}`); + } else { + navigate(`/${formData.whiteLabel}/${uuid}/step-${currentStepId}/${pageNumber - 1}`); + } + }; + const nextStep = (uuid: string, currentStepId: number, pageNumber:number) => { + if (Number(pageNumber + 1) <= formData.householdSize) { + navigate(`/${formData.whiteLabel}/${uuid}/step-${currentStepId}/${pageNumber + 1}`); + return; + } else { + navigate(`/${formData.whiteLabel}/${uuid}/step-${currentStepId + 1}`); + return; + } + } + + const date = new Date(); + const CURRENT_YEAR = date.getFullYear(); + const MAX_AGE = 130; + const YEARS = Array.from({ length: MAX_AGE }, (_, i) => { + const inputYear = CURRENT_YEAR - i; + return String(inputYear); + }); + // I added an empty string to the years array to fix the initial invalid Autocomplete value warning + const YEARSANDINITIALEMPTYSTR = ['', ...YEARS]; + + const autoCompleteOptions = useMemo(() => { + return YEARSANDINITIALEMPTYSTR.map((year) => { + return { label: String(year) }; + }); + }, [YEARS]); + + // birthYear > CURRENT_YEAR || birthYear < CURRENT_YEAR - MAX_AGE; + const oneOrMoreDigitsButNotAllZero = /^(?!0+$)\d+$/; + const incomeAmountRegex = /^\d{0,7}(?:\d\.\d{0,2})?$/; + const incomeSourcesSchema = z + .object({ + incomeStreamName: z.string().min(1), + incomeFrequency: z.string().min(1), + hoursPerWeek: z.string().trim(), + incomeAmount: z + .string() + .trim() + .refine((value) => { + return Number(value) > 0 && incomeAmountRegex.test(value); + }), + }) + .refine( + (data) => { + if (data.incomeFrequency === 'hourly') { + return oneOrMoreDigitsButNotAllZero.test(data.hoursPerWeek.trim()); + } else { + return true; + } + }, + { path: ['hoursPerWeek'] }, + ); + const incomeStreamsSchema = z.array(incomeSourcesSchema); + const hasIncomeSchema = z.string().regex(/^true|false$/); + + const formSchema = z.object({ + /* + z.string().min(1) validates that an option was selected. + The default value of birthMonth is '' when no option is selected. + The possible values that can be selected are '1', '2', ..., '12', + so if one of those options are selected, + then birthMonth would have a minimum string length of 1 which passes validation. + */ + birthMonth: z.string().min(1), + birthYear: z + .string() + .trim() + .refine((value) => { + const year = Number(value); + const age = CURRENT_YEAR - year; + return year <= CURRENT_YEAR && age < MAX_AGE; + }), + healthInsurance: z + .object({ + none: z.boolean(), + employer: z.boolean(), + private: z.boolean(), + medicaid: z.boolean(), + medicare: z.boolean(), + chp: z.boolean(), + emergency_medicaid: z.boolean(), + family_planning: z.boolean(), + va: z.boolean(), + }) + .refine((insuranceOptions) => Object.values(insuranceOptions).some((option) => option === true), { + message: 'Please select at least one health insurance option.', + path: ['healthInsurance'], + }), + conditions: z.object({ + student: z.boolean(), + pregnant: z.boolean(), + blindOrVisuallyImpaired: z.boolean(), + disabled: z.boolean(), + longTermDisability: z.boolean(), + }), + relationshipToHH: z + .string() + .refine((value) => [...Object.keys(relationshipOptions)].includes(value) || pageNumber === 1), + hasIncome: hasIncomeSchema, + incomeStreams: incomeStreamsSchema, + }); + type FormSchema = z.infer; + + const determineDefaultRelationshipToHH = (householdMemberFormData: HouseholdData | undefined) => { + if (householdMemberFormData && householdMemberFormData.relationshipToHH) { + return householdMemberFormData.relationshipToHH; + } else if (pageNumber === 1) { + return 'headOfHousehold'; + } else { + return ''; + } + }; + + const { + control, + formState: { errors }, + handleSubmit, + watch, + setValue, + getValues, + trigger, + } = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + birthMonth: householdMemberFormData?.birthMonth ? String(householdMemberFormData.birthMonth) : '', + birthYear: householdMemberFormData?.birthYear ? String(householdMemberFormData.birthYear) : '', + healthInsurance: householdMemberFormData?.healthInsurance + ? householdMemberFormData.healthInsurance + : { + none: false, + employer: false, + private: false, + medicaid: false, + medicare: false, + chp: false, + emergency_medicaid: false, + family_planning: false, + va: false, + }, + conditions: householdMemberFormData?.conditions + ? householdMemberFormData.conditions + : { + student: false, + pregnant: false, + blindOrVisuallyImpaired: false, + disabled: false, + longTermDisability: false, + }, + relationshipToHH: determineDefaultRelationshipToHH(householdMemberFormData), + hasIncome: householdMemberFormData?.incomeStreams.length > 0 ? 'true' : 'false', + incomeStreams: householdMemberFormData?.incomeStreams ?? [], + }, + }); + const watchHasIncome = watch('hasIncome'); + const hasTruthyIncome = watchHasIncome === 'true'; + const { fields, append, remove, replace } = useFieldArray({ + control, + name: 'incomeStreams', + }); + + useEffect(() => { + const noIncomeStreamsAreListed = Number(getValues('incomeStreams').length === 0); + if (hasTruthyIncome && noIncomeStreamsAreListed) { + append({ + incomeStreamName: '', + incomeAmount: '', + incomeFrequency: '', + hoursPerWeek: '', + }); + } + + if (!hasTruthyIncome) { + replace([]); + } + }, [watchHasIncome]); + + const formSubmitHandler: SubmitHandler> = (memberData) => { + if (!uuid) return; + + const currentMemberDataAtThisIndex = householdMemberFormData; + if (currentMemberDataAtThisIndex) { + // if we have data at this index then replace it + const updatedHouseholdData = [...formData.householdData].map((currentMemberData, index) => { + if (index === currentMemberIndex) { + return memberData; + } else { + return currentMemberData; + } + }); + + const updatedFormData = { ...formData, householdData: updatedHouseholdData }; + setFormData(updatedFormData); + updateScreen(uuid, updatedFormData, locale); + } else { + // if there is no data at this index then we need to push it to the array + const copyOfHouseholdData = [...formData.householdData]; + copyOfHouseholdData.push(memberData); + const updatedFormData = { ...formData, householdData: copyOfHouseholdData }; + setFormData(updatedFormData); + updateScreen(uuid, updatedFormData, locale); + } + + nextStep(uuid, currentStepId, pageNumber); + }; + + const createAgeQuestion = (personIndex: number) => { + return ( + + + {personIndex === 1 ? ( + + ) : ( + + )} + +
+ + + + + ( + <> + + {errors.birthMonth !== undefined && ( + {renderBirthMonthHelperText()} + )} + + )} + /> + + + ( + <> + option.label === value.label} + options={autoCompleteOptions} + getOptionLabel={(option) => option.label ?? ''} + value={{ label: field.value }} + onChange={(_, newValue) => { + field.onChange(newValue?.label); + }} + renderInput={(params) => ( + } + error={errors.birthYear !== undefined} + /> + )} + /> + {errors.birthYear !== undefined && ( + {renderBirthYearHelperText()} + )} + + )} + /> + +
+
+ ); + }; + + const displayHealthCareQuestion = (page: number) => { + if (page === 1) { + return ( + + + + ); + } else { + return ( + + + + ); + } + }; + + const displayHealthInsuranceBlock = (pageNumber: number, healthInsuranceOptions: any) => { + return ( + + + {displayHealthCareQuestion(pageNumber)} + + {errors.healthInsurance !== undefined && ( + {renderHealthInsuranceHelperText()} + )} + + + ); + }; + + const displayConditionsQuestion = (pageNumber: number, conditionOptions) => { + const formattedMsgId = + pageNumber === 1 + ? 'householdDataBlock.createConditionsQuestion-do-these-apply-to-you' + : 'householdDataBlock.createConditionsQuestion-do-these-apply'; + + const formattedMsgDefaultMsg = + pageNumber === 1 ? 'Do any of these apply to you?' : 'Do any of these apply to them?'; + + return ( + + + + + + + + + + ); + }; + + const createHOfHRelationQuestion = (relationshipOptions: Record) => { + return ( + + + + + + + + + ( + <> + + {errors.relationshipToHH !== undefined && ( + {renderRelationshipToHHHelperText()} + )} + + )} + /> + + + ); + }; + + const createIncomeRadioQuestion = (index: number) => { + const translatedAriaLabel = intl.formatMessage({ + id: 'householdDataBlock.createIncomeRadioQuestion-ariaLabel', + defaultMessage: 'has an income', + }); + + const formattedMsgId = + index === 1 ? 'questions.hasIncome' : 'householdDataBlock.createIncomeRadioQuestion-questionLabel'; + + const formattedMsgDefaultMsg = + index === 1 + ? 'Do you have an income?' + : 'Does this individual in your household have significant income you have not already included?'; + + return ( + + + + + + + ( + + } + label={} + /> + } + label={} + /> + + )} + /> + + + ); + }; + const renderIncomeStreamNameHelperText = () => { + return ( + + + + ); + }; + + const renderIncomeStreamNameSelect = (index: number) => { + return ( + + + + + ( + <> + + {errors.incomeStreams?.[index]?.incomeStreamName !== undefined && ( + {renderIncomeStreamNameHelperText()} + )} + + )} + /> + + ); + }; + + const getIncomeStreamSourceLabel = ( + incomeOptions: Record, + incomeStreamName: string, + ) => { + if (incomeStreamName) { + return ( + <> + {'('} + {incomeOptions[incomeStreamName]} + {')'}? + + ); + } + + return '?'; + }; + + const renderIncomeFrequencySelect = ( + incomeOptions: Record, + selectedIncomeSource: string, + index: number, + pageNumber: number, + ) => { + let formattedMsgId = 'personIncomeBlock.createIncomeStreamFrequencyDropdownMenu-youQLabel'; + let formattedMsgDefaultMsg = 'How often are you paid this income '; + if (pageNumber !== 1) { + formattedMsgId = 'personIncomeBlock.createIncomeStreamFrequencyDropdownMenu-questionLabel'; + formattedMsgDefaultMsg = 'How often are they paid this income '; + } + + return ( +
+
+ + + {getIncomeStreamSourceLabel(incomeOptions, selectedIncomeSource)} + + +
+ <> + + + + + ( + <> + + {errors.incomeStreams?.[index]?.incomeFrequency !== undefined && ( + {renderIncomeFrequencyHelperText()} + )} + + )} + /> + + +
+ ); + }; + + const renderHoursPerWeekTextfield = (page: number, index: number, selectedIncomeSource: string) => { + let formattedMsgId = 'personIncomeBlock.createHoursWorkedTextfield-youQLabel'; + let formattedMsgDefaultMsg = 'How many hours do you work per week '; + + if (page !== 1) { + formattedMsgId = 'personIncomeBlock.createHoursWorkedTextfield-questionLabel'; + formattedMsgDefaultMsg = 'How many hours do they work per week '; + } + + return ( + <> +
+ + + {getIncomeStreamSourceLabel(incomeOptions, selectedIncomeSource)} + +
+ ( + <> + + } + variant="outlined" + sx={{ backgroundColor: '#fff' }} + error={errors.incomeStreams?.[index]?.hoursPerWeek !== undefined} + /> + {errors.incomeStreams?.[index]?.hoursPerWeek !== undefined && ( + {renderHoursWorkedHelperText()} + )} + + )} + /> + + ); + }; + + const renderIncomeAmountTextfield = ( + page: number, + index: number, + selectedIncomeFrequency: string, + selectedIncomeStreamSource: string, + ) => { + let questionHeader; + + if (selectedIncomeFrequency === 'hourly') { + let hourlyFormattedMsgId = 'incomeBlock.createIncomeAmountTextfield-hourly-questionLabel'; + let hourlyFormattedMsgDefaultMsg = 'What is your hourly rate '; + + if (page !== 1) { + hourlyFormattedMsgId = 'personIncomeBlock.createIncomeAmountTextfield-hourly-questionLabel'; + hourlyFormattedMsgDefaultMsg = 'What is their hourly rate '; + } + + questionHeader = ; + } else { + let payPeriodFormattedMsgId = 'incomeBlock.createIncomeAmountTextfield-questionLabel'; + let payPeriodFormattedMsgDefaultMsg = 'How much do you receive before taxes each pay period for '; + + if (page !== 1) { + payPeriodFormattedMsgId = 'personIncomeBlock.createIncomeAmountTextfield-questionLabel'; + payPeriodFormattedMsgDefaultMsg = 'How much do they receive before taxes each pay period for '; + } + + questionHeader = ( + + ); + } + + return ( +
+
+ + {questionHeader} + {getIncomeStreamSourceLabel(incomeOptions, selectedIncomeStreamSource)} + +
+ ( + <> + + } + variant="outlined" + sx={{ backgroundColor: '#fff' }} + error={errors.incomeStreams?.[index]?.incomeAmount !== undefined} + InputProps={{ + startAdornment: $, + sx: { backgroundColor: '#FFFFFF' }, + }} + /> + {errors.incomeStreams?.[index]?.incomeAmount !== undefined && ( + {renderIncomeAmountHelperText()} + )} + + )} + /> +
+ ); + }; + + const renderAdditionalIncomeBlockQ = (page: number) => { + let formattedMsgId = 'incomeBlock.createIncomeBlockQuestions-questionLabel'; + let formattedMsgDefaultMsg = 'If you receive another type of income, select it below.'; + + if (page !== 1) { + formattedMsgId = 'personIncomeBlock.createIncomeBlockQuestions-questionLabel'; + formattedMsgDefaultMsg = 'If they receive another type of income, select it below.'; + } + + return ( +
+ + + + + +
+ ); + }; + + const renderFirstIncomeBlockQ = (pageNumber: number) => { + let formattedMsgId = 'questions.hasIncome-a'; + let formattedMsgDefaultMsg = 'What type of income have you had most recently?'; + + if (pageNumber !== 1) { + formattedMsgId = 'personIncomeBlock.return-questionLabel'; + formattedMsgDefaultMsg = 'What type of income have they had most recently?'; + } + + return ( +
+ + + + +
+ ); + }; + + return ( +
+ + {pageNumber === 1 ? ( + + ) : ( + + )} + + +
+ {createAgeQuestion(pageNumber)} + {pageNumber !== 1 && createHOfHRelationQuestion(relationshipOptions)} + {displayHealthInsuranceBlock(pageNumber, healthInsuranceOptions)} + {displayConditionsQuestion(pageNumber, conditionOptions)} +
+ + {createIncomeRadioQuestion(pageNumber)} + {fields.map((field, index) => { + const selectedIncomeStreamSource = watch('incomeStreams')[index].incomeStreamName; + const selectedIncomeFrequency = watch('incomeStreams')[index].incomeFrequency; + + return ( +
+
+ {index !== 0 && ( +
+ remove(index)} /> +
+ )} +
+ {index === 0 && renderFirstIncomeBlockQ(pageNumber)} + {index !== 0 && renderAdditionalIncomeBlockQ(pageNumber)} + {renderIncomeStreamNameSelect(index)} + {renderIncomeFrequencySelect(incomeOptions, selectedIncomeStreamSource, index, pageNumber)} + {selectedIncomeFrequency === 'hourly' && + renderHoursPerWeekTextfield(pageNumber, index, selectedIncomeStreamSource)} + {renderIncomeAmountTextfield( + pageNumber, + index, + selectedIncomeFrequency, + selectedIncomeStreamSource, + )} +
+
+
+ ); + })} + {hasTruthyIncome && ( +
+ +
+ )} +
+
+ backNavigationFunction(uuid, currentStepId, pageNumber)} + /> + +
+ ); +}; + +export default HouseholdMemberForm; diff --git a/src/Components/Steps/HouseholdMembers/MONTHS.tsx b/src/Components/Steps/HouseholdMembers/MONTHS.tsx new file mode 100644 index 000000000..09ff128b2 --- /dev/null +++ b/src/Components/Steps/HouseholdMembers/MONTHS.tsx @@ -0,0 +1,16 @@ +import { FormattedMessage } from 'react-intl'; + +export const MONTHS = { + 1: , + 2: , + 3: , + 4: , + 5: , + 6: , + 7: , + 8: , + 9: , + 10: , + 11: , + 12: , +}; diff --git a/src/Components/IncomeBlock/PersonIncomeBlock.css b/src/Components/Steps/HouseholdMembers/PersonIncomeBlock.css similarity index 97% rename from src/Components/IncomeBlock/PersonIncomeBlock.css rename to src/Components/Steps/HouseholdMembers/PersonIncomeBlock.css index 5010d5119..0d7286ed9 100644 --- a/src/Components/IncomeBlock/PersonIncomeBlock.css +++ b/src/Components/Steps/HouseholdMembers/PersonIncomeBlock.css @@ -45,6 +45,6 @@ } .income-block-container { - padding: 0 0 2rem 0; + padding: 0 0 3rem 0; margin-bottom: 1rem; } diff --git a/src/Components/Steps/SelectHelperFunctions/SelectHelperFunctions.tsx b/src/Components/Steps/SelectHelperFunctions/SelectHelperFunctions.tsx new file mode 100644 index 000000000..d1371fa1f --- /dev/null +++ b/src/Components/Steps/SelectHelperFunctions/SelectHelperFunctions.tsx @@ -0,0 +1,23 @@ +import { MenuItem } from '@mui/material'; +import { FormattedMessageType } from '../../../Types/Questions'; + +export const createMenuItems = ( + options: Record, + disabledSelectFM: FormattedMessageType, +) => { + const disabledSelectMenuItem = ( + + {disabledSelectFM} + + ); + + const menuItems = Object.keys(options).map((menuItemKey) => { + return ( + + {options[menuItemKey]} + + ); + }); + + return [disabledSelectMenuItem, menuItems]; +}; diff --git a/src/Components/Steps/SelectLanguagePage.tsx b/src/Components/Steps/SelectLanguage.tsx similarity index 100% rename from src/Components/Steps/SelectLanguagePage.tsx rename to src/Components/Steps/SelectLanguage.tsx diff --git a/src/Components/Steps/ZipcodeStep.tsx b/src/Components/Steps/Zipcode.tsx similarity index 99% rename from src/Components/Steps/ZipcodeStep.tsx rename to src/Components/Steps/Zipcode.tsx index e734cdc34..532f65879 100644 --- a/src/Components/Steps/ZipcodeStep.tsx +++ b/src/Components/Steps/Zipcode.tsx @@ -17,7 +17,7 @@ import QuestionQuestion from '../QuestionComponents/QuestionQuestion'; import PrevAndContinueButtons from '../PrevAndContinueButtons/PrevAndContinueButtons'; import { useDefaultBackNavigationFunction, useGoToNextStep } from '../QuestionComponents/questionHooks'; -export const ZipcodeStep = () => { +export const Zipcode = () => { const { formData, locale, setFormData } = useContext(Context); const { uuid } = useParams(); const backNavigationFunction = useDefaultBackNavigationFunction('zipcode');