src/applications/personalization/dashboard/components/Dashboard.jsx
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { connect, useDispatch, useSelector } from 'react-redux';
import isEmpty from 'lodash/isEmpty';
import {
fetchMilitaryInformation as fetchMilitaryInformationAction,
fetchHero as fetchHeroAction,
} from '@@profile/actions';
import {
VaAlert,
VaModal,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { toggleValues } from '~/platform/site-wide/feature-toggles/selectors';
import { connectDrupalSourceOfTruthCerner } from '~/platform/utilities/cerner/dsot';
import recordEvent from '~/platform/monitoring/record-event';
import { focusElement } from '~/platform/utilities/ui';
import { useFeatureToggle } from '~/platform/utilities/feature-toggles';
import {
createIsServiceAvailableSelector,
isLOA3 as isLOA3Selector,
isLOA1 as isLOA1Selector,
isVAPatient as isVAPatientSelector,
hasMPIConnectionError,
isNotInMPI,
} from '~/platform/user/selectors';
import {
RequiredLoginView,
RequiredLoginLoader,
} from '~/platform/user/authorization/components/RequiredLoginView';
import backendServices from '~/platform/user/profile/constants/backendServices';
import {
DowntimeNotification,
externalServices,
} from '~/platform/monitoring/DowntimeNotification';
import NameTag from '~/applications/personalization/components/NameTag';
import MPIConnectionError from '~/applications/personalization/components/MPIConnectionError';
import NotInMPIError from '~/applications/personalization/components/NotInMPIError';
import IdentityNotVerified from '~/platform/user/authorization/components/IdentityNotVerified';
import { signInServiceName } from '~/platform/user/authentication/selectors';
import { fetchTotalDisabilityRating as fetchTotalDisabilityRatingAction } from '../../common/actions/ratedDisabilities';
import { hasTotalDisabilityServerError } from '../../common/selectors/ratedDisabilities';
import { API_NAMES } from '../../common/constants';
import useDowntimeApproachingRenderMethod from '../useDowntimeApproachingRenderMethod';
import ClaimsAndAppeals from './claims-and-appeals/ClaimsAndAppeals';
import HealthCare from './health-care/HealthCare';
import CTALink from './CTALink';
import BenefitPayments from './benefit-payments/BenefitPayments';
import Debts from './debts/Debts';
import { getAllPayments } from '../actions/payments';
import Notifications from './notifications/Notifications';
import { canAccess } from '../../common/selectors';
import RenderClaimsWidgetDowntimeNotification from './RenderClaimsWidgetDowntimeNotification';
import BenefitApplications from './benefit-application-drafts/BenefitApplications';
import EducationAndTraining from './education-and-training/EducationAndTraining';
const DashboardHeader = ({ showNotifications, user }) => {
const { useToggleValue, TOGGLE_NAMES } = useFeatureToggle();
const hideNotificationsSection = useToggleValue(
TOGGLE_NAMES.myVaHideNotificationsSection,
);
const displayOnboardingInformation = useToggleValue(
TOGGLE_NAMES.veteranOnboardingBetaFlow,
);
return (
<div>
{displayOnboardingInformation && (
<VaAlert status="info" visible>
<h2> Welcome to VA, {user.profile.userFullName.first}</h2>
<p>
We understand that transitioning out of the military can be a
daunting experience, which is why we offer a range of resources to
help you discover and apply for the benefits you deserve.
</p>
<div className="vads-l-row">
<div className="vads-l-col--4">
<a
className="vads-c-action-link--green"
href="/profile/contact-information"
>
Add your contact information
</a>
</div>
<div className="vads-l-col--8">
<a
className="vads-c-action-link--green"
href="/get-help-from-accredited-representative/find-rep"
>
Find a representative
</a>
</div>
</div>
</VaAlert>
)}
<h1
id="dashboard-title"
data-testid="dashboard-title"
tabIndex="-1"
className="vads-u-margin--0 vads-u-margin-top--2 medium-screen:vads-u-margin-top--3"
>
My VA
</h1>
<CTALink
href="/profile"
text="Go to your profile"
className="vads-u-margin-top--2"
onClick={() => {
recordEvent({
event: 'dashboard-navigation',
'dashboard-action': 'view-link',
'dashboard-product': 'view-your-profile',
});
}}
/>
{showNotifications && !hideNotificationsSection && <Notifications />}
</div>
);
};
const LOA1Content = ({
isLOA1,
isVAPatient,
welcomeModalVisible,
dismissWelcomeModal,
}) => {
const signInService = useSelector(signInServiceName);
const { useToggleValue, TOGGLE_NAMES } = useFeatureToggle();
const showWelcomeToMyVaMessage = useToggleValue(
TOGGLE_NAMES.veteranOnboardingShowWelcomeMessageToNewUsers,
);
return (
<>
<div className="vads-l-row">
<div className="vads-l-col--12 medium-screen:vads-l-col--8 medium-screen:vads-u-padding-right--3">
<IdentityNotVerified
headline="Verify your identity to access more VA.gov tools and features"
signInService={signInService}
/>
</div>
</div>
<ClaimsAndAppeals isLOA1={isLOA1} />
<HealthCare isVAPatient={isVAPatient} isLOA1={isLOA1} />
<EducationAndTraining isLOA1={isLOA1} />
<BenefitApplications />
{showWelcomeToMyVaMessage && (
<VaModal
large
modalTitle="Welcome to My VA"
onCloseEvent={dismissWelcomeModal}
onPrimaryButtonClick={dismissWelcomeModal}
primaryButtonText="Continue"
visible={welcomeModalVisible}
data-testid="welcome-modal"
>
<p>
We’ll help you get started managing your benefits and information
online, as well as help you find resources and support. Once you
have applications or claims in process, you’ll be able to check
status here at My VA.
</p>
</VaModal>
)}
</>
);
};
DashboardHeader.propTypes = {
showNotifications: PropTypes.bool,
user: PropTypes.object,
};
LOA1Content.propTypes = {
dismissWelcomeModal: PropTypes.func,
isLOA1: PropTypes.bool,
isVAPatient: PropTypes.bool,
welcomeModalVisible: PropTypes.bool,
};
const Dashboard = ({
canAccessMilitaryHistory,
canAccessPaymentHistory,
canAccessRatingInfo,
fetchFullName,
fetchMilitaryInformation,
fetchTotalDisabilityRating,
getPayments,
isLOA3,
isLOA1,
payments,
showLoader,
showMPIConnectionError,
showNameTag,
showNotInMPIError,
showNotifications,
isVAPatient,
...props
}) => {
const downtimeApproachingRenderMethod = useDowntimeApproachingRenderMethod();
const dispatch = useDispatch();
const [welcomeModalVisible, setWelcomeModalVisible] = useState(
!localStorage.getItem('welcomeToMyVAModalIsDismissed'),
);
const dismissWelcomeModal = () => {
setWelcomeModalVisible(false);
localStorage.setItem('welcomeToMyVAModalIsDismissed', 'true');
};
useEffect(
() => {
// use Drupal based Cerner facility data
connectDrupalSourceOfTruthCerner(dispatch);
},
[dispatch],
);
// focus on the name tag or the header when we are done loading
useEffect(
() => {
if (!showLoader) {
if (showNameTag) {
focusElement('#name-tag');
} else {
focusElement('#dashboard-title');
}
}
},
[showLoader, showNameTag],
);
// fetch data when we determine they are LOA3
useEffect(
() => {
if (isLOA3) {
fetchFullName();
if (canAccessMilitaryHistory) {
fetchMilitaryInformation();
}
if (canAccessRatingInfo) {
fetchTotalDisabilityRating();
}
}
},
[
canAccessMilitaryHistory,
canAccessRatingInfo,
isLOA3,
fetchFullName,
fetchMilitaryInformation,
fetchTotalDisabilityRating,
],
);
// fetch data when we determine they are LOA3
useEffect(
() => {
if (canAccessPaymentHistory) {
getPayments();
}
},
[canAccessPaymentHistory, getPayments],
);
return (
<RequiredLoginView
serviceRequired={[backendServices.USER_PROFILE]}
user={props.user}
showProfileErrorMessage
>
<DowntimeNotification
appTitle="user dashboard"
loadingIndicator={<RequiredLoginLoader />}
dependencies={[
externalServices.mvi,
externalServices.mhv,
externalServices.appeals,
]}
render={downtimeApproachingRenderMethod}
>
{showLoader && <RequiredLoginLoader />}
{!showLoader && (
<div className="dashboard">
{showNameTag && (
<div id="name-tag">
<NameTag
totalDisabilityRating={props.totalDisabilityRating}
totalDisabilityRatingServerError={
props.totalDisabilityRatingServerError
}
/>
</div>
)}
<div className="vads-l-grid-container vads-u-padding-x--1 vads-u-padding-bottom--3 medium-screen:vads-u-padding-x--2 medium-screen:vads-u-padding-bottom--4">
<DashboardHeader
showNotifications={showNotifications}
user={props.user}
/>
{showMPIConnectionError && (
<div className="vads-l-row">
<MPIConnectionError className="vads-l-col--12 medium-screen:vads-l-col--8 medium-screen:vads-u-padding-right--3 vads-u-margin-top--3" />
</div>
)}
{showNotInMPIError && (
<div className="vads-l-row">
<NotInMPIError
className="vads-l-col--12 medium-screen:vads-l-col--8 medium-screen:vads-u-padding-right--3 vads-u-margin-top--3"
level={2}
/>
</div>
)}
{/* LOA1 user experience */}
{isLOA1 && (
<LOA1Content
isLOA1={isLOA1}
isVAPatient={isVAPatient}
welcomeModalVisible={welcomeModalVisible}
dismissWelcomeModal={dismissWelcomeModal}
/>
)}
{/* LOA3 user experience */}
{props.showClaimsAndAppeals && (
<DowntimeNotification
dependencies={[
externalServices.mhv,
externalServices.appeals,
]}
render={RenderClaimsWidgetDowntimeNotification}
>
<ClaimsAndAppeals />
</DowntimeNotification>
)}
{isLOA3 && (
<>
<HealthCare isVAPatient={isVAPatient} />
<Debts />
<BenefitPayments
payments={payments}
showNotifications={showNotifications}
/>
<EducationAndTraining />
<BenefitApplications />
</>
)}
</div>
</div>
)}
</DowntimeNotification>
</RequiredLoginView>
);
};
const isClaimsAvailableSelector = createIsServiceAvailableSelector(
backendServices.LIGHTHOUSE,
);
const isAppealsAvailableSelector = createIsServiceAvailableSelector(
backendServices.APPEALS_STATUS,
);
const mapStateToProps = state => {
const { isReady: hasLoadedScheduledDowntime } = state.scheduledDowntime;
const isLOA3 = isLOA3Selector(state);
const isLOA1 = isLOA1Selector(state);
const isVAPatient = isVAPatientSelector(state);
const hero = state.vaProfile?.hero;
const hasClaimsOrAppealsService =
isAppealsAvailableSelector(state) || isClaimsAvailableSelector(state);
const hasMHVAccount = ['OK', 'MULTIPLE'].includes(
state.user?.profile?.mhvAccountState,
);
const hasLoadedFullName = !!hero;
const canAccessPaymentHistory = canAccess(state)[API_NAMES.PAYMENT_HISTORY];
const canAccessRatingInfo = canAccess(state)[API_NAMES.RATING_INFO];
const canAccessMilitaryHistory = canAccess(state)[API_NAMES.MILITARY_HISTORY];
const hasLoadedDisabilityRating = canAccessRatingInfo
? state.totalRating?.loading === false
: true;
const hasLoadedMilitaryInformation = canAccessMilitaryHistory
? state.vaProfile?.militaryInformation
: true;
const hasLoadedAllData =
// we do not need to fetch additional data if they are only LOA1
isLOA1 ||
(hasLoadedMilitaryInformation &&
hasLoadedFullName &&
hasLoadedDisabilityRating);
const togglesAreLoaded = !toggleValues(state)?.loading;
const showLoader =
!hasLoadedScheduledDowntime || !hasLoadedAllData || !togglesAreLoaded;
const showValidateIdentityAlert = isLOA1;
const showNameTag = isLOA3 && isEmpty(hero?.errors);
const showMPIConnectionError = hasMPIConnectionError(state);
const showNotInMPIError = isNotInMPI(state);
const showClaimsAndAppeals =
!showMPIConnectionError &&
!showNotInMPIError &&
isLOA3 &&
hasClaimsOrAppealsService;
const showHealthCare =
hasMHVAccount &&
!showMPIConnectionError &&
!showNotInMPIError &&
isLOA3 &&
isVAPatient;
const showNotifications =
!showMPIConnectionError && !showNotInMPIError && isLOA3;
return {
canAccessMilitaryHistory,
canAccessPaymentHistory,
canAccessRatingInfo,
isLOA3,
isLOA1,
showLoader,
showValidateIdentityAlert,
showClaimsAndAppeals,
showHealthCare,
isVAPatient,
showNameTag,
hero,
totalDisabilityRating: state.totalRating?.totalDisabilityRating,
totalDisabilityRatingServerError: hasTotalDisabilityServerError(state),
user: state.user,
showMPIConnectionError,
showNotInMPIError,
showNotifications,
payments: state.allPayments?.payments || [],
};
};
Dashboard.propTypes = {
canAccessMilitaryHistory: PropTypes.bool,
canAccessPaymentHistory: PropTypes.bool,
canAccessRatingInfo: PropTypes.bool,
fetchFullName: PropTypes.func,
fetchMilitaryInformation: PropTypes.func,
fetchTotalDisabilityRating: PropTypes.func,
getPayments: PropTypes.func,
isLOA1: PropTypes.bool,
isLOA3: PropTypes.bool,
isVAPatient: PropTypes.bool,
payments: PropTypes.arrayOf(
PropTypes.shape({
payCheckAmount: PropTypes.string.isRequired,
payCheckDt: PropTypes.string.isRequired,
payCheckId: PropTypes.string.isRequired,
payCheckReturnFiche: PropTypes.string.isRequired,
payCheckType: PropTypes.string.isRequired,
paymentMethod: PropTypes.string.isRequired,
bankName: PropTypes.string.isRequired,
accountNumber: PropTypes.string.isRequired,
}),
),
showClaimsAndAppeals: PropTypes.bool,
showHealthCare: PropTypes.bool,
showLoader: PropTypes.bool,
showMPIConnectionError: PropTypes.bool,
showNameTag: PropTypes.bool,
showNotInMPIError: PropTypes.bool,
showNotifications: PropTypes.bool,
showValidateIdentityAlert: PropTypes.bool,
totalDisabilityRating: PropTypes.number,
totalDisabilityRatingServerError: PropTypes.bool,
user: PropTypes.object,
};
const mapDispatchToProps = {
fetchFullName: fetchHeroAction,
fetchMilitaryInformation: fetchMilitaryInformationAction,
fetchTotalDisabilityRating: fetchTotalDisabilityRatingAction,
getPayments: getAllPayments,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(Dashboard);