diff --git a/.storybook/webpack.config.ts b/.storybook/webpack.config.ts index a7811a5a387d..0aa38f2e3b82 100644 --- a/.storybook/webpack.config.ts +++ b/.storybook/webpack.config.ts @@ -100,6 +100,11 @@ const webpackConfig = ({config}: {config: Configuration}) => { }), ); + config.module.rules?.push({ + test: /\.lottie$/, + type: 'asset/resource', + }); + return config; }; diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 6151f983e8d0..f9fd379d94ce 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -3,12 +3,13 @@ import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useStat import type {NativeEventSubscription} from 'react-native'; import {AppState, Linking, NativeModules} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import Onyx, {withOnyx} from 'react-native-onyx'; +import Onyx, {useOnyx, withOnyx} from 'react-native-onyx'; import ConfirmModal from './components/ConfirmModal'; import DeeplinkWrapper from './components/DeeplinkWrapper'; import EmojiPicker from './components/EmojiPicker/EmojiPicker'; import FocusModeNotification from './components/FocusModeNotification'; import GrowlNotification from './components/GrowlNotification'; +import RequireTwoFactorAuthenticationModal from './components/RequireTwoFactorAuthenticationModal'; import AppleAuthWrapper from './components/SignInButtons/AppleAuthWrapper'; import SplashScreenHider from './components/SplashScreenHider'; import UpdateAppModal from './components/UpdateAppModal'; @@ -37,6 +38,7 @@ import ONYXKEYS from './ONYXKEYS'; import PopoverReportActionContextMenu from './pages/home/report/ContextMenu/PopoverReportActionContextMenu'; import * as ReportActionContextMenu from './pages/home/report/ContextMenu/ReportActionContextMenu'; import type {Route} from './ROUTES'; +import ROUTES from './ROUTES'; import type {ScreenShareRequest, Session} from './types/onyx'; Onyx.registerLogger(({level, message}) => { @@ -101,6 +103,16 @@ function Expensify({ const [isSplashHidden, setIsSplashHidden] = useState(false); const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false); const {translate} = useLocalize(); + const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [shouldShowRequire2FAModal, setShouldShowRequire2FAModal] = useState(false); + + useEffect(() => { + if (!account?.needsTwoFactorAuthSetup || account.requiresTwoFactorAuth) { + return; + } + setShouldShowRequire2FAModal(true); + }, [account?.needsTwoFactorAuthSetup, account?.requiresTwoFactorAuth]); + const [initialUrl, setInitialUrl] = useState(null); useEffect(() => { @@ -253,6 +265,16 @@ function Expensify({ /> ) : null} {focusModeNotification ? : null} + {shouldShowRequire2FAModal ? ( + { + setShouldShowRequire2FAModal(false); + Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.HOME)); + }} + isVisible + description={translate('twoFactorAuth.twoFactorAuthIsRequiredForAdminsDescription')} + /> + ) : null} )} diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 2f3372144610..4e7f9247546b 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -198,7 +198,8 @@ const ROUTES = { }, SETTINGS_2FA: { route: 'settings/security/two-factor-auth', - getRoute: (backTo?: string) => getUrlWithBackToParam('settings/security/two-factor-auth', backTo), + getRoute: (backTo?: string, forwardTo?: string) => + getUrlWithBackToParam(forwardTo ? `settings/security/two-factor-auth?forwardTo=${encodeURIComponent(forwardTo)}` : 'settings/security/two-factor-auth', backTo), }, SETTINGS_STATUS: 'settings/profile/status', diff --git a/src/components/ConnectToXeroButton/index.native.tsx b/src/components/ConnectToXeroButton/index.native.tsx index 15fe201f2ac9..04b5f8722ea5 100644 --- a/src/components/ConnectToXeroButton/index.native.tsx +++ b/src/components/ConnectToXeroButton/index.native.tsx @@ -1,6 +1,6 @@ import React, {useRef, useState} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import {WebView} from 'react-native-webview'; import AccountingConnectionConfirmationModal from '@components/AccountingConnectionConfirmationModal'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; @@ -8,14 +8,17 @@ import Button from '@components/Button'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import Modal from '@components/Modal'; +import RequireTwoFactorAuthenticationModal from '@components/RequireTwoFactorAuthenticationModal'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import {removePolicyConnection} from '@libs/actions/connections'; import {getXeroSetupLink} from '@libs/actions/connections/ConnectToXero'; import getUAForWebView from '@libs/getUAForWebView'; +import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type {Session} from '@src/types/onyx'; import type {ConnectToXeroButtonProps} from './types'; @@ -33,13 +36,22 @@ function ConnectToXeroButton({policyID, session, shouldDisconnectIntegrationBefo const authToken = session?.authToken ?? null; const {isOffline} = useNetwork(); + const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const is2FAEnabled = account?.requiresTwoFactorAuth ?? false; + const renderLoading = () => ; const [isDisconnectModalOpen, setIsDisconnectModalOpen] = useState(false); + const [isRequire2FAModalOpen, setIsRequire2FAModalOpen] = useState(false); return ( <>