Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
e0fe771
feat(mfa): use standalone overlay navigator instead of RHP
dariusz-biela May 8, 2026
3eb973f
refactor(mfa): clean up overlay — remove dead code, simplify rendering
dariusz-biela May 8, 2026
4b5cee2
refactor(mfa): separate modal close from state reset
dariusz-biela May 8, 2026
9af6fb0
fix(mfa): add slide-out animation on overlay close
dariusz-biela May 8, 2026
0e1fa5b
fix(mfa): resolve eslint errors across PR files
dariusz-biela May 8, 2026
c6abb7f
refactor(mfa): drop eslint-disables in overlay open/close effect
dariusz-biela May 8, 2026
90efd4a
fix(mfa): unblock iOS overlay flow and stop close-effect crash
dariusz-biela May 8, 2026
a337316
refactor(mfa): align overlay web options with RHP
dariusz-biela May 11, 2026
577a579
fix(mfa): guard process() during skip-outcome close window
dariusz-biela May 11, 2026
bb5cf61
fix(mfa): disable swipe-back gesture on overlay screens
dariusz-biela May 11, 2026
509ade7
fix(mfa): reset hasInitialLaidOut on overlay close
dariusz-biela May 11, 2026
6fa9cde
refactor(mfa): drop unnecessary double cast in scenario assignment
dariusz-biela May 11, 2026
3360c23
refactor(mfa): share MfaOverlayInternalParamList between navigation a…
dariusz-biela May 11, 2026
2315bcd
refactor(mfa): replace magic zIndex with named mfaOverlayZIndex constant
dariusz-biela May 11, 2026
2692cc0
refactor(mfa): remove manual memoization from overlay component
dariusz-biela May 11, 2026
91c6019
chore(mfa): set displayName on TransparentScreen
dariusz-biela May 11, 2026
05928a2
refactor(mfa): annotate params type instead of casting in navigate()
dariusz-biela May 11, 2026
b93c440
refactor(mfa): move overlay root style into centralized theme styles
dariusz-biela May 11, 2026
8c315be
refactor(mfa): rename overlay navigator to MultifactorAuthenticationM…
dariusz-biela May 11, 2026
7ab2337
refactor(mfa): rename navigation helpers and backdrop shared value fo…
dariusz-biela May 11, 2026
931abfd
feat(mfa): intercept browser/Android back with cancel-confirm modal
dariusz-biela May 12, 2026
efdbdff
refactor(mfa): centralize back handling via requestCancel context API
dariusz-biela May 12, 2026
a5ec489
refactor(mfa): address review — rename initial screen const and reuse…
dariusz-biela May 12, 2026
8548639
Merge branch 'main' into dariusz-biela/feat/3ds/internal-navigation-v3
dariusz-biela May 13, 2026
7def086
fix(mfa): close modal directly when cancelling offline
dariusz-biela May 13, 2026
10264a7
fix(mfa): extract biometrics row to avoid pre-auth TestToolMenu crash
dariusz-biela May 13, 2026
1cb7e05
chore(seatbelt): drop stale entry for deleted BiometricsTestPage
dariusz-biela May 13, 2026
51ca9a9
fix(mfa): gate BackHandler subscription to Android in MFA sync hook
dariusz-biela May 13, 2026
15b1f80
fix(mfa): compare MFA marker presence, not top position, in history sync
dariusz-biela May 13, 2026
e60510a
fix(mfa): guard executeScenario against rapid double-tap
dariusz-biela May 13, 2026
25663e5
refactor(mfa): drop unused screen field and MultifactorAuthentication…
dariusz-biela May 13, 2026
1590b83
refactor(mfa): drop redundant screen as string casts in navigate
dariusz-biela May 13, 2026
333302e
fix(mfa): clear cancel-confirm flag on CLOSE_MODAL to avoid lingering…
dariusz-biela May 13, 2026
3dadf13
refactor(mfa): drop unused cancel from MultifactorAuthenticationConte…
dariusz-biela May 13, 2026
4d705e2
refactor(mfa): inline continuableError destructure in ValidateCodePage
dariusz-biela May 13, 2026
42dc59e
docs(mfa): correct set-state-in-effect rationale in ValidateCodePage
dariusz-biela May 13, 2026
c017089
fix(mfa): close RHP from deny outcome screen on AuthorizeTransactionPage
dariusz-biela May 15, 2026
c1cd1bc
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela May 15, 2026
640a91b
refactor(mfa): split Context module to break import cycle
dariusz-biela May 18, 2026
80a5328
refactor(mfa): drop redundant useCallback for cancel handlers
dariusz-biela May 18, 2026
033aeb5
fix(card): close RHP from ChangePINAtATMPage buttons
dariusz-biela May 18, 2026
95e2d36
fix(mfa): guard INIT in reducer to prevent rapid double-tap race
dariusz-biela May 18, 2026
3430e86
fix(mfa): preserve isModalOpen on REREGISTER to keep flow alive
dariusz-biela May 18, 2026
75b98c7
fix(mfa): disable biometrics test tool buttons when offline
dariusz-biela May 18, 2026
769d81b
fix(mfa): use modal interpolator on web to avoid lost slide animation
dariusz-biela May 19, 2026
87b2fde
refactor(mfa): collapse modal lifecycle flags into phase state
dariusz-biela May 20, 2026
6ebc52e
fix(mfa): close RHP after CHANGE-PIN scenario to restore card details
dariusz-biela May 20, 2026
a96a63c
fix(mfa): use goBack instead of closeRHPFlow after CHANGE-PIN
dariusz-biela May 20, 2026
dbd3517
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela May 20, 2026
e95e0b8
fix(mfa): defer Stack mount until SidePanel close transition ends
dariusz-biela May 20, 2026
22ecc6a
fix(mfa): move setState out of useAwaitSidePanelClose effect
dariusz-biela May 20, 2026
02e90a6
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela May 21, 2026
de770f8
fix(MFA): keep revealed PIN visible after REVEAL_PIN success
dariusz-biela May 22, 2026
31330c4
fix(MFA): defer outcome-screen push until callback transitions finish
dariusz-biela May 22, 2026
8100781
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela May 22, 2026
9b7de81
fix(MFA): pass required enter prop to modalCardStyleInterpolator
dariusz-biela May 22, 2026
2e4775c
fix(MFA): route TransitionTracker access through Navigation higher-le…
dariusz-biela May 22, 2026
6779617
Merge remote-tracking branch 'origin/main' into dariusz-biela/feat/3d…
dariusz-biela May 22, 2026
4b497cc
fix(MFA): keep revealed PAN visible after REVEAL_CARD_DETAILS success
dariusz-biela May 25, 2026
3a721b3
chore(eslint): drop stale react/jsx-props-no-spreading seatbelt entry
dariusz-biela May 25, 2026
4b0769f
fix(MFA): keep marker browser entry aligned across Navigation.goBack
dariusz-biela May 25, 2026
d148bf4
fix(MFA): cancel pending marker re-attach on modal close
dariusz-biela May 25, 2026
13fe014
fix(eslint): drop restricted TransitionTracker import in MFA marker h…
dariusz-biela May 25, 2026
c5ba52e
fix(MFA): clear cancel-confirm on flow complete to unblock outcome sc…
dariusz-biela May 26, 2026
77075bb
refactor(MFA): replace close-timer with runAfterUpcomingTransition
dariusz-biela May 26, 2026
f595398
docs(MFA): name example RHP hosts for OutcomeScreenBase onClose
dariusz-biela May 28, 2026
430013f
docs(MFA): expand executeScenario short-circuit rationale
dariusz-biela May 28, 2026
a89951e
docs(MFA): reflow INIT scenario-cast comment onto two lines
dariusz-biela May 28, 2026
389caee
docs(MFA): correct CLOSE_MODAL cancel-confirm clear rationale
dariusz-biela May 28, 2026
278ff9e
docs(MFA): document why MFA_INITIAL_SCREEN stays module-private
dariusz-biela May 28, 2026
abb7ab9
docs(MFA): convert hasInitialLaidOut explainer to JSDoc
dariusz-biela May 28, 2026
22c4094
refactor(navigation): centralize themePreference→reactNavigation mapping
dariusz-biela May 28, 2026
2e15215
docs(MFA): convert cardStyleInterpolator rationale to JSDoc + add mis…
dariusz-biela May 28, 2026
09009e5
docs(MFA): rewrite CLOSE_MODAL cancel-confirm clear rationale
dariusz-biela May 28, 2026
090875f
docs(MFA): pin CLOSE_MODAL cancel-confirm clear to flow-skipping path
dariusz-biela May 28, 2026
857e445
fix(lint): use relative import for getNavigationBaseTheme in Navigati…
dariusz-biela May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion config/eslint/eslint.seatbelt.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,6 @@
"../../src/pages/EnablePayments/FeesAndTerms/substeps/TermsStep.tsx" "react-hooks/set-state-in-effect" 1
"../../src/pages/EnablePayments/PersonalInfo/PersonalInfo.tsx" "@typescript-eslint/no-deprecated/useSubStep" 1
"../../src/pages/MissingPersonalDetails/subPages/Address.tsx" "react-hooks/refs" 4
"../../src/pages/MultifactorAuthentication/BiometricsTestPage.tsx" "@typescript-eslint/no-deprecated/InteractionManager.runAfterInteractions" 1
"../../src/pages/MultifactorAuthentication/RevokePage.tsx" "@typescript-eslint/no-deprecated/ConfirmModal" 1
"../../src/pages/MultifactorAuthentication/ValidateCodePage.tsx" "react-hooks/set-state-in-effect" 1
"../../src/pages/OnboardingInterestedFeatures/BaseOnboardingInterestedFeatures.tsx" "react-hooks/preserve-manual-memoization" 2
Expand Down
5 changes: 5 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6575,6 +6575,7 @@ const CONST = {

NAVIGATION: {
CUSTOM_HISTORY_ENTRY_SIDE_PANEL: 'CUSTOM_HISTORY-SIDE_PANEL',
CUSTOM_HISTORY_ENTRY_MFA_MODAL_NAVIGATOR: 'CUSTOM_HISTORY-MFA_MODAL_NAVIGATOR',
// Fake history entry used to keep browser Back behavior correct after revealing a route under an RHP.
// addRootHistoryRouterExtension owns when this is added, carried forward, and removed.
CUSTOM_HISTORY_ENTRY_REVEAL_PADDING: 'CUSTOM_HISTORY-REVEAL_PADDING',
Expand All @@ -6598,6 +6599,7 @@ const CONST = {
PUSH_PARAMS: 'PUSH_PARAMS',
REPLACE_PARAMS: 'REPLACE_PARAMS',
TOGGLE_SIDE_PANEL_WITH_HISTORY: 'TOGGLE_SIDE_PANEL_WITH_HISTORY',
TOGGLE_MFA_MODAL_NAVIGATOR_WITH_HISTORY: 'TOGGLE_MFA_MODAL_NAVIGATOR_WITH_HISTORY',
},
},
TIME_PERIOD: {
Expand Down Expand Up @@ -9973,6 +9975,9 @@ const CONST = {
SAFE_AREA: {
DISMISS_KEYBOARD_LANDSCAPE_MODE: 'SafeArea-DismissKeyboardLandscapeMode',
},
MFA_OVERLAY: {
BACKDROP: 'MfaOverlay-Backdrop',
},
DOMAIN: {
GROUPS: {
CREATE_GROUP_BUTTON: 'DomainGroups-CreateGroupButton',
Expand Down
12 changes: 0 additions & 12 deletions src/ROUTES.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*/
import type {TupleToUnion, ValueOf} from 'type-fest';
import type {UpperCaseCharacters} from 'type-fest/source/internal';
import type {MultifactorAuthenticationPromptType} from './components/MultifactorAuthentication/config/types';
import type {SearchFilterKey, SearchQueryString, UserFriendlyKey} from './components/Search/types';
import type CONST from './CONST';
import type {IOUAction, IOURequestType, IOUType, OdometerImageType} from './CONST';
Expand Down Expand Up @@ -4078,17 +4077,6 @@ const ROUTES = {
getRoute: (domainAccountID: number, accountID: number) => `domain/${domainAccountID}/members/${accountID}/move` as const,
},

MULTIFACTOR_AUTHENTICATION_MAGIC_CODE: `multifactor-authentication/magic-code`,
MULTIFACTOR_AUTHENTICATION_BIOMETRICS_TEST: 'multifactor-authentication/scenario/biometrics-test',

MULTIFACTOR_AUTHENTICATION_OUTCOME_SUCCESS: 'multifactor-authentication/outcome/success',
MULTIFACTOR_AUTHENTICATION_OUTCOME_FAILURE: 'multifactor-authentication/outcome/failure',

MULTIFACTOR_AUTHENTICATION_PROMPT: {
route: `multifactor-authentication/prompt/:promptType`,
getRoute: (promptType: MultifactorAuthenticationPromptType) => `multifactor-authentication/prompt/${promptType}` as const,
},
MULTIFACTOR_AUTHENTICATION_NOT_FOUND: 'multifactor-authentication/not-found',
MULTIFACTOR_AUTHENTICATION_REVOKE: 'multifactor-authentication/revoke',

DOMAIN_GROUPS: {
Expand Down
2 changes: 0 additions & 2 deletions src/SCREENS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1076,11 +1076,9 @@ const SCREENS = {
},
MULTIFACTOR_AUTHENTICATION: {
MAGIC_CODE: 'Multifactor_Authentication_Magic_Code',
BIOMETRICS_TEST: 'Multifactor_Authentication_Biometrics_Test',
OUTCOME_SUCCESS: 'Multifactor_Authentication_Outcome_Success',
OUTCOME_FAILURE: 'Multifactor_Authentication_Outcome_Failure',
PROMPT: 'Multifactor_Authentication_Prompt',
NOT_FOUND: 'Multifactor_Authentication_Not_Found',
REVOKE: 'Multifactor_Authentication_Revoke',
AUTHORIZE_TRANSACTION: 'Multifactor_Authentication_Authorize_Transaction',
},
Expand Down
59 changes: 59 additions & 0 deletions src/components/BiometricsTestToolRow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import React, {useState} from 'react';
import {View} from 'react-native';
import useBiometricRegistrationStatus, {REGISTRATION_STATUS} from '@hooks/useBiometricRegistrationStatus';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import {revokeMultifactorAuthenticationCredentials} from '@libs/actions/MultifactorAuthentication';
import CONST from '@src/CONST';
import Button from './Button';
import {useMultifactorAuthentication} from './MultifactorAuthentication/Context';
import TestToolRow from './TestToolRow';

function BiometricsTestToolRow() {
const styles = useThemeStyles();
const {translate} = useLocalize();
const {executeScenario} = useMultifactorAuthentication();
const {localCredentialID, isCurrentDeviceRegistered, otherDeviceCount, registrationStatus} = useBiometricRegistrationStatus();
const {isOffline} = useNetwork();
const [isMFARevokeLoading, setIsMFARevokeLoading] = useState(false);

const statusTextMap = {
[REGISTRATION_STATUS.NEVER_REGISTERED]: translate('multifactorAuthentication.biometricsTest.statusNeverRegistered'),
[REGISTRATION_STATUS.NOT_REGISTERED]: translate('multifactorAuthentication.biometricsTest.statusNotRegistered'),
[REGISTRATION_STATUS.REGISTERED_OTHER_DEVICE]: translate('multifactorAuthentication.biometricsTest.statusRegisteredOtherDevice', {count: otherDeviceCount}),
[REGISTRATION_STATUS.REGISTERED_THIS_DEVICE]: translate('multifactorAuthentication.biometricsTest.statusRegisteredThisDevice'),
};
const biometricsTitle = translate('multifactorAuthentication.biometricsTest.troubleshootBiometricsStatus', {status: statusTextMap[registrationStatus]});

return (
<TestToolRow title={biometricsTitle}>
<View style={[styles.flexRow, styles.gap2]}>
<Button
small
isDisabled={isOffline}
text={translate('multifactorAuthentication.biometricsTest.test')}
onPress={() => executeScenario(CONST.MULTIFACTOR_AUTHENTICATION.SCENARIO.BIOMETRICS_TEST)}
Comment thread
dariusz-biela marked this conversation as resolved.
/>
{isCurrentDeviceRegistered && !!localCredentialID && (
<Button
danger
isDisabled={isOffline}
isLoading={isMFARevokeLoading}
small
text={translate('multifactorAuthentication.revoke.revoke')}
onPress={async () => {
setIsMFARevokeLoading(true);
await revokeMultifactorAuthenticationCredentials({onlyKeyID: localCredentialID});
setIsMFARevokeLoading(false);
}}
/>
)}
</View>
</TestToolRow>
);
}

BiometricsTestToolRow.displayName = 'BiometricsTestToolRow';

export default BiometricsTestToolRow;
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {createContext, useContext} from 'react';
import type {MultifactorAuthenticationActionsContextType} from './types';

const MultifactorAuthenticationActionsContext = createContext<MultifactorAuthenticationActionsContextType | undefined>(undefined);

function useMultifactorAuthenticationActions(): MultifactorAuthenticationActionsContextType {
const context = useContext(MultifactorAuthenticationActionsContext);

if (!context) {
throw new Error('useMultifactorAuthenticationActions must be used within a MultifactorAuthenticationStateProvider');
}

return context;
}

export default MultifactorAuthenticationActionsContext;
export {useMultifactorAuthenticationActions};
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, {useMemo, useReducer} from 'react';
import type {ReactNode} from 'react';
import MultifactorAuthenticationActionsContext from './MultifactorAuthenticationActionsContext';
import {MultifactorAuthenticationContextProvider} from './MultifactorAuthenticationMainContext';
import MultifactorAuthenticationStateContext from './MultifactorAuthenticationStateContext';
import {DEFAULT_STATE, stateReducer} from './stateReducer';

type MultifactorAuthenticationProviderProps = {
children: ReactNode;
};

function MultifactorAuthenticationStateProvider({children}: MultifactorAuthenticationProviderProps) {
const [state, dispatch] = useReducer(stateReducer, DEFAULT_STATE);
const actions = useMemo(() => ({dispatch}), [dispatch]);

return (
<MultifactorAuthenticationStateContext.Provider value={state}>
<MultifactorAuthenticationActionsContext.Provider value={actions}>{children}</MultifactorAuthenticationActionsContext.Provider>
</MultifactorAuthenticationStateContext.Provider>
);
}

MultifactorAuthenticationStateProvider.displayName = 'MultifactorAuthenticationStateProvider';

function MultifactorAuthenticationContextProviders({children}: MultifactorAuthenticationProviderProps) {
return (
<MultifactorAuthenticationStateProvider>
<MultifactorAuthenticationContextProvider>{children}</MultifactorAuthenticationContextProvider>
</MultifactorAuthenticationStateProvider>
);
}

MultifactorAuthenticationContextProviders.displayName = 'MultifactorAuthenticationContextProviders';

export default MultifactorAuthenticationContextProviders;
export {DEFAULT_STATE, MultifactorAuthenticationStateProvider};
Loading
Loading