Skip to content

Commit

Permalink
feat: add address verification for preferences step (#3715)
Browse files Browse the repository at this point in the history
* feat: add collectAddress checkbox with subfields

* test: update preference tests

* fix: add minimum value validation for radius field

* fix: make collect address not required

* fix: expand collect address fields

* fix: change fields order in PreferenceDrawer

* feat: add address holder fields to application

* fix: make added field optional

* fix: display errors properly

* feat: add address holder fields to application summary

* feat: add address verification for preferences step

* feat: adjust padding for application summary

* fix: move address holder name and relationship fields to extraData

* fix: remove redundant backend address holder fields

* fix: use enum for address holder fields

* fix: add alternate address form component

* fix: remove redundant fields from new address form

* fix: verify preferences address when collectAddress true

* fix: block going back on address verification

add string to translation

* fix: use onClick to block address Verification back button
  • Loading branch information
KrissDrawing authored Dec 1, 2023
1 parent a8b13b0 commit 0f9ead7
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 28 deletions.
1 change: 1 addition & 0 deletions shared-helpers/src/locales/general.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@
"application.contact.suggestedAddress": "Suggested Address:",
"application.contact.title": "Thanks %{firstName}. Now we need to know how to contact you.",
"application.contact.verifyAddressTitle": "We have located the following address. Please confirm it's correct.",
"application.contact.verifyMultipleAddresses": "Since there are multiple options for this preference, you’ll need to verify multiple addresses.",
"application.contact.workAddress": "Work Address",
"application.contact.youEntered": "You Entered:",
"application.contact.yourAdditionalPhoneNumber": "Your Second Phone Number",
Expand Down
102 changes: 102 additions & 0 deletions shared-helpers/src/views/address/FormAddressAlternate.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { UseFormMethods } from "react-hook-form"
import { Field, resolveObject, Select, t } from "@bloom-housing/ui-components"
import React from "react"

type FormAddressProps = {
subtitle?: string
dataKey: string
register: UseFormMethods["register"]
errors?: UseFormMethods["errors"]
required?: boolean
stateKeys: string[]
}

export const FormAddressAlternate = ({
subtitle,
dataKey,
register,
errors,
required,
stateKeys,
}: FormAddressProps) => {
return (
<>
<legend className={`text__caps-spaced ${!subtitle ? "sr-only" : ""}`}>
{!subtitle ? t("application.preferences.options.address") : subtitle}
</legend>
<Field
id={`${dataKey}.street`}
name={`${dataKey}.street`}
label={t("application.contact.streetAddress")}
placeholder={t("application.contact.streetAddress")}
validation={{ required: true, maxLength: 64 }}
errorMessage={
resolveObject(`${dataKey}.street`, errors)?.type === "maxLength"
? t("errors.maxLength")
: t("errors.streetError")
}
error={!!resolveObject(`${dataKey}.street`, errors)}
register={register}
/>

<Field
id={`${dataKey}.street2`}
name={`${dataKey}.street2`}
label={t("application.contact.apt")}
placeholder={t("application.contact.apt")}
register={register}
error={!!resolveObject(`${dataKey}.street2`, errors)}
validation={{ maxLength: 64 }}
errorMessage={t("errors.maxLength")}
/>

<div className="flex">
<Field
id={`${dataKey}.city`}
name={`${dataKey}.city`}
label={t("application.contact.cityName")}
placeholder={t("application.contact.cityName")}
register={register}
validation={{ required, maxLength: 64 }}
error={!!resolveObject(`${dataKey}.city`, errors)}
errorMessage={
resolveObject(`${dataKey}.city`, errors)?.type === "maxLength"
? t("errors.maxLength")
: t("errors.cityError")
}
/>

<Select
id={`${dataKey}.state`}
name={`${dataKey}.state`}
label={t("application.contact.state")}
validation={{ required: true }}
error={!!resolveObject(`${dataKey}.state`, errors)}
errorMessage={
resolveObject(`${dataKey}.state`, errors)?.type === "maxLength"
? t("errors.maxLength")
: t("errors.stateError")
}
register={register}
controlClassName="control"
options={stateKeys}
keyPrefix="states"
/>
</div>
<Field
id={`${dataKey}.zipCode`}
name={`${dataKey}.zipCode`}
label={t("application.contact.zip")}
placeholder={t("application.contact.zipCode")}
register={register}
validation={{ required, maxLength: 64 }}
error={!!resolveObject(`${dataKey}.zipCode`, errors)}
errorMessage={
resolveObject(`${dataKey}.zipCode`, errors)?.type === "maxLength"
? t("errors.maxLength")
: t("errors.zipCodeError")
}
/>
</>
)
}
4 changes: 2 additions & 2 deletions shared-helpers/src/views/multiselectQuestions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ import {
ExpandableContent,
Field,
FieldGroup,
FormAddress,
resolveObject,
t,
} from "@bloom-housing/ui-components"
import { stateKeys } from "../utilities/formKeys"
import { AddressHolder } from "../utilities/constants"
import { FormAddressAlternate } from "./address/FormAddressAlternate"

export const listingSectionQuestions = (
listing: Listing,
Expand Down Expand Up @@ -290,7 +290,7 @@ export const getCheckboxOption = (
)}
{watchFields[optionFieldName] && option.collectAddress && (
<div className="pb-4">
<FormAddress
<FormAddressAlternate
subtitle={t("application.preferences.options.qualifyingAddress")}
dataKey={fieldName(question.text, applicationSection, `${option.text}-address`)}
register={register}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState, useEffect, useContext } from "react"
import React, { useContext, useEffect, useMemo, useRef, useState } from "react"
import { useForm } from "react-hook-form"
import { AlertBox, Form, FormCard, t, ProgressNav, Heading } from "@bloom-housing/ui-components"
import { Button } from "@bloom-housing/ui-seeds"
Expand All @@ -7,22 +7,23 @@ import FormBackLink from "./FormBackLink"
import { useFormConductor } from "../../lib/hooks"
import { ApplicationSection, MultiselectOption } from "@bloom-housing/backend-core/types"
import {
OnClientSide,
PageView,
pushGtmEvent,
mapCheckboxesToApi,
mapApiToMultiselectForm,
AuthContext,
getExclusiveKeys,
getCheckboxOption,
getAllOptions,
getPageQuestion,
getCheckboxOption,
getExclusiveKeys,
getInputType,
getPageQuestion,
getRadioFields,
mapRadiosToApi,
listingSectionQuestions,
mapApiToMultiselectForm,
mapCheckboxesToApi,
mapRadiosToApi,
OnClientSide,
PageView,
pushGtmEvent,
} from "@bloom-housing/shared-helpers"
import { UserStatus } from "../../lib/constants"
import { AddressValidationSelection, findValidatedAddress, FoundAddress } from "./ValidateAddress"

export interface ApplicationMultiselectQuestionStepProps {
applicationSection: ApplicationSection
Expand All @@ -40,6 +41,11 @@ const ApplicationMultiselectQuestionStep = ({
applicationSectionNumber,
strings,
}: ApplicationMultiselectQuestionStepProps) => {
const [verifyAddress, setVerifyAddress] = useState(false)
const [verifyAddressStep, setVerifyAddressStep] = useState(0)
const [foundAddress, setFoundAddress] = useState<FoundAddress>({})
const [newAddressSelected, setNewAddressSelected] = useState(true)

const clientLoaded = OnClientSide()
const { profile } = useContext(AuthContext)
const { conductor, application, listing } = useFormConductor(applicationStep)
Expand Down Expand Up @@ -77,27 +83,67 @@ const ApplicationMultiselectQuestionStep = ({
return getAllOptions(question, applicationSection)
}, [question])

const body = useRef(null)

const onSubmit = (data) => {
const body =
questionSetInputType === "checkbox"
? mapCheckboxesToApi(data, question, applicationSection)
: mapRadiosToApi(data.application[applicationSection], question)
if (questions.length > 1 && body) {
if (verifyAddressStep === 0) {
body.current =
questionSetInputType === "checkbox"
? mapCheckboxesToApi(data, question, applicationSection)
: mapRadiosToApi(data.application[applicationSection], question)
}

// Verify address on preferences
if (question?.options.some((item) => item?.collectAddress)) {
const step: number = body.current.options.findIndex(
(option, index) =>
index >= verifyAddressStep && option.checked === true && option.extraData?.[0]?.value
)

if (
newAddressSelected &&
foundAddress.newAddress &&
body.current.options[verifyAddressStep - 1]?.extraData?.[0]?.value
) {
body.current.options[verifyAddressStep - 1].extraData[0].value = foundAddress.newAddress
}

if (step !== -1) {
if (body.current.options[step].extraData[0]?.value) {
setFoundAddress({})
setVerifyAddress(true)
findValidatedAddress(
body.current.options[step].extraData[0]?.value,
setFoundAddress,
setNewAddressSelected
)
setVerifyAddressStep(step + 1)
}

return // Skip rest of the submit process
}
}

if (questions.length > 1 && body.current) {
// If there is more than one question, save the data in segments
const currentQuestions = conductor.currentStep.application[applicationSection].filter(
(question) => {
return question.key !== body.key
return question.key !== body.current.key
}
)
conductor.currentStep.save([...currentQuestions, body])
setApplicationQuestions([...currentQuestions, body])

conductor.currentStep.save([...currentQuestions, body.current])
setApplicationQuestions([...currentQuestions, body.current])
} else {
// Otherwise, submit all at once
conductor.currentStep.save([body])
conductor.currentStep.save([body.current])
}
// Update to the next page if we have more pages
if (page !== questions.length) {
setVerifyAddressStep(0)
setVerifyAddress(false)
setPage(page + 1)
body.current = null
return
}
// Otherwise complete the section and move to the next URL
Expand Down Expand Up @@ -154,15 +200,29 @@ const ApplicationMultiselectQuestionStep = ({
<FormBackLink
url={conductor.determinePreviousUrl()}
onClick={() => {
conductor.setNavigatedBack(true)
setPage(page - 1)
if (!verifyAddress) {
conductor.setNavigatedBack(true)
setPage(page - 1)
body.current = null
}
}}
custom={page !== 1}
custom={page !== 1 || verifyAddress}
/>

<div className="form-card__lead border-b flex flex-col items-center">
<h2 className="form-card__title is-borderless">{strings?.title ?? question?.text}</h2>
{strings?.subTitle && <p className="field-note mt-6">{strings?.subTitle}</p>}
<h2 className="form-card__title is-borderless">
{verifyAddress
? foundAddress.invalid
? t("application.contact.couldntLocateAddress")
: t("application.contact.verifyAddressTitle")
: strings?.title ?? question?.text}
</h2>
{verifyAddress && body.current.options.filter((option) => option.checked).length > 1 && (
<p className="field-note mt-6">{t("application.contact.verifyMultipleAddresses")}</p>
)}
{!verifyAddress && strings?.subTitle && (
<p className="field-note mt-6">{strings?.subTitle}</p>
)}
</div>

{!!Object.keys(errors).length && (
Expand All @@ -172,7 +232,7 @@ const ApplicationMultiselectQuestionStep = ({
)}

<Form onSubmit={handleSubmit(onSubmit)}>
<div key={question?.id}>
<div style={{ display: verifyAddress ? "none" : "block" }} key={question?.id}>
<div className={`form-card__group`}>
{questionSetInputType === "checkbox" ? (
<fieldset>
Expand All @@ -189,6 +249,18 @@ const ApplicationMultiselectQuestionStep = ({
</div>
</div>

{verifyAddress && (
<AddressValidationSelection
{...{
foundAddress,
newAddressSelected,
setNewAddressSelected,
setVerifyAddress,
setVerifyAddressStep,
}}
/>
)}

<div className="form-card__pager">
<div className="form-card__pager-row primary">
<Button
Expand Down
10 changes: 9 additions & 1 deletion sites/public/src/components/applications/ValidateAddress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,17 @@ interface AddressValidationSelectionProps {
newAddressSelected: boolean
setVerifyAddress: React.Dispatch<React.SetStateAction<boolean>>
setNewAddressSelected: React.Dispatch<React.SetStateAction<boolean>>
setVerifyAddressStep?: React.Dispatch<React.SetStateAction<number>>
}

export const AddressValidationSelection = (props: AddressValidationSelectionProps) => {
const { foundAddress, newAddressSelected, setNewAddressSelected, setVerifyAddress } = props
const {
foundAddress,
newAddressSelected,
setNewAddressSelected,
setVerifyAddress,
setVerifyAddressStep,
} = props
return (
<div className="form-card__group">
{foundAddress.newAddress && (
Expand Down Expand Up @@ -132,6 +139,7 @@ export const AddressValidationSelection = (props: AddressValidationSelectionProp
className="mt-0 mr-0"
onClick={() => {
setVerifyAddress(false)
setVerifyAddressStep?.(0)
}}
id="app-edit-original-address"
>
Expand Down

0 comments on commit 0f9ead7

Please sign in to comment.