diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index 66d2108ca5d8..2fb3e3167ff8 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -15,7 +15,7 @@ type AnimatedStepProps = ChildrenProps & { direction: AnimationDirection; /** Callback to fire when the animation ends */ - onAnimationEnd: () => void; + onAnimationEnd?: () => void; }; function AnimatedStep({onAnimationEnd, direction = CONST.ANIMATION_DIRECTION.IN, style, children}: AnimatedStepProps) { diff --git a/src/hooks/useWindowDimensions/index.native.ts b/src/hooks/useWindowDimensions/index.native.ts index f727a40b0c3b..8b420412cf24 100644 --- a/src/hooks/useWindowDimensions/index.native.ts +++ b/src/hooks/useWindowDimensions/index.native.ts @@ -12,6 +12,7 @@ export default function (): WindowDimensions { const isSmallScreenWidth = true; const isMediumScreenWidth = false; const isLargeScreenWidth = false; + const isExtraSmallScreenWidth = windowWidth <= variables.extraSmallMobileResponsiveWidthBreakpoint; const isSmallScreen = true; return { @@ -21,6 +22,7 @@ export default function (): WindowDimensions { isSmallScreenWidth, isMediumScreenWidth, isLargeScreenWidth, + isExtraSmallScreenWidth, isSmallScreen, }; } diff --git a/src/hooks/useWindowDimensions/index.ts b/src/hooks/useWindowDimensions/index.ts index 6e68e696ba89..e257b03e2865 100644 --- a/src/hooks/useWindowDimensions/index.ts +++ b/src/hooks/useWindowDimensions/index.ts @@ -24,6 +24,7 @@ export default function (useCachedViewportHeight = false): WindowDimensions { const isSmallScreenWidth = windowWidth <= variables.mobileResponsiveWidthBreakpoint; const isMediumScreenWidth = windowWidth > variables.mobileResponsiveWidthBreakpoint && windowWidth <= variables.tabletResponsiveWidthBreakpoint; const isLargeScreenWidth = windowWidth > variables.tabletResponsiveWidthBreakpoint; + const isExtraSmallScreenWidth = windowWidth <= variables.extraSmallMobileResponsiveWidthBreakpoint; const lowerScreenDimmension = Math.min(windowWidth, windowHeight); const isSmallScreen = lowerScreenDimmension <= variables.mobileResponsiveWidthBreakpoint; @@ -88,6 +89,7 @@ export default function (useCachedViewportHeight = false): WindowDimensions { isSmallScreenWidth, isMediumScreenWidth, isLargeScreenWidth, + isExtraSmallScreenWidth, isSmallScreen, }; } diff --git a/src/hooks/useWindowDimensions/types.ts b/src/hooks/useWindowDimensions/types.ts index d7a1fe41d187..fa74e03f185c 100644 --- a/src/hooks/useWindowDimensions/types.ts +++ b/src/hooks/useWindowDimensions/types.ts @@ -5,6 +5,7 @@ type WindowDimensions = { isSmallScreenWidth: boolean; isMediumScreenWidth: boolean; isLargeScreenWidth: boolean; + isExtraSmallScreenWidth: boolean; isSmallScreen: boolean; }; diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 6ffbb3a358b0..6790dd5f8f10 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -100,6 +100,10 @@ type WorkspaceSwitcherNavigatorParamList = { [SCREENS.WORKSPACE_SWITCHER.ROOT]: undefined; }; +type BackToParams = { + backTo?: Routes; +}; + type SettingsNavigatorParamList = { [SCREENS.SETTINGS.ROOT]: undefined; [SCREENS.SETTINGS.SHARE_CODE]: undefined; @@ -203,7 +207,7 @@ type SettingsNavigatorParamList = { [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; - [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: undefined; + [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: BackToParams; [SCREENS.SETTINGS.REPORT_CARD_LOST_OR_DAMAGED]: undefined; [SCREENS.KEYBOARD_SHORTCUTS]: undefined; [SCREENS.SETTINGS.EXIT_SURVEY.REASON]: undefined; @@ -632,4 +636,5 @@ export type { WorkspaceSwitcherNavigatorParamList, OnboardEngagementNavigatorParamList, SwitchPolicyIDParams, + BackToParams, }; diff --git a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js b/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.tsx similarity index 68% rename from src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js rename to src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.tsx index ba899a0e2d20..146bdbfff667 100644 --- a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.js +++ b/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.tsx @@ -6,19 +6,35 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useThemeStyles from '@hooks/useThemeStyles'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; -import StepWrapperPropTypes from './StepWrapperPropTypes'; +import type {StepCounterParams} from '@src/languages/types'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; + +type StepWrapperProps = ChildrenProps & { + /** Title of the Header */ + title?: string; + + /** Data to display a step counter in the header */ + stepCounter?: StepCounterParams; + + /** Method to trigger when pressing back button of the header */ + onBackButtonPress?: () => void; + + /** Called when navigated Screen's transition is finished. It does not fire when user exits the page. */ + onEntryTransitionEnd?: () => void; + + /** Flag to indicate if the keyboard avoiding view should be enabled */ + shouldEnableKeyboardAvoidingView?: boolean; +}; function StepWrapper({ title = '', - stepCounter = null, + stepCounter, onBackButtonPress = () => TwoFactorAuthActions.quitAndNavigateBack(), children = null, shouldEnableKeyboardAvoidingView = true, onEntryTransitionEnd, -}) { +}: StepWrapperProps) { const styles = useThemeStyles(); - const shouldShowStepCounter = Boolean(stepCounter); - const {animationDirection} = useAnimatedStepContext(); return ( @@ -34,7 +50,6 @@ function StepWrapper({ > @@ -44,7 +59,6 @@ function StepWrapper({ ); } -StepWrapper.propTypes = StepWrapperPropTypes; StepWrapper.displayName = 'StepWrapper'; export default StepWrapper; diff --git a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapperPropTypes.js b/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapperPropTypes.js deleted file mode 100644 index 3c06cc7bca52..000000000000 --- a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapperPropTypes.js +++ /dev/null @@ -1,28 +0,0 @@ -import PropTypes from 'prop-types'; - -export default { - /** Title of the Header */ - title: PropTypes.string, - - /** Data to display a step counter in the header */ - stepCounter: PropTypes.shape({ - /** Current step */ - step: PropTypes.number, - /** Total number of steps */ - total: PropTypes.number, - /** Text to display next to the step counter */ - text: PropTypes.string, - }), - - /** Method to trigger when pressing back button of the header */ - onBackButtonPress: PropTypes.func, - - /** Called when navigated Screen's transition is finished. It does not fire when user exits the page. */ - onEntryTransitionEnd: PropTypes.func, - - /** Children components */ - children: PropTypes.node, - - /** Flag to indicate if the keyboard avoiding view should be enabled */ - shouldEnableKeyboardAvoidingView: PropTypes.bool, -}; diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx similarity index 83% rename from src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js rename to src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx index aafa144e769f..d6c7a1abcd4f 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.js +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx @@ -1,7 +1,6 @@ import React, {useEffect, useState} from 'react'; import {ActivityIndicator, ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -16,15 +15,18 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import Clipboard from '@libs/Clipboard'; import localFileDownload from '@libs/localFileDownload'; +import type {BackToParams} from '@libs/Navigation/types'; import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; -import {defaultAccount, TwoFactorAuthPropTypes} from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthPropTypes'; +import type {BaseTwoFactorAuthFormOnyxProps} from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types'; import * as Session from '@userActions/Session'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -function CodesStep({account = defaultAccount, backTo}) { +type CodesStepProps = BaseTwoFactorAuthFormOnyxProps & BackToParams; + +function CodesStep({account, backTo}: CodesStepProps) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); @@ -34,7 +36,8 @@ function CodesStep({account = defaultAccount, backTo}) { const {setStep} = useTwoFactorAuthContext(); useEffect(() => { - if (account.requiresTwoFactorAuth || account.recoveryCodes) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (account?.requiresTwoFactorAuth || account?.recoveryCodes) { return; } Session.toggleTwoFactorAuth(true); @@ -63,15 +66,15 @@ function CodesStep({account = defaultAccount, backTo}) { {translate('twoFactorAuth.codesLoseAccess')} - {account.isLoading ? ( + {account?.isLoading ? ( ) : ( <> - {Boolean(account.recoveryCodes) && - _.map(account.recoveryCodes.split(', '), (code) => ( + {Boolean(account?.recoveryCodes) && + account?.recoveryCodes?.split(', ').map((code) => ( { - Clipboard.setString(account.recoveryCodes); + Clipboard.setString(account?.recoveryCodes ?? ''); setError(''); TwoFactorAuthActions.setCodesAreCopied(); }} styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} textStyles={[styles.buttonMediumText]} + accessible={false} + tooltipText="" + tooltipTextChecked="" /> { - localFileDownload('two-factor-auth-codes', account.recoveryCodes); + localFileDownload('two-factor-auth-codes', account?.recoveryCodes ?? ''); setError(''); TwoFactorAuthActions.setCodesAreCopied(); }} inline={false} styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} textStyles={[styles.buttonMediumText]} + accessible={false} + tooltipText="" + tooltipTextChecked="" /> @@ -112,7 +121,7 @@ function CodesStep({account = defaultAccount, backTo}) { - {!_.isEmpty(error) && ( + {Boolean(error) && ( { - if (!account.codesAreCopied) { + if (!account?.codesAreCopied) { setError('twoFactorAuth.errorStepCodes'); return; } @@ -136,10 +145,8 @@ function CodesStep({account = defaultAccount, backTo}) { ); } -CodesStep.propTypes = TwoFactorAuthPropTypes; CodesStep.displayName = 'CodesStep'; -// eslint-disable-next-line rulesdir/onyx-props-must-have-default -export default withOnyx({ +export default withOnyx({ account: {key: ONYXKEYS.ACCOUNT}, })(CodesStep); diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.js b/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx similarity index 100% rename from src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.js rename to src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.js b/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx similarity index 100% rename from src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.js rename to src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.js b/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx similarity index 83% rename from src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.js rename to src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx index de36888f30b8..5e92fb8649ef 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.js +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx @@ -1,24 +1,15 @@ -import PropTypes from 'prop-types'; import React from 'react'; import ConfirmationPage from '@components/ConfirmationPage'; import LottieAnimations from '@components/LottieAnimations'; import useLocalize from '@hooks/useLocalize'; +import type {BackToParams} from '@libs/Navigation/types'; import Navigation from '@navigation/Navigation'; import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; import CONST from '@src/CONST'; -const propTypes = { - /** The route where user needs to be redirected after setting up 2FA */ - backTo: PropTypes.string, -}; - -const defaultProps = { - backTo: '', -}; - -function SuccessStep({backTo}) { +function SuccessStep({backTo}: BackToParams) { const {setStep} = useTwoFactorAuthContext(); const {translate} = useLocalize(); @@ -49,7 +40,4 @@ function SuccessStep({backTo}) { ); } -SuccessStep.propTypes = propTypes; -SuccessStep.defaultProps = defaultProps; - export default SuccessStep; diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx similarity index 77% rename from src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js rename to src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx index e5f809204bd6..d9998c777f3b 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.js +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx @@ -1,11 +1,11 @@ -import PropTypes from 'prop-types'; -import React, {useEffect} from 'react'; +import React, {useEffect, useRef} from 'react'; import {ScrollView, View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import * as Expensicons from '@components/Icon/Expensicons'; +import {useSession} from '@components/OnyxProvider'; import PressableWithDelayToggle from '@components/Pressable/PressableWithDelayToggle'; import QRCode from '@components/QRCode'; import Text from '@components/Text'; @@ -16,25 +16,21 @@ import Clipboard from '@libs/Clipboard'; import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; import TwoFactorAuthForm from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm'; -import {defaultAccount, TwoFactorAuthPropTypes} from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthPropTypes'; +import type {BaseTwoFactorAuthFormOnyxProps, BaseTwoFactorAuthFormRef} from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; const TROUBLESHOOTING_LINK = 'https://community.expensify.com/discussion/7736/faq-troubleshooting-two-factor-authentication-issues/p1?new=1'; -const defaultProps = { - account: defaultAccount, - session: { - email: null, - }, -}; +type VerifyStepProps = BaseTwoFactorAuthFormOnyxProps; -function VerifyStep({account, session}) { +function VerifyStep({account}: VerifyStepProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const session = useSession(); - const formRef = React.useRef(null); + const formRef = useRef(null); const {setStep} = useTwoFactorAuthContext(); @@ -46,19 +42,16 @@ function VerifyStep({account, session}) { }, []); useEffect(() => { - if (!account.requiresTwoFactorAuth) { + if (!account?.requiresTwoFactorAuth) { return; } setStep(CONST.TWO_FACTOR_AUTH_STEPS.SUCCESS); - }, [account.requiresTwoFactorAuth, setStep]); + }, [account?.requiresTwoFactorAuth, setStep]); /** * Splits the two-factor auth secret key in 4 chunks - * - * @param {String} secret - * @returns {string} */ - function splitSecretInChunks(secret) { + function splitSecretInChunks(secret: string) { if (secret.length !== 16) { return secret; } @@ -69,11 +62,9 @@ function VerifyStep({account, session}) { /** * Builds the URL string to generate the QRCode, using the otpauth:// protocol, * so it can be detected by authenticator apps - * - * @returns {string} */ function buildAuthenticatorUrl() { - return `otpauth://totp/Expensify:${account.primaryLogin || session.email}?secret=${account.twoFactorAuthSecretKey}&issuer=Expensify`; + return `otpauth://totp/Expensify:${account?.primaryLogin ?? session?.email}?secret=${account?.twoFactorAuthSecretKey}&issuer=Expensify`; } return ( @@ -106,15 +97,18 @@ function VerifyStep({account, session}) { {translate('twoFactorAuth.addKey')} - {Boolean(account.twoFactorAuthSecretKey) && {splitSecretInChunks(account.twoFactorAuthSecretKey)}} + {Boolean(account?.twoFactorAuthSecretKey) && {splitSecretInChunks(account?.twoFactorAuthSecretKey ?? '')}} Clipboard.setString(account.twoFactorAuthSecretKey)} + onPress={() => Clipboard.setString(account?.twoFactorAuthSecretKey ?? '')} styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCopyCodeButton]} textStyles={[styles.buttonMediumText]} + accessible={false} /> {translate('twoFactorAuth.enterCode')} @@ -127,7 +121,7 @@ function VerifyStep({account, session}) {