Skip to content

Commit 52d011c

Browse files
authored
Merge pull request Expensify#67762 from suneox/65896-magic-code-input
move 2fa code input out of the bottom section
2 parents d057afc + 6813329 commit 52d011c

7 files changed

Lines changed: 72 additions & 27 deletions

File tree

src/components/MagicCodeInput.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ type MagicCodeInputProps = {
7979
/** Function to call when the input is changed */
8080
onChangeText?: (value: string) => void;
8181

82+
/** Callback that is called when the text input is focused */
83+
onFocus?: () => void;
84+
8285
/** Function to call when the input is submitted or fully complete */
8386
onFulfill?: (value: string) => void;
8487

@@ -137,6 +140,7 @@ function MagicCodeInput(
137140
errorText = '',
138141
shouldSubmitOnComplete = true,
139142
onChangeText: onChangeTextProp = () => {},
143+
onFocus: onFocusProps,
140144
maxLength = CONST.MAGIC_CODE_LENGTH,
141145
onFulfill = () => {},
142146
isDisableKeyboard = false,
@@ -257,6 +261,7 @@ function MagicCodeInput(
257261
lastValue.current = TEXT_INPUT_EMPTY_STATE;
258262
setInputAndIndex(lastFocusedIndex.current);
259263
}
264+
onFocusProps?.();
260265
event.preventDefault();
261266
};
262267

@@ -480,7 +485,7 @@ function MagicCodeInput(
480485
inputStyle={[styles.inputTransparent]}
481486
role={CONST.ROLE.PRESENTATION}
482487
style={[styles.inputTransparent]}
483-
textInputContainerStyles={[styles.borderNone, styles.bgTransparent]}
488+
textInputContainerStyles={[styles.borderTransparent, styles.bgTransparent]}
484489
testID={testID}
485490
/>
486491
</View>

src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,15 @@ type BaseTwoFactorAuthFormProps = {
2121
// Set this to false in order to disable 2FA when a valid code is entered.
2222
validateInsteadOfDisable?: boolean;
2323

24+
/** Callback that is called when the text input is focused */
25+
onFocus?: () => void;
26+
2427
shouldAutoFocusOnMobile?: boolean;
2528
};
2629

2730
const isMobile = !canFocusInputOnScreenFocus();
2831

29-
function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable, shouldAutoFocusOnMobile = true}: BaseTwoFactorAuthFormProps, ref: ForwardedRef<BaseTwoFactorAuthFormRef>) {
32+
function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable, onFocus, shouldAutoFocusOnMobile = true}: BaseTwoFactorAuthFormProps, ref: ForwardedRef<BaseTwoFactorAuthFormRef>) {
3033
const {translate} = useLocalize();
3134
const [formError, setFormError] = useState<{twoFactorAuthCode?: string}>({});
3235
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
@@ -117,6 +120,7 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable, shouldAu
117120
name="twoFactorAuthCode"
118121
value={twoFactorAuthCode}
119122
onChangeText={onTextInput}
123+
onFocus={onFocus}
120124
onFulfill={validateAndSubmitForm}
121125
errorText={formError.twoFactorAuthCode ?? getLatestErrorMessage(account)}
122126
ref={inputRef}

src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import React from 'react';
22
import BaseTwoFactorAuthForm from './BaseTwoFactorAuthForm';
33
import type {TwoFactorAuthFormProps} from './types';
44

5-
function TwoFactorAuthForm({innerRef, validateInsteadOfDisable, shouldAutoFocusOnMobile}: TwoFactorAuthFormProps) {
5+
function TwoFactorAuthForm({innerRef, validateInsteadOfDisable, onFocus, shouldAutoFocusOnMobile}: TwoFactorAuthFormProps) {
66
return (
77
<BaseTwoFactorAuthForm
88
ref={innerRef}
99
autoComplete="one-time-code"
1010
validateInsteadOfDisable={validateInsteadOfDisable}
11+
onFocus={onFocus}
1112
shouldAutoFocusOnMobile={shouldAutoFocusOnMobile}
1213
/>
1314
);

src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ type TwoFactorAuthFormProps = {
1212
// Set this to true in order to call the validateTwoFactorAuth action which is used when setting up 2FA for the first time.
1313
// Set this to false in order to disable 2FA when a valid code is entered.
1414
validateInsteadOfDisable?: boolean;
15+
16+
/** Callback that is called when the text input is focused */
17+
onFocus?: () => void;
1518
};
1619

1720
export type {TwoFactorAuthFormProps, BaseTwoFactorAuthFormRef};

src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthWrapper.tsx

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper';
66
import HeaderWithBackButton from '@components/HeaderWithBackButton';
77
import ScreenWrapper from '@components/ScreenWrapper';
88
import useOnyx from '@hooks/useOnyx';
9+
import useViewportOffsetTop from '@hooks/useViewportOffsetTop';
910
import {quitAndNavigateBack} from '@libs/actions/TwoFactorAuthActions';
1011
import CONST from '@src/CONST';
1112
import type {StepCounterParams} from '@src/languages/params';
@@ -28,10 +29,21 @@ type TwoFactorAuthWrapperProps = ChildrenProps & {
2829

2930
/** Flag to indicate if the keyboard avoiding view should be enabled */
3031
shouldEnableKeyboardAvoidingView?: boolean;
32+
33+
/** Flag to indicate if the viewport offset top should be enabled */
34+
shouldEnableViewportOffsetTop?: boolean;
3135
};
3236

33-
function TwoFactorAuthWrapper({stepName, title, stepCounter, onBackButtonPress, shouldEnableKeyboardAvoidingView = true, children}: TwoFactorAuthWrapperProps) {
34-
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
37+
function TwoFactorAuthWrapper({
38+
stepName,
39+
title,
40+
stepCounter,
41+
onBackButtonPress,
42+
shouldEnableKeyboardAvoidingView = true,
43+
shouldEnableViewportOffsetTop = false,
44+
children,
45+
}: TwoFactorAuthWrapperProps) {
46+
const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
3547
const isActingAsDelegate = !!account?.delegatedAccess?.delegate;
3648

3749
// eslint-disable-next-line rulesdir/no-negated-variables
@@ -58,6 +70,8 @@ function TwoFactorAuthWrapper({stepName, title, stepCounter, onBackButtonPress,
5870
}
5971
}, [account, stepName]);
6072

73+
const viewportOffsetTop = useViewportOffsetTop();
74+
6175
if (isActingAsDelegate) {
6276
return (
6377
<ScreenWrapper
@@ -78,6 +92,7 @@ function TwoFactorAuthWrapper({stepName, title, stepCounter, onBackButtonPress,
7892
shouldEnableKeyboardAvoidingView={shouldEnableKeyboardAvoidingView}
7993
shouldEnableMaxHeight
8094
testID={stepName}
95+
style={shouldEnableViewportOffsetTop ? {marginTop: viewportOffsetTop} : undefined}
8196
>
8297
<FullPageNotFoundView
8398
shouldShow={shouldShowNotFound}

src/pages/settings/Security/TwoFactorAuth/VerifyPage.tsx

Lines changed: 36 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import React, {useEffect, useRef} from 'react';
2-
import {View} from 'react-native';
1+
import React, {useCallback, useEffect, useRef} from 'react';
2+
import {InteractionManager, View} from 'react-native';
3+
// eslint-disable-next-line no-restricted-imports
4+
import type {ScrollView as RNScrollView} from 'react-native';
35
import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png';
46
import Button from '@components/Button';
57
import FixedFooter from '@components/FixedFooter';
@@ -70,6 +72,15 @@ function VerifyPage({route}: VerifyPageProps) {
7072
return `otpauth://totp/Expensify:${contactMethod}?secret=${account?.twoFactorAuthSecretKey}&issuer=Expensify`;
7173
}
7274

75+
const scrollViewRef = useRef<RNScrollView>(null);
76+
const handleInputFocus = useCallback(() => {
77+
InteractionManager.runAfterInteractions(() => {
78+
requestAnimationFrame(() => {
79+
scrollViewRef.current?.scrollToEnd({animated: true});
80+
});
81+
});
82+
}, []);
83+
7384
return (
7485
<TwoFactorAuthWrapper
7586
stepName={CONST.TWO_FACTOR_AUTH_STEPS.VERIFY}
@@ -80,12 +91,14 @@ function VerifyPage({route}: VerifyPageProps) {
8091
total: 3,
8192
}}
8293
onBackButtonPress={() => Navigation.goBack(ROUTES.SETTINGS_2FA_ROOT.getRoute(route.params?.backTo, route.params?.forwardTo))}
94+
shouldEnableViewportOffsetTop
8395
>
8496
<ScrollView
97+
ref={scrollViewRef}
8598
keyboardShouldPersistTaps="handled"
8699
contentContainerStyle={styles.flexGrow1}
87100
>
88-
<View style={[styles.ph5, styles.mt3, styles.flexGrow1]}>
101+
<View style={[styles.ph5, styles.mt3]}>
89102
<Text>
90103
{translate('twoFactorAuth.scanCode')}
91104
<TextLink href={TROUBLESHOOTING_LINK}> {translate('twoFactorAuth.authenticatorApp')}</TextLink>.
@@ -116,27 +129,28 @@ function VerifyPage({route}: VerifyPageProps) {
116129
</View>
117130
<Text style={styles.mt11}>{translate('twoFactorAuth.enterCode')}</Text>
118131
</View>
119-
<FixedFooter style={[styles.mt2, styles.pt2]}>
120-
<View style={[styles.mh5, styles.mb4]}>
121-
<TwoFactorAuthForm
122-
innerRef={formRef}
123-
shouldAutoFocusOnMobile={false}
124-
/>
125-
</View>
126-
<Button
127-
success
128-
large
129-
text={translate('common.next')}
130-
isLoading={account?.isLoading}
131-
onPress={() => {
132-
if (!formRef.current) {
133-
return;
134-
}
135-
formRef.current.validateAndSubmitForm();
136-
}}
132+
<View style={[styles.mh5, styles.mb4, styles.mt3]}>
133+
<TwoFactorAuthForm
134+
innerRef={formRef}
135+
shouldAutoFocusOnMobile={false}
136+
onFocus={handleInputFocus}
137137
/>
138-
</FixedFooter>
138+
</View>
139139
</ScrollView>
140+
<FixedFooter style={[styles.mt2, styles.pt2]}>
141+
<Button
142+
success
143+
large
144+
text={translate('common.next')}
145+
isLoading={account?.isLoading}
146+
onPress={() => {
147+
if (!formRef.current) {
148+
return;
149+
}
150+
formRef.current.validateAndSubmitForm();
151+
}}
152+
/>
153+
</FixedFooter>
140154
</TwoFactorAuthWrapper>
141155
);
142156
}

src/styles/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2561,6 +2561,9 @@ const styles = (theme: ThemeColors) =>
25612561
borderWidth: 0,
25622562
borderBottomWidth: 0,
25632563
},
2564+
borderTransparent: {
2565+
borderColor: 'transparent',
2566+
},
25642567

25652568
borderRight: {
25662569
borderRightWidth: 1,

0 commit comments

Comments
 (0)