From 9e42b6ce0af6d209dc0234bd4f5e6375f3cb7538 Mon Sep 17 00:00:00 2001 From: Cong Pham Date: Sun, 27 Jul 2025 18:20:47 +0700 Subject: [PATCH 1/4] move 2fa code input out of the bottom section --- Mobile-Expensify | 2 +- src/components/MagicCodeInput.tsx | 7 ++++- .../BaseTwoFactorAuthForm.tsx | 6 ++++- .../TwoFactorAuth/TwoFactorAuthForm/index.tsx | 3 ++- .../TwoFactorAuth/TwoFactorAuthForm/types.ts | 3 +++ .../TwoFactorAuth/TwoFactorAuthWrapper.tsx | 17 +++++++++++- .../Security/TwoFactorAuth/VerifyPage.tsx | 26 +++++++++++++++---- src/styles/index.ts | 3 +++ 8 files changed, 57 insertions(+), 10 deletions(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 651d716772e87..369934c869e16 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 651d716772e87683cf7fa2a27423c18a6dc84a36 +Subproject commit 369934c869e16af3e6a42be424facda83d737e53 diff --git a/src/components/MagicCodeInput.tsx b/src/components/MagicCodeInput.tsx index c2e2f27bb4be1..fed3fe43fb991 100644 --- a/src/components/MagicCodeInput.tsx +++ b/src/components/MagicCodeInput.tsx @@ -79,6 +79,9 @@ type MagicCodeInputProps = { /** Function to call when the input is changed */ onChangeText?: (value: string) => void; + /** Callback that is called when the text input is focused */ + onFocus?: () => void; + /** Function to call when the input is submitted or fully complete */ onFulfill?: (value: string) => void; @@ -137,6 +140,7 @@ function MagicCodeInput( errorText = '', shouldSubmitOnComplete = true, onChangeText: onChangeTextProp = () => {}, + onFocus: onFocusProps, maxLength = CONST.MAGIC_CODE_LENGTH, onFulfill = () => {}, isDisableKeyboard = false, @@ -248,6 +252,7 @@ function MagicCodeInput( lastValue.current = TEXT_INPUT_EMPTY_STATE; setInputAndIndex(lastFocusedIndex.current); } + onFocusProps?.(); event.preventDefault(); }; @@ -471,7 +476,7 @@ function MagicCodeInput( inputStyle={[styles.inputTransparent]} role={CONST.ROLE.PRESENTATION} style={[styles.inputTransparent]} - textInputContainerStyles={[styles.borderNone, styles.bgTransparent]} + textInputContainerStyles={[styles.borderTransparent, styles.bgTransparent]} testID={testID} /> diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx index 4338514df8da8..e0f5040acabce 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx @@ -19,9 +19,12 @@ type BaseTwoFactorAuthFormProps = { // Set this to true in order to call the validateTwoFactorAuth action which is used when setting up 2FA for the first time. // Set this to false in order to disable 2FA when a valid code is entered. validateInsteadOfDisable?: boolean; + + /** Callback that is called when the text input is focused */ + onFocus?: () => void; }; -function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwoFactorAuthFormProps, ref: ForwardedRef) { +function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable, onFocus}: BaseTwoFactorAuthFormProps, ref: ForwardedRef) { const {translate} = useLocalize(); const [formError, setFormError] = useState<{twoFactorAuthCode?: string}>({}); const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false}); @@ -112,6 +115,7 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo name="twoFactorAuthCode" value={twoFactorAuthCode} onChangeText={onTextInput} + onFocus={onFocus} onFulfill={validateAndSubmitForm} errorText={formError.twoFactorAuthCode ?? getLatestErrorMessage(account)} ref={inputRef} diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/index.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/index.tsx index f72da5d873a0f..66e84cba8a604 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/index.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/index.tsx @@ -2,12 +2,13 @@ import React from 'react'; import BaseTwoFactorAuthForm from './BaseTwoFactorAuthForm'; import type {TwoFactorAuthFormProps} from './types'; -function TwoFactorAuthForm({innerRef, validateInsteadOfDisable}: TwoFactorAuthFormProps) { +function TwoFactorAuthForm({innerRef, validateInsteadOfDisable, onFocus}: TwoFactorAuthFormProps) { return ( ); } diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts index 1f999089977fc..5ecb9c60fa0f3 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts @@ -11,6 +11,9 @@ type TwoFactorAuthFormProps = { // Set this to true in order to call the validateTwoFactorAuth action which is used when setting up 2FA for the first time. // Set this to false in order to disable 2FA when a valid code is entered. validateInsteadOfDisable?: boolean; + + /** Callback that is called when the text input is focused */ + onFocus?: () => void; }; export type {TwoFactorAuthFormProps, BaseTwoFactorAuthFormRef}; diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthWrapper.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthWrapper.tsx index 60774bb0c121c..4154556852ff8 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthWrapper.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthWrapper.tsx @@ -6,6 +6,7 @@ import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useOnyx from '@hooks/useOnyx'; +import useViewportOffsetTop from '@hooks/useViewportOffsetTop'; import {quitAndNavigateBack} from '@libs/actions/TwoFactorAuthActions'; import CONST from '@src/CONST'; import type {StepCounterParams} from '@src/languages/params'; @@ -28,9 +29,20 @@ type TwoFactorAuthWrapperProps = ChildrenProps & { /** Flag to indicate if the keyboard avoiding view should be enabled */ shouldEnableKeyboardAvoidingView?: boolean; + + /** Flag to indicate if the viewport offset top should be enabled */ + shouldEnableViewportOffsetTop?: boolean; }; -function TwoFactorAuthWrapper({stepName, title, stepCounter, onBackButtonPress, shouldEnableKeyboardAvoidingView = true, children}: TwoFactorAuthWrapperProps) { +function TwoFactorAuthWrapper({ + stepName, + title, + stepCounter, + onBackButtonPress, + shouldEnableKeyboardAvoidingView = true, + shouldEnableViewportOffsetTop = false, + children, +}: TwoFactorAuthWrapperProps) { const [account] = useOnyx(ONYXKEYS.ACCOUNT); const isActingAsDelegate = !!account?.delegatedAccess?.delegate; @@ -58,6 +70,8 @@ function TwoFactorAuthWrapper({stepName, title, stepCounter, onBackButtonPress, } }, [account, stepName]); + const viewportOffsetTop = useViewportOffsetTop(); + if (isActingAsDelegate) { return ( (null); + const handleInputFocus = useCallback(() => { + InteractionManager.runAfterInteractions(() => { + requestAnimationFrame(() => { + scrollViewRef.current?.scrollToEnd({animated: true}); + }); + }); + }, []); + return ( Navigation.goBack(ROUTES.SETTINGS_2FA_ROOT.getRoute(route.params?.backTo, route.params?.forwardTo))} + shouldEnableViewportOffsetTop > @@ -116,11 +129,14 @@ function VerifyPage({route}: VerifyPageProps) { {translate('twoFactorAuth.enterCode')} + + + - - -