From cd9594aac260cc5814447e394414b1a6f3144310 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Dec 2024 16:17:41 +0100 Subject: [PATCH 01/62] remove useNativeDriver implementation and related types --- src/libs/useNativeDriver/index.native.ts | 5 ----- src/libs/useNativeDriver/index.ts | 5 ----- src/libs/useNativeDriver/types.ts | 3 --- 3 files changed, 13 deletions(-) delete mode 100644 src/libs/useNativeDriver/index.native.ts delete mode 100644 src/libs/useNativeDriver/index.ts delete mode 100644 src/libs/useNativeDriver/types.ts diff --git a/src/libs/useNativeDriver/index.native.ts b/src/libs/useNativeDriver/index.native.ts deleted file mode 100644 index 93ed069fa807..000000000000 --- a/src/libs/useNativeDriver/index.native.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type UseNativeDriver from './types'; - -const useNativeDriver: UseNativeDriver = true; - -export default useNativeDriver; diff --git a/src/libs/useNativeDriver/index.ts b/src/libs/useNativeDriver/index.ts deleted file mode 100644 index faa9e3597cb9..000000000000 --- a/src/libs/useNativeDriver/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type UseNativeDriver from './types'; - -const useNativeDriver: UseNativeDriver = false; - -export default useNativeDriver; diff --git a/src/libs/useNativeDriver/types.ts b/src/libs/useNativeDriver/types.ts deleted file mode 100644 index de70cd0a49d0..000000000000 --- a/src/libs/useNativeDriver/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -type UseNativeDriver = boolean; - -export default UseNativeDriver; From 8a940c7b7bae5ac3cbe865cd305e2dc73aac096d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Dec 2024 16:17:52 +0100 Subject: [PATCH 02/62] refactor AnimatedStep to use react-native-reanimated for animations --- src/components/AnimatedStep/index.tsx | 36 +++++++++++---------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index 6de7d0c2b013..483e7f313410 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -1,8 +1,6 @@ -import React, {useMemo} from 'react'; +import React, {useEffect, useMemo} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; -import * as Animatable from 'react-native-animatable'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useNativeDriver from '@libs/useNativeDriver'; +import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {AnimationDirection} from './AnimatedStepContext'; @@ -19,30 +17,24 @@ type AnimatedStepProps = ChildrenProps & { }; function AnimatedStep({onAnimationEnd, direction = CONST.ANIMATION_DIRECTION.IN, style, children}: AnimatedStepProps) { - const styles = useThemeStyles(); + const sharedTranslateX = useSharedValue(0); + const transitionValue = useMemo(() => (direction === 'in' ? CONST.ANIMATED_TRANSITION_FROM_VALUE : -CONST.ANIMATED_TRANSITION_FROM_VALUE), [direction]); - const animationStyle = useMemo(() => { - const transitionValue = direction === 'in' ? CONST.ANIMATED_TRANSITION_FROM_VALUE : -CONST.ANIMATED_TRANSITION_FROM_VALUE; + useEffect(() => { + sharedTranslateX.set(withTiming(0, {duration: CONST.ANIMATED_TRANSITION}, onAnimationEnd)); + }, [sharedTranslateX, transitionValue, onAnimationEnd]); - return styles.makeSlideInTranslation('translateX', transitionValue); - }, [direction, styles]); + // Animated style for the reanimated component + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{translateX: sharedTranslateX.get()}], + })); return ( - { - if (!onAnimationEnd) { - return; - } - onAnimationEnd(); - }} - duration={CONST.ANIMATED_TRANSITION} - animation={animationStyle} - // eslint-disable-next-line react-compiler/react-compiler - useNativeDriver={useNativeDriver} - style={style} + {children} - + ); } From 6df093e88cc2e092f9f530f906e302db80278f50 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Dec 2024 16:18:01 +0100 Subject: [PATCH 03/62] refactor styles: remove unused CustomAnimation type and translation logic --- src/styles/index.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/styles/index.ts b/src/styles/index.ts index ae2f97b6b72f..8aab263255f4 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -4,7 +4,6 @@ import lodashClamp from 'lodash/clamp'; import type {LineLayer} from 'react-map-gl'; import type {Animated, ImageStyle, TextStyle, ViewStyle} from 'react-native'; import {Platform, StyleSheet} from 'react-native'; -import type {CustomAnimation} from 'react-native-animatable'; import type {PickerStyle} from 'react-native-picker-select'; import type {SharedValue} from 'react-native-reanimated'; import type {MixedStyleDeclaration, MixedStyleRecord} from 'react-native-render-html'; @@ -70,8 +69,6 @@ type OverlayStylesParams = {progress: Animated.AnimatedInterpolation; type MapDirectionStyle = Pick; @@ -88,7 +85,7 @@ type Styles = Record< | MapDirectionStyle | MapDirectionLayerStyle // eslint-disable-next-line @typescript-eslint/no-explicit-any - | ((...args: any[]) => ViewStyle | TextStyle | ImageStyle | AnchorPosition | CustomAnimation | CustomPickerStyle) + | ((...args: any[]) => ViewStyle | TextStyle | ImageStyle | AnchorPosition | CustomPickerStyle) >; // touchCallout is an iOS safari only property that controls the display of the callout information when you touch and hold a target @@ -3384,16 +3381,6 @@ const styles = (theme: ThemeColors) => }; }, - makeSlideInTranslation: (translationType: Translation, fromValue: number) => - ({ - from: { - [translationType]: fromValue, - }, - to: { - [translationType]: 0, - }, - } satisfies CustomAnimation), - growlNotificationBox: { backgroundColor: theme.inverse, borderRadius: variables.componentBorderRadiusNormal, @@ -4367,7 +4354,7 @@ const styles = (theme: ThemeColors) => } satisfies TextStyle), tabBackground: (hovered: boolean, isFocused: boolean, background: string | Animated.AnimatedInterpolation) => ({ - backgroundColor: hovered && !isFocused ? theme.highlightBG : background, + backgroundColor: (hovered && !isFocused ? theme.highlightBG : background) as string, }), tabOpacity: ( From 7d40c797c8cff36b9afa54b0e55fa57b4efb598e Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 17 Dec 2024 16:18:11 +0100 Subject: [PATCH 04/62] refactor BackgroundImage: replace Animatable with react-native-reanimated for fade-in animations --- .../BackgroundImage/index.tsx | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/pages/signin/SignInPageLayout/BackgroundImage/index.tsx b/src/pages/signin/SignInPageLayout/BackgroundImage/index.tsx index 859392812c57..46acaf5061a4 100644 --- a/src/pages/signin/SignInPageLayout/BackgroundImage/index.tsx +++ b/src/pages/signin/SignInPageLayout/BackgroundImage/index.tsx @@ -1,6 +1,6 @@ import React, {useEffect, useState} from 'react'; import {InteractionManager} from 'react-native'; -import * as Animatable from 'react-native-animatable'; +import Animated, {FadeIn} from 'react-native-reanimated'; import DesktopBackgroundImage from '@assets/images/home-background--desktop.svg'; import MobileBackgroundImage from '@assets/images/home-background--mobile.svg'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -9,15 +9,6 @@ import type BackgroundImageProps from './types'; function BackgroundImage({width, transitionDuration, isSmallScreen = false}: BackgroundImageProps) { const styles = useThemeStyles(); - const fadeIn = { - from: { - opacity: 0, - }, - to: { - opacity: 1, - }, - }; - const [isInteractionComplete, setIsInteractionComplete] = useState(false); const isAnonymous = isAnonymousUser(); @@ -40,24 +31,30 @@ function BackgroundImage({width, transitionDuration, isSmallScreen = false}: Bac return; } - return ( - - {isSmallScreen ? ( + if (isSmallScreen) { + return ( + - ) : ( - - )} - + + ); + } + + return ( + + + ); } From 3e423343ca1a43a26b8245aaad81d6c63c454ed2 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 11:54:49 +0100 Subject: [PATCH 05/62] add stepName prop to StepWrapper for better navigation tracking --- .../Security/TwoFactorAuth/Steps/CodesStep.tsx | 8 +++++--- .../TwoFactorAuth/Steps/DisabledStep.tsx | 6 +++++- .../TwoFactorAuth/Steps/EnabledStep.tsx | 5 ++++- .../Security/TwoFactorAuth/Steps/GetCode.tsx | 18 ++++++------------ .../TwoFactorAuth/Steps/SuccessStep.tsx | 1 + .../TwoFactorAuth/Steps/VerifyStep.tsx | 1 + 6 files changed, 22 insertions(+), 17 deletions(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx index 67b47cb8598d..d59a817cfbe3 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx @@ -32,6 +32,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; type CodesStepProps = BackToParams; @@ -44,7 +45,7 @@ function CodesStep({backTo}: CodesStepProps) { const {isExtraSmallScreenWidth, isSmallScreenWidth} = useResponsiveLayout(); const [error, setError] = useState(''); - const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const [account, accountMetadata] = useOnyx(ONYXKEYS.ACCOUNT); const [user] = useOnyx(ONYXKEYS.USER); const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); @@ -64,17 +65,18 @@ function CodesStep({backTo}: CodesStepProps) { useEffect(() => { setIsValidateModalVisible(!isUserValidated); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - if (account?.requiresTwoFactorAuth || account?.recoveryCodes || !isUserValidated) { + if (isLoadingOnyxValue(accountMetadata) || 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]); + }, [isUserValidated, accountMetadata]); useBeforeRemove(() => setIsValidateModalVisible(false)); return ( + +
(null); @@ -27,6 +25,7 @@ function GetCode({account}: GetCodeProps) { return ( setStep(CONST.TWO_FACTOR_AUTH_STEPS.ENABLED, CONST.ANIMATION_DIRECTION.OUT)} onEntryTransitionEnd={() => formRef.current && formRef.current.focus()} @@ -63,9 +62,4 @@ function GetCode({account}: GetCodeProps) { GetCode.displayName = 'GetCode'; -export default withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, - user: { - key: ONYXKEYS.USER, - }, -})(GetCode); +export default GetCode; diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx index 9604a7d1627f..7e628420438d 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx @@ -24,6 +24,7 @@ function SuccessStep({backTo, forwardTo}: SuccessStepParams) { return ( Date: Fri, 20 Dec 2024 11:55:18 +0100 Subject: [PATCH 06/62] refactor AnimatedStepProvider: enhance step management and animation handling --- .../AnimatedStep/AnimatedStepContext.ts | 10 +++- .../AnimatedStep/AnimatedStepProvider.tsx | 58 ++++++++++++++++++- 2 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/components/AnimatedStep/AnimatedStepContext.ts b/src/components/AnimatedStep/AnimatedStepContext.ts index 14dba4b27cc2..a95669c6b626 100644 --- a/src/components/AnimatedStep/AnimatedStepContext.ts +++ b/src/components/AnimatedStep/AnimatedStepContext.ts @@ -1,13 +1,17 @@ -import type React from 'react'; import {createContext} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; import type {ValueOf} from 'type-fest'; import type CONST from '@src/CONST'; type AnimationDirection = ValueOf; type StepContext = { - animationDirection: AnimationDirection; - setAnimationDirection: React.Dispatch>; + currentStep: string; + previousStep: string | null; + setStep: (newStep: string, direction: AnimationDirection) => void; + renderStep: (stepName: string) => React.ReactNode; + currentScreenAnimatedStyle: StyleProp; + previousScreenAnimatedStyle: StyleProp; }; const AnimatedStepContext = createContext(null); diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index ea268e1d52cb..28bc8420d71c 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -1,12 +1,64 @@ -import React, {useMemo, useState} from 'react'; +import React, {useCallback, useMemo, useState} from 'react'; +import {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import variables from '@styles/variables'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {AnimationDirection} from './AnimatedStepContext'; import AnimatedStepContext from './AnimatedStepContext'; -function AnimatedStepProvider({children}: ChildrenProps): React.ReactNode { +const ANIMATED_SCREEN_TRANSITION = 400; + +type AnimatedStepProviderProps = ChildrenProps & { + initialStep: string; + renderStep: (name: string) => React.ReactNode; +}; + +function AnimatedStepProvider({children, renderStep, initialStep}: AnimatedStepProviderProps): React.ReactNode { const [animationDirection, setAnimationDirection] = useState(CONST.ANIMATION_DIRECTION.IN); - const contextValue = useMemo(() => ({animationDirection, setAnimationDirection}), [animationDirection, setAnimationDirection]); + const [currentStep, setCurrentStep] = useState(initialStep); + const [previousStep, setPreviousStep] = useState(null); + + const currentTranslateX = useSharedValue(0); + const prevTranslateX = useSharedValue(0); + + const setStep = useCallback( + (newStep: string, direction: AnimationDirection) => { + setAnimationDirection(direction); + setPreviousStep(currentStep); + setCurrentStep(newStep); + + const currentStepPosition = direction === 'in' ? variables.sideBarWidth : -CONST.ANIMATED_TRANSITION_FROM_VALUE; + const previousStepPosition = direction === 'in' ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : variables.sideBarWidth; + + currentTranslateX.set(currentStepPosition); + currentTranslateX.set(withTiming(0, {duration: ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => setPreviousStep(null))); + + prevTranslateX.set(0); + prevTranslateX.set(withTiming(previousStepPosition, {duration: ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)})); + }, + [currentStep, currentTranslateX, prevTranslateX], + ); + + const currentScreenAnimatedStyle = useAnimatedStyle(() => ({ + transform: [{translateX: currentTranslateX.get()}], + })); + + const previousScreenAnimatedStyle = useAnimatedStyle(() => ({ + transform: [{translateX: prevTranslateX.get()}], + zIndex: animationDirection === 'in' ? -1 : 1, + })); + + const contextValue = useMemo( + () => ({ + currentStep, + previousStep, + setStep, + currentScreenAnimatedStyle, + previousScreenAnimatedStyle, + renderStep, + }), + [currentStep, previousStep, setStep, currentScreenAnimatedStyle, previousScreenAnimatedStyle, renderStep], + ); return {children}; } From 17d9da4f7c4b2b1f1fbb1f0511d5cee1c64e7bdc Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 12:00:26 +0100 Subject: [PATCH 07/62] Replace StepWrapper with AnimatedStep for improved animation handling --- src/components/AnimatedStep/index.tsx | 81 ++++++++++++++----- .../TwoFactorAuth/StepWrapper/StepWrapper.tsx | 65 --------------- .../TwoFactorAuth/Steps/CodesStep.tsx | 6 +- .../TwoFactorAuth/Steps/DisabledStep.tsx | 6 +- .../TwoFactorAuth/Steps/EnabledStep.tsx | 6 +- .../Security/TwoFactorAuth/Steps/GetCode.tsx | 6 +- .../TwoFactorAuth/Steps/SuccessStep.tsx | 6 +- .../TwoFactorAuth/Steps/VerifyStep.tsx | 6 +- 8 files changed, 77 insertions(+), 105 deletions(-) delete mode 100644 src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.tsx diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index 483e7f313410..4d92b7e8b07c 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -1,42 +1,79 @@ -import React, {useEffect, useMemo} from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; -import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import CONST from '@src/CONST'; +import React, {useEffect} from 'react'; +import Animated from 'react-native-reanimated'; +import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import ScreenWrapper from '@components/ScreenWrapper'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import type {StepCounterParams} from '@src/languages/params'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import type {AnimationDirection} from './AnimatedStepContext'; +import useAnimatedStepContext from './useAnimatedStepContext'; type AnimatedStepProps = ChildrenProps & { - /** Styles to be assigned to Container */ - style: StyleProp; + /** Name of the step */ + stepName: string; - /** Whether we're animating the step in or out */ - direction: AnimationDirection; + /** Title of the Header */ + title?: string; - /** Callback to fire when the animation ends */ - onAnimationEnd?: () => void; + /** Data to display a step counter in the header */ + stepCounter?: StepCounterParams; + + /** Method to trigger when pressing back button of the header */ + onBackButtonPress?: () => void; + + /** Called when navigated Screen's transition is finished. It does not fire when user exits the page. */ + onEntryTransitionEnd?: () => void; + + /** Flag to indicate if the keyboard avoiding view should be enabled */ + shouldEnableKeyboardAvoidingView?: boolean; }; -function AnimatedStep({onAnimationEnd, direction = CONST.ANIMATION_DIRECTION.IN, style, children}: AnimatedStepProps) { - const sharedTranslateX = useSharedValue(0); - const transitionValue = useMemo(() => (direction === 'in' ? CONST.ANIMATED_TRANSITION_FROM_VALUE : -CONST.ANIMATED_TRANSITION_FROM_VALUE), [direction]); +function AnimatedStep({stepName, title = '', stepCounter, onBackButtonPress, children = null, shouldEnableKeyboardAvoidingView = true, onEntryTransitionEnd}: AnimatedStepProps) { + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const theme = useTheme(); + const {previousStep, currentScreenAnimatedStyle, previousScreenAnimatedStyle} = useAnimatedStepContext(); useEffect(() => { - sharedTranslateX.set(withTiming(0, {duration: CONST.ANIMATED_TRANSITION}, onAnimationEnd)); - }, [sharedTranslateX, transitionValue, onAnimationEnd]); + if (previousStep) { + return; + } - // Animated style for the reanimated component - const animatedStyle = useAnimatedStyle(() => ({ - transform: [{translateX: sharedTranslateX.get()}], - })); + onEntryTransitionEnd?.(); + }, [onEntryTransitionEnd, previousStep]); return ( - {children} + + + {children} + ); } AnimatedStep.displayName = 'AnimatedStep'; + export default AnimatedStep; diff --git a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.tsx b/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.tsx deleted file mode 100644 index 2883605b915d..000000000000 --- a/src/pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import AnimatedStep from '@components/AnimatedStep'; -import useAnimatedStepContext from '@components/AnimatedStep/useAnimatedStepContext'; -import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; -import type {StepCounterParams} from '@src/languages/params'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; - -type StepWrapperProps = ChildrenProps & { - /** Title of the Header */ - title?: string; - - /** Data to display a step counter in the header */ - stepCounter?: StepCounterParams; - - /** Method to trigger when pressing back button of the header */ - onBackButtonPress?: () => void; - - /** Called when navigated Screen's transition is finished. It does not fire when user exits the page. */ - onEntryTransitionEnd?: () => void; - - /** Flag to indicate if the keyboard avoiding view should be enabled */ - shouldEnableKeyboardAvoidingView?: boolean; -}; - -function StepWrapper({ - title = '', - stepCounter, - onBackButtonPress = () => TwoFactorAuthActions.quitAndNavigateBack(), - children = null, - shouldEnableKeyboardAvoidingView = true, - onEntryTransitionEnd, -}: StepWrapperProps) { - const styles = useThemeStyles(); - const {animationDirection} = useAnimatedStepContext(); - - return ( - - - - {children} - - - ); -} - -StepWrapper.displayName = 'StepWrapper'; - -export default StepWrapper; diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx index d59a817cfbe3..f18f6f2b845a 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx @@ -2,6 +2,7 @@ import {useRoute} from '@react-navigation/native'; import React, {useEffect, useMemo, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import AnimatedStep from '@components/AnimatedStep'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -23,7 +24,6 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import localFileDownload from '@libs/localFileDownload'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/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 * as Session from '@userActions/Session'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; @@ -75,7 +75,7 @@ function CodesStep({backTo}: CodesStepProps) { useBeforeRemove(() => setIsValidateModalVisible(false)); return ( - - + ); } diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx index c0a9503c4659..723cec22f92f 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx @@ -1,11 +1,11 @@ import React from 'react'; +import AnimatedStep from '@components/AnimatedStep'; import BlockingView from '@components/BlockingViews/BlockingView'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import * as Illustrations from '@components/Icon/Illustrations'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper'; import variables from '@styles/variables'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; import CONST from '@src/CONST'; @@ -15,7 +15,7 @@ function DisabledStep() { const {translate} = useLocalize(); return ( - @@ -34,7 +34,7 @@ function DisabledStep() { onPress={() => TwoFactorAuthActions.quitAndNavigateBack()} /> - + ); } diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx index 14469d3be090..f94515cd4f16 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/EnabledStep.tsx @@ -1,5 +1,6 @@ import React from 'react'; import {View} from 'react-native'; +import AnimatedStep from '@components/AnimatedStep'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; import ScrollView from '@components/ScrollView'; @@ -8,7 +9,6 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; import CONST from '@src/CONST'; @@ -21,7 +21,7 @@ function EnabledStep() { const {translate} = useLocalize(); return ( - @@ -47,7 +47,7 @@ function EnabledStep() {
-
+ ); } diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx index 659c4eddd893..f0cae3517cc7 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx @@ -1,13 +1,13 @@ import React, {useRef} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; +import AnimatedStep from '@components/AnimatedStep'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import ScrollView from '@components/ScrollView'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; import TwoFactorAuthForm from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm'; import type {BaseTwoFactorAuthFormRef} from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types'; @@ -24,7 +24,7 @@ function GetCode() { const {setStep} = useTwoFactorAuthContext(); return ( - setStep(CONST.TWO_FACTOR_AUTH_STEPS.ENABLED, CONST.ANIMATION_DIRECTION.OUT)} @@ -56,7 +56,7 @@ function GetCode() { }} /> - + ); } diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx index 7e628420438d..bac2cd393adf 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx @@ -1,10 +1,10 @@ import React from 'react'; +import AnimatedStep from '@components/AnimatedStep'; import ConfirmationPage from '@components/ConfirmationPage'; import LottieAnimations from '@components/LottieAnimations'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@navigation/Navigation'; -import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; import * as Link from '@userActions/Link'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; @@ -23,7 +23,7 @@ function SuccessStep({backTo, forwardTo}: SuccessStepParams) { const {environmentURL} = useEnvironment(); return ( - - + ); } diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx index ff547c5dc18b..7a31a8deb0b8 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx @@ -2,6 +2,7 @@ import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; import {useOnyx} from 'react-native-onyx'; import expensifyLogo from '@assets/images/expensify-logo-round-transparent.png'; +import AnimatedStep from '@components/AnimatedStep'; import Button from '@components/Button'; import FixedFooter from '@components/FixedFooter'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -14,7 +15,6 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; import * as UserUtils from '@libs/UserUtils'; -import StepWrapper from '@pages/settings/Security/TwoFactorAuth/StepWrapper/StepWrapper'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; import TwoFactorAuthForm from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm'; import type {BaseTwoFactorAuthFormRef} from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types'; @@ -67,7 +67,7 @@ function VerifyStep() { } return ( - - + ); } From 035eb8d093a0089abc6969eff3de0c1ac3fc2bde Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 12:00:58 +0100 Subject: [PATCH 08/62] refactor TwoFactorAuth components: simplify props and enhance step management --- .../TwoFactorAuth/TwoFactorAuthForm/types.ts | 9 +-- .../TwoFactorAuth/TwoFactorAuthPage.tsx | 56 ++++++++++++++- .../TwoFactorAuth/TwoFactorAuthSteps.tsx | 71 ++++--------------- 3 files changed, 66 insertions(+), 70 deletions(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts index 14b5efa1feeb..1f999089977f 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types.ts @@ -1,11 +1,4 @@ import type {ForwardedRef} from 'react'; -import type {OnyxEntry} from 'react-native-onyx'; -import type {Account, User} from '@src/types/onyx'; - -type BaseTwoFactorAuthFormOnyxProps = { - account: OnyxEntry; - user?: OnyxEntry; -}; type BaseTwoFactorAuthFormRef = { validateAndSubmitForm: () => void; @@ -20,4 +13,4 @@ type TwoFactorAuthFormProps = { validateInsteadOfDisable?: boolean; }; -export type {BaseTwoFactorAuthFormOnyxProps, TwoFactorAuthFormProps, BaseTwoFactorAuthFormRef}; +export type {TwoFactorAuthFormProps, BaseTwoFactorAuthFormRef}; diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx index bc3dc588c1b1..d770ad1ac669 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx @@ -1,14 +1,60 @@ -import React from 'react'; +import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import AnimatedStepProvider from '@components/AnimatedStep/AnimatedStepProvider'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; import ScreenWrapper from '@components/ScreenWrapper'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; +import CodesStep from './Steps/CodesStep'; +import DisabledStep from './Steps/DisabledStep'; +import EnabledStep from './Steps/EnabledStep'; +import GetCodeStep from './Steps/GetCode'; +import SuccessStep from './Steps/SuccessStep'; +import VerifyStep from './Steps/VerifyStep'; import TwoFactorAuthSteps from './TwoFactorAuthSteps'; -function TwoFactorAuthPage() { +type TwoFactorAuthPageProps = PlatformStackScreenProps; + +function TwoFactorAuthPage({route}: TwoFactorAuthPageProps) { const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: (account) => !!account?.delegatedAccess?.delegate}); + const [account] = useOnyx(ONYXKEYS.ACCOUNT); + const initialStep = useMemo(() => { + if (account?.twoFactorAuthStep) { + return account.twoFactorAuthStep; + } + return account?.requiresTwoFactorAuth ? CONST.TWO_FACTOR_AUTH_STEPS.ENABLED : CONST.TWO_FACTOR_AUTH_STEPS.CODES; + }, [account?.requiresTwoFactorAuth, account?.twoFactorAuthStep]); + + const backTo = route.params?.backTo ?? ''; + const forwardTo = route.params?.forwardTo ?? ''; + + const renderStep = (name: string) => { + switch (name) { + case CONST.TWO_FACTOR_AUTH_STEPS.CODES: + return ; + case CONST.TWO_FACTOR_AUTH_STEPS.VERIFY: + return ; + case CONST.TWO_FACTOR_AUTH_STEPS.SUCCESS: + return ( + + ); + case CONST.TWO_FACTOR_AUTH_STEPS.ENABLED: + return ; + case CONST.TWO_FACTOR_AUTH_STEPS.DISABLED: + return ; + case CONST.TWO_FACTOR_AUTH_STEPS.GETCODE: + return ; + default: + return ; + } + }; + if (isActingAsDelegate) { return ( ); } + return ( - + ); diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx index 9c70371e76a3..ce52e6d12d1b 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx @@ -1,78 +1,31 @@ -import {useRoute} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo} from 'react'; -import {withOnyx} from 'react-native-onyx'; import type {AnimationDirection} from '@components/AnimatedStep/AnimatedStepContext'; import useAnimatedStepContext from '@components/AnimatedStep/useAnimatedStepContext'; -import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; -import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type SCREENS from '@src/SCREENS'; import type {TwoFactorAuthStep} from '@src/types/onyx/Account'; -import CodesStep from './Steps/CodesStep'; -import DisabledStep from './Steps/DisabledStep'; -import EnabledStep from './Steps/EnabledStep'; -import GetCodeStep from './Steps/GetCode'; -import SuccessStep from './Steps/SuccessStep'; -import VerifyStep from './Steps/VerifyStep'; import TwoFactorAuthContext from './TwoFactorAuthContext'; -import type {BaseTwoFactorAuthFormOnyxProps} from './TwoFactorAuthForm/types'; -type TwoFactorAuthStepProps = BaseTwoFactorAuthFormOnyxProps; - -function TwoFactorAuthSteps({account}: TwoFactorAuthStepProps) { - const route = useRoute>(); - const backTo = route.params?.backTo ?? ''; - const forwardTo = route.params?.forwardTo ?? ''; - - const currentStep = useMemo(() => { - if (account?.twoFactorAuthStep) { - return account.twoFactorAuthStep; - } - return account?.requiresTwoFactorAuth ? CONST.TWO_FACTOR_AUTH_STEPS.ENABLED : CONST.TWO_FACTOR_AUTH_STEPS.CODES; - }, [account?.requiresTwoFactorAuth, account?.twoFactorAuthStep]); - - const {setAnimationDirection} = useAnimatedStepContext(); +function TwoFactorAuthSteps() { + const {currentStep, previousStep, renderStep, setStep} = useAnimatedStepContext(); useEffect(() => () => TwoFactorAuthActions.clearTwoFactorAuthData(), []); const handleSetStep = useCallback( - (step: TwoFactorAuthStep, animationDirection: AnimationDirection = CONST.ANIMATION_DIRECTION.IN) => { - setAnimationDirection(animationDirection); + (step: TwoFactorAuthStep, direction: AnimationDirection = CONST.ANIMATION_DIRECTION.IN) => { + setStep(step, direction); TwoFactorAuthActions.setTwoFactorAuthStep(step); }, - [setAnimationDirection], + [setStep], ); const contextValue = useMemo(() => ({setStep: handleSetStep}), [handleSetStep]); - const renderStep = () => { - switch (currentStep) { - case CONST.TWO_FACTOR_AUTH_STEPS.CODES: - return ; - case CONST.TWO_FACTOR_AUTH_STEPS.VERIFY: - return ; - case CONST.TWO_FACTOR_AUTH_STEPS.SUCCESS: - return ( - - ); - case CONST.TWO_FACTOR_AUTH_STEPS.ENABLED: - return ; - case CONST.TWO_FACTOR_AUTH_STEPS.DISABLED: - return ; - case CONST.TWO_FACTOR_AUTH_STEPS.GETCODE: - return ; - default: - return ; - } - }; - - return {renderStep()}; + return ( + + {renderStep(currentStep)} + {previousStep && renderStep(previousStep)} + + ); } -export default withOnyx({ - account: {key: ONYXKEYS.ACCOUNT}, -})(TwoFactorAuthSteps); +export default TwoFactorAuthSteps; From 9b34bbd95ef7739daefa9a10be58e2215c4c20bd Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 12:01:14 +0100 Subject: [PATCH 09/62] refactor BaseModal: remove unused useNativeDriver props for cleaner code --- src/components/Modal/BaseModal.tsx | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index e1c5a7c48b86..da0346a33cc2 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -14,7 +14,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import Overlay from '@libs/Navigation/AppNavigator/Navigators/Overlay'; -import useNativeDriver from '@libs/useNativeDriver'; import variables from '@styles/variables'; import * as Modal from '@userActions/Modal'; import CONST from '@src/CONST'; @@ -37,8 +36,6 @@ function BaseModal( fullscreen = true, animationIn, animationOut, - useNativeDriver: useNativeDriverProp, - useNativeDriverForBackdrop, hideModalContentWhileAnimating = false, animationInTiming, animationOutTiming, @@ -250,10 +247,8 @@ function BaseModal( deviceWidth={windowWidth} animationIn={animationIn ?? modalStyleAnimationIn} animationOut={animationOut ?? modalStyleAnimationOut} - // eslint-disable-next-line react-compiler/react-compiler - useNativeDriver={useNativeDriverProp && useNativeDriver} - // eslint-disable-next-line react-compiler/react-compiler - useNativeDriverForBackdrop={useNativeDriverForBackdrop && useNativeDriver} + useNativeDriver + useNativeDriverForBackdrop hideModalContentWhileAnimating={hideModalContentWhileAnimating} animationInTiming={animationInTiming} animationOutTiming={animationOutTiming} From 67c9b50b8c3865079610d75962a1c9aa7e46af19 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 12:39:06 +0100 Subject: [PATCH 10/62] refactor AnimatedStep: simplify styles and remove unused hooks for cleaner implementation --- src/components/AnimatedStep/index.tsx | 14 +------------- src/styles/index.ts | 9 +++++++++ 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index 4d92b7e8b07c..0beffc72c3dd 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -3,8 +3,6 @@ import Animated from 'react-native-reanimated'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import type {StepCounterParams} from '@src/languages/params'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -32,8 +30,6 @@ type AnimatedStepProps = ChildrenProps & { function AnimatedStep({stepName, title = '', stepCounter, onBackButtonPress, children = null, shouldEnableKeyboardAvoidingView = true, onEntryTransitionEnd}: AnimatedStepProps) { const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - const theme = useTheme(); const {previousStep, currentScreenAnimatedStyle, previousScreenAnimatedStyle} = useAnimatedStepContext(); useEffect(() => { @@ -46,15 +42,7 @@ function AnimatedStep({stepName, title = '', stepCounter, onBackButtonPress, chi return ( position: 'absolute', }, + animatedStep: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: theme.appBG, + }, + sidebarFooter: { display: 'flex', justifyContent: 'center', From 30e5e8a68d469c48346e8ae3558d34649396001f Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 12:39:21 +0100 Subject: [PATCH 11/62] enhance responsivenes --- .../AnimatedStep/AnimatedStepProvider.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index 28bc8420d71c..9b607fc07eef 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -1,5 +1,7 @@ import React, {useCallback, useMemo, useState} from 'react'; import {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type ChildrenProps from '@src/types/utils/ChildrenProps'; @@ -21,14 +23,20 @@ function AnimatedStepProvider({children, renderStep, initialStep}: AnimatedStepP const currentTranslateX = useSharedValue(0); const prevTranslateX = useSharedValue(0); + const {windowWidth} = useWindowDimensions(); + // We need to use isSmallScreenWidth, to apply the correct width for the sidebar + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isSmallScreenWidth} = useResponsiveLayout(); + const setStep = useCallback( (newStep: string, direction: AnimationDirection) => { setAnimationDirection(direction); setPreviousStep(currentStep); setCurrentStep(newStep); - const currentStepPosition = direction === 'in' ? variables.sideBarWidth : -CONST.ANIMATED_TRANSITION_FROM_VALUE; - const previousStepPosition = direction === 'in' ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : variables.sideBarWidth; + const sideBarWidth = isSmallScreenWidth ? windowWidth : variables.sideBarWidth; + const currentStepPosition = direction === 'in' ? sideBarWidth : -CONST.ANIMATED_TRANSITION_FROM_VALUE; + const previousStepPosition = direction === 'in' ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : sideBarWidth; currentTranslateX.set(currentStepPosition); currentTranslateX.set(withTiming(0, {duration: ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => setPreviousStep(null))); @@ -36,7 +44,7 @@ function AnimatedStepProvider({children, renderStep, initialStep}: AnimatedStepP prevTranslateX.set(0); prevTranslateX.set(withTiming(previousStepPosition, {duration: ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)})); }, - [currentStep, currentTranslateX, prevTranslateX], + [currentStep, currentTranslateX, prevTranslateX, isSmallScreenWidth, windowWidth], ); const currentScreenAnimatedStyle = useAnimatedStyle(() => ({ From 220d8f08e2d5bb604ba79a1bed66bf44751496b3 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 12:49:38 +0100 Subject: [PATCH 12/62] Migrate withOnyx to useOnyx and remove inexistent type --- .../TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx index fe37aa9b228a..c7725f42c038 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx @@ -1,6 +1,6 @@ import React, {forwardRef, useCallback, useImperativeHandle, useRef, useState} from 'react'; -import type {ForwardedRef, RefAttributes} from 'react'; -import {withOnyx} from 'react-native-onyx'; +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'; @@ -8,9 +8,9 @@ import * as ErrorUtils from '@libs/ErrorUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; import * as Session from '@userActions/Session'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {BaseTwoFactorAuthFormOnyxProps, BaseTwoFactorAuthFormRef} from './types'; +import type {BaseTwoFactorAuthFormRef} from './types'; -type BaseTwoFactorAuthFormProps = BaseTwoFactorAuthFormOnyxProps & { +type BaseTwoFactorAuthFormProps = { autoComplete: AutoCompleteVariant; // Set this to true in order to call the validateTwoFactorAuth action which is used when setting up 2FA for the first time. @@ -18,9 +18,10 @@ type BaseTwoFactorAuthFormProps = BaseTwoFactorAuthFormOnyxProps & { validateInsteadOfDisable?: boolean; }; -function BaseTwoFactorAuthForm({account, autoComplete, validateInsteadOfDisable}: BaseTwoFactorAuthFormProps, ref: ForwardedRef) { +function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwoFactorAuthFormProps, ref: ForwardedRef) { const {translate} = useLocalize(); const [formError, setFormError] = useState<{twoFactorAuthCode?: string}>({}); + const [account] = useOnyx(ONYXKEYS.ACCOUNT); const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); const inputRef = useRef(null); const shouldClearData = account?.needsTwoFactorAuthSetup ?? false; @@ -92,6 +93,4 @@ function BaseTwoFactorAuthForm({account, autoComplete, validateInsteadOfDisable} ); } -export default withOnyx, BaseTwoFactorAuthFormOnyxProps>({ - account: {key: ONYXKEYS.ACCOUNT}, -})(forwardRef(BaseTwoFactorAuthForm)); +export default forwardRef(BaseTwoFactorAuthForm); From dad9fd660b85678123dc7f63ca602bd2ffa8f921 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 13:26:36 +0100 Subject: [PATCH 13/62] remove unnecessary eslint-disable comment in GetCode component --- src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx index f0cae3517cc7..d629817a6fff 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/GetCode.tsx @@ -51,7 +51,6 @@ function GetCode() { if (!formRef.current) { return; } - // eslint-disable-next-line @typescript-eslint/no-unsafe-call formRef.current.validateAndSubmitForm(); }} /> From 15033caad9ead746c9cd687f16216b6191d662e9 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 20 Dec 2024 13:49:04 +0100 Subject: [PATCH 14/62] Clean up how use ris redirected to disabled step --- src/libs/actions/Session/index.ts | 3 --- src/libs/actions/TwoFactorAuthActions.ts | 8 ++------ .../TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx | 12 +++++++++++- .../Security/TwoFactorAuth/TwoFactorAuthPage.tsx | 9 ++------- .../Security/TwoFactorAuth/TwoFactorAuthSteps.tsx | 14 +++++++------- src/types/onyx/Account.ts | 3 --- 6 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/libs/actions/Session/index.ts b/src/libs/actions/Session/index.ts index 1dbb01b008dd..d3da72689125 100644 --- a/src/libs/actions/Session/index.ts +++ b/src/libs/actions/Session/index.ts @@ -1000,9 +1000,6 @@ function toggleTwoFactorAuth(enable: boolean, twoFactorAuthCode = '') { key: ONYXKEYS.ACCOUNT, value: { isLoading: false, - - // When disabling 2FA, the user needs to end up on the step that confirms the setting was disabled - twoFactorAuthStep: enable ? undefined : CONST.TWO_FACTOR_AUTH_STEPS.DISABLED, }, }, ]; diff --git a/src/libs/actions/TwoFactorAuthActions.ts b/src/libs/actions/TwoFactorAuthActions.ts index 7c875b886e0b..bf4971d2961c 100644 --- a/src/libs/actions/TwoFactorAuthActions.ts +++ b/src/libs/actions/TwoFactorAuthActions.ts @@ -2,16 +2,12 @@ import Onyx from 'react-native-onyx'; import Navigation from '@libs/Navigation/Navigation'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; -import type {TwoFactorAuthStep} from '@src/types/onyx/Account'; /** * Clear 2FA data if the flow is interrupted without finishing */ function clearTwoFactorAuthData() { - Onyx.merge(ONYXKEYS.ACCOUNT, {recoveryCodes: null, twoFactorAuthSecretKey: null, twoFactorAuthStep: null, codesAreCopied: false}); -} -function setTwoFactorAuthStep(twoFactorAuthStep: TwoFactorAuthStep) { - Onyx.merge(ONYXKEYS.ACCOUNT, {twoFactorAuthStep}); + Onyx.merge(ONYXKEYS.ACCOUNT, {recoveryCodes: null, twoFactorAuthSecretKey: null, codesAreCopied: false}); } function setCodesAreCopied() { @@ -23,4 +19,4 @@ function quitAndNavigateBack(backTo?: Route) { Navigation.goBack(backTo); } -export {clearTwoFactorAuthData, setTwoFactorAuthStep, quitAndNavigateBack, setCodesAreCopied}; +export {clearTwoFactorAuthData, quitAndNavigateBack, setCodesAreCopied}; diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx index c7725f42c038..2ecdf73e9574 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx @@ -1,4 +1,4 @@ -import React, {forwardRef, useCallback, useImperativeHandle, useRef, useState} from 'react'; +import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import type {ForwardedRef} from 'react'; import {useOnyx} from 'react-native-onyx'; import type {AutoCompleteVariant, MagicCodeInputHandle} from '@components/MagicCodeInput'; @@ -6,7 +6,9 @@ import MagicCodeInput from '@components/MagicCodeInput'; import useLocalize from '@hooks/useLocalize'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; +import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; import * as Session from '@userActions/Session'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {BaseTwoFactorAuthFormRef} from './types'; @@ -25,6 +27,7 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo const [twoFactorAuthCode, setTwoFactorAuthCode] = useState(''); const inputRef = useRef(null); const shouldClearData = account?.needsTwoFactorAuthSetup ?? false; + const {setStep} = useTwoFactorAuthContext(); /** * Handle text input and clear formError upon text change @@ -41,6 +44,13 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo [account?.errors], ); + useEffect(() => { + if (!account || account?.requiresTwoFactorAuth) { + return; + } + + setStep(CONST.TWO_FACTOR_AUTH_STEPS.DISABLED); + }, [account, setStep]); /** * Check that all the form fields are valid, then trigger the submit callback */ diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx index d770ad1ac669..dfc81d8c31e1 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx @@ -1,4 +1,4 @@ -import React, {useMemo} from 'react'; +import React from 'react'; import {useOnyx} from 'react-native-onyx'; import AnimatedStepProvider from '@components/AnimatedStep/AnimatedStepProvider'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; @@ -21,12 +21,7 @@ type TwoFactorAuthPageProps = PlatformStackScreenProps !!account?.delegatedAccess?.delegate}); const [account] = useOnyx(ONYXKEYS.ACCOUNT); - const initialStep = useMemo(() => { - if (account?.twoFactorAuthStep) { - return account.twoFactorAuthStep; - } - return account?.requiresTwoFactorAuth ? CONST.TWO_FACTOR_AUTH_STEPS.ENABLED : CONST.TWO_FACTOR_AUTH_STEPS.CODES; - }, [account?.requiresTwoFactorAuth, account?.twoFactorAuthStep]); + const initialStep = account?.requiresTwoFactorAuth ? CONST.TWO_FACTOR_AUTH_STEPS.ENABLED : CONST.TWO_FACTOR_AUTH_STEPS.CODES; const backTo = route.params?.backTo ?? ''; const forwardTo = route.params?.forwardTo ?? ''; diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx index ce52e6d12d1b..5e92a31f8086 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx @@ -1,4 +1,4 @@ -import React, {useCallback, useEffect, useMemo} from 'react'; +import React, {useEffect, useMemo} from 'react'; import type {AnimationDirection} from '@components/AnimatedStep/AnimatedStepContext'; import useAnimatedStepContext from '@components/AnimatedStep/useAnimatedStepContext'; import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; @@ -11,14 +11,14 @@ function TwoFactorAuthSteps() { useEffect(() => () => TwoFactorAuthActions.clearTwoFactorAuthData(), []); - const handleSetStep = useCallback( - (step: TwoFactorAuthStep, direction: AnimationDirection = CONST.ANIMATION_DIRECTION.IN) => { - setStep(step, direction); - TwoFactorAuthActions.setTwoFactorAuthStep(step); - }, + const contextValue = useMemo( + () => ({ + setStep: (step: TwoFactorAuthStep, direction: AnimationDirection = CONST.ANIMATION_DIRECTION.IN) => { + setStep(step, direction); + }, + }), [setStep], ); - const contextValue = useMemo(() => ({setStep: handleSetStep}), [handleSetStep]); return ( diff --git a/src/types/onyx/Account.ts b/src/types/onyx/Account.ts index 33a593ca2b10..5e45621c8eb0 100644 --- a/src/types/onyx/Account.ts +++ b/src/types/onyx/Account.ts @@ -140,9 +140,6 @@ type Account = { /** Whether the two factor authentication codes were copied */ codesAreCopied?: boolean; - /** Current two factor authentication step */ - twoFactorAuthStep?: TwoFactorAuthStep; - /** Referral banners that the user dismissed */ dismissedReferralBanners?: DismissedReferralBanners; From 29840090ae69d9297640d2adc1541ffe0f9cb377 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 7 Jan 2025 16:20:51 +0100 Subject: [PATCH 15/62] Fix junky animations for AnimatedStep --- .../AnimatedStep/AnimatedStepContext.ts | 2 +- .../AnimatedStep/AnimatedStepProvider.tsx | 22 ++++++-- src/components/AnimatedStep/index.tsx | 5 +- .../Security/TwoFactorAuth/Steps/GetCode.tsx | 10 +++- .../BaseTwoFactorAuthForm.tsx | 12 +---- .../TwoFactorAuth/TwoFactorAuthPage.tsx | 50 +++++++++---------- .../TwoFactorAuth/TwoFactorAuthSteps.tsx | 9 +--- 7 files changed, 56 insertions(+), 54 deletions(-) diff --git a/src/components/AnimatedStep/AnimatedStepContext.ts b/src/components/AnimatedStep/AnimatedStepContext.ts index a95669c6b626..61b2cfc75cc5 100644 --- a/src/components/AnimatedStep/AnimatedStepContext.ts +++ b/src/components/AnimatedStep/AnimatedStepContext.ts @@ -9,7 +9,7 @@ type StepContext = { currentStep: string; previousStep: string | null; setStep: (newStep: string, direction: AnimationDirection) => void; - renderStep: (stepName: string) => React.ReactNode; + renderStep: () => React.ReactNode; currentScreenAnimatedStyle: StyleProp; previousScreenAnimatedStyle: StyleProp; }; diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index 9b607fc07eef..ac12986ea827 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -1,3 +1,4 @@ +import type {ReactNode} from 'react'; import React, {useCallback, useMemo, useState} from 'react'; import {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -12,10 +13,10 @@ const ANIMATED_SCREEN_TRANSITION = 400; type AnimatedStepProviderProps = ChildrenProps & { initialStep: string; - renderStep: (name: string) => React.ReactNode; + steps: Record; }; -function AnimatedStepProvider({children, renderStep, initialStep}: AnimatedStepProviderProps): React.ReactNode { +function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProviderProps): React.ReactNode { const [animationDirection, setAnimationDirection] = useState(CONST.ANIMATION_DIRECTION.IN); const [currentStep, setCurrentStep] = useState(initialStep); const [previousStep, setPreviousStep] = useState(null); @@ -30,6 +31,10 @@ function AnimatedStepProvider({children, renderStep, initialStep}: AnimatedStepP const setStep = useCallback( (newStep: string, direction: AnimationDirection) => { + if (currentStep === newStep || !!previousStep) { + return; + } + setAnimationDirection(direction); setPreviousStep(currentStep); setCurrentStep(newStep); @@ -44,7 +49,7 @@ function AnimatedStepProvider({children, renderStep, initialStep}: AnimatedStepP prevTranslateX.set(0); prevTranslateX.set(withTiming(previousStepPosition, {duration: ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)})); }, - [currentStep, currentTranslateX, prevTranslateX, isSmallScreenWidth, windowWidth], + [currentStep, previousStep, isSmallScreenWidth, windowWidth, currentTranslateX, prevTranslateX], ); const currentScreenAnimatedStyle = useAnimatedStyle(() => ({ @@ -63,9 +68,16 @@ function AnimatedStepProvider({children, renderStep, initialStep}: AnimatedStepP setStep, currentScreenAnimatedStyle, previousScreenAnimatedStyle, - renderStep, + renderStep: () => { + return ( + <> + {steps[currentStep]} + {previousStep && steps[previousStep]} + + ); + }, }), - [currentStep, previousStep, setStep, currentScreenAnimatedStyle, previousScreenAnimatedStyle, renderStep], + [currentStep, previousStep, setStep, currentScreenAnimatedStyle, previousScreenAnimatedStyle, steps], ); return {children}; diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index 0beffc72c3dd..914ef67acf9b 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -41,10 +41,7 @@ function AnimatedStep({stepName, title = '', stepCounter, onBackButtonPress, chi }, [onEntryTransitionEnd, previousStep]); return ( - + { + if (account?.requiresTwoFactorAuth) { + return; + } + + setStep(CONST.TWO_FACTOR_AUTH_STEPS.DISABLED); + }, [account?.requiresTwoFactorAuth, setStep]); + return ( (null); const shouldClearData = account?.needsTwoFactorAuthSetup ?? false; - const {setStep} = useTwoFactorAuthContext(); /** * Handle text input and clear formError upon text change @@ -44,13 +41,6 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo [account?.errors], ); - useEffect(() => { - if (!account || account?.requiresTwoFactorAuth) { - return; - } - - setStep(CONST.TWO_FACTOR_AUTH_STEPS.DISABLED); - }, [account, setStep]); /** * Check that all the form fields are valid, then trigger the submit callback */ diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx index dfc81d8c31e1..56708bca7e69 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage.tsx @@ -1,4 +1,5 @@ -import React from 'react'; +import type {ReactNode} from 'react'; +import React, {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; import AnimatedStepProvider from '@components/AnimatedStep/AnimatedStepProvider'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; @@ -26,29 +27,28 @@ function TwoFactorAuthPage({route}: TwoFactorAuthPageProps) { const backTo = route.params?.backTo ?? ''; const forwardTo = route.params?.forwardTo ?? ''; - const renderStep = (name: string) => { - switch (name) { - case CONST.TWO_FACTOR_AUTH_STEPS.CODES: - return ; - case CONST.TWO_FACTOR_AUTH_STEPS.VERIFY: - return ; - case CONST.TWO_FACTOR_AUTH_STEPS.SUCCESS: - return ( - - ); - case CONST.TWO_FACTOR_AUTH_STEPS.ENABLED: - return ; - case CONST.TWO_FACTOR_AUTH_STEPS.DISABLED: - return ; - case CONST.TWO_FACTOR_AUTH_STEPS.GETCODE: - return ; - default: - return ; - } - }; + const steps: Record = useMemo( + () => ({ + [CONST.TWO_FACTOR_AUTH_STEPS.CODES]: ( + + ), + [CONST.TWO_FACTOR_AUTH_STEPS.VERIFY]: , + [CONST.TWO_FACTOR_AUTH_STEPS.SUCCESS]: ( + + ), + [CONST.TWO_FACTOR_AUTH_STEPS.ENABLED]: , + [CONST.TWO_FACTOR_AUTH_STEPS.DISABLED]: , + [CONST.TWO_FACTOR_AUTH_STEPS.GETCODE]: , + }), + [backTo, forwardTo], + ); if (isActingAsDelegate) { return ( @@ -65,7 +65,7 @@ function TwoFactorAuthPage({route}: TwoFactorAuthPageProps) { return ( diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx index 5e92a31f8086..e432eea462d8 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx @@ -7,7 +7,7 @@ import type {TwoFactorAuthStep} from '@src/types/onyx/Account'; import TwoFactorAuthContext from './TwoFactorAuthContext'; function TwoFactorAuthSteps() { - const {currentStep, previousStep, renderStep, setStep} = useAnimatedStepContext(); + const {renderStep, setStep} = useAnimatedStepContext(); useEffect(() => () => TwoFactorAuthActions.clearTwoFactorAuthData(), []); @@ -20,12 +20,7 @@ function TwoFactorAuthSteps() { [setStep], ); - return ( - - {renderStep(currentStep)} - {previousStep && renderStep(previousStep)} - - ); + return {renderStep()}; } export default TwoFactorAuthSteps; From 973115fb292dd72b70c73d6d757ca64a8d0dd4d5 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 Jan 2025 16:28:17 +0100 Subject: [PATCH 16/62] Adjust PR after initial review --- src/components/AnimatedStep/AnimatedStepProvider.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index ac12986ea827..71172f14be82 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -9,8 +9,6 @@ import type ChildrenProps from '@src/types/utils/ChildrenProps'; import type {AnimationDirection} from './AnimatedStepContext'; import AnimatedStepContext from './AnimatedStepContext'; -const ANIMATED_SCREEN_TRANSITION = 400; - type AnimatedStepProviderProps = ChildrenProps & { initialStep: string; steps: Record; @@ -40,14 +38,14 @@ function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProvid setCurrentStep(newStep); const sideBarWidth = isSmallScreenWidth ? windowWidth : variables.sideBarWidth; - const currentStepPosition = direction === 'in' ? sideBarWidth : -CONST.ANIMATED_TRANSITION_FROM_VALUE; - const previousStepPosition = direction === 'in' ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : sideBarWidth; + const currentStepPosition = direction === CONST.ANIMATION_DIRECTION.IN ? sideBarWidth : -CONST.ANIMATED_TRANSITION_FROM_VALUE; + const previousStepPosition = direction === CONST.ANIMATION_DIRECTION.IN ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : sideBarWidth; currentTranslateX.set(currentStepPosition); - currentTranslateX.set(withTiming(0, {duration: ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => setPreviousStep(null))); + currentTranslateX.set(withTiming(0, {duration: CONST.ANIMATED_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => setPreviousStep(null))); prevTranslateX.set(0); - prevTranslateX.set(withTiming(previousStepPosition, {duration: ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)})); + prevTranslateX.set(withTiming(previousStepPosition, {duration: CONST.ANIMATED_TRANSITION, easing: Easing.inOut(Easing.cubic)})); }, [currentStep, previousStep, isSmallScreenWidth, windowWidth, currentTranslateX, prevTranslateX], ); @@ -58,7 +56,7 @@ function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProvid const previousScreenAnimatedStyle = useAnimatedStyle(() => ({ transform: [{translateX: prevTranslateX.get()}], - zIndex: animationDirection === 'in' ? -1 : 1, + zIndex: animationDirection === CONST.ANIMATION_DIRECTION.IN ? -1 : 1, })); const contextValue = useMemo( From f6194bcfd28574a260629fa6cde42f36c5c35e27 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 Jan 2025 16:47:19 +0100 Subject: [PATCH 17/62] Add ANIMATED_SCREEN_TRANSITION constant and update animation durations --- src/CONST.ts | 1 + src/components/AnimatedStep/AnimatedStepProvider.tsx | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 52aac707be0b..0cd55eaf0327 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -303,6 +303,7 @@ const CONST = { ANIMATED_HIGHLIGHT_END_DELAY: 800, ANIMATED_HIGHLIGHT_END_DURATION: 2000, ANIMATED_TRANSITION: 300, + ANIMATED_SCREEN_TRANSITION: 400, ANIMATED_TRANSITION_FROM_VALUE: 100, ANIMATION_IN_TIMING: 100, ANIMATION_DIRECTION: { diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index 71172f14be82..6dfbd28d4a72 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -42,10 +42,10 @@ function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProvid const previousStepPosition = direction === CONST.ANIMATION_DIRECTION.IN ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : sideBarWidth; currentTranslateX.set(currentStepPosition); - currentTranslateX.set(withTiming(0, {duration: CONST.ANIMATED_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => setPreviousStep(null))); + currentTranslateX.set(withTiming(0, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => setPreviousStep(null))); prevTranslateX.set(0); - prevTranslateX.set(withTiming(previousStepPosition, {duration: CONST.ANIMATED_TRANSITION, easing: Easing.inOut(Easing.cubic)})); + prevTranslateX.set(withTiming(previousStepPosition, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)})); }, [currentStep, previousStep, isSmallScreenWidth, windowWidth, currentTranslateX, prevTranslateX], ); From 3d73ccd63cff0eba802b6fa4123de58120521586 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Mon, 20 Jan 2025 17:14:40 +0100 Subject: [PATCH 18/62] Fix reanimated crash --- src/components/AnimatedStep/AnimatedStepProvider.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index 6dfbd28d4a72..2595623e07ae 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -1,6 +1,6 @@ import type {ReactNode} from 'react'; import React, {useCallback, useMemo, useState} from 'react'; -import {Easing, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; +import {Easing, runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useWindowDimensions from '@hooks/useWindowDimensions'; import variables from '@styles/variables'; @@ -42,7 +42,7 @@ function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProvid const previousStepPosition = direction === CONST.ANIMATION_DIRECTION.IN ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : sideBarWidth; currentTranslateX.set(currentStepPosition); - currentTranslateX.set(withTiming(0, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => setPreviousStep(null))); + currentTranslateX.set(withTiming(0, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => runOnJS(setPreviousStep)(null))); prevTranslateX.set(0); prevTranslateX.set(withTiming(previousStepPosition, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)})); From 6a051c6663c78f5227825501500148352b60492d Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 21 Jan 2025 07:00:40 +0100 Subject: [PATCH 19/62] Fix lint --- .../TwoFactorAuth/Steps/CodesStep.tsx | 28 +++++++++---------- .../TwoFactorAuth/Steps/DisabledStep.tsx | 4 +-- .../TwoFactorAuth/Steps/SuccessStep.tsx | 8 +++--- .../TwoFactorAuth/Steps/VerifyStep.tsx | 10 +++---- .../BaseTwoFactorAuthForm.tsx | 16 +++++------ .../TwoFactorAuth/TwoFactorAuthSteps.tsx | 4 +-- 6 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx index f18f6f2b845a..ae2bb94f0335 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx @@ -20,14 +20,14 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {READ_COMMANDS} from '@libs/API/types'; import Clipboard from '@libs/Clipboard'; -import * as ErrorUtils from '@libs/ErrorUtils'; +import {getEarliestErrorField, getLatestErrorField} from '@libs/ErrorUtils'; import localFileDownload from '@libs/localFileDownload'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {BackToParams, SettingsNavigatorParamList} from '@libs/Navigation/types'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; -import * as Session from '@userActions/Session'; -import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; -import * as User from '@userActions/User'; +import {toggleTwoFactorAuth} from '@userActions/Session'; +import {quitAndNavigateBack, setCodesAreCopied} from '@userActions/TwoFactorAuthActions'; +import {clearContactMethodErrors, requestValidateCodeAction, validateSecondaryLogin} from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -55,7 +55,7 @@ function CodesStep({backTo}: CodesStepProps) { const route = useRoute>(); const loginData = useMemo(() => loginList?.[contactMethod], [loginList, contactMethod]); - const validateLoginError = ErrorUtils.getEarliestErrorField(loginData, 'validateLogin'); + const validateLoginError = getEarliestErrorField(loginData, 'validateLogin'); const hasMagicCodeBeenSent = !!validateCodeAction?.validateCodeSent; const [isValidateModalVisible, setIsValidateModalVisible] = useState(!isUserValidated); @@ -68,7 +68,7 @@ function CodesStep({backTo}: CodesStepProps) { if (isLoadingOnyxValue(accountMetadata) || account?.requiresTwoFactorAuth || account?.recoveryCodes || !isUserValidated) { return; } - Session.toggleTwoFactorAuth(true); + toggleTwoFactorAuth(true); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- We want to run this when component mounts }, [isUserValidated, accountMetadata]); @@ -86,7 +86,7 @@ function CodesStep({backTo}: CodesStepProps) { }} // When the 2FA code step is open from Xero flow, we don't need to pass backTo because we build the necessary root route // from the backTo param in the route (in getMatchingRootRouteForRHPRoute) and goBack will not need a fallbackRoute. - onBackButtonPress={() => TwoFactorAuthActions.quitAndNavigateBack(route?.params?.forwardTo?.includes(READ_COMMANDS.CONNECT_POLICY_TO_XERO) ? '' : backTo)} + onBackButtonPress={() => quitAndNavigateBack(route?.params?.forwardTo?.includes(READ_COMMANDS.CONNECT_POLICY_TO_XERO) ? '' : backTo)} > {!!isUserValidated && ( @@ -126,7 +126,7 @@ function CodesStep({backTo}: CodesStepProps) { onPress={() => { Clipboard.setString(account?.recoveryCodes ?? ''); setError(''); - TwoFactorAuthActions.setCodesAreCopied(); + setCodesAreCopied(); }} styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} textStyles={[styles.buttonMediumText]} @@ -140,7 +140,7 @@ function CodesStep({backTo}: CodesStepProps) { onPress={() => { localFileDownload('two-factor-auth-codes', account?.recoveryCodes ?? ''); setError(''); - TwoFactorAuthActions.setCodesAreCopied(); + setCodesAreCopied(); }} inline={false} styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} @@ -185,14 +185,14 @@ function CodesStep({backTo}: CodesStepProps) { isVisible={isValidateModalVisible} hasMagicCodeBeenSent={hasMagicCodeBeenSent} validatePendingAction={loginData?.pendingFields?.validateCodeSent} - sendValidateCode={() => User.requestValidateCodeAction()} - handleSubmitForm={(validateCode) => User.validateSecondaryLogin(loginList, contactMethod, validateCode, true)} - validateError={!isEmptyObject(validateLoginError) ? validateLoginError : ErrorUtils.getLatestErrorField(loginData, 'validateCodeSent')} - clearError={() => User.clearContactMethodErrors(contactMethod, !isEmptyObject(validateLoginError) ? 'validateLogin' : 'validateCodeSent')} + sendValidateCode={() => requestValidateCodeAction()} + handleSubmitForm={(validateCode) => validateSecondaryLogin(loginList, contactMethod, validateCode, true)} + validateError={!isEmptyObject(validateLoginError) ? validateLoginError : getLatestErrorField(loginData, 'validateCodeSent')} + clearError={() => clearContactMethodErrors(contactMethod, !isEmptyObject(validateLoginError) ? 'validateLogin' : 'validateCodeSent')} onModalHide={() => {}} onClose={() => { setIsValidateModalVisible(false); - TwoFactorAuthActions.quitAndNavigateBack(backTo); + quitAndNavigateBack(backTo); }} /> diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx index 723cec22f92f..16c7b967e215 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/DisabledStep.tsx @@ -7,7 +7,7 @@ import * as Illustrations from '@components/Icon/Illustrations'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; +import {quitAndNavigateBack} from '@userActions/TwoFactorAuthActions'; import CONST from '@src/CONST'; function DisabledStep() { @@ -31,7 +31,7 @@ function DisabledStep() { success large text={translate('common.buttonConfirm')} - onPress={() => TwoFactorAuthActions.quitAndNavigateBack()} + onPress={() => quitAndNavigateBack()} /> diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx index bac2cd393adf..948aaea151a9 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/SuccessStep.tsx @@ -6,8 +6,8 @@ import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; import Navigation from '@navigation/Navigation'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; -import * as Link from '@userActions/Link'; -import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; +import {openLink} from '@userActions/Link'; +import {clearTwoFactorAuthData} from '@userActions/TwoFactorAuthActions'; import CONST from '@src/CONST'; import type {Route} from '@src/ROUTES'; @@ -38,13 +38,13 @@ function SuccessStep({backTo, forwardTo}: SuccessStepParams) { shouldShowButton buttonText={translate('common.buttonConfirm')} onButtonPress={() => { - TwoFactorAuthActions.clearTwoFactorAuthData(); + clearTwoFactorAuthData(); setStep(CONST.TWO_FACTOR_AUTH_STEPS.ENABLED); if (backTo) { Navigation.navigate(backTo); } if (forwardTo) { - Link.openLink(forwardTo, environmentURL); + openLink(forwardTo, environmentURL); } }} /> diff --git a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx index 7a31a8deb0b8..51ab6a7226fa 100644 --- a/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/Steps/VerifyStep.tsx @@ -14,11 +14,11 @@ import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; -import * as UserUtils from '@libs/UserUtils'; +import {getContactMethod} from '@libs/UserUtils'; import useTwoFactorAuthContext from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthContext/useTwoFactorAuth'; import TwoFactorAuthForm from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm'; import type {BaseTwoFactorAuthFormRef} from '@pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/types'; -import * as Session from '@userActions/Session'; +import {clearAccountMessages} from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -27,16 +27,16 @@ const TROUBLESHOOTING_LINK = 'https://help.expensify.com/articles/new-expensify/ function VerifyStep() { const styles = useThemeStyles(); const {translate} = useLocalize(); - const contactMethod = UserUtils.getContactMethod(); + const contactMethod = getContactMethod(); const [account] = useOnyx(ONYXKEYS.ACCOUNT); const formRef = useRef(null); const {setStep} = useTwoFactorAuthContext(); useEffect(() => { - Session.clearAccountMessages(); + clearAccountMessages(); return () => { - Session.clearAccountMessages(); + clearAccountMessages(); }; }, []); diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx index c7725f42c038..2ea45ed95088 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthForm/BaseTwoFactorAuthForm.tsx @@ -4,9 +4,9 @@ import {useOnyx} from 'react-native-onyx'; import type {AutoCompleteVariant, MagicCodeInputHandle} from '@components/MagicCodeInput'; import MagicCodeInput from '@components/MagicCodeInput'; import useLocalize from '@hooks/useLocalize'; -import * as ErrorUtils from '@libs/ErrorUtils'; -import * as ValidationUtils from '@libs/ValidationUtils'; -import * as Session from '@userActions/Session'; +import {getLatestErrorMessage} from '@libs/ErrorUtils'; +import {isValidTwoFactorCode} from '@libs/ValidationUtils'; +import {clearAccountMessages, toggleTwoFactorAuth, validateTwoFactorAuth} from '@userActions/Session'; import ONYXKEYS from '@src/ONYXKEYS'; import type {BaseTwoFactorAuthFormRef} from './types'; @@ -35,7 +35,7 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo setFormError({}); if (account?.errors) { - Session.clearAccountMessages(); + clearAccountMessages(); } }, [account?.errors], @@ -53,7 +53,7 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo return; } - if (!ValidationUtils.isValidTwoFactorCode(twoFactorAuthCode)) { + if (!isValidTwoFactorCode(twoFactorAuthCode)) { setFormError({twoFactorAuthCode: translate('twoFactorAuthForm.error.incorrect2fa')}); return; } @@ -61,10 +61,10 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo setFormError({}); if (validateInsteadOfDisable !== false) { - Session.validateTwoFactorAuth(twoFactorAuthCode, shouldClearData); + validateTwoFactorAuth(twoFactorAuthCode, shouldClearData); return; } - Session.toggleTwoFactorAuth(false, twoFactorAuthCode); + toggleTwoFactorAuth(false, twoFactorAuthCode); }, [twoFactorAuthCode, validateInsteadOfDisable, translate, shouldClearData]); useImperativeHandle(ref, () => ({ @@ -86,7 +86,7 @@ function BaseTwoFactorAuthForm({autoComplete, validateInsteadOfDisable}: BaseTwo value={twoFactorAuthCode} onChangeText={onTextInput} onFulfill={validateAndSubmitForm} - errorText={formError.twoFactorAuthCode ?? ErrorUtils.getLatestErrorMessage(account)} + errorText={formError.twoFactorAuthCode ?? getLatestErrorMessage(account)} ref={inputRef} autoFocus={false} /> diff --git a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx index e432eea462d8..e8ca79af212e 100644 --- a/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx +++ b/src/pages/settings/Security/TwoFactorAuth/TwoFactorAuthSteps.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useMemo} from 'react'; import type {AnimationDirection} from '@components/AnimatedStep/AnimatedStepContext'; import useAnimatedStepContext from '@components/AnimatedStep/useAnimatedStepContext'; -import * as TwoFactorAuthActions from '@userActions/TwoFactorAuthActions'; +import {clearTwoFactorAuthData} from '@userActions/TwoFactorAuthActions'; import CONST from '@src/CONST'; import type {TwoFactorAuthStep} from '@src/types/onyx/Account'; import TwoFactorAuthContext from './TwoFactorAuthContext'; @@ -9,7 +9,7 @@ import TwoFactorAuthContext from './TwoFactorAuthContext'; function TwoFactorAuthSteps() { const {renderStep, setStep} = useAnimatedStepContext(); - useEffect(() => () => TwoFactorAuthActions.clearTwoFactorAuthData(), []); + useEffect(() => () => clearTwoFactorAuthData(), []); const contextValue = useMemo( () => ({ From b91ce482717b6de9ae0898eb5c0f6169c3d290ef Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Thu, 23 Jan 2025 14:21:09 +0100 Subject: [PATCH 20/62] Enhance AnimatedStep context and provider with additional type definitions for better clarity and functionality --- src/components/AnimatedStep/AnimatedStepContext.ts | 11 +++++++++++ src/components/AnimatedStep/AnimatedStepProvider.tsx | 2 ++ 2 files changed, 13 insertions(+) diff --git a/src/components/AnimatedStep/AnimatedStepContext.ts b/src/components/AnimatedStep/AnimatedStepContext.ts index 61b2cfc75cc5..4bb1a8cd8aa2 100644 --- a/src/components/AnimatedStep/AnimatedStepContext.ts +++ b/src/components/AnimatedStep/AnimatedStepContext.ts @@ -6,11 +6,22 @@ import type CONST from '@src/CONST'; type AnimationDirection = ValueOf; type StepContext = { + /** The current step */ currentStep: string; + + /** The previous step */ previousStep: string | null; + + /** Method to change the current step */ setStep: (newStep: string, direction: AnimationDirection) => void; + + /** Method to render current steps */ renderStep: () => React.ReactNode; + + /** Animated style for the current screen */ currentScreenAnimatedStyle: StyleProp; + + /** Animated style for the previous screen */ previousScreenAnimatedStyle: StyleProp; }; diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index 2595623e07ae..fabcaae1275e 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -10,7 +10,9 @@ import type {AnimationDirection} from './AnimatedStepContext'; import AnimatedStepContext from './AnimatedStepContext'; type AnimatedStepProviderProps = ChildrenProps & { + /** The initial step to render */ initialStep: string; + /** Object with the steps to render */ steps: Record; }; From b801fa2984260480a55a1e496fbe2d27be7e091a Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Tue, 28 Jan 2025 16:33:37 +0100 Subject: [PATCH 21/62] refactor: Optimize AnimatedStepProvider with Reanimated shared values --- .../AnimatedStep/AnimatedStepProvider.tsx | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index fabcaae1275e..4b13d150f3b6 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -17,12 +17,10 @@ type AnimatedStepProviderProps = ChildrenProps & { }; function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProviderProps): React.ReactNode { - const [animationDirection, setAnimationDirection] = useState(CONST.ANIMATION_DIRECTION.IN); const [currentStep, setCurrentStep] = useState(initialStep); const [previousStep, setPreviousStep] = useState(null); - - const currentTranslateX = useSharedValue(0); - const prevTranslateX = useSharedValue(0); + const translateX = useSharedValue({currentScreen: 0, previousScreen: 0}); + const animationDirection = useSharedValue(CONST.ANIMATION_DIRECTION.IN); const {windowWidth} = useWindowDimensions(); // We need to use isSmallScreenWidth, to apply the correct width for the sidebar @@ -35,7 +33,7 @@ function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProvid return; } - setAnimationDirection(direction); + animationDirection.set(direction); setPreviousStep(currentStep); setCurrentStep(newStep); @@ -43,22 +41,23 @@ function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProvid const currentStepPosition = direction === CONST.ANIMATION_DIRECTION.IN ? sideBarWidth : -CONST.ANIMATED_TRANSITION_FROM_VALUE; const previousStepPosition = direction === CONST.ANIMATION_DIRECTION.IN ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : sideBarWidth; - currentTranslateX.set(currentStepPosition); - currentTranslateX.set(withTiming(0, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => runOnJS(setPreviousStep)(null))); - - prevTranslateX.set(0); - prevTranslateX.set(withTiming(previousStepPosition, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)})); + translateX.set({currentScreen: currentStepPosition, previousScreen: 0}); + translateX.set( + withTiming({currentScreen: 0, previousScreen: previousStepPosition}, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => + runOnJS(setPreviousStep)(null), + ), + ); }, - [currentStep, previousStep, isSmallScreenWidth, windowWidth, currentTranslateX, prevTranslateX], + [currentStep, previousStep, animationDirection, isSmallScreenWidth, windowWidth, translateX], ); const currentScreenAnimatedStyle = useAnimatedStyle(() => ({ - transform: [{translateX: currentTranslateX.get()}], + transform: [{translateX: translateX.get().currentScreen}], })); const previousScreenAnimatedStyle = useAnimatedStyle(() => ({ - transform: [{translateX: prevTranslateX.get()}], - zIndex: animationDirection === CONST.ANIMATION_DIRECTION.IN ? -1 : 1, + transform: [{translateX: translateX.get().previousScreen}], + zIndex: animationDirection.get() === CONST.ANIMATION_DIRECTION.IN ? -1 : 1, })); const contextValue = useMemo( From 3817052e13d4c4b451f3b91a3f3cbbcd088c07c4 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Jan 2025 17:01:16 +0100 Subject: [PATCH 22/62] Remove AnimatedStep --- .../AnimatedStep/AnimatedStepContext.ts | 31 ------- .../AnimatedStep/AnimatedStepProvider.tsx | 86 ------------------- src/components/AnimatedStep/index.tsx | 64 -------------- .../AnimatedStep/useAnimatedStepContext.ts | 13 --- src/styles/index.ts | 9 -- 5 files changed, 203 deletions(-) delete mode 100644 src/components/AnimatedStep/AnimatedStepContext.ts delete mode 100644 src/components/AnimatedStep/AnimatedStepProvider.tsx delete mode 100644 src/components/AnimatedStep/index.tsx delete mode 100644 src/components/AnimatedStep/useAnimatedStepContext.ts diff --git a/src/components/AnimatedStep/AnimatedStepContext.ts b/src/components/AnimatedStep/AnimatedStepContext.ts deleted file mode 100644 index 4bb1a8cd8aa2..000000000000 --- a/src/components/AnimatedStep/AnimatedStepContext.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {createContext} from 'react'; -import type {StyleProp, ViewStyle} from 'react-native'; -import type {ValueOf} from 'type-fest'; -import type CONST from '@src/CONST'; - -type AnimationDirection = ValueOf; - -type StepContext = { - /** The current step */ - currentStep: string; - - /** The previous step */ - previousStep: string | null; - - /** Method to change the current step */ - setStep: (newStep: string, direction: AnimationDirection) => void; - - /** Method to render current steps */ - renderStep: () => React.ReactNode; - - /** Animated style for the current screen */ - currentScreenAnimatedStyle: StyleProp; - - /** Animated style for the previous screen */ - previousScreenAnimatedStyle: StyleProp; -}; - -const AnimatedStepContext = createContext(null); - -export default AnimatedStepContext; -export type {StepContext, AnimationDirection}; diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx deleted file mode 100644 index 4b13d150f3b6..000000000000 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import type {ReactNode} from 'react'; -import React, {useCallback, useMemo, useState} from 'react'; -import {Easing, runOnJS, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import variables from '@styles/variables'; -import CONST from '@src/CONST'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import type {AnimationDirection} from './AnimatedStepContext'; -import AnimatedStepContext from './AnimatedStepContext'; - -type AnimatedStepProviderProps = ChildrenProps & { - /** The initial step to render */ - initialStep: string; - /** Object with the steps to render */ - steps: Record; -}; - -function AnimatedStepProvider({children, steps, initialStep}: AnimatedStepProviderProps): React.ReactNode { - const [currentStep, setCurrentStep] = useState(initialStep); - const [previousStep, setPreviousStep] = useState(null); - const translateX = useSharedValue({currentScreen: 0, previousScreen: 0}); - const animationDirection = useSharedValue(CONST.ANIMATION_DIRECTION.IN); - - const {windowWidth} = useWindowDimensions(); - // We need to use isSmallScreenWidth, to apply the correct width for the sidebar - // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth - const {isSmallScreenWidth} = useResponsiveLayout(); - - const setStep = useCallback( - (newStep: string, direction: AnimationDirection) => { - if (currentStep === newStep || !!previousStep) { - return; - } - - animationDirection.set(direction); - setPreviousStep(currentStep); - setCurrentStep(newStep); - - const sideBarWidth = isSmallScreenWidth ? windowWidth : variables.sideBarWidth; - const currentStepPosition = direction === CONST.ANIMATION_DIRECTION.IN ? sideBarWidth : -CONST.ANIMATED_TRANSITION_FROM_VALUE; - const previousStepPosition = direction === CONST.ANIMATION_DIRECTION.IN ? -CONST.ANIMATED_TRANSITION_FROM_VALUE : sideBarWidth; - - translateX.set({currentScreen: currentStepPosition, previousScreen: 0}); - translateX.set( - withTiming({currentScreen: 0, previousScreen: previousStepPosition}, {duration: CONST.ANIMATED_SCREEN_TRANSITION, easing: Easing.inOut(Easing.cubic)}, () => - runOnJS(setPreviousStep)(null), - ), - ); - }, - [currentStep, previousStep, animationDirection, isSmallScreenWidth, windowWidth, translateX], - ); - - const currentScreenAnimatedStyle = useAnimatedStyle(() => ({ - transform: [{translateX: translateX.get().currentScreen}], - })); - - const previousScreenAnimatedStyle = useAnimatedStyle(() => ({ - transform: [{translateX: translateX.get().previousScreen}], - zIndex: animationDirection.get() === CONST.ANIMATION_DIRECTION.IN ? -1 : 1, - })); - - const contextValue = useMemo( - () => ({ - currentStep, - previousStep, - setStep, - currentScreenAnimatedStyle, - previousScreenAnimatedStyle, - renderStep: () => { - return ( - <> - {steps[currentStep]} - {previousStep && steps[previousStep]} - - ); - }, - }), - [currentStep, previousStep, setStep, currentScreenAnimatedStyle, previousScreenAnimatedStyle, steps], - ); - - return {children}; -} - -AnimatedStepProvider.displayName = 'AnimatedStepProvider'; -export default AnimatedStepProvider; diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx deleted file mode 100644 index 914ef67acf9b..000000000000 --- a/src/components/AnimatedStep/index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, {useEffect} from 'react'; -import Animated from 'react-native-reanimated'; -import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; -import HeaderWithBackButton from '@components/HeaderWithBackButton'; -import ScreenWrapper from '@components/ScreenWrapper'; -import useThemeStyles from '@hooks/useThemeStyles'; -import type {StepCounterParams} from '@src/languages/params'; -import type ChildrenProps from '@src/types/utils/ChildrenProps'; -import useAnimatedStepContext from './useAnimatedStepContext'; - -type AnimatedStepProps = ChildrenProps & { - /** Name of the step */ - stepName: string; - - /** Title of the Header */ - title?: string; - - /** Data to display a step counter in the header */ - stepCounter?: StepCounterParams; - - /** Method to trigger when pressing back button of the header */ - onBackButtonPress?: () => void; - - /** Called when navigated Screen's transition is finished. It does not fire when user exits the page. */ - onEntryTransitionEnd?: () => void; - - /** Flag to indicate if the keyboard avoiding view should be enabled */ - shouldEnableKeyboardAvoidingView?: boolean; -}; - -function AnimatedStep({stepName, title = '', stepCounter, onBackButtonPress, children = null, shouldEnableKeyboardAvoidingView = true, onEntryTransitionEnd}: AnimatedStepProps) { - const styles = useThemeStyles(); - const {previousStep, currentScreenAnimatedStyle, previousScreenAnimatedStyle} = useAnimatedStepContext(); - - useEffect(() => { - if (previousStep) { - return; - } - - onEntryTransitionEnd?.(); - }, [onEntryTransitionEnd, previousStep]); - - return ( - - - - {children} - - - ); -} - -AnimatedStep.displayName = 'AnimatedStep'; - -export default AnimatedStep; diff --git a/src/components/AnimatedStep/useAnimatedStepContext.ts b/src/components/AnimatedStep/useAnimatedStepContext.ts deleted file mode 100644 index 2adde8fd576e..000000000000 --- a/src/components/AnimatedStep/useAnimatedStepContext.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {useContext} from 'react'; -import type {StepContext} from './AnimatedStepContext'; -import AnimatedStepContext from './AnimatedStepContext'; - -function useAnimatedStepContext(): StepContext { - 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/styles/index.ts b/src/styles/index.ts index b5bf8d291ad9..027ef0cfc5d9 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1609,15 +1609,6 @@ const styles = (theme: ThemeColors) => position: 'absolute', }, - animatedStep: { - position: 'absolute', - top: 0, - left: 0, - right: 0, - bottom: 0, - backgroundColor: theme.appBG, - }, - sidebarFooter: { display: 'flex', justifyContent: 'center', From 01a90d52a1ebd7fd76288b8c996f3b01a7e8f4c3 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Jan 2025 17:02:10 +0100 Subject: [PATCH 23/62] Configure navigation --- src/ROUTES.ts | 12 +++++++++++- src/SCREENS.ts | 9 ++++++++- src/libs/Navigation/types.ts | 7 ++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c4f266ae2590..36cf00631eb6 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -261,11 +261,21 @@ const ROUTES = { route: 'settings/profile/contact-methods/new', getRoute: (backTo?: string) => getUrlWithBackToParam('settings/profile/contact-methods/new', backTo), }, - SETTINGS_2FA: { + + SETTINGS_2FA_CODES_STEP: { route: 'settings/security/two-factor-auth', + getRoute: (backTo?: string) => getUrlWithBackToParam('settings/security/two-factor-auth', backTo), + }, + SETTINGS_2FA_VERIFY: 'settings/security/two-factor-auth/verify', + SETTINGS_2FA_SUCCESS: { + route: 'settings/security/two-factor-auth/success', getRoute: (backTo?: string, forwardTo?: string) => getUrlWithBackToParam(forwardTo ? `settings/security/two-factor-auth?forwardTo=${encodeURIComponent(forwardTo)}` : 'settings/security/two-factor-auth', backTo), }, + SETTINGS_2FA_ENABLED: 'settings/security/two-factor-auth/enabled', + SETTINGS_2FA_DISABLED: 'settings/security/two-factor-auth/disabled', + SETTINGS_2FA_GET_CODE: 'settings/security/two-factor-auth/get-code', + SETTINGS_STATUS: 'settings/profile/status', SETTINGS_STATUS_CLEAR_AFTER: 'settings/profile/status/clear-after', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 04bb3c6297ba..9d58c968ce3e 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -75,7 +75,14 @@ const SCREENS = { ADD_BANK_ACCOUNT: 'Settings_Add_Bank_Account', ADD_US_BANK_ACCOUNT: 'Settings_Add_US_Bank_Account', CLOSE: 'Settings_Close', - TWO_FACTOR_AUTH: 'Settings_TwoFactorAuth', + TWO_FACTOR_AUTH: { + CODES_STEP: 'Settings_TwoFactorAuth_Codes_Step', + VERIFY: 'Settings_TwoFactorAuth_Verify', + SUCCESS: 'Settings_TwoFactorAuth_Success', + ENABLED: 'Settings_TwoFactorAuth_Enabled', + DISABLED: 'Settings_TwoFactorAuth_Disabled', + GET_CODE: 'Settings_TwoFactorAuth_GetCode', + }, REPORT_CARD_LOST_OR_DAMAGED: 'Settings_ReportCardLostOrDamaged', TROUBLESHOOT: 'Settings_Troubleshoot', CONSOLE: 'Settings_Console', diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 2e75b1e36280..6d1184686092 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -763,10 +763,15 @@ type SettingsNavigatorParamList = { [SCREENS.GET_ASSISTANCE]: { backTo: Routes; }; - [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: { + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.CODES_STEP]: { backTo?: Routes; forwardTo?: string; }; + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.VERIFY]: undefined; + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.SUCCESS]: undefined; + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.ENABLED]: undefined; + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.DISABLED]: undefined; + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.GET_CODE]: undefined; [SCREENS.SETTINGS.DELEGATE.ADD_DELEGATE]: undefined; [SCREENS.SETTINGS.DELEGATE.DELEGATE_ROLE]: { login: string; From 582a2e3f3d66aa7c81f27097dc2b13e2a89e8191 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Jan 2025 17:02:36 +0100 Subject: [PATCH 24/62] Update linking config --- src/libs/Navigation/linkingConfig/config.ts | 25 +++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 81e0bb4889c8..a3934a79a059 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -312,10 +312,31 @@ const config: LinkingOptions['config'] = { path: ROUTES.SETTINGS_ADDRESS_STATE.route, exact: true, }, - [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: { - path: ROUTES.SETTINGS_2FA.route, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.CODES_STEP]: { + path: ROUTES.SETTINGS_2FA_CODES_STEP.route, exact: true, }, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.VERIFY]: { + path: ROUTES.SETTINGS_2FA_VERIFY, + exact: true, + }, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.SUCCESS]: { + path: ROUTES.SETTINGS_2FA_SUCCESS.route, + exact: true, + }, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.ENABLED]: { + path: ROUTES.SETTINGS_2FA_ENABLED, + exact: true, + }, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.DISABLED]: { + path: ROUTES.SETTINGS_2FA_DISABLED, + exact: true, + }, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.GET_CODE]: { + path: ROUTES.SETTINGS_2FA_GET_CODE, + exact: true, + }, + [SCREENS.SETTINGS.DELEGATE.ADD_DELEGATE]: { path: ROUTES.SETTINGS_ADD_DELEGATE, exact: true, From 98858413572fe4579c047ecbcb1edcb517aaf4bb Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Jan 2025 17:03:28 +0100 Subject: [PATCH 25/62] Update links to new 2fa screen --- src/Expensify.tsx | 2 +- src/components/ConnectToXeroFlow/index.native.tsx | 2 +- src/components/ConnectToXeroFlow/index.tsx | 2 +- .../ConnectBankAccount/components/Enable2FACard.tsx | 2 +- src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx | 2 +- src/pages/settings/Security/SecuritySettingsPage.tsx | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 1d0100add00f..103aa2fb0867 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -275,7 +275,7 @@ function Expensify() { { setShouldShowRequire2FAModal(false); - Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.HOME)); + Navigation.navigate(ROUTES.SETTINGS_2FA_CODES_STEP.getRoute(ROUTES.HOME)); }} isVisible description={translate('twoFactorAuth.twoFactorAuthIsRequiredForAdminsDescription')} diff --git a/src/components/ConnectToXeroFlow/index.native.tsx b/src/components/ConnectToXeroFlow/index.native.tsx index 6c9adfe8dbd7..f5e4ff4b50ee 100644 --- a/src/components/ConnectToXeroFlow/index.native.tsx +++ b/src/components/ConnectToXeroFlow/index.native.tsx @@ -43,7 +43,7 @@ function ConnectToXeroFlow({policyID}: ConnectToXeroFlowProps) { { setIsRequire2FAModalOpen(false); - Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.POLICY_ACCOUNTING.getRoute(policyID), getXeroSetupLink(policyID))); + Navigation.navigate(ROUTES.SETTINGS_2FA_CODES_STEP.getRoute(ROUTES.POLICY_ACCOUNTING.getRoute(policyID), getXeroSetupLink(policyID))); }} onCancel={() => setIsRequire2FAModalOpen(false)} isVisible={isRequire2FAModalOpen} diff --git a/src/components/ConnectToXeroFlow/index.tsx b/src/components/ConnectToXeroFlow/index.tsx index 7ed2c73ba348..b42c541cc8ad 100644 --- a/src/components/ConnectToXeroFlow/index.tsx +++ b/src/components/ConnectToXeroFlow/index.tsx @@ -33,7 +33,7 @@ function ConnectToXeroFlow({policyID}: ConnectToXeroFlowProps) { { setIsRequire2FAModalOpen(false); - Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.POLICY_ACCOUNTING.getRoute(policyID), getXeroSetupLink(policyID))); + Navigation.navigate(ROUTES.SETTINGS_2FA_CODES_STEP.getRoute(ROUTES.POLICY_ACCOUNTING.getRoute(policyID), getXeroSetupLink(policyID))); }} onCancel={() => { setIsRequire2FAModalOpen(false); diff --git a/src/pages/ReimbursementAccount/ConnectBankAccount/components/Enable2FACard.tsx b/src/pages/ReimbursementAccount/ConnectBankAccount/components/Enable2FACard.tsx index 27f73c64232f..e1323246ebda 100644 --- a/src/pages/ReimbursementAccount/ConnectBankAccount/components/Enable2FACard.tsx +++ b/src/pages/ReimbursementAccount/ConnectBankAccount/components/Enable2FACard.tsx @@ -27,7 +27,7 @@ function Enable2FACard({policyID}: Enable2FACardProps) { { title: translate('validationStep.secureYourAccount'), onPress: () => { - Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID))); + Navigation.navigate(ROUTES.SETTINGS_2FA_CODES_STEP.getRoute(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID))); }, icon: Expensicons.Shield, shouldShowRightIcon: true, diff --git a/src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx b/src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx index 0821f100814a..e6240766d779 100644 --- a/src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx +++ b/src/pages/ReimbursementAccount/NonUSD/Finish/index.tsx @@ -65,7 +65,7 @@ function Finish() { { title: translate('finishStep.secure'), onPress: () => { - Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID))); + Navigation.navigate(ROUTES.SETTINGS_2FA_CODES_STEP.getRoute(ROUTES.BANK_ACCOUNT_WITH_STEP_TO_OPEN.getRoute(policyID))); }, icon: Expensicons.Shield, shouldShowRightIcon: true, diff --git a/src/pages/settings/Security/SecuritySettingsPage.tsx b/src/pages/settings/Security/SecuritySettingsPage.tsx index ea6207b562dd..6b28b504e5ae 100644 --- a/src/pages/settings/Security/SecuritySettingsPage.tsx +++ b/src/pages/settings/Security/SecuritySettingsPage.tsx @@ -115,7 +115,7 @@ function SecuritySettingsPage() { { translationKey: 'twoFactorAuth.headerTitle', icon: Expensicons.Shield, - action: isActingAsDelegate ? showDelegateNoAccessMenu : waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_2FA.getRoute())), + action: isActingAsDelegate ? showDelegateNoAccessMenu : waitForNavigate(() => Navigation.navigate(ROUTES.SETTINGS_2FA_CODES_STEP.getRoute())), }, { translationKey: 'closeAccountPage.closeAccount', From be848b66c28fafc3bd3c86b57d429bae1337d3ae Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Jan 2025 17:03:45 +0100 Subject: [PATCH 26/62] Update settings stack --- .../Navigation/AppNavigator/ModalStackNavigators/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index dae1f68d9082..c762230516db 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -389,7 +389,8 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/accounting/qbd/import/QuickbooksDesktopItemsPage').default, [SCREENS.REIMBURSEMENT_ACCOUNT]: () => require('../../../../pages/ReimbursementAccount/ReimbursementAccountPage').default, [SCREENS.GET_ASSISTANCE]: () => require('../../../../pages/GetAssistancePage').default, - [SCREENS.SETTINGS.TWO_FACTOR_AUTH]: () => require('../../../../pages/settings/Security/TwoFactorAuth/TwoFactorAuthPage').default, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.CODES_STEP]: () => require('../../../../pages/settings/Security/TwoFactorAuth/CodesStepPage').default, + [SCREENS.SETTINGS.TWO_FACTOR_AUTH.VERIFY]: () => require('../../../../pages/settings/Security/TwoFactorAuth/VerifyStepPage').default, [SCREENS.SETTINGS.REPORT_CARD_LOST_OR_DAMAGED]: () => require('../../../../pages/settings/Wallet/ReportCardLostPage').default, [SCREENS.KEYBOARD_SHORTCUTS]: () => require('../../../../pages/KeyboardShortcutsPage').default, [SCREENS.SETTINGS.EXIT_SURVEY.REASON]: () => require('../../../../pages/settings/ExitSurvey/ExitSurveyReasonPage').default, From c8667e5aeaa599e9d3d45725246d16996b0bcf54 Mon Sep 17 00:00:00 2001 From: Blazej Kustra Date: Fri, 31 Jan 2025 17:04:20 +0100 Subject: [PATCH 27/62] Make CodesStep as the main screen --- .../Security/TwoFactorAuth/CodesStepPage.tsx | 222 ++++++++++++++++++ .../TwoFactorAuth/Steps/CodesStep.tsx | 204 ---------------- 2 files changed, 222 insertions(+), 204 deletions(-) create mode 100644 src/pages/settings/Security/TwoFactorAuth/CodesStepPage.tsx delete mode 100644 src/pages/settings/Security/TwoFactorAuth/Steps/CodesStep.tsx diff --git a/src/pages/settings/Security/TwoFactorAuth/CodesStepPage.tsx b/src/pages/settings/Security/TwoFactorAuth/CodesStepPage.tsx new file mode 100644 index 000000000000..d94f2b87208d --- /dev/null +++ b/src/pages/settings/Security/TwoFactorAuth/CodesStepPage.tsx @@ -0,0 +1,222 @@ +import React, {useEffect, useMemo, useState} from 'react'; +import {ActivityIndicator, View} from 'react-native'; +import {useOnyx} from 'react-native-onyx'; +import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; +import Button from '@components/Button'; +import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; +import FixedFooter from '@components/FixedFooter'; +import FormHelpMessage from '@components/FormHelpMessage'; +import HeaderWithBackButton from '@components/HeaderWithBackButton'; +import * as Expensicons from '@components/Icon/Expensicons'; +import * as Illustrations from '@components/Icon/Illustrations'; +import PressableWithDelayToggle from '@components/Pressable/PressableWithDelayToggle'; +import ScreenWrapper from '@components/ScreenWrapper'; +import ScrollView from '@components/ScrollView'; +import Section from '@components/Section'; +import Text from '@components/Text'; +import ValidateCodeActionModal from '@components/ValidateCodeActionModal'; +import useBeforeRemove from '@hooks/useBeforeRemove'; +import useLocalize from '@hooks/useLocalize'; +import useResponsiveLayout from '@hooks/useResponsiveLayout'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; +import {READ_COMMANDS} from '@libs/API/types'; +import Clipboard from '@libs/Clipboard'; +import {getEarliestErrorField, getLatestErrorField} from '@libs/ErrorUtils'; +import localFileDownload from '@libs/localFileDownload'; +import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; +import {toggleTwoFactorAuth} from '@userActions/Session'; +import {quitAndNavigateBack, setCodesAreCopied} from '@userActions/TwoFactorAuthActions'; +import {clearContactMethodErrors, requestValidateCodeAction, validateSecondaryLogin} from '@userActions/User'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; +import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; + +type CodesStepPageProps = PlatformStackScreenProps; + +function CodesStepPage({route}: CodesStepPageProps) { + const theme = useTheme(); + const styles = useThemeStyles(); + const {translate} = useLocalize(); + // We need to use isSmallScreenWidth instead of shouldUseNarrowLayout to use correct style + // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth + const {isExtraSmallScreenWidth, isSmallScreenWidth} = useResponsiveLayout(); + const [error, setError] = useState(''); + + const [account, accountMetadata] = useOnyx(ONYXKEYS.ACCOUNT); + const [user] = useOnyx(ONYXKEYS.USER); + const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST); + const [validateCodeAction] = useOnyx(ONYXKEYS.VALIDATE_ACTION_CODE); + + const isUserValidated = user?.validated; + const contactMethod = account?.primaryLogin ?? ''; + + const loginData = useMemo(() => loginList?.[contactMethod], [loginList, contactMethod]); + const validateLoginError = getEarliestErrorField(loginData, 'validateLogin'); + const hasMagicCodeBeenSent = !!validateCodeAction?.validateCodeSent; + + const [isValidateModalVisible, setIsValidateModalVisible] = useState(!isUserValidated); + + useEffect(() => { + setIsValidateModalVisible(!isUserValidated); + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if (isLoadingOnyxValue(accountMetadata) || account?.requiresTwoFactorAuth || account?.recoveryCodes || !isUserValidated) { + return; + } + toggleTwoFactorAuth(true); + // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- We want to run this when component mounts + }, [isUserValidated, accountMetadata]); + + useBeforeRemove(() => setIsValidateModalVisible(false)); + + const isActingAsDelegate = !!account?.delegatedAccess?.delegate; + if (isActingAsDelegate) { + return ( + + + + ); + } + + return ( + + quitAndNavigateBack(route?.params?.forwardTo?.includes(READ_COMMANDS.CONNECT_POLICY_TO_XERO) ? '' : route?.params?.backTo)} + /> + + + {!!isUserValidated && ( +
+ + {translate('twoFactorAuth.codesLoseAccess')} + + + {account?.isLoading ? ( + + + + ) : ( + <> + + {!!account?.recoveryCodes && + account?.recoveryCodes?.split(', ').map((code) => ( + + {code} + + ))} + + + { + Clipboard.setString(account?.recoveryCodes ?? ''); + setError(''); + setCodesAreCopied(); + }} + styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} + textStyles={[styles.buttonMediumText]} + accessible={false} + tooltipText="" + tooltipTextChecked="" + /> + { + localFileDownload('two-factor-auth-codes', account?.recoveryCodes ?? ''); + setError(''); + setCodesAreCopied(); + }} + inline={false} + styles={[styles.button, styles.buttonMedium, styles.twoFactorAuthCodesButton]} + textStyles={[styles.buttonMediumText]} + accessible={false} + tooltipText="" + tooltipTextChecked="" + /> + + + )} + +
+ )} + + {!!error && ( + + )} +