diff --git a/src/CONST.js b/src/CONST.js index 619a28cc9f0e..2dba46e87e7a 100755 --- a/src/CONST.js +++ b/src/CONST.js @@ -32,6 +32,10 @@ const CONST = { ANIMATED_TRANSITION: 300, ANIMATED_TRANSITION_FROM_VALUE: 100, ANIMATION_IN_TIMING: 100, + ANIMATION_DIRECTION: { + IN: 'in', + OUT: 'out', + }, ARROW_HIDE_DELAY: 3000, API_ATTACHMENT_VALIDATIONS: { @@ -2524,6 +2528,13 @@ const CONST = { AFTER_WEEK: 'afterWeek', CUSTOM: 'custom', }, + TWO_FACTOR_AUTH_STEPS: { + CODES: 'CODES', + VERIFY: 'VERIFY', + SUCCESS: 'SUCCESS', + ENABLED: 'ENABLED', + DISABLED: 'DISABLED', + }, TAB: { RECEIPT_TAB_ID: 'ReceiptTab', MANUAL: 'manual', diff --git a/src/ROUTES.js b/src/ROUTES.js index 2ad3cd7a5cd4..4afafa2a2d1b 100644 --- a/src/ROUTES.js +++ b/src/ROUTES.js @@ -58,11 +58,7 @@ export default { SETTINGS_CONTACT_METHOD_DETAILS: `${SETTINGS_CONTACT_METHODS}/:contactMethod/details`, getEditContactMethodRoute: (contactMethod) => `${SETTINGS_CONTACT_METHODS}/${encodeURIComponent(contactMethod)}/details`, SETTINGS_NEW_CONTACT_METHOD: `${SETTINGS_CONTACT_METHODS}/new`, - SETTINGS_2FA_IS_ENABLED: 'settings/security/two-factor-auth/enabled', - SETTINGS_2FA_DISABLE: 'settings/security/two-factor-auth/disable', - SETTINGS_2FA_CODES: 'settings/security/two-factor-auth/codes', - SETTINGS_2FA_VERIFY: 'settings/security/two-factor-auth/verify', - SETTINGS_2FA_SUCCESS: 'settings/security/two-factor-auth/success', + SETTINGS_2FA: 'settings/security/two-factor-auth', SETTINGS_STATUS, SETTINGS_STATUS_SET, NEW_GROUP: 'new/group', diff --git a/src/components/AnimatedStep/AnimatedStepContext.js b/src/components/AnimatedStep/AnimatedStepContext.js new file mode 100644 index 000000000000..30377147fdb8 --- /dev/null +++ b/src/components/AnimatedStep/AnimatedStepContext.js @@ -0,0 +1,5 @@ +import {createContext} from 'react'; + +const AnimatedStepContext = createContext(); + +export default AnimatedStepContext; diff --git a/src/components/AnimatedStep/AnimatedStepProvider.js b/src/components/AnimatedStep/AnimatedStepProvider.js new file mode 100644 index 000000000000..280fbd1a2776 --- /dev/null +++ b/src/components/AnimatedStep/AnimatedStepProvider.js @@ -0,0 +1,17 @@ +import React, {useState} from 'react'; +import PropTypes from 'prop-types'; +import AnimatedStepContext from './AnimatedStepContext'; +import CONST from '../../CONST'; + +const propTypes = { + children: PropTypes.node.isRequired, +}; + +function AnimatedStepProvider({children}) { + const [animationDirection, setAnimationDirection] = useState(CONST.ANIMATION_DIRECTION.IN); + + return {children}; +} + +AnimatedStepProvider.propTypes = propTypes; +export default AnimatedStepProvider; diff --git a/src/components/AnimatedStep.js b/src/components/AnimatedStep/index.js similarity index 58% rename from src/components/AnimatedStep.js rename to src/components/AnimatedStep/index.js index dce06cb33760..a8b9b80fcc0e 100644 --- a/src/components/AnimatedStep.js +++ b/src/components/AnimatedStep/index.js @@ -1,8 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import * as Animatable from 'react-native-animatable'; -import CONST from '../CONST'; -import styles from '../styles/styles'; +import CONST from '../../CONST'; +import styles from '../../styles/styles'; const propTypes = { /** Children to wrap in AnimatedStep. */ @@ -14,27 +14,37 @@ const propTypes = { /** Whether we're animating the step in or out */ direction: PropTypes.oneOf(['in', 'out']), + + /** Callback to fire when the animation ends */ + onAnimationEnd: PropTypes.func, }; const defaultProps = { direction: 'in', style: [], + onAnimationEnd: () => {}, }; -function AnimatedStep(props) { - function getAnimationStyle(direction) { - let animationStyle; - - if (direction === 'in') { - animationStyle = styles.makeSlideInTranslation('translateX', CONST.ANIMATED_TRANSITION_FROM_VALUE); - } else if (direction === 'out') { - animationStyle = styles.makeSlideInTranslation('translateX', -CONST.ANIMATED_TRANSITION_FROM_VALUE); - } - return animationStyle; +function getAnimationStyle(direction) { + let transitionValue; + + if (direction === 'in') { + transitionValue = CONST.ANIMATED_TRANSITION_FROM_VALUE; + } else if (direction === 'out') { + transitionValue = -CONST.ANIMATED_TRANSITION_FROM_VALUE; } + return styles.makeSlideInTranslation('translateX', transitionValue); +} +function AnimatedStep(props) { return ( { + if (!props.onAnimationEnd) { + return; + } + props.onAnimationEnd(); + }} duration={CONST.ANIMATED_TRANSITION} animation={getAnimationStyle(props.direction)} useNativeDriver diff --git a/src/components/AnimatedStep/useAnimatedStepContext.js b/src/components/AnimatedStep/useAnimatedStepContext.js new file mode 100644 index 000000000000..e2af9514e20e --- /dev/null +++ b/src/components/AnimatedStep/useAnimatedStepContext.js @@ -0,0 +1,12 @@ +import {useContext} from 'react'; +import AnimatedStepContext from './AnimatedStepContext'; + +function useAnimatedStepContext() { + const context = useContext(AnimatedStepContext); + if (!context) { + throw new Error('useAnimatedStepContext must be used within an AnimatedStepContextProvider'); + } + return context; +} + +export default useAnimatedStepContext; diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index ab9e2dcd73b2..5cf88c4259db 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -630,38 +630,10 @@ const SettingsModalStackNavigator = createModalStackNavigator([ }, { getComponent: () => { - const SettingsTwoFactorAuthIsEnabled = require('../../../pages/settings/Security/TwoFactorAuth/IsEnabledPage').default; - return SettingsTwoFactorAuthIsEnabled; + const SettingsTwoFactorAuth = require('../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default; + return SettingsTwoFactorAuth; }, - name: 'Settings_TwoFactorAuthIsEnabled', - }, - { - getComponent: () => { - const SettingsTwoFactorAuthDisable = require('../../../pages/settings/Security/TwoFactorAuth/DisablePage').default; - return SettingsTwoFactorAuthDisable; - }, - name: 'Settings_TwoFactorAuthDisable', - }, - { - getComponent: () => { - const SettingsTwoFactorAuthCodes = require('../../../pages/settings/Security/TwoFactorAuth/CodesPage').default; - return SettingsTwoFactorAuthCodes; - }, - name: 'Settings_TwoFactorAuthCodes', - }, - { - getComponent: () => { - const SettingsTwoFactorAuthVerify = require('../../../pages/settings/Security/TwoFactorAuth/VerifyPage').default; - return SettingsTwoFactorAuthVerify; - }, - name: 'Settings_TwoFactorAuthVerify', - }, - { - getComponent: () => { - const SettingsTwoFactorAuthSuccess = require('../../../pages/settings/Security/TwoFactorAuth/SuccessPage').default; - return SettingsTwoFactorAuthSuccess; - }, - name: 'Settings_TwoFactorAuthSuccess', + name: 'Settings_TwoFactorAuth', }, ]); diff --git a/src/libs/Navigation/linkingConfig.js b/src/libs/Navigation/linkingConfig.js index 5a3f0a9abe71..ad9da4bf4c3c 100644 --- a/src/libs/Navigation/linkingConfig.js +++ b/src/libs/Navigation/linkingConfig.js @@ -152,24 +152,8 @@ export default { path: ROUTES.SETTINGS_PERSONAL_DETAILS_ADDRESS, exact: true, }, - Settings_TwoFactorAuthIsEnabled: { - path: ROUTES.SETTINGS_2FA_IS_ENABLED, - exact: true, - }, - Settings_TwoFactorAuthDisable: { - path: ROUTES.SETTINGS_2FA_DISABLE, - exact: true, - }, - Settings_TwoFactorAuthCodes: { - path: ROUTES.SETTINGS_2FA_CODES, - exact: true, - }, - Settings_TwoFactorAuthVerify: { - path: ROUTES.SETTINGS_2FA_VERIFY, - exact: true, - }, - Settings_TwoFactorAuthSuccess: { - path: ROUTES.SETTINGS_2FA_SUCCESS, + Settings_TwoFactorAuth: { + path: ROUTES.SETTINGS_2FA, exact: true, }, Settings_Share_Code: { diff --git a/src/libs/actions/TwoFactorAuthActions.js b/src/libs/actions/TwoFactorAuthActions.js index f778b1f2ceae..3bc64b8ada4c 100644 --- a/src/libs/actions/TwoFactorAuthActions.js +++ b/src/libs/actions/TwoFactorAuthActions.js @@ -1,14 +1,26 @@ import Onyx from 'react-native-onyx'; import ONYXKEYS from '../../ONYXKEYS'; +import Navigation from '../Navigation/Navigation'; +import ROUTES from '../../ROUTES'; /** * Clear 2FA data if the flow is interrupted without finishing */ function clearTwoFactorAuthData() { - Onyx.merge(ONYXKEYS.ACCOUNT, {recoveryCodes: '', twoFactorAuthSecretKey: ''}); + Onyx.merge(ONYXKEYS.ACCOUNT, {recoveryCodes: '', twoFactorAuthSecretKey: '', twoFactorAuthStep: '', codesAreCopied: false}); } -export { - // eslint-disable-next-line import/prefer-default-export - clearTwoFactorAuthData, -}; +function setTwoFactorAuthStep(twoFactorAuthStep) { + Onyx.merge(ONYXKEYS.ACCOUNT, {twoFactorAuthStep}); +} + +function setCodesAreCopied() { + Onyx.merge(ONYXKEYS.ACCOUNT, {codesAreCopied: true}); +} + +function quitAndNavigateBackToSettings() { + clearTwoFactorAuthData(); + Navigation.goBack(ROUTES.SETTINGS_SECURITY); +} + +export {clearTwoFactorAuthData, setTwoFactorAuthStep, quitAndNavigateBackToSettings, setCodesAreCopied}; diff --git a/src/pages/settings/Security/SecuritySettingsPage.js b/src/pages/settings/Security/SecuritySettingsPage.js index 65fa265c425a..90b006669527 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.js +++ b/src/pages/settings/Security/SecuritySettingsPage.js @@ -13,7 +13,6 @@ import withLocalize, {withLocalizePropTypes} from '../../../components/withLocal import MenuItem from '../../../components/MenuItem'; import compose from '../../../libs/compose'; import ONYXKEYS from '../../../ONYXKEYS'; -import * as Session from '../../../libs/actions/Session'; const propTypes = { ...withLocalizePropTypes, @@ -36,14 +35,7 @@ function SecuritySettingsPage(props) { { translationKey: 'twoFactorAuth.headerTitle', icon: Expensicons.Shield, - action: () => { - if (props.account.requiresTwoFactorAuth) { - Navigation.navigate(ROUTES.SETTINGS_2FA_IS_ENABLED); - } else { - Session.toggleTwoFactorAuth(true); - Navigation.navigate(ROUTES.SETTINGS_2FA_CODES); - } - }, + action: () => Navigation.navigate(ROUTES.SETTINGS_2FA), }, { translationKey: 'closeAccountPage.closeAccount', diff --git a/src/pages/settings/Security/TwoFactorAuth/CodesPage.js b/src/pages/settings/Security/TwoFactorAuth/CodesPage.js deleted file mode 100644 index 29cefdc156a0..000000000000 --- a/src/pages/settings/Security/TwoFactorAuth/CodesPage.js +++ /dev/null @@ -1,152 +0,0 @@ -import React, {useEffect, useState} from 'react'; -import {withOnyx} from 'react-native-onyx'; -import {ActivityIndicator, View} from 'react-native'; -import {ScrollView} from 'react-native-gesture-handler'; -import _ from 'underscore'; -import PropTypes from 'prop-types'; -import HeaderWithBackButton from '../../../../components/HeaderWithBackButton'; -import Navigation from '../../../../libs/Navigation/Navigation'; -import ScreenWrapper from '../../../../components/ScreenWrapper'; -import * as Expensicons from '../../../../components/Icon/Expensicons'; -import withLocalize, {withLocalizePropTypes} from '../../../../components/withLocalize'; -import compose from '../../../../libs/compose'; -import ROUTES from '../../../../ROUTES'; -import FullPageOfflineBlockingView from '../../../../components/BlockingViews/FullPageOfflineBlockingView'; -import * as Illustrations from '../../../../components/Icon/Illustrations'; -import withWindowDimensions, {windowDimensionsPropTypes} from '../../../../components/withWindowDimensions'; -import styles from '../../../../styles/styles'; -import FixedFooter from '../../../../components/FixedFooter'; -import Button from '../../../../components/Button'; -import PressableWithDelayToggle from '../../../../components/Pressable/PressableWithDelayToggle'; -import Text from '../../../../components/Text'; -import Section from '../../../../components/Section'; -import ONYXKEYS from '../../../../ONYXKEYS'; -import Clipboard from '../../../../libs/Clipboard'; -import themeColors from '../../../../styles/themes/default'; -import localFileDownload from '../../../../libs/localFileDownload'; -import * as TwoFactorAuthActions from '../../../../libs/actions/TwoFactorAuthActions'; - -const propTypes = { - ...withLocalizePropTypes, - ...windowDimensionsPropTypes, - account: PropTypes.shape({ - /** User recovery codes for setting up 2-FA */ - recoveryCodes: PropTypes.string, - - /** If recovery codes are loading */ - isLoading: PropTypes.bool, - }), -}; - -const defaultProps = { - account: { - recoveryCodes: '', - }, -}; - -function CodesPage(props) { - const [isNextButtonDisabled, setIsNextButtonDisabled] = useState(true); - - // Here, this eslint rule will make the unmount effect unreadable, possibly confusing with mount - // eslint-disable-next-line arrow-body-style - useEffect(() => { - return () => { - TwoFactorAuthActions.clearTwoFactorAuthData(); - }; - }, []); - - return ( - - Navigation.goBack(ROUTES.SETTINGS_SECURITY)} - /> - - -
- - {props.translate('twoFactorAuth.codesLoseAccess')} - - - {props.account.isLoading ? ( - - - - ) : ( - <> - - {Boolean(props.account.recoveryCodes) && - _.map(props.account.recoveryCodes.split(', '), (code) => ( - - {code} - - ))} - - - { - Clipboard.setString(props.account.recoveryCodes); - setIsNextButtonDisabled(false); - }} - styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} - textStyles={[styles.buttonMediumText]} - /> - { - localFileDownload('two-factor-auth-codes', props.account.recoveryCodes); - setIsNextButtonDisabled(false); - }} - inline={false} - styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} - textStyles={[styles.buttonMediumText]} - /> - - - )} - -
-
- -