department-of-veterans-affairs/vets-website

View on GitHub
src/applications/mhv-secure-messaging/components/shared/SmRouteNavigationGuard.jsx

Summary

Maintainability
A
3 hrs
Test Coverage
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import { Prompt } from 'react-router-dom';
import PropTypes from 'prop-types';
import { VaModal } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { resetUserSession } from '../../util/helpers';

const SmRouteNavigationGuard = ({
  when,
  onConfirmButtonClick,
  onCancelButtonClick,
  modalTitle,
  modalText,
  confirmButtonText,
  cancelButtonText,
}) => {
  const [isBlocking, setIsBlocking] = useState(false);
  const [modalVisible, updateModalVisible] = useState(false);

  const closeModal = () => {
    setIsBlocking(true); // Keep blocking navigation if the user closes modal
    updateModalVisible(false);
  };

  const handleConfirm = useCallback(
    e => {
      setIsBlocking(true); // Keep blocking navigation if the user chooses to stay on page
      onConfirmButtonClick(e);
      updateModalVisible(false);
    },
    [onConfirmButtonClick],
  );

  const handleCancel = useCallback(
    async e => {
      await setIsBlocking(false); // stop blocking navigation if the user chooses to cancel changes and leave page
      onCancelButtonClick(e);
    },
    [onCancelButtonClick],
  );

  const handleBlockedNavigation = useCallback(
    () => {
      if (isBlocking) {
        updateModalVisible(true);
        setIsBlocking(false);
        return false; // Blocks Navigation
      }
      return true; // Allows Navigation
    },
    [isBlocking, setIsBlocking, updateModalVisible],
  );

  const localStorageValues = useMemo(() => {
    return {
      atExpires: localStorage.atExpires,
      hasSession: localStorage.hasSession,
      sessionExpiration: localStorage.sessionExpiration,
      userFirstName: localStorage.userFirstName,
    };
  }, []);

  const { signOutMessage, timeoutId } = resetUserSession(localStorageValues);

  const noTimeout = useCallback(
    () => {
      clearTimeout(timeoutId);
    },
    [timeoutId],
  );

  const beforeUnloadHandler = useCallback(
    e => {
      e.preventDefault();
      window.onbeforeunload = () => signOutMessage;
      e.returnValue = signOutMessage; // Included for legacy support, e.g. Chrome/Edge < 119
    },
    [signOutMessage],
  );

  // Update blocking state based on the `when` prop
  useEffect(
    () => {
      setIsBlocking(when);

      // beforeunload prevents the user from navigating away from the page
      // without saving their work. This is a browser feature and cannot be
      // disabled. The message displayed to the user is also a browser feature
      if (when) {
        window.addEventListener('beforeunload', beforeUnloadHandler);
      } else {
        window.removeEventListener('beforeunload', beforeUnloadHandler);
        noTimeout();
      }
      return () => {
        window.removeEventListener('beforeunload', beforeUnloadHandler);
        window.onbeforeunload = null;
        noTimeout();
      };
    },
    [when, beforeUnloadHandler, noTimeout],
  );

  return (
    <>
      <Prompt when={when} message={handleBlockedNavigation} />
      <VaModal
        modalTitle={modalTitle}
        modalText={modalText}
        onCloseEvent={closeModal}
        status="warning"
        visible={modalVisible}
        data-dd-action-name="SM Route Navigation Guard Close Modal Button"
        data-testid="sm-route-navigation-guard-modal"
      >
        <p>{modalText}</p>
        <va-button
          class="vads-u-margin-top--1"
          text={confirmButtonText}
          onClick={handleConfirm}
          data-dd-action-name="SM Route Navigation Guard Confirm Button"
          data-testid="sm-route-navigation-guard-confirm-button"
        />
        <va-button
          class="vads-u-margin-top--1"
          secondary
          text={cancelButtonText}
          onClick={handleCancel}
          data-dd-action-name="SM Route Navigation Guard Cancel Button"
          data-testid="sm-route-navigation-guard-cancel-button"
        />
      </VaModal>
    </>
  );
};

SmRouteNavigationGuard.propTypes = {
  when: PropTypes.bool.isRequired,
  cancelButtonText: PropTypes.string,
  confirmButtonText: PropTypes.string,
  modalText: PropTypes.string,
  modalTitle: PropTypes.string,
  onCancelButtonClick: PropTypes.func,
  onConfirmButtonClick: PropTypes.func,
};

export default SmRouteNavigationGuard;