diff --git a/app/javascript/packages/document-capture/components/full-address-search-input.tsx b/app/javascript/packages/document-capture/components/full-address-search-input.tsx new file mode 100644 index 00000000000..e81c5f9f771 --- /dev/null +++ b/app/javascript/packages/document-capture/components/full-address-search-input.tsx @@ -0,0 +1,177 @@ +import { TextInput, SelectInput } from '@18f/identity-components'; +import type { FormattedLocation, LocationQuery } from '@18f/identity-address-search/types'; +import type { RegisterFieldCallback } from '@18f/identity-form-steps'; +import { useDidUpdateEffect } from '@18f/identity-react-hooks'; +import { SpinnerButtonRefHandle, SpinnerButton } from '@18f/identity-spinner-button'; +import { ValidatedField } from '@18f/identity-validated-field'; +import { t } from '@18f/identity-i18n'; +import { useCallback, useContext, useEffect, useRef, useState } from 'react'; +import { InPersonContext } from '../context'; +import useValidatedUspsLocations from '../hooks/use-validated-usps-locations'; + +interface FullAddressSearchProps { + registerField?: RegisterFieldCallback; + onFoundLocations?: ( + address: LocationQuery | null, + locations: FormattedLocation[] | null | undefined, + ) => void; + onLoadingLocations?: (isLoading: boolean) => void; + onError?: (error: Error | null) => void; + disabled?: boolean; + locationsURL: string; +} + +export default function FullAddressSearchInput({ + registerField = () => undefined, + onFoundLocations = () => undefined, + onLoadingLocations = () => undefined, + onError = () => undefined, + disabled = false, + locationsURL, +}: FullAddressSearchProps) { + const spinnerButtonRef = useRef(null); + const [addressValue, setAddressValue] = useState(''); + const [cityValue, setCityValue] = useState(''); + const [stateValue, setStateValue] = useState(''); + const [zipCodeValue, setZipCodeValue] = useState(''); + const { + locationQuery, + locationResults, + uspsError, + isLoading, + handleLocationSearch: onSearch, + validatedAddressFieldRef, + validatedCityFieldRef, + validatedStateFieldRef, + validatedZipCodeFieldRef, + } = useValidatedUspsLocations(locationsURL); + + const inputChangeHandler = + (input) => + (event: React.ChangeEvent) => { + const { target } = event; + input(target.value); + }; + + type SelectChangeEvent = React.ChangeEvent; + + const onAddressChange = inputChangeHandler(setAddressValue); + const onCityChange = inputChangeHandler(setCityValue); + const onStateChange = (e: SelectChangeEvent) => setStateValue(e.target.value); + const onZipCodeChange = inputChangeHandler(setZipCodeValue); + + useEffect(() => { + spinnerButtonRef.current?.toggleSpinner(isLoading); + onLoadingLocations(isLoading); + }, [isLoading]); + + useEffect(() => { + uspsError && onError(uspsError); + }, [uspsError]); + + useDidUpdateEffect(() => { + onFoundLocations(locationQuery, locationResults); + }, [locationResults]); + + const handleSearch = useCallback( + (event) => { + onError(null); + onSearch(event, addressValue, cityValue, stateValue, zipCodeValue); + }, + [addressValue, cityValue, stateValue, zipCodeValue], + ); + + const { usStatesTerritories } = useContext(InPersonContext); + + return ( + <> + + + + + + + + + + {usStatesTerritories.map(([name, abbr]) => ( + + ))} + + + + + +
+ + {t('in_person_proofing.body.location.po_search.search_button')} + +
+ + ); +} diff --git a/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx b/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx index 713fe90b93b..d879b20da61 100644 --- a/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx +++ b/app/javascript/packages/document-capture/components/in-person-full-address-search.spec.tsx @@ -16,7 +16,13 @@ describe('FullAddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findAllByText } = render( new Map() }}> - + , ); @@ -32,7 +38,13 @@ describe('FullAddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findByLabelText, findAllByText } = render( new Map() }}> - + , ); @@ -64,7 +76,13 @@ describe('FullAddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findByLabelText, queryByText } = render( new Map() }}> - + , ); @@ -109,7 +127,13 @@ describe('FullAddressSearch', () => { const handleLocationsFound = sandbox.stub(); const { findByText, findByLabelText } = render( new Map() }}> - + , ); diff --git a/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx b/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx index 0f3d3639feb..1cbd826ed70 100644 --- a/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx +++ b/app/javascript/packages/document-capture/components/in-person-full-address-search.tsx @@ -1,177 +1,55 @@ -import { TextInput, SelectInput } from '@18f/identity-components'; -import { useState, useRef, useEffect, useCallback, useContext } from 'react'; +import { Alert, PageHeading } from '@18f/identity-components'; +import { useState } from 'react'; import { t } from '@18f/identity-i18n'; -import ValidatedField from '@18f/identity-validated-field/validated-field'; -import SpinnerButton, { SpinnerButtonRefHandle } from '@18f/identity-spinner-button/spinner-button'; -import type { RegisterFieldCallback } from '@18f/identity-form-steps'; -import { useDidUpdateEffect } from '@18f/identity-react-hooks'; +import { InPersonLocations } from '@18f/identity-address-search'; import type { LocationQuery, FormattedLocation } from '@18f/identity-address-search/types'; -import { InPersonContext } from '../context'; -import useValidatedUspsLocations from '../hooks/use-validated-usps-locations'; - -interface FullAddressSearchProps { - registerField?: RegisterFieldCallback; - onFoundLocations?: ( - address: LocationQuery | null, - locations: FormattedLocation[] | null | undefined, - ) => void; - onLoadingLocations?: (isLoading: boolean) => void; - onError?: (error: Error | null) => void; - disabled?: boolean; - locationsURL: string; -} +import FullAddressSearchInput from './full-address-search-input'; function FullAddressSearch({ - registerField = () => undefined, - onFoundLocations = () => undefined, - onLoadingLocations = () => undefined, - onError = () => undefined, - disabled = false, + registerField, locationsURL, -}: FullAddressSearchProps) { - const spinnerButtonRef = useRef(null); - const [addressValue, setAddressValue] = useState(''); - const [cityValue, setCityValue] = useState(''); - const [stateValue, setStateValue] = useState(''); - const [zipCodeValue, setZipCodeValue] = useState(''); - const { - locationQuery, - locationResults, - uspsError, - isLoading, - handleLocationSearch: onSearch, - validatedAddressFieldRef, - validatedCityFieldRef, - validatedStateFieldRef, - validatedZipCodeFieldRef, - } = useValidatedUspsLocations(locationsURL); - - const inputChangeHandler = - (input) => - (event: React.ChangeEvent) => { - const { target } = event; - input(target.value); - }; - - type SelectChangeEvent = React.ChangeEvent; - - const onAddressChange = inputChangeHandler(setAddressValue); - const onCityChange = inputChangeHandler(setCityValue); - const onStateChange = (e: SelectChangeEvent) => setStateValue(e.target.value); - const onZipCodeChange = inputChangeHandler(setZipCodeValue); - - useEffect(() => { - spinnerButtonRef.current?.toggleSpinner(isLoading); - onLoadingLocations(isLoading); - }, [isLoading]); - - useEffect(() => { - uspsError && onError(uspsError); - }, [uspsError]); - - useDidUpdateEffect(() => { - onFoundLocations(locationQuery, locationResults); - }, [locationResults]); - - const handleSearch = useCallback( - (event) => { - onError(null); - onSearch(event, addressValue, cityValue, stateValue, zipCodeValue); - }, - [addressValue, cityValue, stateValue, zipCodeValue], + handleLocationSelect, + disabled, + onFoundLocations, +}) { + const [apiError, setApiError] = useState(null); + const [foundAddress, setFoundAddress] = useState(null); + const [locationResults, setLocationResults] = useState( + null, ); - - const { usStatesTerritories } = useContext(InPersonContext); + const [isLoadingLocations, setLoadingLocations] = useState(false); return ( <> - - - - - - - - - - {usStatesTerritories.map(([name, abbr]) => ( - - ))} - - - + {t('idv.failure.exceptions.post_office_search_error')} + + )} + {t('in_person_proofing.headings.po_search.location')} +

{t('in_person_proofing.body.location.po_search.po_search_about')}

+ { + setFoundAddress(address); + setLocationResults(locations); + onFoundLocations(locations); }} - > - + {locationResults && foundAddress && !isLoadingLocations && ( + -
-
- - {t('in_person_proofing.body.location.po_search.search_button')} - -
+ )} ); } diff --git a/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx b/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx index 564c9fdcb0c..1ce97987a37 100644 --- a/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx +++ b/app/javascript/packages/document-capture/components/in-person-location-full-address-entry-post-office-search-step.tsx @@ -1,10 +1,8 @@ import { useState, useEffect, useCallback, useRef, useContext } from 'react'; -import { t } from '@18f/identity-i18n'; -import { Alert, PageHeading } from '@18f/identity-components'; import { request } from '@18f/identity-request'; import { forceRedirect } from '@18f/identity-url'; -import { transformKeys, snakeCase, InPersonLocations } from '@18f/identity-address-search'; -import type { LocationQuery, FormattedLocation } from '@18f/identity-address-search/types'; +import { transformKeys, snakeCase } from '@18f/identity-address-search'; +import type { FormattedLocation } from '@18f/identity-address-search/types'; import FullAddressSearch from './in-person-full-address-search'; import BackButton from './back-button'; import AnalyticsContext from '../context/analytics'; @@ -19,25 +17,14 @@ function InPersonLocationFullAddressEntryPostOfficeSearchStep({ }) { const { inPersonURL } = useContext(InPersonContext); const [inProgress, setInProgress] = useState(false); - const [isLoadingLocations, setLoadingLocations] = useState(false); const [autoSubmit, setAutoSubmit] = useState(false); const { trackEvent } = useContext(AnalyticsContext); const [locationResults, setLocationResults] = useState( null, ); - const [foundAddress, setFoundAddress] = useState(null); - const [apiError, setApiError] = useState(null); const [disabledAddressSearch, setDisabledAddressSearch] = useState(false); const { flowPath } = useContext(UploadContext); - const onFoundLocations = ( - address: LocationQuery | null, - locations: FormattedLocation[] | null | undefined, - ) => { - setFoundAddress(address); - setLocationResults(locations); - }; - // ref allows us to avoid a memory leak const mountedRef = useRef(false); @@ -105,28 +92,13 @@ function InPersonLocationFullAddressEntryPostOfficeSearchStep({ return ( <> - {apiError && ( - - {t('idv.failure.exceptions.post_office_search_error')} - - )} - {t('in_person_proofing.headings.po_search.location')} -

{t('in_person_proofing.body.location.po_search.po_search_about')}

- {locationResults && foundAddress && !isLoadingLocations && ( - - )} );