Skip to content

Commit 80236f5

Browse files
authored
Merge pull request #44635 from etCoderDysto/validateAccount
fix: No recovery codes in 2FA page
2 parents 44a7516 + 3612ea6 commit 80236f5

File tree

6 files changed

+143
-114
lines changed

6 files changed

+143
-114
lines changed

src/ROUTES.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ const ROUTES = {
193193
},
194194
SETTINGS_CONTACT_METHOD_DETAILS: {
195195
route: 'settings/profile/contact-methods/:contactMethod/details',
196-
getRoute: (contactMethod: string) => `settings/profile/contact-methods/${encodeURIComponent(contactMethod)}/details` as const,
196+
getRoute: (contactMethod: string, backTo?: string) => getUrlWithBackToParam(`settings/profile/contact-methods/${encodeURIComponent(contactMethod)}/details`, backTo),
197197
},
198198
SETTINGS_NEW_CONTACT_METHOD: {
199199
route: 'settings/profile/contact-methods/new',
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import React from 'react';
2+
import {View} from 'react-native';
3+
import {useOnyx} from 'react-native-onyx';
4+
import useLocalize from '@hooks/useLocalize';
5+
import useTheme from '@hooks/useTheme';
6+
import useThemeStyles from '@hooks/useThemeStyles';
7+
import Navigation from '@libs/Navigation/Navigation';
8+
import variables from '@styles/variables';
9+
import * as Session from '@userActions/Session';
10+
import ONYXKEYS from '@src/ONYXKEYS';
11+
import ROUTES from '@src/ROUTES';
12+
import Icon from './Icon';
13+
import * as Expensicons from './Icon/Expensicons';
14+
import Text from './Text';
15+
import TextLink from './TextLink';
16+
17+
type ValidateAccountMessageProps = {backTo?: string | undefined};
18+
function ValidateAccountMessage({backTo}: ValidateAccountMessageProps) {
19+
const theme = useTheme();
20+
const styles = useThemeStyles();
21+
const {translate} = useLocalize();
22+
const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
23+
const loginNames = Object.keys(loginList ?? {});
24+
25+
return (
26+
<View style={[styles.flexRow, styles.alignItemsCenter, styles.m4]}>
27+
<Icon
28+
src={Expensicons.Exclamation}
29+
fill={theme.danger}
30+
/>
31+
32+
<Text style={[styles.mutedTextLabel, styles.ml4, styles.flex1]}>
33+
{translate('bankAccount.validateAccountError.phrase1')}
34+
<TextLink
35+
fontSize={variables.fontSizeLabel}
36+
onPress={() => Session.signOutAndRedirectToSignIn()}
37+
>
38+
{translate('bankAccount.validateAccountError.phrase2')}
39+
</TextLink>
40+
{translate('bankAccount.validateAccountError.phrase3')}
41+
<TextLink
42+
fontSize={variables.fontSizeLabel}
43+
onPress={() => {
44+
const login = loginList?.[loginNames?.[0]] ?? {};
45+
Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.getRoute(login?.partnerUserID ?? loginNames?.[0], backTo));
46+
}}
47+
>
48+
{translate('bankAccount.validateAccountError.phrase4')}
49+
</TextLink>
50+
.
51+
</Text>
52+
</View>
53+
);
54+
}
55+
56+
export default ValidateAccountMessage;

src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,7 @@ function getMatchingRootRouteForRHPRoute(route: NavigationPartialRoute): Navigat
124124

125125
// If there is rhpNavigator in the state generated for backTo url, we want to get root route matching to this rhp screen.
126126
if (rhpNavigator && rhpNavigator.state) {
127-
const isRHPinState = stateForBackTo.routes[0].name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR;
128-
129-
if (isRHPinState) {
130-
return getMatchingRootRouteForRHPRoute(findFocusedRoute(stateForBackTo) as NavigationPartialRoute);
131-
}
127+
return getMatchingRootRouteForRHPRoute(findFocusedRoute(stateForBackTo) as NavigationPartialRoute);
132128
}
133129

134130
// If we know that backTo targets the root route (central pane or full screen) we want to use it.

src/pages/ReimbursementAccount/BankAccountStep.tsx

Lines changed: 2 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,14 @@ import ScrollView from '@components/ScrollView';
1414
import Section from '@components/Section';
1515
import Text from '@components/Text';
1616
import TextLink from '@components/TextLink';
17+
import ValidateAccountMessage from '@components/ValidateAccountMessage';
1718
import useLocalize from '@hooks/useLocalize';
1819
import useTheme from '@hooks/useTheme';
1920
import useThemeStyles from '@hooks/useThemeStyles';
2021
import getPlaidDesktopMessage from '@libs/getPlaidDesktopMessage';
21-
import Navigation from '@libs/Navigation/Navigation';
22-
import variables from '@styles/variables';
2322
import * as BankAccounts from '@userActions/BankAccounts';
2423
import * as Link from '@userActions/Link';
2524
import * as ReimbursementAccount from '@userActions/ReimbursementAccount';
26-
import * as Session from '@userActions/Session';
2725
import CONST from '@src/CONST';
2826
import ONYXKEYS from '@src/ONYXKEYS';
2927
import ROUTES from '@src/ROUTES';
@@ -38,9 +36,6 @@ type BankAccountStepOnyxProps = {
3836

3937
/** If the plaid button has been disabled */
4038
isPlaidDisabled: OnyxEntry<boolean>;
41-
42-
/** Login list for the user that is signed in */
43-
loginList: OnyxEntry<OnyxTypes.LoginList>;
4439
};
4540

4641
type BankAccountStepProps = BankAccountStepOnyxProps & {
@@ -73,7 +68,6 @@ function BankAccountStep({
7368
receivedRedirectURI,
7469
reimbursementAccount,
7570
onBackButtonPress,
76-
loginList,
7771
isPlaidDisabled = false,
7872
}: BankAccountStepProps) {
7973
const theme = useTheme();
@@ -86,7 +80,6 @@ function BankAccountStep({
8680
}
8781
const plaidDesktopMessage = getPlaidDesktopMessage();
8882
const bankAccountRoute = `${ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('new', policyID, ROUTES.WORKSPACE_INITIAL.getRoute(policyID))}`;
89-
const loginNames = Object.keys(loginList ?? {});
9083

9184
const removeExistingBankAccountDetails = () => {
9285
const bankAccountData: Partial<ReimbursementAccountForm> = {
@@ -168,35 +161,7 @@ function BankAccountStep({
168161
/>
169162
</View>
170163
</Section>
171-
{!user?.validated && (
172-
<View style={[styles.flexRow, styles.alignItemsCenter, styles.m4]}>
173-
<Icon
174-
src={Expensicons.Exclamation}
175-
fill={theme.danger}
176-
/>
177-
178-
<Text style={[styles.mutedTextLabel, styles.ml4, styles.flex1]}>
179-
{translate('bankAccount.validateAccountError.phrase1')}
180-
<TextLink
181-
fontSize={variables.fontSizeLabel}
182-
onPress={() => Session.signOutAndRedirectToSignIn()}
183-
>
184-
{translate('bankAccount.validateAccountError.phrase2')}
185-
</TextLink>
186-
{translate('bankAccount.validateAccountError.phrase3')}
187-
<TextLink
188-
fontSize={variables.fontSizeLabel}
189-
onPress={() => {
190-
const login = loginList?.[loginNames?.[0]] ?? {};
191-
Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.getRoute(login?.partnerUserID ?? loginNames?.[0]));
192-
}}
193-
>
194-
{translate('bankAccount.validateAccountError.phrase4')}
195-
</TextLink>
196-
.
197-
</Text>
198-
</View>
199-
)}
164+
{!user?.validated && <ValidateAccountMessage />}
200165
<View style={[styles.mv0, styles.mh5, styles.flexRow, styles.justifyContentBetween]}>
201166
<TextLink href={CONST.PRIVACY_URL}>{translate('common.privacy')}</TextLink>
202167
<PressableWithoutFeedback
@@ -230,7 +195,4 @@ export default withOnyx<BankAccountStepProps, BankAccountStepOnyxProps>({
230195
isPlaidDisabled: {
231196
key: ONYXKEYS.IS_PLAID_DISABLED,
232197
},
233-
loginList: {
234-
key: ONYXKEYS.LOGIN_LIST,
235-
},
236198
})(BankAccountStep);
Lines changed: 81 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type {RouteProp} from '@react-navigation/native';
2+
import {useRoute} from '@react-navigation/native';
13
import React, {useEffect, useState} from 'react';
24
import {ActivityIndicator, View} from 'react-native';
35
import {withOnyx} from 'react-native-onyx';
@@ -10,40 +12,45 @@ import PressableWithDelayToggle from '@components/Pressable/PressableWithDelayTo
1012
import ScrollView from '@components/ScrollView';
1113
import Section from '@components/Section';
1214
import Text from '@components/Text';
15+
import ValidateAccountMessage from '@components/ValidateAccountMessage';
1316
import useLocalize from '@hooks/useLocalize';
1417
import useResponsiveLayout from '@hooks/useResponsiveLayout';
1518
import useTheme from '@hooks/useTheme';
1619
import useThemeStyles from '@hooks/useThemeStyles';
1720
import Clipboard from '@libs/Clipboard';
1821
import localFileDownload from '@libs/localFileDownload';
19-
import type {BackToParams} from '@libs/Navigation/types';
22+
import type {BackToParams, SettingsNavigatorParamList} from '@libs/Navigation/types';
2023
import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper';
2124
import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth';
2225
import type {BaseTwoFactorAuthFormOnyxProps} from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types';
2326
import * as Session from '@userActions/Session';
2427
import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions';
2528
import CONST from '@src/CONST';
2629
import ONYXKEYS from '@src/ONYXKEYS';
30+
import ROUTES from '@src/ROUTES';
31+
import type SCREENS from '@src/SCREENS';
2732

2833
type CodesStepProps = BaseTwoFactorAuthFormOnyxProps & BackToParams;
2934

30-
function CodesStep({account, backTo}: CodesStepProps) {
35+
function CodesStep({account, user, backTo}: CodesStepProps) {
3136
const theme = useTheme();
3237
const styles = useThemeStyles();
3338
const {translate} = useLocalize();
3439
const {isExtraSmallScreenWidth, isSmallScreenWidth} = useResponsiveLayout();
3540
const [error, setError] = useState('');
41+
const isUserValidated = user?.validated;
42+
const route = useRoute<RouteProp<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.TWO_FACTOR_AUTH>>();
3643

3744
const {setStep} = useTwoFactorAuthContext();
3845

3946
useEffect(() => {
4047
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
41-
if (account?.requiresTwoFactorAuth || account?.recoveryCodes) {
48+
if (account?.requiresTwoFactorAuth || account?.recoveryCodes || !isUserValidated) {
4249
return;
4350
}
4451
Session.toggleTwoFactorAuth(true);
4552
// eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- We want to run this when component mounts
46-
}, []);
53+
}, [isUserValidated]);
4754

4855
return (
4956
<StepWrapper
@@ -57,70 +64,73 @@ function CodesStep({account, backTo}: CodesStepProps) {
5764
onBackButtonPress={() => TwoFactorAuthActions.quitAndNavigateBack(backTo)}
5865
>
5966
<ScrollView contentContainerStyle={styles.flexGrow1}>
60-
<Section
61-
title={translate('twoFactorAuth.keepCodesSafe')}
62-
icon={Illustrations.ShieldYellow}
63-
containerStyles={[styles.twoFactorAuthSection]}
64-
iconContainerStyles={[styles.ml6]}
65-
>
66-
<View style={styles.mv3}>
67-
<Text>{translate('twoFactorAuth.codesLoseAccess')}</Text>
68-
</View>
69-
<View style={styles.twoFactorAuthCodesBox({isExtraSmallScreenWidth, isSmallScreenWidth})}>
70-
{account?.isLoading ? (
71-
<View style={styles.twoFactorLoadingContainer}>
72-
<ActivityIndicator color={theme.spinner} />
73-
</View>
74-
) : (
75-
<>
76-
<View style={styles.twoFactorAuthCodesContainer}>
77-
{!!account?.recoveryCodes &&
78-
account?.recoveryCodes?.split(', ').map((code) => (
79-
<Text
80-
style={styles.twoFactorAuthCode}
81-
key={code}
82-
>
83-
{code}
84-
</Text>
85-
))}
67+
{isUserValidated && (
68+
<Section
69+
title={translate('twoFactorAuth.keepCodesSafe')}
70+
icon={Illustrations.ShieldYellow}
71+
containerStyles={[styles.twoFactorAuthSection]}
72+
iconContainerStyles={[styles.ml6]}
73+
>
74+
<View style={styles.mv3}>
75+
<Text>{translate('twoFactorAuth.codesLoseAccess')}</Text>
76+
</View>
77+
<View style={styles.twoFactorAuthCodesBox({isExtraSmallScreenWidth, isSmallScreenWidth})}>
78+
{account?.isLoading ? (
79+
<View style={styles.twoFactorLoadingContainer}>
80+
<ActivityIndicator color={theme.spinner} />
8681
</View>
87-
<View style={styles.twoFactorAuthCodesButtonsContainer}>
88-
<PressableWithDelayToggle
89-
text={translate('twoFactorAuth.copy')}
90-
textChecked={translate('common.copied')}
91-
icon={Expensicons.Copy}
92-
inline={false}
93-
onPress={() => {
94-
Clipboard.setString(account?.recoveryCodes ?? '');
95-
setError('');
96-
TwoFactorAuthActions.setCodesAreCopied();
97-
}}
98-
styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]}
99-
textStyles={[styles.buttonMediumText]}
100-
accessible={false}
101-
tooltipText=""
102-
tooltipTextChecked=""
103-
/>
104-
<PressableWithDelayToggle
105-
text={translate('common.download')}
106-
icon={Expensicons.Download}
107-
onPress={() => {
108-
localFileDownload('two-factor-auth-codes', account?.recoveryCodes ?? '');
109-
setError('');
110-
TwoFactorAuthActions.setCodesAreCopied();
111-
}}
112-
inline={false}
113-
styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]}
114-
textStyles={[styles.buttonMediumText]}
115-
accessible={false}
116-
tooltipText=""
117-
tooltipTextChecked=""
118-
/>
119-
</View>
120-
</>
121-
)}
122-
</View>
123-
</Section>
82+
) : (
83+
<>
84+
<View style={styles.twoFactorAuthCodesContainer}>
85+
{!!account?.recoveryCodes &&
86+
account?.recoveryCodes?.split(', ').map((code) => (
87+
<Text
88+
style={styles.twoFactorAuthCode}
89+
key={code}
90+
>
91+
{code}
92+
</Text>
93+
))}
94+
</View>
95+
<View style={styles.twoFactorAuthCodesButtonsContainer}>
96+
<PressableWithDelayToggle
97+
text={translate('twoFactorAuth.copy')}
98+
textChecked={translate('common.copied')}
99+
icon={Expensicons.Copy}
100+
inline={false}
101+
onPress={() => {
102+
Clipboard.setString(account?.recoveryCodes ?? '');
103+
setError('');
104+
TwoFactorAuthActions.setCodesAreCopied();
105+
}}
106+
styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]}
107+
textStyles={[styles.buttonMediumText]}
108+
accessible={false}
109+
tooltipText=""
110+
tooltipTextChecked=""
111+
/>
112+
<PressableWithDelayToggle
113+
text={translate('common.download')}
114+
icon={Expensicons.Download}
115+
onPress={() => {
116+
localFileDownload('two-factor-auth-codes', account?.recoveryCodes ?? '');
117+
setError('');
118+
TwoFactorAuthActions.setCodesAreCopied();
119+
}}
120+
inline={false}
121+
styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]}
122+
textStyles={[styles.buttonMediumText]}
123+
accessible={false}
124+
tooltipText=""
125+
tooltipTextChecked=""
126+
/>
127+
</View>
128+
</>
129+
)}
130+
</View>
131+
</Section>
132+
)}
133+
{!isUserValidated && <ValidateAccountMessage backTo={ROUTES.SETTINGS_2FA.getRoute(route?.params?.backTo)} />}
124134
<FixedFooter style={[styles.mtAuto, styles.pt5]}>
125135
{!!error && (
126136
<FormHelpMessage
@@ -132,6 +142,7 @@ function CodesStep({account, backTo}: CodesStepProps) {
132142
<Button
133143
success
134144
large
145+
isDisabled={!isUserValidated}
135146
text={translate('common.next')}
136147
onPress={() => {
137148
if (!account?.codesAreCopied) {
@@ -151,4 +162,7 @@ CodesStep.displayName = 'CodesStep';
151162

152163
export default withOnyx<CodesStepProps, BaseTwoFactorAuthFormOnyxProps>({
153164
account: {key: ONYXKEYS.ACCOUNT},
165+
user: {
166+
key: ONYXKEYS.USER,
167+
},
154168
})(CodesStep);

0 commit comments

Comments
 (0)