diff --git a/Gemfile.lock b/Gemfile.lock index 0f3e99521c1..d6f072de731 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -197,7 +197,7 @@ GEM debug_inspector (>= 0.0.1) bootsnap (1.15.0) msgpack (~> 1.2) - brakeman (5.4.0) + brakeman (5.4.1) browser (5.3.1) builder (3.2.4) bullet (7.0.7) diff --git a/app/components/phone_input_component.html.erb b/app/components/phone_input_component.html.erb index efe8510bb5c..24a495525de 100644 --- a/app/components/phone_input_component.html.erb +++ b/app/components/phone_input_component.html.erb @@ -36,12 +36,6 @@ :phone, class: 'usa-label', ) { t('two_factor_authentication.phone_label') } %> -
- <%= f.hint(capture do %> - <%= t('forms.example') %> - - <% end) %> -
<%= render ValidatedFieldComponent.new( form: f, name: :phone, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index ec78024eaef..9c17fec259f 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -65,6 +65,7 @@ def analytics sp: current_sp&.issuer, session: session, ahoy: ahoy, + irs_session_id: irs_attempts_api_session_id, ) end diff --git a/app/controllers/concerns/idv/verify_info_concern.rb b/app/controllers/concerns/idv/verify_info_concern.rb index 0635f2c248f..9e1acd7dce2 100644 --- a/app/controllers/concerns/idv/verify_info_concern.rb +++ b/app/controllers/concerns/idv/verify_info_concern.rb @@ -97,7 +97,8 @@ def async_state_done(current_async_state) extra: { address_edited: !!flow_session['address_edited'], address_line2_present: !pii[:address2].blank?, - pii_like_keypaths: [[:errors, :ssn], [:response_body, :first_name]], + pii_like_keypaths: [[:errors, :ssn], [:response_body, :first_name], + [:state_id, :state_id_jurisdiction]], }, ) log_idv_verification_submitted_event( @@ -105,15 +106,14 @@ def async_state_done(current_async_state) failure_reason: irs_attempts_api_tracker.parse_failure_reason(form_response), ) - if form_response.success? - response = check_ssn - form_response = form_response.merge(response) - end + form_response = form_response.merge(check_ssn) if form_response.success? summarize_result_and_throttle_failures(form_response) delete_async if form_response.success? + move_applicant_to_idv_session idv_session.mark_verify_info_step_complete! + idv_session.invalidate_steps_after_verify_info! redirect_to idv_phone_url else idv_session.invalidate_verify_info_step! @@ -196,27 +196,13 @@ def log_idv_verification_submitted_event(success: false, failure_reason: nil) end def check_ssn - result = Idv::SsnForm.new(current_user).submit(ssn: pii[:ssn]) - - if result.success? - save_legacy_state - delete_pii - end - - result + Idv::SsnForm.new(current_user).submit(ssn: pii[:ssn]) end - def save_legacy_state - skip_legacy_steps + def move_applicant_to_idv_session idv_session.applicant = pii idv_session.applicant['uuid'] = current_user.uuid - end - - def skip_legacy_steps - idv_session.mark_verify_info_step_complete! - idv_session.vendor_phone_confirmation = false - idv_session.user_phone_confirmation = false - idv_session.address_verification_mechanism = 'phone' + delete_pii end def add_proofing_costs(results) diff --git a/app/controllers/idv/in_person/address_search_controller.rb b/app/controllers/idv/in_person/address_search_controller.rb index e96e4208fee..725ab329e1d 100644 --- a/app/controllers/idv/in_person/address_search_controller.rb +++ b/app/controllers/idv/in_person/address_search_controller.rb @@ -1,10 +1,6 @@ module Idv module InPerson class AddressSearchController < ApplicationController - include RenderConditionConcern - - check_or_render_not_found -> { IdentityConfig.store.arcgis_search_enabled } - def index response = addresses(params[:address]) render(**response) diff --git a/app/controllers/idv/in_person/usps_locations_controller.rb b/app/controllers/idv/in_person/usps_locations_controller.rb index 6e62cb11ecf..ec7e9cc2a16 100644 --- a/app/controllers/idv/in_person/usps_locations_controller.rb +++ b/app/controllers/idv/in_person/usps_locations_controller.rb @@ -20,26 +20,21 @@ class UspsLocationsController < ApplicationController # retrieve the list of nearby IPP Post Office locations with a POST request def index - response = [] - if IdentityConfig.store.arcgis_search_enabled - candidate = UspsInPersonProofing::Applicant.new( - address: search_params['street_address'], - city: search_params['city'], state: search_params['state'], - zip_code: search_params['zip_code'] + candidate = UspsInPersonProofing::Applicant.new( + address: search_params['street_address'], + city: search_params['city'], state: search_params['state'], + zip_code: search_params['zip_code'] + ) + response = proofer.request_facilities(candidate) + if response.length > 0 + analytics.idv_in_person_locations_searched( + success: true, + result_total: response.length, ) - response = proofer.request_facilities(candidate) - if response.length > 0 - analytics.idv_in_person_locations_searched( - success: true, - result_total: response.length, - ) - else - analytics.idv_in_person_locations_searched( - success: false, errors: 'No USPS locations found', - ) - end else - response = proofer.request_pilot_facilities + analytics.idv_in_person_locations_searched( + success: false, errors: 'No USPS locations found', + ) end render json: response.to_json end diff --git a/app/controllers/redirect/policy_controller.rb b/app/controllers/redirect/policy_controller.rb new file mode 100644 index 00000000000..9e75c5dfd79 --- /dev/null +++ b/app/controllers/redirect/policy_controller.rb @@ -0,0 +1,10 @@ +module Redirect + class PolicyController < RedirectController + def show + redirect_to_and_log( + MarketingSite.security_and_privacy_practices_url, + tracker_method: analytics.method(:policy_redirect), + ) + end + end + end diff --git a/app/javascript/packages/document-capture/components/document-capture.tsx b/app/javascript/packages/document-capture/components/document-capture.tsx index 42e6f2bacc1..5dd591c2de5 100644 --- a/app/javascript/packages/document-capture/components/document-capture.tsx +++ b/app/javascript/packages/document-capture/components/document-capture.tsx @@ -9,7 +9,6 @@ import { getConfigValue } from '@18f/identity-config'; import { UploadFormEntriesError } from '../services/upload'; import DocumentsStep from './documents-step'; import InPersonPrepareStep from './in-person-prepare-step'; -import InPersonLocationStep from './in-person-location-step'; import InPersonLocationPostOfficeSearchStep from './in-person-location-post-office-search-step'; import InPersonSwitchBackStep from './in-person-switch-back-step'; import ReviewIssuesStep from './review-issues-step'; @@ -60,7 +59,7 @@ function DocumentCapture({ isAsyncForm = false, onStepChange = () => {} }: Docum const { t } = useI18n(); const { flowPath } = useContext(UploadContext); const { trackSubmitEvent, trackVisitEvent } = useContext(AnalyticsContext); - const { inPersonURL, arcgisSearchEnabled } = useContext(InPersonContext); + const { inPersonURL } = useContext(InPersonContext); const appName = getConfigValue('appName'); useDidUpdateEffect(onStepChange, [stepName]); @@ -114,7 +113,7 @@ function DocumentCapture({ isAsyncForm = false, onStepChange = () => {} }: Docum : ([ { name: 'location', - form: arcgisSearchEnabled ? InPersonLocationPostOfficeSearchStep : InPersonLocationStep, + form: InPersonLocationPostOfficeSearchStep, title: t('in_person_proofing.headings.po_search.location'), }, { diff --git a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx index 4df8ac8d242..e88a91c6e57 100644 --- a/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx +++ b/app/javascript/packages/document-capture/components/in-person-location-post-office-search-step.spec.tsx @@ -7,9 +7,7 @@ import type { SetupServerApi } from 'msw/node'; import { SWRConfig } from 'swr'; import { I18nContext } from '@18f/identity-react-i18n'; import { ComponentType } from 'react'; -import { LOCATIONS_URL } from './in-person-location-step'; -import { ADDRESS_SEARCH_URL } from './address-search'; -import InPersonContext from '../context/in-person'; +import { ADDRESS_SEARCH_URL, LOCATIONS_URL } from './address-search'; import InPersonLocationPostOfficeSearchStep from './in-person-location-post-office-search-step'; const DEFAULT_RESPONSE = [ @@ -58,11 +56,9 @@ const DEFAULT_PROPS = { registerField() {}, }; -describe('InPersonLocationStep', () => { +describe('InPersonPostOfficeSearchStep', () => { const wrapper: ComponentType = ({ children }) => ( - - new Map() }}>{children} - + new Map() }}>{children} ); let server: SetupServerApi; @@ -90,9 +86,7 @@ describe('InPersonLocationStep', () => { it('displays a try again error message', async () => { const { findByText, findByLabelText } = render( new Map() }}> - - - + , ); diff --git a/app/javascript/packages/document-capture/components/in-person-location-step.spec.tsx b/app/javascript/packages/document-capture/components/in-person-location-step.spec.tsx deleted file mode 100644 index a5de88ed931..00000000000 --- a/app/javascript/packages/document-capture/components/in-person-location-step.spec.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import sinon from 'sinon'; -import { useContext } from 'react'; -import { render } from '@testing-library/react'; -import { getAllByRole } from '@testing-library/dom'; -import userEvent from '@testing-library/user-event'; -import { setupServer } from 'msw/node'; -import { rest } from 'msw'; -import type { SetupServerApi } from 'msw/node'; -import AnalyticsContext, { AnalyticsContextProvider } from '../context/analytics'; -import InPersonLocationStep, { LOCATIONS_URL } from './in-person-location-step'; -import { ADDRESS_SEARCH_URL } from './address-search'; - -const DEFAULT_RESPONSE = [ - { - address: '100 Main St E, Bronwood, Georgia, 39826', - location: { - latitude: 31.831686000000005, - longitude: -84.363768, - }, - street_address: '100 Main St E', - city: 'Bronwood', - state: 'GA', - zip_code: '39826', - }, -]; - -const DEFAULT_PROPS = { - toPreviousStep() {}, - onChange() {}, - value: {}, - registerField() {}, -}; - -describe('InPersonLocationStep', () => { - let server: SetupServerApi; - before(() => { - server = setupServer( - rest.post(LOCATIONS_URL, (_req, res, ctx) => res(ctx.json([{ name: 'Baltimore' }]))), - rest.post(ADDRESS_SEARCH_URL, (_req, res, ctx) => res(ctx.json(DEFAULT_RESPONSE))), - rest.put(LOCATIONS_URL, (_req, res, ctx) => res(ctx.json([{ success: true }]))), - ); - server.listen(); - }); - - after(() => { - server.close(); - }); - - it('logs step submission with selected location', async () => { - const trackEvent = sinon.stub(); - function MetadataValue() { - return <>{JSON.stringify(useContext(AnalyticsContext).submitEventMetadata)}; - } - const { findByText } = render( - - - - , - ); - - const item = await findByText('Baltimore — in_person_proofing.body.location.post_office'); - const button = getAllByRole(item.closest('.location-collection-item')!, 'button')[0]; - - await userEvent.click(button); - - await findByText('{"selected_location":"Baltimore","in_person_cta_variant":""}'); - }); -}); diff --git a/app/javascript/packages/document-capture/components/in-person-location-step.tsx b/app/javascript/packages/document-capture/components/in-person-location-step.tsx deleted file mode 100644 index 6712e3b8ef1..00000000000 --- a/app/javascript/packages/document-capture/components/in-person-location-step.tsx +++ /dev/null @@ -1,201 +0,0 @@ -import { useState, useEffect, useCallback, useRef, useContext } from 'react'; -import { useI18n } from '@18f/identity-react-i18n'; -import { PageHeading, SpinnerDots } from '@18f/identity-components'; -import { request } from '@18f/identity-request'; -import BackButton from './back-button'; -import LocationCollection from './location-collection'; -import LocationCollectionItem from './location-collection-item'; -import AnalyticsContext from '../context/analytics'; -import { InPersonContext } from '../context'; - -interface PostOffice { - address: string; - city: string; - name: string; - saturday_hours: string; - state: string; - sunday_hours: string; - weekday_hours: string; - zip_code_4: string; - zip_code_5: string; -} - -interface FormattedLocation { - formattedCityStateZip: string; - id: number; - name: string; - saturdayHours: string; - streetAddress: string; - sundayHours: string; - weekdayHours: string; -} -interface LocationQuery { - streetAddress: string; - city: string; - state: string; - zipCode: string; -} - -export const LOCATIONS_URL = '/verify/in_person/usps_locations'; - -const getUspsLocations = (address) => - request(LOCATIONS_URL, { - method: 'post', - json: { address }, - }); - -const formatLocation = (postOffices: PostOffice[]) => { - const formattedLocations = [] as FormattedLocation[]; - postOffices.forEach((po: PostOffice, index) => { - const location = { - formattedCityStateZip: `${po.city}, ${po.state}, ${po.zip_code_5}-${po.zip_code_4}`, - id: index, - name: po.name, - saturdayHours: po.saturday_hours, - streetAddress: po.address, - sundayHours: po.sunday_hours, - weekdayHours: po.weekday_hours, - } as FormattedLocation; - formattedLocations.push(location); - }); - return formattedLocations; -}; - -const snakeCase = (value: string) => - value - .split(/(?=[A-Z])/) - .join('_') - .toLowerCase(); - -// snake case the keys of the location -const prepToSend = (location: object) => { - const sendObject = {}; - Object.keys(location).forEach((key) => { - sendObject[snakeCase(key)] = location[key]; - }); - return sendObject; -}; - -function InPersonLocationStep({ onChange, toPreviousStep }) { - const { inPersonCtaVariantActive } = useContext(InPersonContext); - const { t } = useI18n(); - const [locationData, setLocationData] = useState([] as FormattedLocation[]); - const [foundAddress] = useState({} as LocationQuery); - const [inProgress, setInProgress] = useState(false); - const [autoSubmit, setAutoSubmit] = useState(false); - const [isLoadingComplete, setIsLoadingComplete] = useState(false); - const { setSubmitEventMetadata } = useContext(AnalyticsContext); - - // ref allows us to avoid a memory leak - const mountedRef = useRef(false); - - useEffect(() => { - mountedRef.current = true; - return () => { - mountedRef.current = false; - }; - }, []); - - // useCallBack here prevents unnecessary rerenders due to changing function identity - const handleLocationSelect = useCallback( - async (e: any, id: number) => { - const selectedLocation = locationData[id]; - const { name: selectedLocationName } = selectedLocation; - setSubmitEventMetadata({ - selected_location: selectedLocationName, - in_person_cta_variant: inPersonCtaVariantActive, - }); - onChange({ selectedLocationName }); - if (autoSubmit) { - return; - } - // prevent navigation from continuing - e.preventDefault(); - if (inProgress) { - return; - } - const selected = prepToSend(selectedLocation); - setInProgress(true); - await request(LOCATIONS_URL, { - json: selected, - method: 'PUT', - }) - .then(() => { - if (!mountedRef.current) { - return; - } - setAutoSubmit(true); - setImmediate(() => { - // continue with navigation - e.target.click(); - // allow process to be re-triggered in case submission did not work as expected - setAutoSubmit(false); - }); - }) - .finally(() => { - if (!mountedRef.current) { - return; - } - setInProgress(false); - }); - }, - [locationData, inProgress], - ); - - useEffect(() => { - let didCancel = false; - (async () => { - try { - const fetchedLocations = await getUspsLocations(prepToSend(foundAddress)); - - if (!didCancel) { - const formattedLocations = formatLocation(fetchedLocations); - setLocationData(formattedLocations); - } - } finally { - if (!didCancel) { - setIsLoadingComplete(true); - } - } - })(); - return () => { - didCancel = true; - }; - }, [foundAddress]); - - let locationsContent: React.ReactNode; - if (!isLoadingComplete) { - locationsContent = ; - } else if (locationData.length < 1) { - locationsContent =

{t('in_person_proofing.body.location.none_found')}

; - } else { - locationsContent = ( - - {locationData.map((item, index) => ( - - ))} - - ); - } - - return ( - <> - {t('in_person_proofing.headings.location')} -

{t('in_person_proofing.body.location.location_step_about')}

- {locationsContent} - - - ); -} - -export default InPersonLocationStep; diff --git a/app/javascript/packages/document-capture/components/in-person-prepare-step.tsx b/app/javascript/packages/document-capture/components/in-person-prepare-step.tsx index 1043d3afdcc..91f5bbcf829 100644 --- a/app/javascript/packages/document-capture/components/in-person-prepare-step.tsx +++ b/app/javascript/packages/document-capture/components/in-person-prepare-step.tsx @@ -20,8 +20,7 @@ function InPersonPrepareStep({ toPreviousStep, value }) { const { flowPath } = useContext(UploadContext); const { trackEvent } = useContext(AnalyticsContext); const { securityAndPrivacyHowItWorksURL } = useContext(MarketingSiteContext); - /* Remove selectedLocationName when request_pilot_facilities removed */ - const { selectedLocationName, selectedLocationAddress } = value; + const { selectedLocationAddress } = value; const onContinue: MouseEventHandler = async (event) => { event.preventDefault(); @@ -36,14 +35,6 @@ function InPersonPrepareStep({ toPreviousStep, value }) { return ( <> - {/* Remove selectedLocationName version of alert when request_pilot_facilities removed */} - {selectedLocationName && ( - - {t('in_person_proofing.body.prepare.alert_selected_po_name', { - name: selectedLocationName, - })} - - )} {selectedLocationAddress && ( {t('in_person_proofing.body.prepare.alert_selected_post_office', { diff --git a/app/javascript/packages/document-capture/context/in-person.ts b/app/javascript/packages/document-capture/context/in-person.ts index 90e7fdd6ced..f8bb51ba8fd 100644 --- a/app/javascript/packages/document-capture/context/in-person.ts +++ b/app/javascript/packages/document-capture/context/in-person.ts @@ -1,11 +1,6 @@ import { createContext } from 'react'; export interface InPersonContextProps { - /** - * Feature flag for enabling address search - */ - arcgisSearchEnabled?: boolean; - /** * Whether or not A/B testing of the in-person proofing CTA is enabled. */ @@ -23,7 +18,6 @@ export interface InPersonContextProps { } const InPersonContext = createContext({ - arcgisSearchEnabled: false, inPersonCtaVariantTestingEnabled: false, inPersonCtaVariantActive: '', }); diff --git a/app/javascript/packages/phone-input/index.spec.ts b/app/javascript/packages/phone-input/index.spec.ts index c8c09d97ec5..75505c5fdb9 100644 --- a/app/javascript/packages/phone-input/index.spec.ts +++ b/app/javascript/packages/phone-input/index.spec.ts @@ -80,10 +80,6 @@ describe('PhoneInput', () => { ${!isSingleOption && !isNonUSSingleOption ? MULTIPLE_OPTIONS_HTML : ''} -
- Example: - -