department-of-veterans-affairs/vets-website

View on GitHub
src/applications/representative-appoint/components/SelectAccreditedRepresentative.jsx

Summary

Maintainability
C
1 day
Test Coverage
import React, { useState, useRef } from 'react';
import { connect } from 'react-redux';
import { setData } from '~/platform/forms-system/src/js/actions';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router';
import FormNavButtons from 'platform/forms-system/src/js/components/FormNavButtons';
import { scrollToFirstError } from 'platform/utilities/ui';
import { isLoggedIn } from 'platform/user/selectors';
import { fetchRepresentatives } from '../api/fetchRepresentatives';
import { fetchRepStatus } from '../api/fetchRepStatus';
import SearchResult from './SearchResult';
import SearchInput from './SearchInput';
import { useReviewPage } from '../hooks/useReviewPage';

const SelectAccreditedRepresentative = props => {
  const {
    loggedIn,
    setFormData,
    formData,
    goBack,
    goForward,
    goToPath,
  } = props;
  const [loadingReps, setLoadingReps] = useState(false);
  const [loadingPOA, setLoadingPOA] = useState(false);
  const [error, setError] = useState(null);
  const representativeResults =
    formData?.['view:representativeSearchResults'] || null;

  const currentSelectedRep = useRef(formData?.['view:selectedRepresentative']);

  const query = formData['view:representativeQuery'];
  const invalidQuery = query === undefined || !query.trim();

  const noSearchError =
    'Enter the name of the accredited representative or VSO you’d like to appoint';

  const noSelectionError =
    'Select the accredited representative or VSO you’d like to appoint below.';

  const isReviewPage = useReviewPage();

  const getRepStatus = async () => {
    if (loggedIn) {
      setLoadingPOA(true);

      try {
        const res = await fetchRepStatus();
        setLoadingPOA(false);
        return res.data;
      } catch {
        setLoadingPOA(false);
      }
    }

    return null;
  };

  const handleGoBack = () => {
    if (isReviewPage) {
      goToPath('/claimant-type');
    } else {
      goBack(formData);
    }
  };

  const handleGoForward = ({ selectionMade = false }) => {
    const selection = formData['view:selectedRepresentative'];
    const noSelectionExists = !selection && !selectionMade;

    if (noSelectionExists && !invalidQuery) {
      setError(noSelectionError);
      scrollToFirstError({ focusOnAlertRole: true });
    } else if (noSelectionExists && invalidQuery) {
      setError(noSearchError);
      scrollToFirstError({ focusOnAlertRole: true });
    } else if (isReviewPage) {
      if (selection === currentSelectedRep.current) {
        goToPath('/review-and-submit');
      } else {
        goToPath('/representative-contact?review=true');
      }
    } else {
      goForward(formData);
    }
  };

  const handleSearch = async () => {
    if (invalidQuery) {
      setError(noSearchError);
      scrollToFirstError({ focusOnAlertRole: true });
      return;
    }

    setLoadingReps(true);
    setError(null);

    try {
      const res = await fetchRepresentatives({ query });
      setFormData({
        ...formData,
        'view:representativeSearchResults': res,
      });
    } catch (err) {
      setError(err.errorMessage);
    } finally {
      setLoadingReps(false);
    }
  };

  const handleSelectRepresentative = async selectedRepResult => {
    if (selectedRepResult === currentSelectedRep.current && isReviewPage) {
      goToPath('/review-and-submit');
    } else {
      const repStatus = await getRepStatus();

      const tempData = {
        ...formData,
        'view:selectedRepresentative': selectedRepResult,
        'view:representativeStatus': repStatus,
        // when a new representative is selected, we want to nil out the
        //   selected organization to prevent weird states. For example,
        //   we wouldn't want a user to select a representative, an organization,
        //   go backwards to select an attorney, and then our state variables
        //   say an attorney was selected with a non-null organization id
        selectedAccreditedOrganizationId: null,
      };

      setFormData({
        ...tempData,
      });

      // similar to the tempData trick above with async state variables,
      //  we need to trick our routing logic to know that a selection has
      //  been made before that selection is reflected in formData.
      //  Otherwise, one would have to double click the select
      //  representative button to register that a selection was made.
      handleGoForward({ selectionMade: true });
    }
  };

  if (loadingPOA) {
    return <va-loading-indicator set-focus />;
  }

  return (
    <div>
      <h3>Select the accredited representative or VSO you’d like to appoint</h3>

      <SearchInput
        error={error}
        formData={formData}
        setFormData={setFormData}
        onSubmit={handleSearch}
      />
      {loadingReps ? (
        <va-loading-indicator
          message="Finding accredited representatives..."
          set-focus
        />
      ) : null}
      {representativeResults &&
        representativeResults.map((rep, index) => (
          <SearchResult
            key={index}
            representative={rep}
            formData={formData}
            setFormData={setFormData}
            currentSelectedRep={currentSelectedRep.current}
            goToPath={goToPath}
            handleSelectRepresentative={handleSelectRepresentative}
          />
        ))}
      <p className="vads-u-margin-y--4">
        <strong>Note:</strong> If you don’t know who you’d like to appoint, you
        can use our online tool to search for an accredited attorney, claims
        agent, or VSO representative.
      </p>
      <va-link
        href="/get-help-from-accredited-representative/find-rep"
        text="Find a VA accredited representative or VSO"
        external
      />
      <FormNavButtons goBack={handleGoBack} goForward={handleGoForward} />
    </div>
  );
};

SelectAccreditedRepresentative.propTypes = {
  fetchRepresentatives: PropTypes.func,
  formData: PropTypes.object,
  goBack: PropTypes.func,
  goForward: PropTypes.func,
  goToPath: PropTypes.func,
  loggedIn: PropTypes.bool,
  setFormData: PropTypes.func,
};

const mapStateToProps = state => ({
  formData: state.form?.data,
  loggedIn: isLoggedIn(state),
});

const mapDispatchToProps = {
  setFormData: setData,
};

export { SelectAccreditedRepresentative }; // Named export for testing

export default withRouter(
  connect(
    mapStateToProps,
    mapDispatchToProps,
  )(SelectAccreditedRepresentative),
);