18F/identity-idp

View on GitHub
app/javascript/packages/document-capture/components/review-issues-step.tsx

Summary

Maintainability
A
0 mins
Test Coverage
import { useContext, useEffect, useLayoutEffect, useState } from 'react';
import { useDidUpdateEffect } from '@18f/identity-react-hooks';
import { FormStepsContext } from '@18f/identity-form-steps';
import type { FormStepComponentProps } from '@18f/identity-form-steps';
import type { PII } from '../services/upload';
import AnalyticsContext from '../context/analytics';
import BarcodeAttentionWarning from './barcode-attention-warning';
import FailedCaptureAttemptsContext from '../context/failed-capture-attempts';
import SelfieCaptureContext from '../context/selfie-capture';
import DocumentCaptureWarning from './document-capture-warning';
import DocumentCaptureReviewIssues from './document-capture-review-issues';

export interface ReviewIssuesStepValue {
  /**
   * Front image value.
   */
  front?: Blob | string | null | undefined;

  /**
   * Back image value.
   */
  back?: Blob | string | null | undefined;

  /**
   * Selfie image value.
   */
  selfie?: Blob | string | null | undefined;

  /**
   * Front image metadata.
   */
  front_image_metadata?: string;
  /**
   * Back image metadata.
   */
  back_image_metadata?: string;
}

interface ReviewIssuesStepProps extends FormStepComponentProps<ReviewIssuesStepValue> {
  remainingSubmitAttempts?: number;
  isResultCodeInvalid?: boolean;
  isFailedResult?: boolean;
  isFailedSelfie?: boolean;
  isFailedDocType?: boolean;
  isFailedSelfieLivenessOrQuality?: boolean;
  captureHints?: boolean;
  pii?: PII;
  failedImageFingerprints?: { front: string[] | null; back: string[] | null };
}

function ReviewIssuesStep({
  value = {},
  onChange = () => {},
  errors = [],
  unknownFieldErrors = [],
  onError = () => {},
  registerField = () => undefined,
  toPreviousStep = () => undefined,
  remainingSubmitAttempts = Infinity,
  isResultCodeInvalid = false,
  isFailedResult = false,
  isFailedDocType = false,
  isFailedSelfie = false,
  isFailedSelfieLivenessOrQuality = false,
  pii,
  captureHints = false,
  failedImageFingerprints = { front: [], back: [] },
}: ReviewIssuesStepProps) {
  const { trackEvent } = useContext(AnalyticsContext);
  const { isSelfieCaptureEnabled } = useContext(SelfieCaptureContext);
  const [hasDismissed, setHasDismissed] = useState(remainingSubmitAttempts === Infinity);
  const { onPageTransition, changeStepCanComplete } = useContext(FormStepsContext);
  const [skipWarning, setSkipWarning] = useState(false);
  useDidUpdateEffect(onPageTransition, [hasDismissed]);

  const { onFailedSubmissionAttempt, failedSubmissionImageFingerprints } = useContext(
    FailedCaptureAttemptsContext,
  );
  useEffect(() => onFailedSubmissionAttempt(failedImageFingerprints), []);

  useLayoutEffect(() => {
    let frontMetaData: { fingerprint: string | null } = { fingerprint: null };
    try {
      frontMetaData = JSON.parse(
        typeof value.front_image_metadata === 'undefined' ? '{}' : value.front_image_metadata,
      );
    } catch (e) {}
    const frontHasFailed = !!failedSubmissionImageFingerprints?.front?.includes(
      frontMetaData?.fingerprint ?? '',
    );

    let backMetaData: { fingerprint: string | null } = { fingerprint: null };
    try {
      backMetaData = JSON.parse(
        typeof value.back_image_metadata === 'undefined' ? '{}' : value.back_image_metadata,
      );
    } catch (e) {}
    const backHasFailed = !!failedSubmissionImageFingerprints?.back?.includes(
      backMetaData?.fingerprint ?? '',
    );
    if (frontHasFailed || backHasFailed) {
      setSkipWarning(true);
    }
  }, []);

  function onWarningPageDismissed() {
    trackEvent('IdV: Capture troubleshooting dismissed', {
      liveness_checking_required: isSelfieCaptureEnabled,
    });

    setHasDismissed(true);
  }

  // let FormSteps know, via FormStepsContext, whether this page
  // is ready to submit form values
  useEffect(() => {
    changeStepCanComplete(!!hasDismissed && !skipWarning);
  }, [hasDismissed]);

  if (!hasDismissed && pii) {
    return <BarcodeAttentionWarning onDismiss={onWarningPageDismissed} pii={pii} />;
  }

  // Show warning screen
  if (!hasDismissed && !skipWarning) {
    // Warning(try again screen)
    return (
      <DocumentCaptureWarning
        isResultCodeInvalid={isResultCodeInvalid}
        isFailedDocType={isFailedDocType}
        isFailedResult={isFailedResult}
        isFailedSelfie={isFailedSelfie}
        isFailedSelfieLivenessOrQuality={isFailedSelfieLivenessOrQuality}
        remainingSubmitAttempts={remainingSubmitAttempts}
        unknownFieldErrors={unknownFieldErrors}
        actionOnClick={onWarningPageDismissed}
        hasDismissed={false}
      />
    );
  }
  // Show review issue screen, hasDismissed = true
  return (
    <DocumentCaptureReviewIssues
      isFailedSelfie={isFailedSelfie}
      isFailedDocType={isFailedDocType}
      isFailedSelfieLivenessOrQuality={isFailedSelfieLivenessOrQuality}
      remainingSubmitAttempts={remainingSubmitAttempts}
      captureHints={captureHints}
      value={value}
      unknownFieldErrors={unknownFieldErrors}
      registerField={registerField}
      errors={errors}
      onChange={onChange}
      onError={onError}
      toPreviousStep={toPreviousStep}
      hasDismissed
    />
  );
}

export default ReviewIssuesStep;