Skip to content

Commit

Permalink
Merge pull request Expensify#44635 from etCoderDysto/validateAccount
Browse files Browse the repository at this point in the history
fix: No recovery codes in 2FA page
  • Loading branch information
techievivek authored Aug 20, 2024
2 parents 44a7516 + 3612ea6 commit 80236f5
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 114 deletions.
2 changes: 1 addition & 1 deletion src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ const ROUTES = {
},
SETTINGS_CONTACT_METHOD_DETAILS: {
route: 'settings/profile/contact-methods/:contactMethod/details',
getRoute: (contactMethod: string) => `settings/profile/contact-methods/${encodeURIComponent(contactMethod)}/details` as const,
getRoute: (contactMethod: string, backTo?: string) => getUrlWithBackToParam(`settings/profile/contact-methods/${encodeURIComponent(contactMethod)}/details`, backTo),
},
SETTINGS_NEW_CONTACT_METHOD: {
route: 'settings/profile/contact-methods/new',
Expand Down
56 changes: 56 additions & 0 deletions src/components/ValidateAccountMessage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import * as Session from '@userActions/Session';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import Text from './Text';
import TextLink from './TextLink';

type ValidateAccountMessageProps = {backTo?: string | undefined};
function ValidateAccountMessage({backTo}: ValidateAccountMessageProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST);
const loginNames = Object.keys(loginList ?? {});

return (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.m4]}>
<Icon
src={Expensicons.Exclamation}
fill={theme.danger}
/>

<Text style={[styles.mutedTextLabel, styles.ml4, styles.flex1]}>
{translate('bankAccount.validateAccountError.phrase1')}
<TextLink
fontSize={variables.fontSizeLabel}
onPress={() => Session.signOutAndRedirectToSignIn()}
>
{translate('bankAccount.validateAccountError.phrase2')}
</TextLink>
{translate('bankAccount.validateAccountError.phrase3')}
<TextLink
fontSize={variables.fontSizeLabel}
onPress={() => {
const login = loginList?.[loginNames?.[0]] ?? {};
Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.getRoute(login?.partnerUserID ?? loginNames?.[0], backTo));
}}
>
{translate('bankAccount.validateAccountError.phrase4')}
</TextLink>
.
</Text>
</View>
);
}

export default ValidateAccountMessage;
6 changes: 1 addition & 5 deletions src/libs/Navigation/linkingConfig/getAdaptedStateFromPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,7 @@ function getMatchingRootRouteForRHPRoute(route: NavigationPartialRoute): Navigat

// If there is rhpNavigator in the state generated for backTo url, we want to get root route matching to this rhp screen.
if (rhpNavigator && rhpNavigator.state) {
const isRHPinState = stateForBackTo.routes[0].name === NAVIGATORS.RIGHT_MODAL_NAVIGATOR;

if (isRHPinState) {
return getMatchingRootRouteForRHPRoute(findFocusedRoute(stateForBackTo) as NavigationPartialRoute);
}
return getMatchingRootRouteForRHPRoute(findFocusedRoute(stateForBackTo) as NavigationPartialRoute);
}

// If we know that backTo targets the root route (central pane or full screen) we want to use it.
Expand Down
42 changes: 2 additions & 40 deletions src/pages/ReimbursementAccount/BankAccountStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import Text from '@components/Text';
import TextLink from '@components/TextLink';
import ValidateAccountMessage from '@components/ValidateAccountMessage';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import getPlaidDesktopMessage from '@libs/getPlaidDesktopMessage';
import Navigation from '@libs/Navigation/Navigation';
import variables from '@styles/variables';
import * as BankAccounts from '@userActions/BankAccounts';
import * as Link from '@userActions/Link';
import * as ReimbursementAccount from '@userActions/ReimbursementAccount';
import * as Session from '@userActions/Session';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -38,9 +36,6 @@ type BankAccountStepOnyxProps = {

/** If the plaid button has been disabled */
isPlaidDisabled: OnyxEntry<boolean>;

/** Login list for the user that is signed in */
loginList: OnyxEntry<OnyxTypes.LoginList>;
};

type BankAccountStepProps = BankAccountStepOnyxProps & {
Expand Down Expand Up @@ -73,7 +68,6 @@ function BankAccountStep({
receivedRedirectURI,
reimbursementAccount,
onBackButtonPress,
loginList,
isPlaidDisabled = false,
}: BankAccountStepProps) {
const theme = useTheme();
Expand All @@ -86,7 +80,6 @@ function BankAccountStep({
}
const plaidDesktopMessage = getPlaidDesktopMessage();
const bankAccountRoute = `${ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute('new', policyID, ROUTES.WORKSPACE_INITIAL.getRoute(policyID))}`;
const loginNames = Object.keys(loginList ?? {});

const removeExistingBankAccountDetails = () => {
const bankAccountData: Partial<ReimbursementAccountForm> = {
Expand Down Expand Up @@ -168,35 +161,7 @@ function BankAccountStep({
/>
</View>
</Section>
{!user?.validated && (
<View style={[styles.flexRow, styles.alignItemsCenter, styles.m4]}>
<Icon
src={Expensicons.Exclamation}
fill={theme.danger}
/>

<Text style={[styles.mutedTextLabel, styles.ml4, styles.flex1]}>
{translate('bankAccount.validateAccountError.phrase1')}
<TextLink
fontSize={variables.fontSizeLabel}
onPress={() => Session.signOutAndRedirectToSignIn()}
>
{translate('bankAccount.validateAccountError.phrase2')}
</TextLink>
{translate('bankAccount.validateAccountError.phrase3')}
<TextLink
fontSize={variables.fontSizeLabel}
onPress={() => {
const login = loginList?.[loginNames?.[0]] ?? {};
Navigation.navigate(ROUTES.SETTINGS_CONTACT_METHOD_DETAILS.getRoute(login?.partnerUserID ?? loginNames?.[0]));
}}
>
{translate('bankAccount.validateAccountError.phrase4')}
</TextLink>
.
</Text>
</View>
)}
{!user?.validated && <ValidateAccountMessage />}
<View style={[styles.mv0, styles.mh5, styles.flexRow, styles.justifyContentBetween]}>
<TextLink href={CONST.PRIVACY_URL}>{translate('common.privacy')}</TextLink>
<PressableWithoutFeedback
Expand Down Expand Up @@ -230,7 +195,4 @@ export default withOnyx<BankAccountStepProps, BankAccountStepOnyxProps>({
isPlaidDisabled: {
key: ONYXKEYS.IS_PLAID_DISABLED,
},
loginList: {
key: ONYXKEYS.LOGIN_LIST,
},
})(BankAccountStep);
148 changes: 81 additions & 67 deletions src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {RouteProp} from '@react-navigation/native';
import {useRoute} from '@react-navigation/native';
import React, {useEffect, useState} from 'react';
import {ActivityIndicator, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
Expand All @@ -10,40 +12,45 @@ import PressableWithDelayToggle from '@components/Pressable/PressableWithDelayTo
import ScrollView from '@components/ScrollView';
import Section from '@components/Section';
import Text from '@components/Text';
import ValidateAccountMessage from '@components/ValidateAccountMessage';
import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import Clipboard from '@libs/Clipboard';
import localFileDownload from '@libs/localFileDownload';
import type {BackToParams} from '@libs/Navigation/types';
import type {BackToParams, SettingsNavigatorParamList} from '@libs/Navigation/types';
import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper';
import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth';
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';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';

type CodesStepProps = BaseTwoFactorAuthFormOnyxProps & BackToParams;

function CodesStep({account, backTo}: CodesStepProps) {
function CodesStep({account, user, backTo}: CodesStepProps) {
const theme = useTheme();
const styles = useThemeStyles();
const {translate} = useLocalize();
const {isExtraSmallScreenWidth, isSmallScreenWidth} = useResponsiveLayout();
const [error, setError] = useState('');
const isUserValidated = user?.validated;
const route = useRoute<RouteProp<SettingsNavigatorParamList, typeof SCREENS.SETTINGS.TWO_FACTOR_AUTH>>();

const {setStep} = useTwoFactorAuthContext();

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

return (
<StepWrapper
Expand All @@ -57,70 +64,73 @@ function CodesStep({account, backTo}: CodesStepProps) {
onBackButtonPress={() => TwoFactorAuthActions.quitAndNavigateBack(backTo)}
>
<ScrollView contentContainerStyle={styles.flexGrow1}>
<Section
title={translate('twoFactorAuth.keepCodesSafe')}
icon={Illustrations.ShieldYellow}
containerStyles={[styles.twoFactorAuthSection]}
iconContainerStyles={[styles.ml6]}
>
<View style={styles.mv3}>
<Text>{translate('twoFactorAuth.codesLoseAccess')}</Text>
</View>
<View style={styles.twoFactorAuthCodesBox({isExtraSmallScreenWidth, isSmallScreenWidth})}>
{account?.isLoading ? (
<View style={styles.twoFactorLoadingContainer}>
<ActivityIndicator color={theme.spinner} />
</View>
) : (
<>
<View style={styles.twoFactorAuthCodesContainer}>
{!!account?.recoveryCodes &&
account?.recoveryCodes?.split(', ').map((code) => (
<Text
style={styles.twoFactorAuthCode}
key={code}
>
{code}
</Text>
))}
{isUserValidated && (
<Section
title={translate('twoFactorAuth.keepCodesSafe')}
icon={Illustrations.ShieldYellow}
containerStyles={[styles.twoFactorAuthSection]}
iconContainerStyles={[styles.ml6]}
>
<View style={styles.mv3}>
<Text>{translate('twoFactorAuth.codesLoseAccess')}</Text>
</View>
<View style={styles.twoFactorAuthCodesBox({isExtraSmallScreenWidth, isSmallScreenWidth})}>
{account?.isLoading ? (
<View style={styles.twoFactorLoadingContainer}>
<ActivityIndicator color={theme.spinner} />
</View>
<View style={styles.twoFactorAuthCodesButtonsContainer}>
<PressableWithDelayToggle
text={translate('twoFactorAuth.copy')}
textChecked={translate('common.copied')}
icon={Expensicons.Copy}
inline={false}
onPress={() => {
Clipboard.setString(account?.recoveryCodes ?? '');
setError('');
TwoFactorAuthActions.setCodesAreCopied();
}}
styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]}
textStyles={[styles.buttonMediumText]}
accessible={false}
tooltipText=""
tooltipTextChecked=""
/>
<PressableWithDelayToggle
text={translate('common.download')}
icon={Expensicons.Download}
onPress={() => {
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=""
/>
</View>
</>
)}
</View>
</Section>
) : (
<>
<View style={styles.twoFactorAuthCodesContainer}>
{!!account?.recoveryCodes &&
account?.recoveryCodes?.split(', ').map((code) => (
<Text
style={styles.twoFactorAuthCode}
key={code}
>
{code}
</Text>
))}
</View>
<View style={styles.twoFactorAuthCodesButtonsContainer}>
<PressableWithDelayToggle
text={translate('twoFactorAuth.copy')}
textChecked={translate('common.copied')}
icon={Expensicons.Copy}
inline={false}
onPress={() => {
Clipboard.setString(account?.recoveryCodes ?? '');
setError('');
TwoFactorAuthActions.setCodesAreCopied();
}}
styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]}
textStyles={[styles.buttonMediumText]}
accessible={false}
tooltipText=""
tooltipTextChecked=""
/>
<PressableWithDelayToggle
text={translate('common.download')}
icon={Expensicons.Download}
onPress={() => {
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=""
/>
</View>
</>
)}
</View>
</Section>
)}
{!isUserValidated && <ValidateAccountMessage backTo={ROUTES.SETTINGS_2FA.getRoute(route?.params?.backTo)} />}
<FixedFooter style={[styles.mtAuto, styles.pt5]}>
{!!error && (
<FormHelpMessage
Expand All @@ -132,6 +142,7 @@ function CodesStep({account, backTo}: CodesStepProps) {
<Button
success
large
isDisabled={!isUserValidated}
text={translate('common.next')}
onPress={() => {
if (!account?.codesAreCopied) {
Expand All @@ -151,4 +162,7 @@ CodesStep.displayName = 'CodesStep';

export default withOnyx<CodesStepProps, BaseTwoFactorAuthFormOnyxProps>({
account: {key: ONYXKEYS.ACCOUNT},
user: {
key: ONYXKEYS.USER,
},
})(CodesStep);
Loading

0 comments on commit 80236f5

Please sign in to comment.