From c5eafa2a5e9ac9b2f8c23c764c924434259e2640 Mon Sep 17 00:00:00 2001 From: Daniel Jordan Date: Thu, 20 Feb 2025 21:54:55 +0000 Subject: [PATCH 1/4] initial commit, looks pretty good but need to double check a few things --- .envrc | 2 +- package.json | 2 + .../OrdersInfoForm/OrdersInfoForm.jsx | 87 ++- .../OrdersInfoForm/OrdersInfoForm.test.jsx | 90 ++- .../WizardNavigation.module.scss | 5 + .../LoadingSpinner/LoadingSpinner.jsx | 24 + .../LoadingSpinner/LoadingSpinner.module.scss | 27 + .../LoadingSpinner/LoadingSpinner.test.jsx | 24 + src/scenes/MyMove/index.jsx | 628 ++++++++---------- src/scenes/MyMove/index.test.js | 254 ------- src/scenes/MyMove/index.test.jsx | 150 +++++ src/store/auth/selectors.js | 8 + src/store/general/action.test.js | 19 +- src/store/general/actions.js | 10 + src/store/general/reducer.js | 11 +- src/store/general/reducer.test.js | 10 +- yarn.lock | 111 +++- 17 files changed, 796 insertions(+), 666 deletions(-) create mode 100644 src/components/LoadingSpinner/LoadingSpinner.jsx create mode 100644 src/components/LoadingSpinner/LoadingSpinner.module.scss create mode 100644 src/components/LoadingSpinner/LoadingSpinner.test.jsx delete mode 100644 src/scenes/MyMove/index.test.js create mode 100644 src/scenes/MyMove/index.test.jsx diff --git a/.envrc b/.envrc index 32fd448864a..5192706c5d5 100644 --- a/.envrc +++ b/.envrc @@ -154,7 +154,7 @@ export FEATURE_FLAG_NTS=true export FEATURE_FLAG_NTSR=true export FEATURE_FLAG_BOAT=true export FEATURE_FLAG_MOBILE_HOME=true -export FEATURE_FLAG_UNACCOMPANIED_BAGGAGE=false +export FEATURE_FLAG_UNACCOMPANIED_BAGGAGE=true # Feature flag to allow Bulk Assigment options to be displayed export FEATURE_FLAG_BULK_ASSIGNMENT=true diff --git a/package.json b/package.json index 77eeb2bcc59..47bd74802d8 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "react-filepond": "^7.1.2", "react-idle-timer": "^5.7.2", "react-imask": "^7.6.1", + "react-loader-spinner": "^6.1.6", "react-markdown": "^8.0.7", "react-query": "^3.39.2", "react-rangeslider": "^2.2.0", @@ -93,6 +94,7 @@ "loader-utils": "^2.0.3", "minimist": "^1.2.6", "node-fetch": "^2.6.7", + "pdfjs-dist": "4.8.69", "react-router": "6.24.1", "react-router-dom": "6.24.1", "recursive-readdir": "^2.2.3", diff --git a/src/components/Customer/OrdersInfoForm/OrdersInfoForm.jsx b/src/components/Customer/OrdersInfoForm/OrdersInfoForm.jsx index 51ca8552b27..704d3db9953 100644 --- a/src/components/Customer/OrdersInfoForm/OrdersInfoForm.jsx +++ b/src/components/Customer/OrdersInfoForm/OrdersInfoForm.jsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { Formik, Field } from 'formik'; import * as Yup from 'yup'; import { Radio, FormGroup, Label, Link as USWDSLink } from '@trussworks/react-uswds'; +import { connect } from 'react-redux'; import { isBooleanFlagEnabled } from '../../../utils/featureFlags'; import { FEATURE_FLAG_KEYS } from '../../../shared/constants'; @@ -23,10 +24,13 @@ import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigat import Callout from 'components/Callout'; import { formatLabelReportByDate, dropdownInputOptions } from 'utils/formatters'; import { showCounselingOffices } from 'services/internalApi'; +import { setShowLoadingSpinner as setShowLoadingSpinnerAction } from 'store/general/actions'; +import retryPageLoading from 'utils/retryPageLoading'; +import { milmoveLogger } from 'utils/milmoveLog'; let originMeta; let newDutyMeta = ''; -const OrdersInfoForm = ({ ordersTypeOptions, initialValues, onSubmit, onBack }) => { +const OrdersInfoForm = ({ ordersTypeOptions, initialValues, onSubmit, onBack, setShowLoadingSpinner }) => { const payGradeOptions = dropdownInputOptions(ORDERS_PAY_GRADE_OPTIONS); const [currentDutyLocation, setCurrentDutyLocation] = useState(''); const [newDutyLocation, setNewDutyLocation] = useState(''); @@ -68,6 +72,7 @@ const OrdersInfoForm = ({ ordersTypeOptions, initialValues, onSubmit, onBack }) ? Yup.number().min(0).required('Required') : Yup.number().notRequired(), }); + useEffect(() => { // Functional component version of "componentDidMount" // By leaving the dependency array empty this will only run once @@ -79,37 +84,55 @@ const OrdersInfoForm = ({ ordersTypeOptions, initialValues, onSubmit, onBack }) }; checkUBFeatureFlag(); }, []); + useEffect(() => { - // If current duty location is defined, show the counseling offices - if (currentDutyLocation?.id) { - showCounselingOffices(currentDutyLocation.id).then((fetchedData) => { - if (fetchedData.body) { - const counselingOffices = fetchedData.body.map((item) => ({ - key: item.id, - value: item.name, - })); - setCounselingOfficeOptions(counselingOffices); + const fetchCounselingOffices = async () => { + if (currentDutyLocation?.id && !counselingOfficeOptions) { + setShowLoadingSpinner(true, 'Loading counseling offices'); + try { + const fetchedData = await showCounselingOffices(currentDutyLocation.id); + if (fetchedData.body) { + const counselingOffices = fetchedData.body.map((item) => ({ + key: item.id, + value: item.name, + })); + setCounselingOfficeOptions(counselingOffices); + } + } catch (error) { + const { message } = error; + milmoveLogger.error({ message, info: null }); + retryPageLoading(error); } - }); - } - // Check if either currentDutyLocation or newDutyLocation is OCONUS - if (currentDutyLocation?.address?.isOconus || newDutyLocation?.address?.isOconus) { - setIsOconusMove(true); - } else { - setIsOconusMove(false); - } - if (currentDutyLocation?.address && newDutyLocation?.address && enableUB) { - // Only if one of the duty locations is OCONUS should accompanied tour and dependent - // age fields display - if (isOconusMove && hasDependents) { - setShowAccompaniedTourField(true); - setShowDependentAgeFields(true); + setShowLoadingSpinner(false, null); + } + + // Check if either currentDutyLocation or newDutyLocation is OCONUS + if (currentDutyLocation?.address?.isOconus || newDutyLocation?.address?.isOconus) { + setIsOconusMove(true); } else { - setShowAccompaniedTourField(false); - setShowDependentAgeFields(false); + setIsOconusMove(false); } - } - }, [currentDutyLocation, newDutyLocation, isOconusMove, hasDependents, enableUB]); + + if (currentDutyLocation?.address && newDutyLocation?.address && enableUB) { + if (isOconusMove && hasDependents) { + setShowAccompaniedTourField(true); + setShowDependentAgeFields(true); + } else { + setShowAccompaniedTourField(false); + setShowDependentAgeFields(false); + } + } + }; + fetchCounselingOffices(); + }, [ + currentDutyLocation, + newDutyLocation, + isOconusMove, + hasDependents, + enableUB, + setShowLoadingSpinner, + counselingOfficeOptions, + ]); useEffect(() => { const fetchData = async () => { @@ -441,7 +464,7 @@ OrdersInfoForm.propTypes = { issue_date: PropTypes.string, report_by_date: PropTypes.string, has_dependents: PropTypes.string, - new_duty_location: PropTypes.shape({}), + new_duty_location: DutyLocationShape, grade: PropTypes.string, origin_duty_location: DutyLocationShape, dependents_under_twelve: PropTypes.string, @@ -453,4 +476,8 @@ OrdersInfoForm.propTypes = { onBack: PropTypes.func.isRequired, }; -export default OrdersInfoForm; +const mapDispatchToProps = { + setShowLoadingSpinner: setShowLoadingSpinnerAction, +}; + +export default connect(() => ({}), mapDispatchToProps)(OrdersInfoForm); diff --git a/src/components/Customer/OrdersInfoForm/OrdersInfoForm.test.jsx b/src/components/Customer/OrdersInfoForm/OrdersInfoForm.test.jsx index f9a676707be..05c413649b7 100644 --- a/src/components/Customer/OrdersInfoForm/OrdersInfoForm.test.jsx +++ b/src/components/Customer/OrdersInfoForm/OrdersInfoForm.test.jsx @@ -1,6 +1,7 @@ import React from 'react'; import { render, waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { Provider } from 'react-redux'; import { isBooleanFlagEnabled } from '../../../utils/featureFlags'; @@ -8,6 +9,7 @@ import OrdersInfoForm from './OrdersInfoForm'; import { showCounselingOffices } from 'services/internalApi'; import { ORDERS_TYPE, ORDERS_TYPE_OPTIONS } from 'constants/orders'; +import { configureStore } from 'shared/store'; jest.setTimeout(60000); @@ -195,9 +197,15 @@ const testProps = { ], }; +const mockStore = configureStore({}); + describe('OrdersInfoForm component', () => { it('renders the form inputs', async () => { - const { getByLabelText } = render(); + const { getByLabelText } = render( + + + , + ); await waitFor(() => { expect(getByLabelText(/Orders type/)).toBeInstanceOf(HTMLSelectElement); @@ -218,7 +226,11 @@ describe('OrdersInfoForm component', () => { isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); showCounselingOffices.mockImplementation(() => Promise.resolve({})); - const { getByLabelText } = render(); + const { getByLabelText } = render( + + + , + ); const ordersTypeDropdown = getByLabelText(/Orders type/); expect(ordersTypeDropdown).toBeInstanceOf(HTMLSelectElement); @@ -246,7 +258,11 @@ describe('OrdersInfoForm component', () => { }); it('allows new and current duty location to be the same', async () => { - render(); + render( + + + , + ); await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); @@ -275,7 +291,11 @@ describe('OrdersInfoForm component', () => { }); it('shows an error message if trying to submit an invalid form', async () => { - const { getByRole, getAllByTestId } = render(); + const { getByRole, getAllByTestId } = render( + + + , + ); // Touch required fields to show validation errors await userEvent.click(screen.getByLabelText(/Orders type/)); @@ -317,7 +337,11 @@ describe('OrdersInfoForm component', () => { ], }; - render(); + render( + + + , + ); await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); @@ -361,8 +385,11 @@ describe('OrdersInfoForm component', () => { ], }; - render(); - + render( + + + , + ); await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); await userEvent.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); @@ -381,7 +408,11 @@ describe('OrdersInfoForm component', () => { }); it('submits the form when its valid', async () => { - render(); + render( + + + , + ); await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); @@ -455,8 +486,11 @@ describe('OrdersInfoForm component', () => { }); it('submits the form when temporary duty orders type is selected', async () => { - render(); - + render( + + + , + ); await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.TEMPORARY_DUTY); await userEvent.type(screen.getByLabelText(/Orders date/), '28 Oct 2024'); await userEvent.type(screen.getByLabelText(/Report by date/), '28 Oct 2024'); @@ -522,7 +556,11 @@ describe('OrdersInfoForm component', () => { }); it('implements the onBack handler when the Back button is clicked', async () => { - const { getByRole } = render(); + const { getByRole } = render( + + + , + ); const backBtn = getByRole('button', { name: 'Back' }); await userEvent.click(backBtn); @@ -576,7 +614,9 @@ describe('OrdersInfoForm component', () => { it('pre-fills the inputs', async () => { const { getByRole, queryByText, getByLabelText } = render( - , + + + , ); await waitFor(() => { @@ -598,7 +638,11 @@ describe('OrdersInfoForm component', () => { }); it('has dependents is yes and disabled when order type is student travel', async () => { - render(); + render( + + + , + ); await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.STUDENT_TRAVEL); @@ -613,7 +657,11 @@ describe('OrdersInfoForm component', () => { }); it('has dependents is yes and disabled when order type is early return', async () => { - render(); + render( + + + , + ); await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); @@ -628,8 +676,11 @@ describe('OrdersInfoForm component', () => { }); it('has dependents becomes disabled and then re-enabled for order type student travel', async () => { - render(); - + render( + + + , + ); // set order type to perm change and verify the "has dependents" state await userEvent.selectOptions(screen.getByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); @@ -661,8 +712,11 @@ describe('OrdersInfoForm component', () => { }); it('has dependents becomes disabled and then re-enabled for order type early return', async () => { - render(); - + render( + + + , + ); // set order type to perm change and verify the "has dependents" state await userEvent.selectOptions(screen.getByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); diff --git a/src/components/Customer/WizardNavigation/WizardNavigation.module.scss b/src/components/Customer/WizardNavigation/WizardNavigation.module.scss index 5c4bb2514fe..7ff53c922ba 100644 --- a/src/components/Customer/WizardNavigation/WizardNavigation.module.scss +++ b/src/components/Customer/WizardNavigation/WizardNavigation.module.scss @@ -1,5 +1,6 @@ @import 'shared/styles/colors'; @import 'shared/styles/_basics'; +@import 'shared/styles/_variables'; .WizardNavigation { display: flex; @@ -15,6 +16,10 @@ > .button + .button { @include u-margin-top(0); @include u-margin-left('105'); + + @media (max-width: $tablet) { + margin-left: 0; + } } *:last-child { diff --git a/src/components/LoadingSpinner/LoadingSpinner.jsx b/src/components/LoadingSpinner/LoadingSpinner.jsx new file mode 100644 index 00000000000..d658ac919fd --- /dev/null +++ b/src/components/LoadingSpinner/LoadingSpinner.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Oval } from 'react-loader-spinner'; + +import styles from './LoadingSpinner.module.scss'; + +const LoadingSpinner = ({ message }) => ( +
+
+ +

{message || 'Loading, please wait...'}

+
+
+); + +LoadingSpinner.propTypes = { + message: PropTypes.string, +}; + +LoadingSpinner.defaultProps = { + message: '', +}; + +export default LoadingSpinner; diff --git a/src/components/LoadingSpinner/LoadingSpinner.module.scss b/src/components/LoadingSpinner/LoadingSpinner.module.scss new file mode 100644 index 00000000000..77b8b5d7786 --- /dev/null +++ b/src/components/LoadingSpinner/LoadingSpinner.module.scss @@ -0,0 +1,27 @@ +.container { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(255, 255, 255, 0.9); + z-index: 9999; + flex-direction: column; +} + +.spinnerWrapper { + display: flex; + flex-direction: column; + align-items: center; +} + +.message { + margin-top: 1rem; + font-size: 1.2rem; + color: #333; + text-align: center; + font-weight: bold; +} \ No newline at end of file diff --git a/src/components/LoadingSpinner/LoadingSpinner.test.jsx b/src/components/LoadingSpinner/LoadingSpinner.test.jsx new file mode 100644 index 00000000000..a698275056c --- /dev/null +++ b/src/components/LoadingSpinner/LoadingSpinner.test.jsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import LoadingSpinner from './LoadingSpinner'; + +describe('LoadingSpinner Component', () => { + test('renders the loading spinner with default message', () => { + render(); + + const spinner = screen.getByTestId('loading-spinner'); + expect(spinner).toBeInTheDocument(); + + expect(screen.getByText('Loading, please wait...')).toBeInTheDocument(); + }); + + test('renders the loading spinner with a custom message', () => { + const customMessage = 'Fetching data...'; + render(); + + expect(screen.getByTestId('loading-spinner')).toBeInTheDocument(); + + expect(screen.getByText(customMessage)).toBeInTheDocument(); + }); +}); diff --git a/src/scenes/MyMove/index.jsx b/src/scenes/MyMove/index.jsx index cd11158f72a..1c40c635d68 100644 --- a/src/scenes/MyMove/index.jsx +++ b/src/scenes/MyMove/index.jsx @@ -1,4 +1,4 @@ -import React, { Component, lazy } from 'react'; +import React, { lazy, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Route, Routes, Navigate } from 'react-router-dom'; import { isBooleanFlagEnabled } from '../../utils/featureFlags'; @@ -10,9 +10,6 @@ import 'styles/customer.scss'; import { getWorkflowRoutes } from './getWorkflowRoutes'; -// Logger -import { milmoveLogger } from 'utils/milmoveLog'; -import { retryPageLoading } from 'utils/retryPageLoading'; import BypassBlock from 'components/BypassBlock'; import CUIHeader from 'components/CUIHeader/CUIHeader'; import LoggedOutHeader from 'containers/Headers/LoggedOutHeader'; @@ -21,7 +18,6 @@ import Alert from 'shared/Alert'; import Footer from 'components/Customer/Footer'; import ConnectedLogoutOnInactivity from 'layout/LogoutOnInactivity'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; -import SomethingWentWrong from 'shared/SomethingWentWrong'; import { loadInternalSchema } from 'shared/Swagger/ducks'; import { withContext } from 'shared/AppContext'; import { no_op } from 'shared/utils'; @@ -32,9 +28,10 @@ import { selectCacValidated, selectGetCurrentUserIsLoading, selectIsLoggedIn, + selectLoadingSpinnerMessage, + selectShowLoadingSpinner, selectUnderMaintenance, } from 'store/auth/selectors'; -import { selectConusStatus } from 'store/onboarding/selectors'; import { selectServiceMemberFromLoggedInUser, selectCurrentMove, @@ -59,6 +56,7 @@ import UploadOrders from 'pages/MyMove/UploadOrders'; import SmartCardRedirect from 'shared/SmartCardRedirect/SmartCardRedirect'; import OktaErrorBanner from 'components/OktaErrorBanner/OktaErrorBanner'; import MaintenancePage from 'pages/Maintenance/MaintenancePage'; +import LoadingSpinner from 'components/LoadingSpinner/LoadingSpinner'; // Pages should be lazy-loaded (they correspond to unique routes & only need to be loaded when that URL is accessed) const SignIn = lazy(() => import('pages/SignIn/SignIn')); const InvalidPermissions = lazy(() => import('pages/InvalidPermissions/InvalidPermissions')); @@ -89,358 +87,283 @@ const PPMFinalCloseout = lazy(() => import('pages/MyMove/PPM/Closeout/FinalClose const AdditionalDocuments = lazy(() => import('pages/MyMove/AdditionalDocuments/AdditionalDocuments')); const PPMFeedback = lazy(() => import('pages/MyMove/PPM/Closeout/Feedback/Feedback')); -export class CustomerApp extends Component { - constructor(props) { - super(props); - - this.state = { - hasError: false, - error: undefined, - info: undefined, - multiMoveFeatureFlag: false, - cacValidatedFeatureFlag: false, - validationCodeRequired: false, - oktaErrorBanner: false, - }; - } - - componentDidMount() { - const { loadUser, initOnboarding, loadInternalSchema } = this.props; +const CustomerApp = ({ loadUser, initOnboarding, loadInternalSchema, ...props }) => { + const [multiMoveFeatureFlag, setMultiMoveFeatureFlag] = useState(false); + const [cacValidatedFeatureFlag, setCacValidatedFeatureFlag] = useState(false); + const [oktaErrorBanner, setOktaErrorBanner] = useState(false); + useEffect(() => { loadInternalSchema(); loadUser(); initOnboarding(); - isBooleanFlagEnabled('multi_move').then((enabled) => { - this.setState({ - multiMoveFeatureFlag: enabled, - }); - }); - isBooleanFlagEnabled('cac_validated_login').then((enabled) => { - this.setState({ - cacValidatedFeatureFlag: enabled, - }); - }); - isBooleanFlagEnabled('validation_code_required').then((enabled) => { - this.setState({ - validationCodeRequired: enabled, - }); - }); - // if the params "okta_error=true" are appended to the url, then we need to change state to display a banner - // this occurs when a user is trying to use an office user's email to access the customer application - // Okta config rules do not allow the same email to be used for both office & customer apps - const currentUrl = new URL(window.location.href); - const oktaErrorParam = currentUrl.searchParams.get('okta_error'); - if (oktaErrorParam === 'true') { - this.setState({ - oktaErrorBanner: true, - }); + + isBooleanFlagEnabled('multi_move').then(setMultiMoveFeatureFlag); + isBooleanFlagEnabled('cac_validated_login').then(setCacValidatedFeatureFlag); + + const urlParams = new URLSearchParams(window.location.search); + if (urlParams.get('okta_error') === 'true') { + setOktaErrorBanner(true); } document.title = generatePageTitle('Sign In'); - } + }, [loadUser, initOnboarding, loadInternalSchema]); - componentDidCatch(error, info) { - const { message } = error; - milmoveLogger.error({ message, info }); - this.setState({ - hasError: true, - error, - info, - }); - retryPageLoading(error); + if (props.underMaintenance) { + return ; } - render() { - const { props } = this; - const { userIsLoggedIn, loginIsLoading, cacValidated, underMaintenance } = props; - const { hasError, multiMoveFeatureFlag, cacValidatedFeatureFlag, oktaErrorBanner } = this.state; - const script = document.createElement('script'); - - script.src = '//rum-static.pingdom.net/pa-6567b05deff3250012000426.js'; - script.async = true; - document.body.appendChild(script); - - if (underMaintenance) { - return ; - } - - return ( - <> -
- - - - - - {userIsLoggedIn ? : } - -
- - -
- {props.swaggerError && ( -
-
-
- - There was an error contacting the server. - -
+ return ( + <> +
+ + + + + + {props.userIsLoggedIn ? : } + +
+ + +
+ {props.swaggerError && ( +
+
+
+ + There was an error contacting the server. +
- )} -
- - {oktaErrorBanner && } - - {hasError && } - - {/* Showing Smart Card info page until user signs in with SC one time */} - {userIsLoggedIn && !cacValidated && cacValidatedFeatureFlag && } - - {/* No Auth Routes */} - {!userIsLoggedIn && ( - - } /> - } /> - } /> - -

You are forbidden to use this endpoint

-
- } - /> - -

We are experiencing an internal server error

-
- } - /> - } /> - ) || } - /> - +
)} - - {/* when the cacValidated feature flag is on, we need to check for the cacValidated value for rendering */} - {cacValidatedFeatureFlag - ? !hasError && - !props.swaggerError && - userIsLoggedIn && - cacValidated && ( - - {/* no auth routes should still exist */} - } /> - } /> - } /> - } /> - - {/* auth required */} - {/* } /> */} - - {/* ROOT */} - {/* If multiMove is enabled home page will route to dashboard element. Otherwise, it will route to the move page. */} - {multiMoveFeatureFlag ? ( - } /> - ) : ( - } /> - )} - - {getWorkflowRoutes(props)} - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } - /> - } - /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - - {/* Errors */} - -

You are forbidden to use this endpoint

-
- } - /> - -

We are experiencing an internal server error

-
- } - /> - } /> - - {/* 404 - user logged in but at unknown route */} - } /> - - ) - : !hasError && - !props.swaggerError && - userIsLoggedIn && ( - - {/* no auth routes should still exist */} - } /> - } /> - } /> - } /> - - {/* auth required */} - {/* } /> */} - - {/* ROOT */} - {/* If multiMove is enabled home page will route to dashboard element. Otherwise, it will route to the move page. */} - {multiMoveFeatureFlag ? ( - } /> - ) : ( - } /> - )} - - {getWorkflowRoutes(props)} - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } - /> - } - /> - } - /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - } /> - } /> - } /> - - {/* Errors */} - -

You are forbidden to use this endpoint

-
- } - /> - -

We are experiencing an internal server error

- - } - /> - } /> - - {/* 404 - user logged in but at unknown route */} - } /> - - )} - -