department-of-veterans-affairs/vets-website

View on GitHub
src/applications/personalization/profile/components/connected-apps/ConnectedApps.jsx

Summary

Maintainability
C
1 day
Test Coverage
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { isEmpty } from 'lodash';

import {
  deleteConnectedApp,
  dismissDeletedAppAlert,
  loadConnectedApps,
} from '@@profile/components/connected-apps/actions';
import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports';
import { focusElement } from '@department-of-veterans-affairs/platform-utilities/ui';
import LoadFail from '../alerts/LoadFail';
import Headline from '../ProfileSectionHeadline';
import { AppDeletedAlert } from './AppDeletedAlert';
import { ConnectedApp } from './ConnectedApp';

export class ConnectedApps extends Component {
  constructor(...args) {
    super(...args);
    this.connectedAppsEvent = this.connectedAppsEvent.bind(this);
    this.faqEvent = this.faqEvent.bind(this);
  }

  componentDidMount() {
    const { loadConnectedApps: dispatchLoadConnectedApps } = this.props;
    focusElement('[data-focus-target]');
    document.title = `Connected Apps | Veterans Affairs`;
    dispatchLoadConnectedApps();
  }

  componentDidUpdate(prevProps) {
    const { loading } = this.props;
    if (prevProps.loading && !loading) {
      focusElement('[data-focus-target]');
    }
  }

  confirmDelete = appId => {
    const { deleteConnectedApp: dispatchDeleteConnectedApp } = this.props;
    dispatchDeleteConnectedApp(appId);
  };

  connectedAppsEvent = () => {
    recordEvent({
      event: 'go-to-app-directory',
      'profile-action': 'view-link',
      'profile-section': 'connected-apps',
    });
  };

  dismissAlert = appId => {
    const {
      dismissDeletedAppAlert: dispatchDismissDeletedAppAlert,
    } = this.props;
    dispatchDismissDeletedAppAlert(appId);
  };

  faqEvent = () => {
    recordEvent({
      event: 'profile-navigation',
      'profile-action': 'view-link',
      'profile-section': 'vets-faqs',
    });
  };

  render() {
    const { apps, loading, errors } = this.props;
    const deletedApps = apps ? apps.filter(app => app.deleted) : [];
    const activeApps = apps ? apps.filter(app => !app.deleted) : [];

    const allAppsDeleted = deletedApps?.length === apps?.length;
    // We treat this 404 'Record not found' error as an empty apps array
    const checkRecordNotFound = error =>
      error?.title === 'Record not found' && error?.status === '404';
    const recordNotFound = errors?.some(checkRecordNotFound);
    const showHasNoConnectedApps =
      !apps || (allAppsDeleted && !loading) || recordNotFound;
    const showHasConnectedApps = apps && !allAppsDeleted;
    // Check if any of the active apps have errors
    const disconnectErrorApps = activeApps.filter(app => !isEmpty(app.errors));
    const showHasServerError = !isEmpty(errors) && !recordNotFound;

    return (
      <div className="va-connected-apps">
        <Headline>Connected apps</Headline>
        {showHasConnectedApps && (
          <p className="va-introtext vads-u-font-size--md">
            Your VA.gov profile is connected to the third-party (non-VA) apps
            listed below. If you want to stop sharing information with an app,
            you can disconnect it from your profile at any time.
          </p>
        )}

        {showHasNoConnectedApps && (
          <>
            <p className="va-introtext vads-u-font-size--md">
              Connected apps are third-party (non-VA) applications or websites
              that can share certain information from your VA.gov profile, with
              your permission. For example, you can connect information from
              your VA health record to an app that helps you track your health.
            </p>

            <p className="va-introtext vads-u-font-size--md">
              We offer this feature for your convenience. It’s always your
              choice whether to connect, or stay connected, to a third-party
              app.
            </p>

            <p className="va-introtext vads-u-font-size--md">
              You don’t have any third-party apps connected to your profile. Go
              to the app directory to find out what apps are available to
              connect to your profile.
            </p>
          </>
        )}

        {showHasServerError && <LoadFail />}

        {deletedApps.map(app => (
          <AppDeletedAlert
            title={app?.attributes?.title}
            privacyUrl={app?.attributes?.privacyUrl}
            key={app.id}
          />
        ))}

        {showHasNoConnectedApps && (
          <a
            className="vads-u-margin-bottom--3"
            href="/resources/find-apps-you-can-use"
            onClick={this.connectedAppsEvent}
          >
            Go to app directory
          </a>
        )}

        {loading && (
          <va-loading-indicator
            setFocus
            message="Loading your connected apps..."
            data-testid="connected-apps-loading-indicator"
          />
        )}
        {!isEmpty(disconnectErrorApps) &&
          disconnectErrorApps.map(app => (
            <>
              <va-alert
                status="error"
                background-only
                key={`${app.attributes?.title}`}
                uswds
              >
                <div>
                  <p
                    className="vads-u-margin-y--0"
                    role="alert"
                    aria-live="polite"
                  >
                    We’re sorry. We can’t disconnect {app.attributes?.title}{' '}
                    from your VA.gov profile right now. We’re working to fix
                    this problem. Please check back later.
                  </p>
                </div>
              </va-alert>
            </>
          ))}

        {activeApps.map(app => (
          <ConnectedApp
            key={app.id}
            confirmDelete={this.confirmDelete}
            {...app}
          />
        ))}

        {!isEmpty(activeApps) && (
          <div className="vads-u-margin-y--3 available-connected-apps">
            <va-additional-info
              disable-border
              trigger="What other third-party apps can I connect to my profile?"
              uswds
            >
              To find out what other third-party apps are available to connect
              to your profile,{' '}
              <a
                href="/resources/find-apps-you-can-use"
                onClick={this.connectedAppsEvent}
              >
                go to the app directory
              </a>
            </va-additional-info>
          </div>
        )}

        <va-summary-box uswds class="vads-u-margin-top--2">
          <h2
            slot="headline"
            className="vads-u-margin-top--0 vads-u-font-size--lg"
          >
            Have more questions about connected apps?
          </h2>
          <p>
            <a
              className="vads-u-color--primary-alt-darkest"
              onClick={this.faqEvent}
              href="/resources/connected-apps-faqs/"
            >
              Go to FAQs about connecting third-party apps to your VA.gov
              profile
            </a>
          </p>
        </va-summary-box>
      </div>
    );
  }
}

const mapStateToProps = state => ({
  ...state.connectedApps,
});

const mapDispatchToProps = {
  loadConnectedApps,
  deleteConnectedApp,
  dismissDeletedAppAlert,
};

ConnectedApps.propTypes = {
  apps: PropTypes.arrayOf(
    PropTypes.shape({
      id: PropTypes.string.isRequired,
      type: PropTypes.string.isRequired,
      errors: PropTypes.array,
      deleting: PropTypes.bool,
      attributes: PropTypes.shape({
        title: PropTypes.string.isRequired,
        logo: PropTypes.string.isRequired,
        grants: PropTypes.arrayOf(
          PropTypes.shape({
            created: PropTypes.string.isRequired,
            id: PropTypes.string.isRequired,
            title: PropTypes.string.isRequired,
          }),
        ).isRequired,
      }),
    }),
  ).isRequired,
  deleteConnectedApp: PropTypes.func.isRequired,
  dismissDeletedAppAlert: PropTypes.func.isRequired,
  loadConnectedApps: PropTypes.func.isRequired,
  errors: PropTypes.array,
  loading: PropTypes.bool,
};

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