department-of-veterans-affairs/vets-website

View on GitHub
src/platform/user/profile/vap-svc/reducers/index.js

Summary

Maintainability
F
3 days
Test Coverage
import * as VAP_SERVICE from 'platform/user/profile/vap-svc/constants';

import { isEmpty, isEqual, pickBy } from 'lodash';

import { COPY_ADDRESS_MODAL_STATUS } from 'platform/user/profile/vap-svc/constants';

import { isFailedTransaction } from '../util/transactions';

import {
  UPDATE_PROFILE_FORM_FIELD,
  OPEN_MODAL,
  VAP_SERVICE_CLEAR_LAST_SAVED,
  VAP_SERVICE_TRANSACTIONS_FETCH_SUCCESS,
  VAP_SERVICE_TRANSACTION_REQUESTED,
  VAP_SERVICE_TRANSACTION_REQUEST_SUCCEEDED,
  VAP_SERVICE_TRANSACTION_REQUEST_FAILED,
  VAP_SERVICE_TRANSACTION_UPDATED,
  VAP_SERVICE_TRANSACTION_CLEARED,
  VAP_SERVICE_TRANSACTION_REQUEST_CLEARED,
  VAP_SERVICE_TRANSACTION_UPDATE_REQUESTED,
  VAP_SERVICE_TRANSACTION_UPDATE_FAILED,
  VAP_SERVICE_NO_CHANGES_DETECTED,
  ADDRESS_VALIDATION_CONFIRM,
  ADDRESS_VALIDATION_ERROR,
  ADDRESS_VALIDATION_RESET,
  UPDATE_SELECTED_ADDRESS,
  ADDRESS_VALIDATION_INITIALIZE,
  ADDRESS_VALIDATION_UPDATE,
  COPY_ADDRESS_MODAL,
} from '../actions';

const initialAddressValidationState = {
  addressValidationType: '',
  suggestedAddresses: [],
  confirmedSuggestions: [],
  addressFromUser: {
    addressLine1: '',
    addressLine2: '',
    addressLine3: '',
    city: '',
    stateCode: '',
    zipCode: '',
    countryCodeIso3: '',
  },
  addressValidationError: false,
  validationKey: null,
  selectedAddress: {},
  selectedAddressId: null,
};

const initialState = {
  hasUnsavedEdits: false,
  initialFormFields: {},
  modal: null,
  modalData: null,
  formFields: {},
  transactions: [],
  fieldTransactionMap: {},
  transactionsAwaitingUpdate: [],
  metadata: {
    mostRecentErroredTransactionId: '',
  },
  addressValidation: {
    ...initialAddressValidationState,
  },
  copyAddressModal: null,
};

export default function vapService(state = initialState, action) {
  switch (action.type) {
    case VAP_SERVICE_TRANSACTIONS_FETCH_SUCCESS: {
      const transactions = action.data.map(transactionData =>
        // Wrap in a "data" property to imitate the API response for a single transaction
        ({ data: transactionData }),
      );
      return {
        ...state,
        transactions,
      };
    }

    case VAP_SERVICE_TRANSACTION_REQUESTED: {
      return {
        ...state,
        fieldTransactionMap: {
          ...state.fieldTransactionMap,
          [action.fieldName]: { isPending: true, method: action.method },
        },
      };
    }
    case VAP_SERVICE_TRANSACTION_REQUEST_FAILED: {
      let copyAddressModal = null;
      if (
        action.fieldName === VAP_SERVICE.FIELD_NAMES.MAILING_ADDRESS &&
        state?.copyAddressModal ===
          VAP_SERVICE.COPY_ADDRESS_MODAL_STATUS.PENDING
      ) {
        copyAddressModal = VAP_SERVICE.COPY_ADDRESS_MODAL_STATUS.FAILURE;
      }

      return {
        ...state,
        fieldTransactionMap: {
          ...state.fieldTransactionMap,
          [action.fieldName]: {
            ...state.fieldTransactionMap[action.fieldName],
            isPending: false,
            isFailed: true,
            error: action.error,
          },
        },
        copyAddressModal,
      };
    }

    case VAP_SERVICE_TRANSACTION_REQUEST_SUCCEEDED: {
      return {
        ...state,
        transactions: state.transactions.concat(action.transaction),
        fieldTransactionMap: {
          ...state.fieldTransactionMap,
          [action.fieldName]: {
            ...state.fieldTransactionMap[action.fieldName],
            isPending: false,
            transactionId: action.transaction.data.attributes.transactionId,
          },
        },
        initialFormFields: {},
        hasUnsavedEdits: false,
      };
    }

    case VAP_SERVICE_NO_CHANGES_DETECTED: {
      return {
        ...state,
        mostRecentlySavedField: action.fieldName,
        fieldTransactionMap: {
          ...state.fieldTransactionMap,
          [action.fieldName]: {
            ...state.fieldTransactionMap[action.fieldName],
            isPending: false,
            transactionId: action?.transaction?.data?.attributes?.transactionId,
          },
        },
        initialFormFields: {},
        hasUnsavedEdits: false,
      };
    }

    case VAP_SERVICE_TRANSACTION_UPDATE_REQUESTED: {
      const { transactionId } = action.transaction.data.attributes;
      return {
        ...state,
        transactionsAwaitingUpdate: state.transactionsAwaitingUpdate.concat(
          transactionId,
        ),
      };
    }

    case VAP_SERVICE_TRANSACTION_UPDATED: {
      const { transaction } = action;
      const {
        transactionId: updatedTransactionId,
      } = transaction.data.attributes;

      let copyAddressModal = null;

      const metadata = { ...state.metadata };
      if (isFailedTransaction(transaction)) {
        metadata.mostRecentErroredTransactionId = updatedTransactionId;
        if (state.copyAddressModal === COPY_ADDRESS_MODAL_STATUS.PENDING) {
          copyAddressModal = COPY_ADDRESS_MODAL_STATUS.FAILURE;
        }
      }

      return {
        ...state,
        metadata,
        transactionsAwaitingUpdate: state.transactionsAwaitingUpdate?.filter(
          tid => tid !== updatedTransactionId,
        ),
        transactions: state.transactions.map(
          t =>
            t.data.attributes.transactionId === updatedTransactionId
              ? transaction
              : t,
        ),
        copyAddressModal,
      };
    }

    case VAP_SERVICE_TRANSACTION_UPDATE_FAILED: {
      const { transactionId } = action.transaction.data.attributes;
      return {
        ...state,
        transactionsAwaitingUpdate: state.transactionsAwaitingUpdate?.filter(
          tid => tid !== transactionId,
        ),
      };
    }

    case VAP_SERVICE_CLEAR_LAST_SAVED: {
      return {
        ...state,
        mostRecentlySavedField: null,
      };
    }

    case VAP_SERVICE_TRANSACTION_CLEARED: {
      const finishedTransactionId =
        action.transaction.data.attributes.transactionId;
      const fieldTransactionMap = { ...state.fieldTransactionMap };

      let mostRecentlySavedField;
      let copyAddressModal;

      Object.keys(fieldTransactionMap).forEach(field => {
        const transactionRequest = fieldTransactionMap[field];
        if (
          transactionRequest &&
          transactionRequest.transactionId === finishedTransactionId
        ) {
          delete fieldTransactionMap[field];
          mostRecentlySavedField = field;
          if (field === VAP_SERVICE.FIELD_NAMES.RESIDENTIAL_ADDRESS) {
            copyAddressModal = VAP_SERVICE.COPY_ADDRESS_MODAL_STATUS.CHECKING;
          }

          if (
            field === VAP_SERVICE.FIELD_NAMES.MAILING_ADDRESS &&
            state?.copyAddressModal ===
              VAP_SERVICE.COPY_ADDRESS_MODAL_STATUS.PENDING &&
            action.transaction?.data?.attributes?.transactionStatus ===
              VAP_SERVICE.TRANSACTION_STATUS.COMPLETED_SUCCESS &&
            state?.mostRecentlySavedField ===
              VAP_SERVICE.FIELD_NAMES.RESIDENTIAL_ADDRESS
          ) {
            mostRecentlySavedField = [
              VAP_SERVICE.FIELD_NAMES.RESIDENTIAL_ADDRESS,
              VAP_SERVICE.FIELD_NAMES.MAILING_ADDRESS,
            ];
            copyAddressModal = VAP_SERVICE.COPY_ADDRESS_MODAL_STATUS.SUCCESS;
          }
        }
      });

      const metadata = { ...state.metadata };
      if (metadata.mostRecentErroredTransactionId === finishedTransactionId) {
        metadata.mostRecentErroredTransactionId = null;
      }

      return {
        ...state,
        metadata,
        transactions: state.transactions?.filter(
          t => t.data.attributes.transactionId !== finishedTransactionId,
        ),
        transactionsAwaitingUpdate: state.transactionsAwaitingUpdate?.filter(
          tid => tid !== finishedTransactionId,
        ),
        modal: null,
        fieldTransactionMap,
        mostRecentlySavedField,
        copyAddressModal,
      };
    }

    case VAP_SERVICE_TRANSACTION_REQUEST_CLEARED: {
      const fieldTransactionMap = { ...state.fieldTransactionMap };
      delete fieldTransactionMap[action.fieldName];

      return {
        ...state,
        fieldTransactionMap,
      };
    }

    case UPDATE_PROFILE_FORM_FIELD: {
      const formFields = {
        ...state.formFields,
        [action.field]: action.newState,
      };

      // The action gets fired upon initial opening of the edit modal
      // We only want to capture initialFormFields once, it should not update
      const initialFormFields = isEmpty(state.initialFormFields)
        ? formFields
        : state.initialFormFields;

      const fieldName = state?.modal;
      let formFieldValues = formFields[fieldName]?.value;

      // Initial form fields does not have 'view' properties, those get added to formFields
      // After editing a field. So we need to strip of those 'view' fields to be able to compare
      formFieldValues = pickBy(formFieldValues, value => value !== undefined);
      formFieldValues = pickBy(
        formFieldValues,
        (value, key) => !key.startsWith('view:'),
      );

      const initialFormFieldValues = pickBy(
        state.initialFormFields[fieldName]?.value,
        (value, key) => !key.startsWith('view:'),
      );

      const hasUnsavedEdits =
        !isEmpty(initialFormFieldValues) &&
        !isEqual(formFieldValues, initialFormFieldValues);

      return {
        ...state,
        formFields,
        hasUnsavedEdits,
        initialFormFields,
      };
    }

    case OPEN_MODAL:
      return {
        ...state,
        modal: action.modal,
        modalData: action.modalData,
        hasUnsavedEdits: false,
        initialFormFields: {},
        mostRecentlySavedField: null,
      };

    case ADDRESS_VALIDATION_INITIALIZE:
      return {
        ...state,
        addressValidation: {
          ...initialAddressValidationState,
        },
        fieldTransactionMap: {
          ...state.fieldTransactionMap,
          [action.fieldName]: { isPending: true },
        },
      };

    case ADDRESS_VALIDATION_CONFIRM:
      return {
        ...state,
        fieldTransactionMap: {
          ...state.fieldTransactionMap,
          [action.addressValidationType]: { isPending: false },
        },
        addressValidation: {
          ...state.addressValidation,
          addressFromUser: action.addressFromUser,
          addressValidationType: action.addressValidationType,
          suggestedAddresses: action.suggestedAddresses,
          validationKey: action.validationKey,
          selectedAddress: action.selectedAddress,
          selectedAddressId: action.selectedAddressId,
          confirmedSuggestions: action.confirmedSuggestions,
          addressValidationError: false,
        },
        modal: 'addressValidation',
      };

    case ADDRESS_VALIDATION_ERROR:
      return {
        ...state,
        fieldTransactionMap: {
          ...state.fieldTransactionMap,
          [action.fieldName]: {
            ...state.fieldTransactionMap[action.fieldName],
            isPending: false,
            isFailed: true,
            error: action.error,
          },
        },
        addressValidation: {
          ...initialAddressValidationState,
          addressValidationError: action.addressValidationError,
          addressValidationType: action.fieldName,
          validationKey: action.validationKey || null,
          addressFromUser: action.addressFromUser,
        },
        modal: action.fieldName,
      };

    case ADDRESS_VALIDATION_RESET:
      return {
        ...state,
        addressValidation: { ...initialAddressValidationState },
      };

    case ADDRESS_VALIDATION_UPDATE:
      return {
        ...state,
        fieldTransactionMap: {
          ...state.fieldTransactionMap,
          [action.fieldName]: { isPending: true },
        },
      };

    case UPDATE_SELECTED_ADDRESS:
      return {
        ...state,
        addressValidation: {
          ...state.addressValidation,
          selectedAddress: action.selectedAddress,
          selectedAddressId: action.selectedAddressId,
        },
      };

    case COPY_ADDRESS_MODAL:
      return {
        ...state,
        copyAddressModal: action?.value,
      };

    default:
      return state;
  }
}