diff --git a/src/App.tsx b/src/App.tsx
index 683f6772a9bb..abd15d47c8fe 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -47,6 +47,7 @@ import HybridAppHandler from './HybridAppHandler';
import OnyxUpdateManager from './libs/actions/OnyxUpdateManager';
import './libs/HybridApp';
import {AttachmentModalContextProvider} from './pages/media/AttachmentModalScreen/AttachmentModalContext';
+import ExpensifyCardContextProvider from './pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardContextProvider';
import './setup/backgroundTask';
import './setup/fraudProtection';
import './setup/hybridApp';
@@ -121,6 +122,7 @@ function App() {
FullScreenBlockingViewContextProvider,
FullScreenLoaderContextProvider,
SidePanelContextProvider,
+ ExpensifyCardContextProvider,
]}
>
diff --git a/src/ROUTES.ts b/src/ROUTES.ts
index 5c8496f59b69..8fc0ec871cbe 100644
--- a/src/ROUTES.ts
+++ b/src/ROUTES.ts
@@ -259,6 +259,10 @@ const ROUTES = {
route: 'settings/wallet/card/:cardID?',
getRoute: (cardID: string) => `settings/wallet/card/${cardID}` as const,
},
+ SETTINGS_WALLET_DOMAIN_CARD_CONFIRM_MAGIC_CODE: {
+ route: 'settings/wallet/card/:cardID/confirm-magic-code',
+ getRoute: (cardID: string) => `settings/wallet/card/${cardID}/confirm-magic-code` as const,
+ },
SETTINGS_DOMAIN_CARD_DETAIL: {
route: 'settings/card/:cardID?',
getRoute: (cardID: string) => `settings/card/${cardID}` as const,
diff --git a/src/SCREENS.ts b/src/SCREENS.ts
index a4236feaed70..1ce7dd940c17 100644
--- a/src/SCREENS.ts
+++ b/src/SCREENS.ts
@@ -140,6 +140,7 @@ const SCREENS = {
ROOT: 'Settings_Wallet',
VERIFY_ACCOUNT: 'Settings_Wallet_VerifyAccount',
DOMAIN_CARD: 'Settings_Wallet_DomainCard',
+ DOMAIN_CARD_CONFIRM_MAGIC_CODE: 'Settings_Wallet_DomainCard_ConfirmMagicCode',
TRANSFER_BALANCE: 'Settings_Wallet_Transfer_Balance',
CHOOSE_TRANSFER_ACCOUNT: 'Settings_Wallet_Choose_Transfer_Account',
ENABLE_PAYMENTS: 'Settings_Wallet_EnablePayments',
diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
index 302414f20a00..9ecf8b75ffbe 100644
--- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
+++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx
@@ -281,7 +281,7 @@ const ExpensifyCardModalStackNavigator = createModalStackNavigator({
});
const DomainCardModalStackNavigator = createModalStackNavigator({
- [SCREENS.DOMAIN_CARD.DOMAIN_CARD_DETAIL]: () => require('../../../../pages/settings/Wallet/ExpensifyCardPage').default,
+ [SCREENS.DOMAIN_CARD.DOMAIN_CARD_DETAIL]: () => require('../../../../pages/settings/Wallet/ExpensifyCardPage/index').default,
[SCREENS.DOMAIN_CARD.DOMAIN_CARD_REPORT_FRAUD]: () => require('../../../../pages/settings/Wallet/ReportVirtualCardFraudPage').default,
});
@@ -351,7 +351,9 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/settings/AboutPage/ShareLogPage').default,
[SCREENS.SETTINGS.WALLET.CARDS_DIGITAL_DETAILS_UPDATE_ADDRESS]: () => require('../../../../pages/settings/Profile/PersonalDetails/PersonalAddressPage').default,
[SCREENS.SETTINGS.WALLET.VERIFY_ACCOUNT]: () => require('../../../../pages/settings/Wallet/VerifyAccountPage').default,
- [SCREENS.SETTINGS.WALLET.DOMAIN_CARD]: () => require('../../../../pages/settings/Wallet/ExpensifyCardPage').default,
+ [SCREENS.SETTINGS.WALLET.DOMAIN_CARD]: () => require('../../../../pages/settings/Wallet/ExpensifyCardPage/index').default,
+ [SCREENS.SETTINGS.WALLET.DOMAIN_CARD_CONFIRM_MAGIC_CODE]: () =>
+ require('../../../../pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardVerifyAccountPage').default,
[SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD]: () => require('../../../../pages/settings/Wallet/ReportVirtualCardFraudPage').default,
[SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD_CONFIRM_MAGIC_CODE]: () =>
require('../../../../pages/settings/Wallet/ReportVirtualCardFraudVerifyAccountPage').default,
diff --git a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
index 8c8a4930b017..9a841547c3ac 100755
--- a/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
+++ b/src/libs/Navigation/linkingConfig/RELATIONS/SETTINGS_TO_RHP.ts
@@ -31,6 +31,7 @@ const SETTINGS_TO_RHP: Partial['config'] = {
path: ROUTES.SETTINGS_WALLET_DOMAIN_CARD.route,
exact: true,
},
+ [SCREENS.SETTINGS.WALLET.DOMAIN_CARD_CONFIRM_MAGIC_CODE]: {
+ path: ROUTES.SETTINGS_WALLET_DOMAIN_CARD_CONFIRM_MAGIC_CODE.route,
+ exact: true,
+ },
[SCREENS.SETTINGS.WALLET.VERIFY_ACCOUNT]: {
path: ROUTES.SETTINGS_WALLET_VERIFY_ACCOUNT,
exact: true,
diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts
index c431b82e8eed..0bbe8d8af9fe 100644
--- a/src/libs/Navigation/types.ts
+++ b/src/libs/Navigation/types.ts
@@ -146,6 +146,10 @@ type SettingsNavigatorParamList = {
/** cardID of selected card */
cardID: string;
};
+ [SCREENS.SETTINGS.WALLET.DOMAIN_CARD_CONFIRM_MAGIC_CODE]: {
+ /** cardID of selected card */
+ cardID: string;
+ };
[SCREENS.SETTINGS.WALLET.REPORT_VIRTUAL_CARD_FRAUD]: {
/** cardID of selected card */
cardID: string;
diff --git a/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardContextProvider.tsx b/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardContextProvider.tsx
new file mode 100644
index 000000000000..22972219725c
--- /dev/null
+++ b/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardContextProvider.tsx
@@ -0,0 +1,47 @@
+import type {PropsWithChildren} from 'react';
+import React, {createContext, useMemo, useState} from 'react';
+import type {ExpensifyCardDetails} from '@src/types/onyx/Card';
+
+type ExpensifyCardContextProviderProps = {
+ cardsDetails: Record;
+ setCardsDetails: React.Dispatch>>;
+ isCardDetailsLoading: Record;
+ setIsCardDetailsLoading: React.Dispatch>>;
+ cardsDetailsErrors: Record;
+ setCardsDetailsErrors: React.Dispatch>>;
+};
+
+const ExpensifyCardContext = createContext({
+ cardsDetails: {},
+ setCardsDetails: () => {},
+ isCardDetailsLoading: {},
+ setIsCardDetailsLoading: () => {},
+ cardsDetailsErrors: {},
+ setCardsDetailsErrors: () => {},
+});
+
+/**
+ * Context to display revealed expensify card data and pass it between screens.
+ */
+function ExpensifyCardContextProvider({children}: PropsWithChildren) {
+ const [cardsDetails, setCardsDetails] = useState>({});
+ const [isCardDetailsLoading, setIsCardDetailsLoading] = useState>({});
+ const [cardsDetailsErrors, setCardsDetailsErrors] = useState>({});
+
+ const value = useMemo(
+ () => ({
+ cardsDetails,
+ setCardsDetails,
+ isCardDetailsLoading,
+ setIsCardDetailsLoading,
+ cardsDetailsErrors,
+ setCardsDetailsErrors,
+ }),
+ [cardsDetails, setCardsDetails, isCardDetailsLoading, setIsCardDetailsLoading, cardsDetailsErrors, setCardsDetailsErrors],
+ );
+
+ return {children};
+}
+
+export default ExpensifyCardContextProvider;
+export {ExpensifyCardContext};
diff --git a/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardVerifyAccountPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardVerifyAccountPage.tsx
new file mode 100644
index 000000000000..d5662c1a24e2
--- /dev/null
+++ b/src/pages/settings/Wallet/ExpensifyCardPage/ExpensifyCardVerifyAccountPage.tsx
@@ -0,0 +1,79 @@
+import React, {useState} from 'react';
+import ValidateCodeActionContent from '@components/ValidateCodeActionModal/ValidateCodeActionContent';
+import useLocalize from '@hooks/useLocalize';
+import useOnyx from '@hooks/useOnyx';
+import {revealVirtualCardDetails} from '@libs/actions/Card';
+import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User';
+import Navigation from '@libs/Navigation/Navigation';
+import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
+import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
+import ONYXKEYS from '@src/ONYXKEYS';
+import ROUTES from '@src/ROUTES';
+import type SCREENS from '@src/SCREENS';
+import type {ExpensifyCardDetails} from '@src/types/onyx/Card';
+import type {Errors} from '@src/types/onyx/OnyxCommon';
+import useExpensifyCardContext from './useExpensifyCardContext';
+
+type ExpensifyCardVerifyAccountPageProps = PlatformStackScreenProps;
+
+function ExpensifyCardVerifyAccountPage({
+ route: {
+ params: {cardID = ''},
+ },
+}: ExpensifyCardVerifyAccountPageProps) {
+ const {translate} = useLocalize();
+ const [validateError, setValidateError] = useState({});
+ const [account] = useOnyx(ONYXKEYS.ACCOUNT, {canBeMissing: false});
+ const primaryLogin = account?.primaryLogin ?? '';
+ const {setIsCardDetailsLoading, setCardsDetails, setCardsDetailsErrors} = useExpensifyCardContext();
+
+ const handleRevealCardDetails = (validateCode: string) => {
+ setIsCardDetailsLoading((prevState: Record) => ({
+ ...prevState,
+ [cardID]: true,
+ }));
+ // We can't store the response in Onyx for security reasons.
+ // That is why this action is handled manually and the response is stored in a local state.
+ // Hence eslint disable here.
+ // eslint-disable-next-line rulesdir/no-thenable-actions-in-views
+ revealVirtualCardDetails(Number.parseInt(cardID, 10), validateCode)
+ .then((value) => {
+ setCardsDetails((prevState: Record) => ({...prevState, [cardID]: value}));
+ setCardsDetailsErrors((prevState) => ({
+ ...prevState,
+ [cardID]: '',
+ }));
+ Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID));
+ })
+ .catch((error: string) => {
+ // Displaying magic code errors is handled in the modal, no need to set it on the card
+ setCardsDetailsErrors((prevState) => ({
+ ...prevState,
+ [cardID]: error,
+ }));
+ })
+ .finally(() => {
+ setIsCardDetailsLoading((prevState: Record) => ({...prevState, [cardID]: false}));
+ });
+ };
+
+ return (
+ requestValidateCodeAction()}
+ validateCodeActionErrorField="revealExpensifyCardDetails"
+ handleSubmitForm={handleRevealCardDetails}
+ validateError={validateError}
+ clearError={() => setValidateError({})}
+ onClose={() => {
+ resetValidateActionCodeSent();
+ Navigation.goBack(ROUTES.SETTINGS_WALLET_DOMAIN_CARD.getRoute(cardID));
+ }}
+ />
+ );
+}
+
+ExpensifyCardVerifyAccountPage.displayName = 'ExpensifyCardVerifyAccountPage';
+
+export default ExpensifyCardVerifyAccountPage;
diff --git a/src/pages/settings/Wallet/ExpensifyCardPage.tsx b/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx
similarity index 82%
rename from src/pages/settings/Wallet/ExpensifyCardPage.tsx
rename to src/pages/settings/Wallet/ExpensifyCardPage/index.tsx
index cfdf3695610d..af29c821243a 100644
--- a/src/pages/settings/Wallet/ExpensifyCardPage.tsx
+++ b/src/pages/settings/Wallet/ExpensifyCardPage/index.tsx
@@ -12,23 +12,23 @@ import MenuItem from '@components/MenuItem';
import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription';
import ScreenWrapper from '@components/ScreenWrapper';
import ScrollView from '@components/ScrollView';
-import ValidateCodeActionModal from '@components/ValidateCodeActionModal';
import useBeforeRemove from '@hooks/useBeforeRemove';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import useThemeStyles from '@hooks/useThemeStyles';
-import {requestValidateCodeAction, resetValidateActionCodeSent} from '@libs/actions/User';
+import {resetValidateActionCodeSent} from '@libs/actions/User';
import {formatCardExpiration, getDomainCards, maskCard, maskPin} from '@libs/CardUtils';
import {convertToDisplayString, getCurrencyKeyByCountryCode} from '@libs/CurrencyUtils';
-import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import type {SettingsNavigatorParamList} from '@libs/Navigation/types';
import {buildCannedSearchQuery} from '@libs/SearchQueryUtils';
import NotFoundPage from '@pages/ErrorPage/NotFoundPage';
-import {clearActivatedCardPin, revealVirtualCardDetails} from '@userActions/Card';
+import RedDotCardSection from '@pages/settings/Wallet/RedDotCardSection';
+import CardDetails from '@pages/settings/Wallet/WalletPage/CardDetails';
+import {clearActivatedCardPin} from '@userActions/Card';
import {openOldDotLink} from '@userActions/Link';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
@@ -36,11 +36,8 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type SCREENS from '@src/SCREENS';
import type {CurrencyList} from '@src/types/onyx';
-import type {ExpensifyCardDetails} from '@src/types/onyx/Card';
-import type {Errors} from '@src/types/onyx/OnyxCommon';
import {getEmptyObject} from '@src/types/utils/EmptyObject';
-import RedDotCardSection from './RedDotCardSection';
-import CardDetails from './WalletPage/CardDetails';
+import useExpensifyCardContext from './useExpensifyCardContext';
type ExpensifyCardPageProps = PlatformStackScreenProps;
@@ -76,8 +73,6 @@ function ExpensifyCardPage({
const styles = useThemeStyles();
const {isOffline} = useNetwork();
const {translate} = useLocalize();
- const [isValidateCodeActionModalVisible, setIsValidateCodeActionModalVisible] = useState(false);
- const [currentCardID, setCurrentCardID] = useState(-1);
const isTravelCard = cardList?.[cardID]?.nameValuePairs?.isTravelCard;
const shouldDisplayCardDomain = !isTravelCard && (!cardList?.[cardID]?.nameValuePairs?.issuedBy || !cardList?.[cardID]?.nameValuePairs?.isVirtual);
const domain = cardList?.[cardID]?.domainName ?? '';
@@ -93,8 +88,6 @@ function ExpensifyCardPage({
return [cardList?.[cardID]];
}, [shouldDisplayCardDomain, cardList, cardID, domain]);
- useBeforeRemove(() => setIsValidateCodeActionModalVisible(false));
-
useEffect(() => {
return () => {
if (!pin) {
@@ -118,52 +111,15 @@ function ExpensifyCardPage({
const cardToAdd = useMemo(() => {
return virtualCards?.at(0);
}, [virtualCards]);
- const [cardsDetails, setCardsDetails] = useState>({});
- const [isCardDetailsLoading, setIsCardDetailsLoading] = useState>({});
- const [cardsDetailsErrors, setCardsDetailsErrors] = useState>({});
- const [validateError, setValidateError] = useState({});
- const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext);
- const openValidateCodeModal = (revealedCardID: number) => {
- setCurrentCardID(revealedCardID);
- setIsValidateCodeActionModalVisible(true);
- };
+ const {cardsDetails, setCardsDetails, isCardDetailsLoading, cardsDetailsErrors} = useExpensifyCardContext();
- const handleRevealDetails = (validateCode: string) => {
- setIsCardDetailsLoading((prevState: Record) => ({
- ...prevState,
- [currentCardID]: true,
- }));
- // We can't store the response in Onyx for security reasons.
- // That is why this action is handled manually and the response is stored in a local state
- // Hence eslint disable here.
- // eslint-disable-next-line rulesdir/no-thenable-actions-in-views
- revealVirtualCardDetails(currentCardID, validateCode)
- .then((value) => {
- setCardsDetails((prevState: Record) => ({...prevState, [currentCardID]: value}));
- setCardsDetailsErrors((prevState) => ({
- ...prevState,
- [currentCardID]: '',
- }));
- setIsValidateCodeActionModalVisible(false);
- })
- .catch((error: string) => {
- // Displaying magic code errors is handled in the modal, no need to set it on the card
- // TODO: remove setValidateError once backend deploys https://github.com/Expensify/Web-Expensify/pull/46007
- if (error === 'validateCodeForm.error.incorrectMagicCode') {
- setValidateError(() => getMicroSecondOnyxErrorWithTranslationKey('validateCodeForm.error.incorrectMagicCode'));
- return;
- }
- setCardsDetailsErrors((prevState) => ({
- ...prevState,
- [currentCardID]: error,
- }));
- setIsValidateCodeActionModalVisible(false);
- })
- .finally(() => {
- setIsCardDetailsLoading((prevState: Record) => ({...prevState, [currentCardID]: false}));
- });
- };
+ // This resets card details when we exit the page.
+ useBeforeRemove(() => {
+ setCardsDetails((oldCardDetails) => ({...oldCardDetails, [cardID]: null}));
+ });
+
+ const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext);
const hasDetectedDomainFraud = cardsToShow?.some((card) => card?.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.DOMAIN);
const hasDetectedIndividualFraud = cardsToShow?.some((card) => card?.fraud === CONST.EXPENSIFY_CARD.FRAUD_TYPES.INDIVIDUAL);
@@ -176,7 +132,6 @@ function ExpensifyCardPage({
const formattedAvailableSpendAmount = convertToDisplayString(cardsToShow?.at(0)?.availableSpend, currency);
const {limitNameKey, limitTitleKey} = getLimitTypeTranslationKeys(cardsToShow?.at(0)?.nameValuePairs?.limitType);
- const primaryLogin = account?.primaryLogin ?? '';
const isSignedInAsDelegate = !!account?.delegatedAccess?.delegate || false;
if (isNotFound) {
@@ -261,7 +216,7 @@ function ExpensifyCardPage({
showLockedAccountModal();
return;
}
- openValidateCodeModal(card.cardID);
+ Navigation.navigate(ROUTES.SETTINGS_WALLET_DOMAIN_CARD_CONFIRM_MAGIC_CODE.getRoute(cardID));
}}
isDisabled={isCardDetailsLoading[card.cardID] || isOffline}
isLoading={isCardDetailsLoading[card.cardID]}
@@ -313,7 +268,7 @@ function ExpensifyCardPage({
!isSignedInAsDelegate ? (