From c719091e37cec9caf02a549fc195fb4d7a5bd70e Mon Sep 17 00:00:00 2001 From: thelullabyy Date: Sun, 29 Jun 2025 22:49:11 +0700 Subject: [PATCH 1/3] fix: The 2FA page is not fully displayed --- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 75 ++++++++----------- .../BaseTwoFactorAuthForm.tsx | 1 + 2 files changed, 34 insertions(+), 42 deletions(-) diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index b9f6b75e346a..ba5230779c36 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -118,7 +118,6 @@ function BaseValidateCodeForm({ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case const shouldDisableResendValidateCode = !!isOffline || account?.isLoading; - const focusTimeoutRef = useRef(null); const [timeRemaining, setTimeRemaining] = useState(CONST.REQUEST_CODE_DELAY as number); const [canShowError, setCanShowError] = useState(false); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true}); @@ -126,18 +125,45 @@ function BaseValidateCodeForm({ const latestValidateCodeError = getLatestErrorField(validateCodeAction, validateCodeActionErrorField); const timerRef = useRef(undefined); + /** + * Check that all the form fields are valid, then trigger the submit callback + */ + const validateAndSubmitForm = useCallback(() => { + // Clear flow specific error + clearError(); + + // Clear "incorrect magic" code error + clearValidateCodeActionError(validateCodeActionErrorField); + setCanShowError(true); + if (!validateCode.trim()) { + setFormError({validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}); + return; + } + + if (!isValidValidateCode(validateCode)) { + setFormError({validateCode: 'validateCodeForm.error.incorrectMagicCode'}); + return; + } + + setFormError({}); + handleSubmitForm(validateCode); + }, [validateCode, handleSubmitForm, validateCodeActionErrorField, clearError]); + useImperativeHandle(innerRef, () => ({ + validateAndSubmitForm() { + validateAndSubmitForm(); + }, focus() { - inputValidateCodeRef.current?.focus(); + if (!inputValidateCodeRef.current) { + return; + } + inputValidateCodeRef.current.focus(); }, focusLastSelected() { if (!inputValidateCodeRef.current) { return; } - if (focusTimeoutRef.current) { - clearTimeout(focusTimeoutRef.current); - } - focusTimeoutRef.current = setTimeout(() => { + setTimeout(() => { inputValidateCodeRef.current?.focusLastSelected(); }, CONST.ANIMATED_TRANSITION); }, @@ -148,25 +174,14 @@ function BaseValidateCodeForm({ if (!inputValidateCodeRef.current) { return; } - if (focusTimeoutRef.current) { - clearTimeout(focusTimeoutRef.current); - } - // Keyboard won't show if we focus the input with a delay, so we need to focus immediately. if (!isMobileSafari()) { - focusTimeoutRef.current = setTimeout(() => { + setTimeout(() => { inputValidateCodeRef.current?.focusLastSelected(); }, CONST.ANIMATED_TRANSITION); } else { inputValidateCodeRef.current?.focusLastSelected(); } - - return () => { - if (!focusTimeoutRef.current) { - return; - } - clearTimeout(focusTimeoutRef.current); - }; }, []), ); @@ -216,30 +231,6 @@ function BaseValidateCodeForm({ [validateError, clearError, latestValidateCodeError, validateCodeActionErrorField], ); - /** - * Check that all the form fields are valid, then trigger the submit callback - */ - const validateAndSubmitForm = useCallback(() => { - // Clear flow specific error - clearError(); - - // Clear "incorrect magic" code error - clearValidateCodeActionError(validateCodeActionErrorField); - setCanShowError(true); - if (!validateCode.trim()) { - setFormError({validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}); - return; - } - - if (!isValidValidateCode(validateCode)) { - setFormError({validateCode: 'validateCodeForm.error.incorrectMagicCode'}); - return; - } - - setFormError({}); - handleSubmitForm(validateCode); - }, [validateCode, handleSubmitForm, validateCodeActionErrorField, clearError]); - const errorText = useMemo(() => { if (!canShowError) { return ''; diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx index a6eadc931e04..0f955e3b998b 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx @@ -88,6 +88,7 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo onFulfill={validateAndSubmitForm} errorText={formError.twoFactorAuthCode ?? getLatestErrorMessage(account)} ref={inputRef} + autoFocus={false} /> ); } From c5916a1af90ce679e699df95fd81cc22142a65ae Mon Sep 17 00:00:00 2001 From: thelullabyy Date: Mon, 30 Jun 2025 11:45:02 +0700 Subject: [PATCH 2/3] fix: keyboard auto open when 2FA page shown --- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 10 +++++++ .../BaseTwoFactorAuthForm.tsx | 29 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index ba5230779c36..d3b376abf814 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -118,6 +118,7 @@ function BaseValidateCodeForm({ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case const shouldDisableResendValidateCode = !!isOffline || account?.isLoading; + const focusTimeoutRef = useRef(null); const [timeRemaining, setTimeRemaining] = useState(CONST.REQUEST_CODE_DELAY as number); const [canShowError, setCanShowError] = useState(false); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE, {canBeMissing: true}); @@ -174,6 +175,9 @@ function BaseValidateCodeForm({ if (!inputValidateCodeRef.current) { return; } + if (focusTimeoutRef.current) { + clearTimeout(focusTimeoutRef.current); + } // Keyboard won't show if we focus the input with a delay, so we need to focus immediately. if (!isMobileSafari()) { setTimeout(() => { @@ -182,6 +186,12 @@ function BaseValidateCodeForm({ } else { inputValidateCodeRef.current?.focusLastSelected(); } + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; }, []), ); diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx index 0f955e3b998b..5caff43ab90b 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx @@ -1,12 +1,15 @@ +import {useFocusEffect} from '@react-navigation/native'; import React, {forwardRef, useCallback, useImperativeHandle, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {useOnyx} from 'react-native-onyx'; import type {AutoCompleteVariant, MagicCodeInputHandle} from '@components/MagicCodeInput'; import MagicCodeInput from '@components/MagicCodeInput'; import useLocalize from '@hooks/useLocalize'; +import {isMobileSafari} from '@libs/Browser'; import {getLatestErrorMessage} from '@libs/ErrorUtils'; import {isValidTwoFactorCode} from '@libs/ValidationUtils'; import {clearAccountMessages, toggleTwoFactorAuth, validateTwoFactorAuth} from '@userActions/Session'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {BaseTwoFactorAuthFormRef} from './types'; @@ -25,6 +28,7 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); const inputRef = useRef(null); const shouldClearData = account?.needsTwoFactorAuthSetup ?? false; + const focusTimeoutRef = useRef(null); /** * Handle text input and clear formError upon text change @@ -79,6 +83,31 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo }, })); + useFocusEffect( + useCallback(() => { + if (!inputRef.current) { + return; + } + if (focusTimeoutRef.current) { + clearTimeout(focusTimeoutRef.current); + } + // Keyboard won't show if we focus the input with a delay, so we need to focus immediately. + if (!isMobileSafari()) { + setTimeout(() => { + inputRef.current?.focusLastSelected(); + }, CONST.ANIMATED_TRANSITION); + } else { + inputRef.current?.focusLastSelected(); + } + return () => { + if (!focusTimeoutRef.current) { + return; + } + clearTimeout(focusTimeoutRef.current); + }; + }, []), + ); + return ( Date: Mon, 30 Jun 2025 15:53:34 +0700 Subject: [PATCH 3/3] update code --- .../ValidateCodeForm/BaseValidateCodeForm.tsx | 65 +++++++++---------- .../BaseTwoFactorAuthForm.tsx | 18 +++-- 2 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx index d3b376abf814..b9f6b75e346a 100644 --- a/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx +++ b/src/components/ValidateCodeActionModal/ValidateCodeForm/BaseValidateCodeForm.tsx @@ -126,45 +126,18 @@ function BaseValidateCodeForm({ const latestValidateCodeError = getLatestErrorField(validateCodeAction, validateCodeActionErrorField); const timerRef = useRef(undefined); - /** - * Check that all the form fields are valid, then trigger the submit callback - */ - const validateAndSubmitForm = useCallback(() => { - // Clear flow specific error - clearError(); - - // Clear "incorrect magic" code error - clearValidateCodeActionError(validateCodeActionErrorField); - setCanShowError(true); - if (!validateCode.trim()) { - setFormError({validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}); - return; - } - - if (!isValidValidateCode(validateCode)) { - setFormError({validateCode: 'validateCodeForm.error.incorrectMagicCode'}); - return; - } - - setFormError({}); - handleSubmitForm(validateCode); - }, [validateCode, handleSubmitForm, validateCodeActionErrorField, clearError]); - useImperativeHandle(innerRef, () => ({ - validateAndSubmitForm() { - validateAndSubmitForm(); - }, focus() { - if (!inputValidateCodeRef.current) { - return; - } - inputValidateCodeRef.current.focus(); + inputValidateCodeRef.current?.focus(); }, focusLastSelected() { if (!inputValidateCodeRef.current) { return; } - setTimeout(() => { + if (focusTimeoutRef.current) { + clearTimeout(focusTimeoutRef.current); + } + focusTimeoutRef.current = setTimeout(() => { inputValidateCodeRef.current?.focusLastSelected(); }, CONST.ANIMATED_TRANSITION); }, @@ -178,14 +151,16 @@ function BaseValidateCodeForm({ if (focusTimeoutRef.current) { clearTimeout(focusTimeoutRef.current); } + // Keyboard won't show if we focus the input with a delay, so we need to focus immediately. if (!isMobileSafari()) { - setTimeout(() => { + focusTimeoutRef.current = setTimeout(() => { inputValidateCodeRef.current?.focusLastSelected(); }, CONST.ANIMATED_TRANSITION); } else { inputValidateCodeRef.current?.focusLastSelected(); } + return () => { if (!focusTimeoutRef.current) { return; @@ -241,6 +216,30 @@ function BaseValidateCodeForm({ [validateError, clearError, latestValidateCodeError, validateCodeActionErrorField], ); + /** + * Check that all the form fields are valid, then trigger the submit callback + */ + const validateAndSubmitForm = useCallback(() => { + // Clear flow specific error + clearError(); + + // Clear "incorrect magic" code error + clearValidateCodeActionError(validateCodeActionErrorField); + setCanShowError(true); + if (!validateCode.trim()) { + setFormError({validateCode: 'validateCodeForm.error.pleaseFillMagicCode'}); + return; + } + + if (!isValidValidateCode(validateCode)) { + setFormError({validateCode: 'validateCodeForm.error.incorrectMagicCode'}); + return; + } + + setFormError({}); + handleSubmitForm(validateCode); + }, [validateCode, handleSubmitForm, validateCodeActionErrorField, clearError]); + const errorText = useMemo(() => { if (!canShowError) { return ''; diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx index 5caff43ab90b..e78fc6a947de 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx @@ -28,7 +28,6 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); const inputRef = useRef(null); const shouldClearData = account?.needsTwoFactorAuthSetup ?? false; - const focusTimeoutRef = useRef(null); /** * Handle text input and clear formError upon text change @@ -81,6 +80,14 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo } inputRef.current.focus(); }, + focusLastSelected() { + if (!inputRef.current) { + return; + } + setTimeout(() => { + inputRef.current?.focusLastSelected(); + }, CONST.ANIMATED_TRANSITION); + }, })); useFocusEffect( @@ -88,9 +95,6 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo if (!inputRef.current) { return; } - if (focusTimeoutRef.current) { - clearTimeout(focusTimeoutRef.current); - } // Keyboard won't show if we focus the input with a delay, so we need to focus immediately. if (!isMobileSafari()) { setTimeout(() => { @@ -99,12 +103,6 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo } else { inputRef.current?.focusLastSelected(); } - return () => { - if (!focusTimeoutRef.current) { - return; - } - clearTimeout(focusTimeoutRef.current); - }; }, []), );