diff --git a/app/javascript/packages/document-capture/components/document-capture.jsx b/app/javascript/packages/document-capture/components/document-capture.tsx similarity index 50% rename from app/javascript/packages/document-capture/components/document-capture.jsx rename to app/javascript/packages/document-capture/components/document-capture.tsx index 243c89c392f..11ef3275a0b 100644 --- a/app/javascript/packages/document-capture/components/document-capture.jsx +++ b/app/javascript/packages/document-capture/components/document-capture.tsx @@ -2,7 +2,8 @@ import { useState, useMemo, useContext } from 'react'; import { Alert } from '@18f/identity-components'; import { useI18n } from '@18f/identity-react-i18n'; import { FormSteps, PromptOnNavigate } from '@18f/identity-form-steps'; -import { FlowContext } from '@18f/identity-verify-flow'; +import { FlowContext, VerifyFlowStepIndicator } from '@18f/identity-verify-flow'; +import type { FormStep } from '@18f/identity-form-steps'; import { UploadFormEntriesError } from '../services/upload'; import DocumentsStep from './documents-step'; import SelfieStep from './selfie-step'; @@ -20,20 +21,15 @@ import SuspenseErrorBoundary from './suspense-error-boundary'; import SubmissionInterstitial from './submission-interstitial'; import withProps from '../higher-order/with-props'; -/** @typedef {import('react').ReactNode} ReactNode */ -/** @typedef {import('@18f/identity-form-steps').FormStep} FormStep */ - /** * Returns a new object with specified keys removed. * - * @template {Record} T - * - * @param {T} object Original object. - * @param {...string} keys Keys to remove. + * @param object Original object. + * @param keys Keys to remove. * - * @return {Partial} Object with keys removed. + * @return Object with keys removed. */ -export const except = (object, ...keys) => +export const except = >(object: T, ...keys: string[]): Partial => Object.entries(object).reduce((result, [key, value]) => { if (!keys.includes(key)) { result[key] = value; @@ -42,19 +38,21 @@ export const except = (object, ...keys) => return result; }, {}); -/** - * @typedef DocumentCaptureProps - * - * @prop {boolean=} isAsyncForm Whether submission should poll for async response. - * @prop {()=>void=} onStepChange Callback triggered on step change. - */ +interface DocumentCaptureProps { + /** + * Whether submission should poll for async response. + */ + isAsyncForm?: boolean; -/** - * @param {DocumentCaptureProps} props - */ -function DocumentCapture({ isAsyncForm = false, onStepChange }) { - const [formValues, setFormValues] = useState(/** @type {Record?} */ (null)); - const [submissionError, setSubmissionError] = useState(/** @type {Error=} */ (undefined)); + /** + * Callback triggered on step change. + */ + onStepChange?: () => void; +} + +function DocumentCapture({ isAsyncForm = false, onStepChange }: DocumentCaptureProps) { + const [formValues, setFormValues] = useState | null>(null); + const [submissionError, setSubmissionError] = useState(undefined); const { t } = useI18n(); const serviceProvider = useContext(ServiceProviderContext); const { flowPath } = useContext(UploadContext); @@ -63,9 +61,9 @@ function DocumentCapture({ isAsyncForm = false, onStepChange }) { /** * Clears error state and sets form values for submission. * - * @param {Record} nextFormValues Submitted form values. + * @param nextFormValues Submitted form values. */ - function submitForm(nextFormValues) { + function submitForm(nextFormValues: Record) { setSubmissionError(undefined); setFormValues(nextFormValues); } @@ -98,10 +96,10 @@ function DocumentCapture({ isAsyncForm = false, onStepChange }) { } } - const inPersonSteps = + const inPersonSteps: FormStep[] = inPersonURL === undefined ? [] - : /** @type {FormStep[]} */ ([ + : ([ { name: 'location', form: InPersonLocationStep, @@ -114,70 +112,72 @@ function DocumentCapture({ isAsyncForm = false, onStepChange }) { name: 'switch_back', form: InPersonSwitchBackStep, }, - ]).filter(Boolean); + ].filter(Boolean) as FormStep[]); - /** @type {FormStep[]} */ - const steps = submissionError - ? /** @type {FormStep[]} */ ([ - { - name: 'review', - form: - submissionError instanceof UploadFormEntriesError - ? withProps({ - remainingAttempts: submissionError.remainingAttempts, - isFailedResult: submissionError.isFailedResult, - captureHints: submissionError.hints, - pii: submissionError.pii, - })(ReviewIssuesStep) - : ReviewIssuesStep, - }, - ]) - .concat(inPersonSteps) - .filter(Boolean) - : /** @type {FormStep[]} */ ( + const steps: FormStep[] = submissionError + ? ( [ { - name: 'documents', - form: DocumentsStep, - }, - serviceProvider.isLivenessRequired && { - name: 'selfie', - form: SelfieStep, + name: 'review', + form: + submissionError instanceof UploadFormEntriesError + ? withProps({ + remainingAttempts: submissionError.remainingAttempts, + isFailedResult: submissionError.isFailedResult, + captureHints: submissionError.hints, + pii: submissionError.pii, + })(ReviewIssuesStep) + : ReviewIssuesStep, }, - ].filter(Boolean) - ); + ] as FormStep[] + ).concat(inPersonSteps) + : ([ + { + name: 'documents', + form: DocumentsStep, + }, + serviceProvider.isLivenessRequired && { + name: 'selfie', + form: SelfieStep, + }, + ].filter(Boolean) as FormStep[]); - return submissionFormValues && - (!submissionError || submissionError instanceof RetrySubmissionError) ? ( - <> - - } - onError={setSubmissionError} - handledError={submissionError} - > - {submissionError instanceof RetrySubmissionError ? ( - - ) : ( - - )} - - - ) : ( + return ( <> - {submissionError && !(submissionError instanceof UploadFormEntriesError) && ( - - {t('doc_auth.errors.general.network_error')} - + + {submissionFormValues && + (!submissionError || submissionError instanceof RetrySubmissionError) ? ( + <> + + } + onError={setSubmissionError} + handledError={submissionError} + > + {submissionError instanceof RetrySubmissionError ? ( + + ) : ( + + )} + + + ) : ( + <> + {submissionError && !(submissionError instanceof UploadFormEntriesError) && ( + + {t('doc_auth.errors.general.network_error')} + + )} + + )} - ); } diff --git a/app/javascript/packages/verify-flow/index.ts b/app/javascript/packages/verify-flow/index.ts index cddcd75b676..d0d51dcd59d 100644 --- a/app/javascript/packages/verify-flow/index.ts +++ b/app/javascript/packages/verify-flow/index.ts @@ -4,6 +4,7 @@ export { default as FlowContext } from './context/flow-context'; export { SecretsContextProvider } from './context/secrets-context'; export { default as Cancel } from './cancel'; export { default as VerifyFlow } from './verify-flow'; +export { default as VerifyFlowStepIndicator } from './verify-flow-step-indicator'; export { default as personalKeyStep } from './steps/personal-key'; export { default as personalKeyConfirmStep } from './steps/personal-key-confirm'; diff --git a/app/javascript/packages/verify-flow/verify-flow-step-indicator.tsx b/app/javascript/packages/verify-flow/verify-flow-step-indicator.tsx index 1932a531a60..63529ef5ee7 100644 --- a/app/javascript/packages/verify-flow/verify-flow-step-indicator.tsx +++ b/app/javascript/packages/verify-flow/verify-flow-step-indicator.tsx @@ -15,6 +15,7 @@ type VerifyFlowStepIndicatorStep = * Mapping of flow form steps to corresponding step indicator step. */ const FLOW_STEP_STEP_MAPPING: Record = { + document_capture: 'verify_id', password_confirm: 'secure_account', personal_key: 'secure_account', personal_key_confirm: 'secure_account', diff --git a/app/services/flow/flow_state_machine.rb b/app/services/flow/flow_state_machine.rb index 9efb5546c0c..9dc96e54f61 100644 --- a/app/services/flow/flow_state_machine.rb +++ b/app/services/flow/flow_state_machine.rb @@ -139,8 +139,9 @@ def call_optional_show_step(optional_step) end def step_indicator_params + return if !flow.class.const_defined?('STEP_INDICATOR_STEPS') handler = flow.step_handler(current_step) - return if !flow.class.const_defined?('STEP_INDICATOR_STEPS') || !handler + return if !handler || !handler.const_defined?('STEP_INDICATOR_STEP') { steps: flow.class::STEP_INDICATOR_STEPS, current_step: handler::STEP_INDICATOR_STEP, diff --git a/app/services/idv/steps/document_capture_step.rb b/app/services/idv/steps/document_capture_step.rb index c9e0230f6e2..4bd28822b96 100644 --- a/app/services/idv/steps/document_capture_step.rb +++ b/app/services/idv/steps/document_capture_step.rb @@ -1,8 +1,6 @@ module Idv module Steps class DocumentCaptureStep < DocAuthBaseStep - STEP_INDICATOR_STEP = :verify_id - IMAGE_UPLOAD_PARAM_NAMES = %i[ front_image back_image selfie_image ].freeze diff --git a/spec/controllers/idv/capture_doc_controller_spec.rb b/spec/controllers/idv/capture_doc_controller_spec.rb index 0a79d5b42b4..4ee21685bad 100644 --- a/spec/controllers/idv/capture_doc_controller_spec.rb +++ b/spec/controllers/idv/capture_doc_controller_spec.rb @@ -89,10 +89,6 @@ :flow_session, step_template: 'idv/capture_doc/document_capture', flow_namespace: 'idv', - step_indicator: hash_including( - :steps, - current_step: :verify_id, - ), ), ).and_call_original @@ -107,10 +103,6 @@ :flow_session, step_template: 'idv/capture_doc/capture_complete', flow_namespace: 'idv', - step_indicator: hash_including( - :steps, - current_step: :verify_id, - ), ), ).and_call_original diff --git a/spec/controllers/idv/doc_auth_controller_spec.rb b/spec/controllers/idv/doc_auth_controller_spec.rb index b3bd9a62f36..79a0da47c19 100644 --- a/spec/controllers/idv/doc_auth_controller_spec.rb +++ b/spec/controllers/idv/doc_auth_controller_spec.rb @@ -73,10 +73,6 @@ :flow_session, step_template: 'idv/doc_auth/document_capture', flow_namespace: 'idv', - step_indicator: hash_including( - :steps, - current_step: :verify_id, - ), ), ).and_call_original