department-of-veterans-affairs/vets-website

View on GitHub
src/platform/site-wide/user-nav/containers/Main.jsx

Summary

Maintainability
A
0 mins
Test Coverage
// Node modules.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import appendQuery from 'append-query';
import URLSearchParams from 'url-search-params';
// Relative imports.
import FormSignInModal from 'platform/forms/save-in-progress/FormSignInModal';
import SignInModal from 'platform/user/authentication/components/SignInModal';
import { SAVE_STATUSES } from 'platform/forms/save-in-progress/actions';
import { getBackendStatuses } from 'platform/monitoring/external-services/actions';
import { initializeProfile } from 'platform/user/profile/actions';
import { isInProgressPath } from 'platform/forms/helpers';
import { signInServiceEnabled } from 'platform/user/authentication/selectors';
import {
  isLoggedIn,
  isProfileLoading,
  isLOA3,
  selectUser,
} from 'platform/user/selectors';
import {
  toggleFormSignInModal,
  toggleLoginModal,
  toggleSearchHelpUserMenu,
} from 'platform/site-wide/user-nav/actions';
import { updateLoggedInStatus } from 'platform/user/authentication/actions';
import SearchHelpSignIn from '../components/SearchHelpSignIn';
import AutoSSO from './AutoSSO';
import { selectUserGreeting } from '../selectors';

export class Main extends Component {
  componentDidMount() {
    // Close any open modals when navigating to different routes within an app.
    window.addEventListener('popstate', this.closeModals);
    window.addEventListener('storage', this.handleSessionChange);
    this.bindModalTriggers();
    // In some cases this component is mounted on a url that is part of the login process and doesn't need to make another
    // request, because that data will be passed to the parent window and done there instead.
    if (!window.location.pathname.includes('auth/login/callback')) {
      window.addEventListener('load', this.checkLoggedInStatus);
    }
  }

  componentDidUpdate() {
    const { currentlyLoggedIn } = this.props;

    if (currentlyLoggedIn) {
      this.executeRedirect();
      this.closeModals();
    }
  }

  handleSessionChange = event => {
    if (!this.props.currentlyLoggedIn) return;

    const { key, newValue } = event;
    if (!key || (key === 'hasSession' && !newValue)) {
      this.props.updateLoggedInStatus(false);
    }
  };

  closeLoginModal = () => {
    this.props.toggleLoginModal(false);
  };

  closeModals = () => {
    if (this.props.showFormSignInModal) this.closeFormSignInModal();
    if (this.props.showLoginModal) this.closeLoginModal();
  };

  openLoginModal = () => {
    this.props.toggleLoginModal(true);
  };

  signInSignUp = () => {
    if (this.props.shouldConfirmLeavingForm) {
      this.props.toggleFormSignInModal(true);
    } else {
      /**
       * Make only one upfront request to get all backend statuses to prevent
       * each identity dependency's warning banner from making duplicate
       * requests when the sign-in modal renders.
       */
      this.props.getBackendStatuses();
      this.props.toggleLoginModal(true, 'header');
    }
  };

  getNextParameter = () => {
    return new URLSearchParams(window.location.search).get('next');
  };

  closeFormSignInModal = () => {
    this.props.toggleFormSignInModal(false);
  };

  bindModalTriggers = () => {
    const triggers = Array.from(
      document.querySelectorAll('.signin-signup-modal-trigger'),
    );
    triggers.forEach(t => t.addEventListener('click', this.openLoginModal));
  };

  checkLoggedInStatus = () => {
    if (JSON.parse(localStorage.getItem('hasSession'))) {
      this.props.initializeProfile();
    } else {
      this.props.updateLoggedInStatus(false);
      if (this.getNextParameter()) this.openLoginModal();
    }
  };

  executeRedirect() {
    const redirectUrl = this.formatNextParameter();
    const shouldRedirect =
      redirectUrl && !window.location.pathname.includes('verify');

    if (shouldRedirect) {
      window.location.replace(appendQuery(redirectUrl, 'postLogin=true'));
    }
  }

  formatNextParameter() {
    const nextParam = this.getNextParameter();
    if (nextParam && nextParam !== 'loginModal') {
      return nextParam.startsWith('/') ? nextParam : `/${nextParam}`;
    }

    return null;
  }

  render() {
    return (
      <div className="profile-nav-container">
        <SearchHelpSignIn
          {...this.props}
          isLoggedIn={this.props.currentlyLoggedIn}
          isMenuOpen={this.props.utilitiesMenuIsOpen}
          onSignInSignUp={this.signInSignUp}
          toggleMenu={this.props.toggleSearchHelpUserMenu}
        />
        <FormSignInModal
          onClose={this.closeFormSignInModal}
          onSignIn={this.openLoginModal}
          visible={this.props.showFormSignInModal}
        />
        <SignInModal
          onClose={this.closeLoginModal}
          visible={this.props.showLoginModal}
          useSiS={this.props.useSignInService}
        />
        <AutoSSO />
      </div>
    );
  }
}

export const shouldConfirmLeavingCheck = form => {
  const isFormAnObject = typeof form === 'object';
  if (!isFormAnObject) return false;

  const { autoSavedStatus = undefined, additionalRoutes = undefined } = form;
  const additionalSafePaths =
    (additionalRoutes && additionalRoutes.map(route => route?.path)) ??
    undefined;

  return (
    typeof autoSavedStatus !== 'undefined' &&
    autoSavedStatus !== SAVE_STATUSES.success &&
    isInProgressPath(window.location.pathname, additionalSafePaths)
  );
};

export const mapStateToProps = state => {
  const { form } = state;
  const shouldConfirmLeavingForm = shouldConfirmLeavingCheck(form);

  return {
    currentlyLoggedIn: isLoggedIn(state),
    isLOA3: isLOA3(state),
    isProfileLoading: isProfileLoading(state),
    shouldConfirmLeavingForm,
    useSignInService: signInServiceEnabled(state),
    user: selectUser(state),
    userGreeting: selectUserGreeting(state),
    ...state.navigation,
  };
};

const mapDispatchToProps = {
  getBackendStatuses,
  initializeProfile,
  toggleFormSignInModal,
  toggleLoginModal,
  toggleSearchHelpUserMenu,
  updateLoggedInStatus,
};

export default connect(
  mapStateToProps,
  mapDispatchToProps,
)(Main);

Main.propTypes = {
  getBackendStatuses: PropTypes.func.isRequired,
  initializeProfile: PropTypes.func.isRequired,
  toggleFormSignInModal: PropTypes.func.isRequired,
  toggleLoginModal: PropTypes.func.isRequired,
  toggleSearchHelpUserMenu: PropTypes.func.isRequired,
  updateLoggedInStatus: PropTypes.func.isRequired,

  currentlyLoggedIn: PropTypes.bool,
  isHeaderV2: PropTypes.bool,
  isLOA3: PropTypes.bool,
  isProfileLoading: PropTypes.bool,
  shouldConfirmLeavingForm: PropTypes.bool,
  showFormSignInModal: PropTypes.bool,
  showLoginModal: PropTypes.bool,
  useSignInService: PropTypes.bool,
  userGreeting: PropTypes.oneOfType([
    PropTypes.node,
    PropTypes.object,
    PropTypes.arrayOf(PropTypes.node),
  ]),
  utilitiesMenuIsOpen: PropTypes.object,
};