diff --git a/src/ROUTES.ts b/src/ROUTES.ts index b065091c0eb7..46cf90385ba4 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -2157,6 +2157,46 @@ const ROUTES = { // eslint-disable-next-line no-restricted-syntax -- Legacy route generation getRoute: (policyID: string, backTo?: string) => getUrlWithBackToParam(`workspaces/${policyID}/company-cards/add-card-feed`, backTo), }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY: { + route: 'workspaces/:policyID/company-cards/add-card-feed/select-country', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/select-country` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_BANK: { + route: 'workspaces/:policyID/company-cards/add-card-feed/select-bank', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/select-bank` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_FEED_TYPE: { + route: 'workspaces/:policyID/company-cards/add-card-feed/select-feed-type', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/select-feed-type` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_TYPE: { + route: 'workspaces/:policyID/company-cards/add-card-feed/card-type', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/card-type` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_INSTRUCTIONS: { + route: 'workspaces/:policyID/company-cards/add-card-feed/card-instructions', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/card-instructions` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_NAME: { + route: 'workspaces/:policyID/company-cards/add-card-feed/card-name', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/card-name` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_DETAILS: { + route: 'workspaces/:policyID/company-cards/add-card-feed/card-details', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/card-details` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_AMEX_CUSTOM_FEED: { + route: 'workspaces/:policyID/company-cards/add-card-feed/amex-custom-feed', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/amex-custom-feed` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_STATEMENT_CLOSE_DATE: { + route: 'workspaces/:policyID/company-cards/add-card-feed/select-statement-close-date', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/select-statement-close-date` as const, + }, + WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_DIRECT_STATEMENT_CLOSE_DATE: { + route: 'workspaces/:policyID/company-cards/add-card-feed/select-direct-statement-close-date', + getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/add-card-feed/select-direct-statement-close-date` as const, + }, WORKSPACE_COMPANY_CARDS_SELECT_FEED: { route: 'workspaces/:policyID/company-cards/select-feed', getRoute: (policyID: string) => `workspaces/${policyID}/company-cards/select-feed` as const, @@ -2167,6 +2207,34 @@ const ROUTES = { // eslint-disable-next-line no-restricted-syntax -- Legacy route generation getRoute: (policyID: string, feed: string, backTo?: string) => getUrlWithBackToParam(`workspaces/${policyID}/company-cards/${encodeURIComponent(feed)}/assign-card`, backTo), }, + WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE: { + route: 'workspaces/:policyID/company-cards/:feed/assign-card/assignee', + getRoute: (policyID: string, feed: string) => `workspaces/${policyID}/company-cards/${feed}/assign-card/assignee` as const, + }, + WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_INVITE_NEW_MEMBER: { + route: 'workspaces/:policyID/company-cards/:feed/assign-card/invite-new-member', + getRoute: (policyID: string, feed: string) => `workspaces/${policyID}/company-cards/${feed}/assign-card/invite-new-member` as const, + }, + WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_SELECT: { + route: 'workspaces/:policyID/company-cards/:feed/assign-card/select', + getRoute: (policyID: string, feed: string) => `workspaces/${policyID}/company-cards/${feed}/assign-card/select` as const, + }, + WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_TRANSACTION_START_DATE_STEP: { + route: 'workspaces/:policyID/company-cards/:feed/assign-card/transaction-start-date-step', + getRoute: (policyID: string, feed: string) => `workspaces/${policyID}/company-cards/${feed}/assign-card/transaction-start-date-step` as const, + }, + WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_NAME: { + route: 'workspaces/:policyID/company-cards/:feed/assign-card/name', + getRoute: (policyID: string, feed: string) => `workspaces/${policyID}/company-cards/${feed}/assign-card/name` as const, + }, + WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION: { + route: 'workspaces/:policyID/company-cards/:feed/assign-card/confirmation', + getRoute: (policyID: string, feed: string) => `workspaces/${policyID}/company-cards/${feed}/assign-card/confirmation` as const, + }, + WORKSPACE_COMPANY_CARDS_PLAID_CONNECTION: { + route: 'workspaces/:policyID/company-cards/:feed/assign-card/plaid-connection', + getRoute: (policyID: string, feed: string) => `workspaces/${policyID}/company-cards/${feed}/assign-card/plaid-connection` as const, + }, WORKSPACE_COMPANY_CARD_DETAILS: { route: 'workspaces/:policyID/company-cards/:bank/:cardID', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 2e074b7bde2a..1b3b58c4a678 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -579,9 +579,28 @@ const SCREENS = { PROFILE: 'Workspace_Overview', COMPANY_CARDS: 'Workspace_CompanyCards', COMPANY_CARDS_ASSIGN_CARD: 'Workspace_CompanyCards_AssignCard', + + COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE: 'Workspace_CompanyCards_AssignCard_Assignee', + COMPANY_CARDS_ASSIGN_CARD_INVITE_NEW_MEMBER: 'Workspace_CompanyCards_AssignCard_InviteNewMember', + COMPANY_CARDS_ASSIGN_CARD_SELECT: 'Workspace_CompanyCards_AssignCard_Select', + COMPANY_CARDS_ASSIGN_CARD_TRANSACTION_START_DATE_STEP: 'Workspace_CompanyCards_AssignCard_TransactionStartDateStep', + COMPANY_CARDS_ASSIGN_CARD_NAME: 'Workspace_CompanyCards_AssignCard_CardNameStep', + COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION: 'Workspace_CompanyCards_AssignCard_ConfirmationStep', + COMPANY_CARDS_PLAID_CONNECTION: 'Workspace_CompanyCards_PlaidConnectionStep', + COMPANY_CARDS_SELECT_FEED: 'Workspace_CompanyCards_Select_Feed', COMPANY_CARDS_BANK_CONNECTION: 'Workspace_CompanyCards_BankConnection', COMPANY_CARDS_ADD_NEW: 'Workspace_CompanyCards_New', + COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY: 'Workspace_CompanyCards_AddNew_SelectCountry', + COMPANY_CARDS_ADD_NEW_SELECT_BANK: 'Workspace_CompanyCards_AddNew_SelectBank', + COMPANY_CARDS_ADD_NEW_SELECT_FEED_TYPE: 'Workspace_CompanyCards_AddNew_SelectFeedType', + COMPANY_CARDS_ADD_NEW_CARD_TYPE: 'Workspace_CompanyCards_AddNew_CardType', + COMPANY_CARDS_ADD_NEW_CARD_INSTRUCTIONS: 'Workspace_CompanyCards_AddNew_CardInstructions', + COMPANY_CARDS_ADD_NEW_CARD_NAME: 'Workspace_CompanyCards_AddNew_CardName', + COMPANY_CARDS_ADD_NEW_CARD_DETAILS: 'Workspace_CompanyCards_AddNew_CardDetails', + COMPANY_CARDS_ADD_NEW_AMEX_CUSTOM_FEED: 'Workspace_CompanyCards_AddNew_AmexCustomFeed', + COMPANY_CARDS_ADD_NEW_SELECT_STATEMENT_CLOSE_DATE: 'Workspace_CompanyCards_AddNew_SelectStatementCloseDate', + COMPANY_CARDS_ADD_NEW_SELECT_DIRECT_STATEMENT_CLOSE_DATE: 'Workspace_CompanyCards_AddNew_SelectDirectStatementCloseDate', COMPANY_CARDS_TYPE: 'Workspace_CompanyCards_Type', COMPANY_CARDS_INSTRUCTIONS: 'Workspace_CompanyCards_Instructions', COMPANY_CARDS_NAME: 'Workspace_CompanyCards_Name', diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx index e70dac00a280..89334f94f854 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators/index.tsx @@ -761,9 +761,31 @@ const SettingsModalStackNavigator = createModalStackNavigator require('../../../../pages/workspace/invoices/WorkspaceInvoicingDetailsWebsite').default, [SCREENS.WORKSPACE.INVOICES_VERIFY_ACCOUNT]: () => require('../../../../pages/workspace/invoices/WorkspaceInvoicesVerifyAccountPage').default, [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD]: () => require('../../../../pages/workspace/companyCards/assignCard/AssignCardFeedPage').default, + + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE]: () => require('../../../../pages/workspace/companyCards/assignCard/AssigneeStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_INVITE_NEW_MEMBER]: () => require('../../../../pages/workspace/companyCards/assignCard/InviteNewMemberStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_SELECT]: () => require('../../../../pages/workspace/companyCards/assignCard/CardSelectionStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_TRANSACTION_START_DATE_STEP]: () => + require('../../../../pages/workspace/companyCards/assignCard/TransactionStartDateStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_NAME]: () => require('../../../../pages/workspace/companyCards/assignCard/CardNameStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION]: () => require('../../../../pages/workspace/companyCards/assignCard/ConfirmationStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_PLAID_CONNECTION]: () => require('../../../../pages/workspace/companyCards/addNew/PlaidConnectionStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_SELECT_FEED]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardFeedSelectorPage').default, [SCREENS.WORKSPACE.COMPANY_CARDS_BANK_CONNECTION]: () => require('../../../../pages/workspace/companyCards/BankConnection').default, [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW]: () => require('../../../../pages/workspace/companyCards/addNew/AddNewCardPage').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY]: () => require('../../../../pages/workspace/companyCards/addNew/SelectCountryStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_BANK]: () => require('../../../../pages/workspace/companyCards/addNew/SelectBankStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_FEED_TYPE]: () => require('../../../../pages/workspace/companyCards/addNew/SelectFeedType').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_TYPE]: () => require('../../../../pages/workspace/companyCards/addNew/CardTypeStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_INSTRUCTIONS]: () => require('../../../../pages/workspace/companyCards/addNew/CardInstructionsStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_NAME]: () => require('../../../../pages/workspace/companyCards/addNew/CardNameStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_DETAILS]: () => require('../../../../pages/workspace/companyCards/addNew/DetailsStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_AMEX_CUSTOM_FEED]: () => require('../../../../pages/workspace/companyCards/addNew/AmexCustomFeed').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_STATEMENT_CLOSE_DATE]: () => + require('../../../../pages/workspace/companyCards/addNew/StatementCloseDateStep').default, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_DIRECT_STATEMENT_CLOSE_DATE]: () => + require('../../../../pages/workspace/companyCards/addNew/DirectStatementCloseDatePage').default, [SCREENS.WORKSPACE.COMPANY_CARD_DETAILS]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardDetailsPage').default, [SCREENS.WORKSPACE.COMPANY_CARD_NAME]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardEditCardNamePage').default, [SCREENS.WORKSPACE.COMPANY_CARD_EXPORT]: () => require('../../../../pages/workspace/companyCards/WorkspaceCompanyCardAccountSelectCardPage').default, diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 9a222edfd068..26eb9d191030 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -761,9 +761,60 @@ const config: LinkingOptions['config'] = { [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW]: { path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.route, }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_BANK]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_BANK.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_FEED_TYPE]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_FEED_TYPE.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_TYPE]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_TYPE.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_INSTRUCTIONS]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_INSTRUCTIONS.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_NAME]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_NAME.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_DETAILS]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_DETAILS.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_AMEX_CUSTOM_FEED]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_AMEX_CUSTOM_FEED.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_STATEMENT_CLOSE_DATE]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_STATEMENT_CLOSE_DATE.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_DIRECT_STATEMENT_CLOSE_DATE]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_DIRECT_STATEMENT_CLOSE_DATE.route, + }, [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD]: { path: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD.route, }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_INVITE_NEW_MEMBER]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_INVITE_NEW_MEMBER.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_SELECT]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_SELECT.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_TRANSACTION_START_DATE_STEP]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_TRANSACTION_START_DATE_STEP.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_NAME]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_NAME.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION.route, + }, + [SCREENS.WORKSPACE.COMPANY_CARDS_PLAID_CONNECTION]: { + path: ROUTES.WORKSPACE_COMPANY_CARDS_PLAID_CONNECTION.route, + }, [SCREENS.WORKSPACE.INVITE]: { path: ROUTES.WORKSPACE_INVITE.route, }, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index 6a3b78395267..8b7b55679b41 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -24,7 +24,7 @@ import type {Country, IOUAction, IOUType} from '@src/CONST'; import type NAVIGATORS from '@src/NAVIGATORS'; import type {Route as ExpensifyRoute, Route as Routes} from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {CompanyCardFeed} from '@src/types/onyx'; +import type {CompanyCardFeed, CompanyCardFeedWithDomainID} from '@src/types/onyx'; import type {ConnectionName, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {CustomFieldType} from '@src/types/onyx/PolicyEmployee'; import type {FileObject} from '@src/types/utils/Attachment'; @@ -1201,6 +1201,34 @@ type SettingsNavigatorParamList = { // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md backTo?: Routes; }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE]: { + policyID: string; + feed: CompanyCardFeedWithDomainID; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_INVITE_NEW_MEMBER]: { + policyID: string; + feed: CompanyCardFeed; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_SELECT]: { + policyID: string; + feed: CompanyCardFeedWithDomainID; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_TRANSACTION_START_DATE_STEP]: { + policyID: string; + feed: CompanyCardFeed; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_NAME]: { + policyID: string; + feed: CompanyCardFeed; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION]: { + policyID: string; + feed: CompanyCardFeedWithDomainID; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_PLAID_CONNECTION]: { + policyID: string; + feed: CompanyCardFeedWithDomainID; + }; [SCREENS.WORKSPACE.COMPANY_CARDS_SETTINGS_FEED_NAME]: { policyID: string; }; @@ -2297,8 +2325,36 @@ type WorkspaceSplitNavigatorParamList = { }; [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW]: { policyID: string; - // eslint-disable-next-line no-restricted-syntax -- `backTo` usages in this file are legacy. Do not add new `backTo` params to screens. See contributingGuides/NAVIGATION.md - backTo?: Routes; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_BANK]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_FEED_TYPE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_TYPE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_INSTRUCTIONS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_NAME]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_CARD_DETAILS]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_AMEX_CUSTOM_FEED]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_STATEMENT_CLOSE_DATE]: { + policyID: string; + }; + [SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW_SELECT_DIRECT_STATEMENT_CLOSE_DATE]: { + policyID: string; }; [SCREENS.WORKSPACE.PER_DIEM]: { policyID: string; diff --git a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx index abb05a8accce..24c09cd7bdae 100644 --- a/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx +++ b/src/pages/workspace/companyCards/addNew/AddNewCardPage.tsx @@ -1,53 +1,32 @@ import {isActingAsDelegateSelector} from '@selectors/Account'; -import React, {useEffect, useState} from 'react'; -import {View} from 'react-native'; -import ConfirmModal from '@components/ConfirmModal'; +import React, {useEffect} from 'react'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import ScreenWrapper from '@components/ScreenWrapper'; import useIsBlockedToAddFeed from '@hooks/useIsBlockedToAddFeed'; -import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; -import usePermissions from '@hooks/usePermissions'; -import useThemeStyles from '@hooks/useThemeStyles'; import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; -import {navigateToConciergeChat} from '@libs/actions/Report'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import Navigation from '@navigation/Navigation'; -import BankConnection from '@pages/workspace/companyCards/BankConnection'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; -import {clearAddNewCardFlow, openPolicyAddCardFeedPage} from '@userActions/CompanyCards'; +import {clearAddNewCardFlow, clearAssignCardStepAndData, openPolicyAddCardFeedPage} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -import AmexCustomFeed from './AmexCustomFeed'; -import CardInstructionsStep from './CardInstructionsStep'; -import CardNameStep from './CardNameStep'; -import CardTypeStep from './CardTypeStep'; -import DetailsStep from './DetailsStep'; -import DirectStatementCloseDateStep from './DirectStatementCloseDatePage'; -import PlaidConnectionStep from './PlaidConnectionStep'; -import SelectBankStep from './SelectBankStep'; -import SelectCountryStep from './SelectCountryStep'; -import SelectFeedType from './SelectFeedType'; -import StatementCloseDateStep from './StatementCloseDateStep'; +import type SCREENS from '@src/SCREENS'; -function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) { +type AddNewCardPageProps = PlatformStackScreenProps & WithPolicyAndFullscreenLoadingProps; + +function AddNewCardPage({policy}: AddNewCardPageProps) { const policyID = policy?.id; - const styles = useThemeStyles(); const workspaceAccountID = useWorkspaceAccountID(policyID); - const [addNewCardFeed, addNewCardFeedMetadata] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: false}); - const {currentStep} = addNewCardFeed ?? {}; - const {isBetaEnabled} = usePermissions(); - const {isBlockedToAddNewFeeds, isAllFeedsResultLoading} = useIsBlockedToAddFeed(policyID); - const [isModalVisible, setIsModalVisible] = useState(false); - const {translate} = useLocalize(); + const {isBlockedToAddNewFeeds} = useIsBlockedToAddFeed(policyID); const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isActingAsDelegateSelector, canBeMissing: false}); - const isAddCardFeedLoading = isLoadingOnyxValue(addNewCardFeedMetadata); - useEffect(() => { if (!policyID || !isBlockedToAddNewFeeds) { return; @@ -60,6 +39,7 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) { useEffect(() => { return () => { clearAddNewCardFlow(); + clearAssignCardStepAndData(); }; }, []); @@ -73,9 +53,7 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) { openPolicyAddCardFeedPage(policyID); }, [workspaceAccountID, policyID]); - if (isAddCardFeedLoading || isAllFeedsResultLoading || isBlockedToAddNewFeeds) { - return ; - } + useAddNewCardNavigation(policyID, true); if (isActingAsDelegate) { return ( @@ -89,63 +67,10 @@ function AddNewCardPage({policy}: WithPolicyAndFullscreenLoadingProps) { ); } - let CurrentStep: React.JSX.Element; - switch (currentStep) { - case CONST.COMPANY_CARDS.STEP.SELECT_BANK: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.CARD_TYPE: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.BANK_CONNECTION: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.CARD_NAME: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.CARD_DETAILS: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.AMEX_CUSTOM_FEED: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.PLAID_CONNECTION: - CurrentStep = setIsModalVisible(true)} />; - break; - case CONST.COMPANY_CARDS.STEP.SELECT_STATEMENT_CLOSE_DATE: - CurrentStep = ; - break; - case CONST.COMPANY_CARDS.STEP.SELECT_DIRECT_STATEMENT_CLOSE_DATE: - CurrentStep = ; - break; - default: - CurrentStep = isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) ? : ; - break; - } - return ( - <> - {CurrentStep} - setIsModalVisible(false)} - onConfirm={() => { - setIsModalVisible(false); - navigateToConciergeChat(); - }} - /> - + + + ); } diff --git a/src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx b/src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx index 380b2d7162af..10220940a656 100644 --- a/src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx +++ b/src/pages/workspace/companyCards/addNew/AmexCustomFeed.tsx @@ -11,14 +11,23 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; -function AmexCustomFeed() { +type AmexCustomFeedProps = PlatformStackScreenProps; + +function AmexCustomFeed({route}: AmexCustomFeedProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); + const policyID = route.params?.policyID; + + useAddNewCardNavigation(policyID); const [typeSelected, setTypeSelected] = useState>(); const [hasError, setHasError] = useState(false); diff --git a/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx index 92f7bce11d76..960b197a891f 100644 --- a/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardInstructionsStep.tsx @@ -15,16 +15,16 @@ import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; import {updateSelectedFeed} from '@libs/actions/Card'; import {setAddNewCompanyCardStepAndData} from '@libs/actions/CompanyCards'; import {getBankName, getCompanyCardFeedWithDomainID} from '@libs/CardUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import Parser from '@libs/Parser'; import Navigation from '@navigation/Navigation'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import type {CardFeedProvider} from '@src/types/onyx/CardFeeds'; -type CardInstructionsStepProps = { - policyID?: string; -}; - function getCardInstructionHeader(feedProvider: CardFeedProvider) { if (feedProvider === CONST.COMPANY_CARD.FEED_BANK_NAME.VISA) { return 'workspace.companyCards.addNewCard.enableFeed.visa'; @@ -36,9 +36,14 @@ function getCardInstructionHeader(feedProvider: CardFeedProvider) { return 'workspace.companyCards.addNewCard.enableFeed.heading'; } -function CardInstructionsStep({policyID}: CardInstructionsStepProps) { +type CardInstructionsStepProps = PlatformStackScreenProps; + +function CardInstructionsStep({route}: CardInstructionsStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const policyID = route.params?.policyID; + + useAddNewCardNavigation(policyID); const {isOffline} = useNetwork(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); diff --git a/src/pages/workspace/companyCards/addNew/CardNameStep.tsx b/src/pages/workspace/companyCards/addNew/CardNameStep.tsx index 1b3762fb6323..b949edadc964 100644 --- a/src/pages/workspace/companyCards/addNew/CardNameStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardNameStep.tsx @@ -11,17 +11,26 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/AddNewCardFeedForm'; -function CardNameStep() { +type CardNameStepProps = PlatformStackScreenProps; + +function CardNameStep({route}: CardNameStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {inputCallbackRef} = useAutoFocusInput(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); + const policyID = route.params?.policyID; + + useAddNewCardNavigation(policyID); const validate = (values: FormOnyxValues): FormInputErrors => { const errors = getFieldRequiredErrors(values, [INPUT_IDS.CARD_TITLE]); diff --git a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx index 6d8ee682644e..509e38a81f0f 100644 --- a/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx +++ b/src/pages/workspace/companyCards/addNew/CardTypeStep.tsx @@ -15,10 +15,14 @@ import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import {isPlaidSupportedCountry} from '@libs/CardUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import variables from '@styles/variables'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import type {CardFeedProvider} from '@src/types/onyx/CardFeeds'; type AvailableCompanyCardTypes = { @@ -84,7 +88,9 @@ function getAvailableCompanyCardTypes({translate, typeSelected, styles, canUsePl ]; } -function CardTypeStep() { +type CardTypeStepProps = PlatformStackScreenProps; + +function CardTypeStep({route}: CardTypeStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const companyCardBankIcons = useCompanyCardBankIcons(); @@ -92,6 +98,9 @@ function CardTypeStep() { const [typeSelected, setTypeSelected] = useState(); const [isError, setIsError] = useState(false); const {isBetaEnabled} = usePermissions(); + const policyID = route.params?.policyID; + + useAddNewCardNavigation(policyID); const data = getAvailableCompanyCardTypes({ translate, typeSelected, diff --git a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx index b9257e00326a..49b2cc5eda5d 100644 --- a/src/pages/workspace/companyCards/addNew/DetailsStep.tsx +++ b/src/pages/workspace/companyCards/addNew/DetailsStep.tsx @@ -15,14 +15,20 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import variables from '@styles/variables'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/AddNewCardFeedForm'; -function DetailsStep() { +type DetailsStepProps = PlatformStackScreenProps; + +function DetailsStep({route}: DetailsStepProps) { const {translate} = useLocalize(); const theme = useTheme(); const styles = useThemeStyles(); @@ -30,6 +36,9 @@ function DetailsStep() { const icons = useMemoizedLazyExpensifyIcons(['QuestionMark'] as const); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: false}); + const policyID = route.params?.policyID; + + useAddNewCardNavigation(policyID); const feedProvider = addNewCard?.data?.feedType; const isStripeFeedProvider = feedProvider === CONST.COMPANY_CARD.FEED_BANK_NAME.STRIPE; diff --git a/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx b/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx index 79a36cc0203c..70c28401cc6a 100644 --- a/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx +++ b/src/pages/workspace/companyCards/addNew/DirectStatementCloseDatePage.tsx @@ -7,19 +7,24 @@ import useWorkspaceAccountID from '@hooks/useWorkspaceAccountID'; import {clearErrorField, setFeedStatementPeriodEndDay} from '@libs/actions/CompanyCards'; import {getCompanyCardFeed, getCompanyFeeds, getDomainOrWorkspaceAccountID, getSelectedFeed} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import WorkspaceCompanyCardStatementCloseDateSelectionList from '@pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; import type {StatementPeriodEnd, StatementPeriodEndDay} from '@src/types/onyx/CardFeeds'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; -type DirectStatementCloseDateStepProps = { - /** ID of the current policy */ - policyID?: string; -}; -function DirectStatementCloseDateStep({policyID}: DirectStatementCloseDateStepProps) { +type DirectStatementCloseDateStepProps = PlatformStackScreenProps; + +function DirectStatementCloseDateStep({route}: DirectStatementCloseDateStepProps) { const {translate} = useLocalize(); + const policyID = route.params?.policyID; const [lastSelectedFeed, lastSelectedFeedResult] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); + + useAddNewCardNavigation(policyID); const [cardFeeds, cardFeedsResult] = useCardFeeds(policyID); const selectedFeed = getSelectedFeed(lastSelectedFeed, cardFeeds); const feed = selectedFeed ? getCompanyCardFeed(selectedFeed) : undefined; diff --git a/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx b/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx index de9c170f7b6f..fcebb03f14b3 100644 --- a/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx +++ b/src/pages/workspace/companyCards/addNew/PlaidConnectionStep.tsx @@ -1,9 +1,10 @@ -import React, {useCallback, useEffect, useRef} from 'react'; +import React, {useCallback, useEffect, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; import type {LinkSuccessMetadata} from 'react-native-plaid-link-sdk'; import type {PlaidLinkOnSuccessMetadata} from 'react-plaid-link/src/types'; import ActivityIndicator from '@components/ActivityIndicator'; import FullPageOfflineBlockingView from '@components/BlockingViews/FullPageOfflineBlockingView'; +import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import PlaidLink from '@components/PlaidLink'; import ScreenWrapper from '@components/ScreenWrapper'; @@ -13,19 +14,25 @@ import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {setAddNewCompanyCardStepAndData, setAssignCardStepAndData} from '@libs/actions/CompanyCards'; +import {navigateToConciergeChat} from '@libs/actions/Report'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import Log from '@libs/Log'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {getDomainNameForPolicy} from '@libs/PolicyUtils'; import Navigation from '@navigation/Navigation'; +import {useAddNewCardNavigation, useAssignCardNavigation} from '@pages/workspace/companyCards/utils'; import {handleRestrictedEvent} from '@userActions/App'; import {setPlaidEvent} from '@userActions/BankAccounts'; import {importPlaidAccounts, openPlaidCompanyCardLogin} from '@userActions/Plaid'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type SCREENS from '@src/SCREENS'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeedWithDomainID; policyID?: string; onExit?: () => void}) { +type PlaidConnectionStepProps = PlatformStackScreenProps; + +function PlaidConnectionStep({route}: PlaidConnectionStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); @@ -39,7 +46,12 @@ function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeedWi // eslint-disable-next-line @typescript-eslint/non-nullable-type-assertion-style const plaidDataErrorMessage = !isEmptyObject(plaidErrors) ? (Object.values(plaidErrors).at(0) as string) : ''; const {isOffline} = useNetwork(); + const {policyID, feed} = route.params; const domain = getDomainNameForPolicy(policyID); + const [isExitModalVisible, setIsExitModalVisible] = useState(false); + + useAssignCardNavigation(policyID, feed); + useAddNewCardNavigation(policyID); const isAuthenticatedWithPlaid = useCallback(() => !!plaidData?.bankAccounts?.length || !isEmptyObject(plaidData?.errors), [plaidData?.bankAccounts?.length, plaidData?.errors]); @@ -96,9 +108,13 @@ function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeedWi previousNetworkState.current = isOffline; }, [addNewCard?.data?.selectedCountry, domain, feed, isAuthenticatedWithPlaid, isOffline]); - const handleBackButtonPress = () => { + const handleBackButtonPress = (showingExitModal = false) => { + if (showingExitModal) { + return; + } + if (feed) { - Navigation.goBack(); + Navigation.dismissModal(); return; } setAddNewCompanyCardStepAndData({step: isUSCountry ? CONST.COMPANY_CARDS.STEP.SELECT_BANK : CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE}); @@ -181,8 +197,8 @@ function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeedWi // User prematurely exited the Plaid flow // eslint-disable-next-line react/jsx-props-no-multi-spaces onExit={() => { - onExit?.(); - handleBackButtonPress(); + setIsExitModalVisible(true); + handleBackButtonPress(true); }} /> ); @@ -217,6 +233,22 @@ function PlaidConnectionStep({feed, policyID, onExit}: {feed?: CompanyCardFeedWi ) : ( {renderPlaidLink()} )} + { + setIsExitModalVisible(false); + Navigation.dismissModal(); + }} + onConfirm={() => { + setIsExitModalVisible(false); + navigateToConciergeChat(); + }} + /> ); } diff --git a/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx b/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx index 5aaf9498693a..c41215935eab 100644 --- a/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectBankStep.tsx @@ -1,4 +1,3 @@ -import {useRoute} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; @@ -17,22 +16,28 @@ import usePermissions from '@hooks/usePermissions'; import useThemeIllustrations from '@hooks/useThemeIllustrations'; import useThemeStyles from '@hooks/useThemeStyles'; import {getBankCardDetailsImage, getCorrectStepForPlaidSelectedBank, getCorrectStepForSelectedBank} from '@libs/CardUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import Navigation from '@navigation/Navigation'; -import type {PlatformStackRouteProp} from '@navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import variables from '@styles/variables'; import {clearAddNewCardFlow, setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -function SelectBankStep() { +type SelectBankStepProps = PlatformStackScreenProps; + +function SelectBankStep({route}: SelectBankStepProps) { const {translate} = useLocalize(); - const route = useRoute>(); const styles = useThemeStyles(); const illustrations = useThemeIllustrations(); const companyCardBankIcons = useCompanyCardBankIcons(); const {isBetaEnabled} = usePermissions(); + const policyID = route.params?.policyID; + + useAddNewCardNavigation(policyID); const {isOffline} = useNetwork(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); @@ -64,14 +69,10 @@ function SelectBankStep() { }, [addNewCard?.data.selectedBank]); const handleBackButtonPress = () => { - if (route?.params?.backTo) { - Navigation.navigate(route.params.backTo); - return; - } if (isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS)) { setAddNewCompanyCardStepAndData({step: CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE, data: {selectedBank: null}}); } else { - Navigation.goBack(); + Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.getRoute(policyID)); } }; diff --git a/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx b/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx index 2e6f6b8b4334..8375284ef1ce 100644 --- a/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectCountryStep.tsx @@ -1,4 +1,3 @@ -import {useRoute} from '@react-navigation/native'; import React, {useCallback, useEffect, useMemo, useState} from 'react'; import {View} from 'react-native'; import FormHelpMessage from '@components/FormHelpMessage'; @@ -13,12 +12,13 @@ import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import {getPlaidCountry, isPlaidSupportedCountry} from '@libs/CardUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import searchOptions from '@libs/searchOptions'; import type {Option} from '@libs/searchOptions'; import StringUtils from '@libs/StringUtils'; import Navigation from '@navigation/Navigation'; -import type {PlatformStackRouteProp} from '@navigation/PlatformStackNavigation/types'; import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import {clearAddNewCardFlow, setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; @@ -27,15 +27,15 @@ import type SCREENS from '@src/SCREENS'; import type {CurrencyList} from '@src/types/onyx'; import {getEmptyObject} from '@src/types/utils/EmptyObject'; -type CountryStepProps = { - policyID?: string; -}; +type CountryStepProps = PlatformStackScreenProps; -function SelectCountryStep({policyID}: CountryStepProps) { +function SelectCountryStep({route}: CountryStepProps) { const {translate} = useLocalize(); - const route = useRoute>(); const styles = useThemeStyles(); + const policyID = route.params?.policyID; const policy = usePolicy(policyID); + + useAddNewCardNavigation(policyID); const [currencyList = getEmptyObject()] = useOnyx(ONYXKEYS.CURRENCY_LIST, {canBeMissing: true}); const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); @@ -77,11 +77,7 @@ function SelectCountryStep({policyID}: CountryStepProps) { }, [getCountry]); const handleBackButtonPress = () => { - if (route?.params?.backTo) { - Navigation.navigate(route.params.backTo); - return; - } - Navigation.goBack(); + Navigation.dismissModal(); }; const onSelectionChange = useCallback((country: Option) => { diff --git a/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx b/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx index a26a9da3076a..5d52d0489fcd 100644 --- a/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx +++ b/src/pages/workspace/companyCards/addNew/SelectFeedType.tsx @@ -13,14 +13,23 @@ import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; import {isPlaidSupportedCountry} from '@libs/CardUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import {setAddNewCompanyCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; -function SelectFeedType() { +type SelectFeedTypeProps = PlatformStackScreenProps; + +function SelectFeedType({route}: SelectFeedTypeProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); + const policyID = route.params?.policyID; + + useAddNewCardNavigation(policyID); const [typeSelected, setTypeSelected] = useState>(); const [hasError, setHasError] = useState(false); const {isBetaEnabled} = usePermissions(); diff --git a/src/pages/workspace/companyCards/addNew/StatementCloseDateStep.tsx b/src/pages/workspace/companyCards/addNew/StatementCloseDateStep.tsx index 7acb1aca5d38..d68b016c66cb 100644 --- a/src/pages/workspace/companyCards/addNew/StatementCloseDateStep.tsx +++ b/src/pages/workspace/companyCards/addNew/StatementCloseDateStep.tsx @@ -5,24 +5,28 @@ import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import {addNewCompanyCardsFeed, setAddNewCompanyCardStepAndData} from '@libs/actions/CompanyCards'; import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {WorkspaceSplitNavigatorParamList} from '@navigation/types'; +import {useAddNewCardNavigation} from '@pages/workspace/companyCards/utils'; import WorkspaceCompanyCardStatementCloseDateSelectionList from '@pages/workspace/companyCards/WorkspaceCompanyCardStatementCloseDateSelectionList'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; +import type SCREENS from '@src/SCREENS'; import type {StatementPeriodEnd, StatementPeriodEndDay} from '@src/types/onyx/CardFeeds'; -type StatementCloseDateStepProps = { - /** ID of the current policy */ - policyID: string | undefined; -}; +type StatementCloseDateStepProps = PlatformStackScreenProps; -function StatementCloseDateStep({policyID}: StatementCloseDateStepProps) { +function StatementCloseDateStep({route}: StatementCloseDateStepProps) { const {translate} = useLocalize(); const {isBetaEnabled} = usePermissions(); + const policyID = route.params?.policyID; const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: false}); const [lastSelectedFeed] = useOnyx(`${ONYXKEYS.COLLECTION.LAST_SELECTED_FEED}${policyID}`, {canBeMissing: true}); const [cardFeeds] = useCardFeeds(policyID); + useAddNewCardNavigation(policyID); + const isPlaid = isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) && !!addNewCard?.data?.publicToken; const submit = useCallback( diff --git a/src/pages/workspace/companyCards/assignCard/AssignCardFeedPage.tsx b/src/pages/workspace/companyCards/assignCard/AssignCardFeedPage.tsx index 28661e5034cc..093ab26e7540 100644 --- a/src/pages/workspace/companyCards/assignCard/AssignCardFeedPage.tsx +++ b/src/pages/workspace/companyCards/assignCard/AssignCardFeedPage.tsx @@ -1,13 +1,12 @@ import {isActingAsDelegateSelector} from '@selectors/Account'; import React, {useEffect} from 'react'; import DelegateNoAccessWrapper from '@components/DelegateNoAccessWrapper'; +import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; import ScreenWrapper from '@components/ScreenWrapper'; -import useInitial from '@hooks/useInitial'; import useOnyx from '@hooks/useOnyx'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@navigation/types'; -import PlaidConnectionStep from '@pages/workspace/companyCards/addNew/PlaidConnectionStep'; -import BankConnection from '@pages/workspace/companyCards/BankConnection'; +import {useAssignCardNavigation} from '@pages/workspace/companyCards/utils'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; import withPolicyAndFullscreenLoading from '@pages/workspace/withPolicyAndFullscreenLoading'; import {clearAssignCardStepAndData} from '@userActions/CompanyCards'; @@ -15,25 +14,13 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; -import AssigneeStep from './AssigneeStep'; -import CardNameStep from './CardNameStep'; -import CardSelectionStep from './CardSelectionStep'; -import ConfirmationStep from './ConfirmationStep'; -import InviteNewMemberStep from './InviteNewMemberStep'; -import TransactionStartDateStep from './TransactionStartDateStep'; type AssignCardFeedPageProps = PlatformStackScreenProps & WithPolicyAndFullscreenLoadingProps; function AssignCardFeedPage({route, policy}: AssignCardFeedPageProps) { - const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: true}); - const currentStep = assignCard?.currentStep; - const feed = decodeURIComponent(route.params?.feed) as CompanyCardFeedWithDomainID; - const backTo = route.params?.backTo; const policyID = policy?.id; const [isActingAsDelegate] = useOnyx(ONYXKEYS.ACCOUNT, {selector: isActingAsDelegateSelector, canBeMissing: true}); - const firstAssigneeEmail = useInitial(assignCard?.data?.email); - const shouldUseBackToParam = !firstAssigneeEmail || firstAssigneeEmail === assignCard?.data?.email; useEffect(() => { return () => { @@ -41,6 +28,8 @@ function AssignCardFeedPage({route, policy}: AssignCardFeedPageProps) { }; }, []); + useAssignCardNavigation(policyID, feed, true); + if (isActingAsDelegate) { return ( - ); - case CONST.COMPANY_CARD.STEP.PLAID_CONNECTION: - return ( - - ); - case CONST.COMPANY_CARD.STEP.ASSIGNEE: - return ( - - ); - case CONST.COMPANY_CARD.STEP.CARD: - return ( - - ); - case CONST.COMPANY_CARD.STEP.TRANSACTION_START_DATE: - return ; - case CONST.COMPANY_CARD.STEP.CARD_NAME: - return ; - case CONST.COMPANY_CARD.STEP.CONFIRMATION: - return ( - - ); - case CONST.COMPANY_CARD.STEP.INVITE_NEW_MEMBER: - return ( - - ); - default: - return ( - - ); - } + return ( + + + + ); } AssignCardFeedPage.displayName = 'AssignCardFeedPage'; diff --git a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx index fa86c380fcae..26b8ed8b0d69 100644 --- a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx @@ -1,7 +1,5 @@ import React, {useEffect, useMemo, useState} from 'react'; import {Keyboard} from 'react-native'; -import type {OnyxEntry} from 'react-native-onyx'; -import * as Expensicons from '@components/Icon/Expensicons'; import InteractiveStepWrapper from '@components/InteractiveStepWrapper'; import SelectionList from '@components/SelectionList'; import UserListItem from '@components/SelectionList/ListItem/UserListItem'; @@ -9,6 +7,7 @@ import type {ListItem} from '@components/SelectionList/types'; import Text from '@components/Text'; import useCardFeeds from '@hooks/useCardFeeds'; import useCardsList from '@hooks/useCardsList'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; @@ -17,45 +16,42 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {setDraftInviteAccountID} from '@libs/actions/Card'; import {searchInServer} from '@libs/actions/Report'; import {getDefaultCardName, getFilteredCardList, hasOnlyOneCardToAssign} from '@libs/CardUtils'; -import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {getHeaderMessage, getSearchValueForPhoneOrEmail, sortAlphabetically} from '@libs/OptionsListUtils'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import {getIneligibleInvitees, isDeletedPolicyEmployee} from '@libs/PolicyUtils'; import tokenizedSearch from '@libs/tokenizedSearch'; import Navigation from '@navigation/Navigation'; +import {useAssignCardNavigation} from '@pages/workspace/companyCards/utils'; import {setAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; -import type * as OnyxTypes from '@src/types/onyx'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; import type {AssignCardData, AssignCardStep} from '@src/types/onyx/AssignCard'; -type AssigneeStepProps = { - /** The policy that the card will be issued under */ - policy: OnyxEntry; +type AssigneeStepProps = PlatformStackScreenProps; - /** Selected feed */ - feed: OnyxTypes.CompanyCardFeedWithDomainID; - - /** Route params */ - route: PlatformStackRouteProp; -}; - -function AssigneeStep({policy, feed, route}: AssigneeStepProps) { - const policyID = route.params.policyID; +function AssigneeStep({route}: AssigneeStepProps) { const {translate, formatPhoneNumber, localeCompare} = useLocalize(); const styles = useThemeStyles(); + const icons = useMemoizedLazyExpensifyIcons(['FallbackAvatar'] as const); const {isOffline} = useNetwork(); const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: true}); const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: false}); const [countryCode = CONST.DEFAULT_COUNTRY_CODE] = useOnyx(ONYXKEYS.COUNTRY_CODE, {canBeMissing: false}); + const policyID = route.params?.policyID; + const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: true}); + const feed = decodeURIComponent(route.params?.feed) as CompanyCardFeedWithDomainID; const [list] = useCardsList(feed); - const [cardFeeds] = useCardFeeds(policyID); + const [cardFeeds] = useCardFeeds(policy?.id); const filteredCardList = getFilteredCardList(list, cardFeeds?.[feed]?.accountList, workspaceCardFeeds); const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false); const [isSearchingForReports] = useOnyx(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, {initWithStoredValues: false, canBeMissing: true}); + useAssignCardNavigation(policyID, feed); + const excludedUsers = useMemo(() => { const ineligibleInvites = getIneligibleInvitees(policy?.employeeList); return ineligibleInvites.reduce( @@ -129,7 +125,7 @@ function AssigneeStep({policy, feed, route}: AssigneeStepProps) { }); return; } - Navigation.goBack(); + Navigation.dismissModal(); }; const membersDetails = useMemo(() => { @@ -153,7 +149,7 @@ function AssigneeStep({policy, feed, route}: AssigneeStepProps) { isSelected: assignCard?.data?.email === email, icons: [ { - source: personalDetail?.avatar ?? Expensicons.FallbackAvatar, + source: personalDetail?.avatar ?? icons.FallbackAvatar, name: formatPhoneNumber(email), type: CONST.ICON_TYPE_AVATAR, id: personalDetail?.accountID, @@ -165,7 +161,7 @@ function AssigneeStep({policy, feed, route}: AssigneeStepProps) { membersList = sortAlphabetically(membersList, 'text', localeCompare); return membersList; - }, [isOffline, policy?.employeeList, assignCard?.data?.email, formatPhoneNumber, localeCompare]); + }, [isOffline, policy?.employeeList, assignCard?.data?.email, formatPhoneNumber, localeCompare, icons.FallbackAvatar]); const assignees = useMemo(() => { if (!debouncedSearchTerm) { diff --git a/src/pages/workspace/companyCards/assignCard/CardNameStep.tsx b/src/pages/workspace/companyCards/assignCard/CardNameStep.tsx index 9cc69d794657..2e9c2d4c86ba 100644 --- a/src/pages/workspace/companyCards/assignCard/CardNameStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardNameStep.tsx @@ -11,26 +11,31 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {addErrorMessage} from '@libs/ErrorUtils'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {getFieldRequiredErrors} from '@libs/ValidationUtils'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; +import {useAssignCardNavigation} from '@pages/workspace/companyCards/utils'; import {setAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/EditExpensifyCardNameForm'; +import type {CompanyCardFeed} from '@src/types/onyx'; -type CardNameStepProps = { - /** Current policy id */ - policyID: string | undefined; -}; +type CardNameStepProps = PlatformStackScreenProps; -function CardNameStep({policyID}: CardNameStepProps) { +function CardNameStep({route}: CardNameStepProps) { const {translate} = useLocalize(); const {inputCallbackRef} = useAutoFocusInput(); const styles = useThemeStyles(); - const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD); - + const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: true}); + const policyID = route.params?.policyID; + const feed = decodeURIComponent(route.params?.feed) as CompanyCardFeed; const data = assignCard?.data; + useAssignCardNavigation(policyID, feed); + const submit = (values: FormOnyxValues) => { setAssignCardStepAndData({ currentStep: CONST.COMPANY_CARD.STEP.CONFIRMATION, diff --git a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx index 5ac90a17b158..a556b44e87a0 100644 --- a/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/CardSelectionStep.tsx @@ -21,22 +21,20 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {setAssignCardStepAndData} from '@libs/actions/CompanyCards'; import {getCardFeedIcon, getCompanyCardFeed, getFilteredCardList, getPlaidInstitutionIconUrl, lastFourNumbersFromCardName, maskCardNumber} from '@libs/CardUtils'; import Navigation from '@libs/Navigation/Navigation'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import tokenizedSearch from '@libs/tokenizedSearch'; +import {useAssignCardNavigation} from '@pages/workspace/companyCards/utils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {CompanyCardFeedWithDomainID} from '@src/types/onyx'; +import type SCREENS from '@src/SCREENS'; +import type {CompanyCardFeedWithDomainID} from '@src/types/onyx/CardFeeds'; -type CardSelectionStepProps = { - /** Selected feed */ - feed: CompanyCardFeedWithDomainID; +type CardSelectionStepProps = PlatformStackScreenProps; - /** Current policy id */ - policyID: string | undefined; -}; - -function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { +function CardSelectionStep({route}: CardSelectionStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const illustrations = useThemeIllustrations(); @@ -44,6 +42,8 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { const lazyIllustrations = useMemoizedLazyIllustrations(['BrokenMagnifyingGlass'] as const); const [searchText, setSearchText] = useState(''); const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: false}); + const feed = decodeURIComponent(route.params?.feed) as CompanyCardFeedWithDomainID; + const policyID = route.params?.policyID; const [list] = useCardsList(feed); const [workspaceCardFeeds] = useOnyx(ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST, {canBeMissing: false}); const [cardFeeds] = useCardFeeds(policyID); @@ -77,6 +77,8 @@ function CardSelectionStep({feed, policyID}: CardSelectionStepProps) { ), })); + useAssignCardNavigation(policyID, feed); + const handleBackButtonPress = () => { if (isEditing) { setAssignCardStepAndData({ diff --git a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx index 8337bde4dd04..d5bd976824de 100644 --- a/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/ConfirmationStep.tsx @@ -14,34 +14,29 @@ import useRootNavigationState from '@hooks/useRootNavigationState'; import useThemeStyles from '@hooks/useThemeStyles'; import {getCompanyCardFeed, getPlaidCountry, getPlaidInstitutionId, isSelectedFeedExpired, lastFourNumbersFromCardName, maskCardNumber} from '@libs/CardUtils'; import {isFullScreenName} from '@libs/Navigation/helpers/isNavigatorName'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import Navigation from '@navigation/Navigation'; +import {useAssignCardNavigation} from '@pages/workspace/companyCards/utils'; import {assignWorkspaceCompanyCard, clearAssignCardStepAndData, setAddNewCompanyCardStepAndData, setAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type {CompanyCardFeedWithDomainID, CurrencyList} from '@src/types/onyx'; import type {AssignCardStep} from '@src/types/onyx/AssignCard'; import {getEmptyObject} from '@src/types/utils/EmptyObject'; -type ConfirmationStepProps = { - /** Current policy id */ - policyID: string | undefined; +type ConfirmationStepProps = PlatformStackScreenProps; - /** Route to go back to */ - backTo?: Route; - - /** Selected feed */ - feed: CompanyCardFeedWithDomainID; -}; - -function ConfirmationStep({policyID, feed, backTo}: ConfirmationStepProps) { +function ConfirmationStep({route}: ConfirmationStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); const {isOffline} = useNetwork(); + const policyID = route.params?.policyID; + const feed = decodeURIComponent(route.params?.feed) as CompanyCardFeedWithDomainID; const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: false}); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, {canBeMissing: false}); const [countryByIp] = useOnyx(ONYXKEYS.COUNTRY, {canBeMissing: false}); @@ -52,7 +47,9 @@ function ConfirmationStep({policyID, feed, backTo}: ConfirmationStepProps) { const data = assignCard?.data; const cardholderName = getPersonalDetailByEmail(data?.email ?? '')?.displayName ?? ''; - const currentFullScreenRoute = useRootNavigationState((state) => state?.routes?.findLast((route) => isFullScreenName(route.name))); + const currentFullScreenRoute = useRootNavigationState((state) => state?.routes?.findLast((_route) => isFullScreenName(_route.name))); + + useAssignCardNavigation(policyID, feed); useEffect(() => { if (!assignCard?.isAssigned) { @@ -60,14 +57,14 @@ function ConfirmationStep({policyID, feed, backTo}: ConfirmationStepProps) { } const lastRoute = currentFullScreenRoute?.state?.routes.at(-1); - if (backTo ?? lastRoute?.name === SCREENS.WORKSPACE.COMPANY_CARDS) { - Navigation.goBack(backTo); + if (lastRoute?.name === SCREENS.WORKSPACE.COMPANY_CARDS) { + Navigation.goBack(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID)); } else { Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policyID), {forceReplace: true}); } // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => clearAssignCardStepAndData()); - }, [assignCard?.isAssigned, backTo, policyID, currentFullScreenRoute?.state?.routes]); + }, [assignCard?.isAssigned, policyID, currentFullScreenRoute?.state?.routes]); const submit = () => { if (!policyID) { @@ -90,6 +87,7 @@ function ConfirmationStep({policyID, feed, backTo}: ConfirmationStepProps) { return; } assignWorkspaceCompanyCard(policy, {...data, bankName}); + Navigation.dismissModal(); }; const editStep = (step: AssignCardStep) => { diff --git a/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx b/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx index dc391b8bdb12..dd441579bacd 100644 --- a/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/TransactionStartDateStep.tsx @@ -10,13 +10,19 @@ import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; +import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; +import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {getPersonalDetailByEmail} from '@libs/PersonalDetailsUtils'; import {isRequiredFulfilled} from '@libs/ValidationUtils'; +import {useAssignCardNavigation} from '@pages/workspace/companyCards/utils'; import {setAssignCardStepAndData} from '@userActions/CompanyCards'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type SCREENS from '@src/SCREENS'; -function TransactionStartDateStep() { +type TransactionStartDateStepProps = PlatformStackScreenProps; + +function TransactionStartDateStep({route}: TransactionStartDateStepProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -24,11 +30,13 @@ function TransactionStartDateStep() { const isEditing = assignCard?.isEditing; const data = assignCard?.data; const assigneeDisplayName = getPersonalDetailByEmail(data?.email ?? '')?.displayName ?? ''; - + const {policyID, feed} = route.params; const [dateOptionSelected, setDateOptionSelected] = useState(data?.dateOption ?? CONST.COMPANY_CARD.TRANSACTION_START_DATE_OPTIONS.CUSTOM); const [errorText, setErrorText] = useState(''); const [startDate, setStartDate] = useState(() => assignCard?.startDate ?? data?.startDate ?? format(new Date(), CONST.DATE.FNS_FORMAT_STRING)); + useAssignCardNavigation(policyID, feed); + const handleBackButtonPress = () => { if (isEditing) { setAssignCardStepAndData({ diff --git a/src/pages/workspace/companyCards/utils.tsx b/src/pages/workspace/companyCards/utils.tsx index 7e5d2331e430..8fbecd7b5406 100644 --- a/src/pages/workspace/companyCards/utils.tsx +++ b/src/pages/workspace/companyCards/utils.tsx @@ -1,12 +1,42 @@ +import {useEffect, useRef} from 'react'; import type {ValueOf} from 'type-fest'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {SelectorType} from '@components/SelectionScreen'; +import useInitial from '@hooks/useInitial'; +import useOnyx from '@hooks/useOnyx'; +import usePermissions from '@hooks/usePermissions'; +import Navigation from '@libs/Navigation/Navigation'; import {findSelectedBankAccountWithDefaultSelect, findSelectedVendorWithDefaultSelect, getCurrentConnectionName, getSageIntacctNonReimbursableActiveDefaultVendor} from '@libs/PolicyUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Card, Policy} from '@src/types/onyx'; +import type {Card, CompanyCardFeed, CompanyCardFeedWithDomainID, Policy} from '@src/types/onyx'; import type {Account, PolicyConnectionName} from '@src/types/onyx/Policy'; +type AssignCardRoute = + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + +type AddNewCardRoute = + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + type ExportIntegration = { title?: string; description?: string; @@ -357,5 +387,89 @@ function getExportMenuItem( } } +function useAssignCardNavigation(policyID: string | undefined, feed: CompanyCardFeed | CompanyCardFeedWithDomainID | undefined, isStartStep = false) { + const [assignCard] = useOnyx(ONYXKEYS.ASSIGN_CARD, {canBeMissing: true}); + const currentStep = assignCard?.currentStep; + const previousStepRef = useRef(currentStep); + const firstAssigneeEmail = useInitial(assignCard?.data?.email); + const shouldUseBackToParam = !firstAssigneeEmail || firstAssigneeEmail === assignCard?.data?.email; + + useEffect(() => { + if (!policyID || !currentStep || !feed) { + return; + } + + if (currentStep === previousStepRef.current && !isStartStep) { + return; + } + + previousStepRef.current = currentStep; + + const stepRoutes: Record = { + [CONST.COMPANY_CARD.STEP.ASSIGNEE]: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE.getRoute(policyID, feed), + [CONST.COMPANY_CARD.STEP.INVITE_NEW_MEMBER]: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_INVITE_NEW_MEMBER.getRoute(policyID, feed), + [CONST.COMPANY_CARD.STEP.BANK_CONNECTION]: ROUTES.WORKSPACE_COMPANY_CARDS_BANK_CONNECTION.getRoute( + policyID, + feed, + ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD.getRoute(policyID, feed), + ), + [CONST.COMPANY_CARD.STEP.PLAID_CONNECTION]: ROUTES.WORKSPACE_COMPANY_CARDS_PLAID_CONNECTION.getRoute(policyID, feed), + [CONST.COMPANY_CARD.STEP.CARD]: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_SELECT.getRoute(policyID, feed), + [CONST.COMPANY_CARD.STEP.TRANSACTION_START_DATE]: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_TRANSACTION_START_DATE_STEP.getRoute(policyID, feed), + [CONST.COMPANY_CARD.STEP.CARD_NAME]: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_NAME.getRoute(policyID, feed), + [CONST.COMPANY_CARD.STEP.CONFIRMATION]: ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION.getRoute(policyID, feed), + }; + + const targetRoute: AssignCardRoute = stepRoutes[currentStep]; + if (targetRoute) { + Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(targetRoute)); + } + }, [currentStep, policyID, feed, isStartStep, shouldUseBackToParam]); +} + +function useAddNewCardNavigation(policyID: string | undefined, isStartStep = false) { + const [addNewCard] = useOnyx(ONYXKEYS.ADD_NEW_COMPANY_CARD, {canBeMissing: true}); + const currentStep = addNewCard?.currentStep; + const previousStepRef = useRef(currentStep); + const {isBetaEnabled} = usePermissions(); + const defaultStep = isBetaEnabled(CONST.BETAS.PLAID_COMPANY_CARDS) ? CONST.COMPANY_CARDS.STEP.SELECT_COUNTRY : CONST.COMPANY_CARDS.STEP.SELECT_BANK; + + useEffect(() => { + if (!policyID) { + return; + } + + if (currentStep === previousStepRef.current && !isStartStep) { + return; + } + + previousStepRef.current = currentStep; + + const stepRoutes: Record = { + [CONST.COMPANY_CARDS.STEP.SELECT_COUNTRY]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.SELECT_BANK]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_BANK.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_FEED_TYPE.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.CARD_TYPE]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_TYPE.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.BANK_CONNECTION]: ROUTES.WORKSPACE_COMPANY_CARDS_BANK_CONNECTION.getRoute( + policyID, + addNewCard?.data?.selectedBank ?? '', + ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.getRoute(policyID), + ), + [CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_INSTRUCTIONS.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.CARD_NAME]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_NAME.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.CARD_DETAILS]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_DETAILS.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.AMEX_CUSTOM_FEED]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_AMEX_CUSTOM_FEED.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.PLAID_CONNECTION]: ROUTES.WORKSPACE_COMPANY_CARDS_PLAID_CONNECTION.getRoute(policyID, addNewCard?.data?.selectedBank ?? ''), + [CONST.COMPANY_CARDS.STEP.SELECT_STATEMENT_CLOSE_DATE]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_STATEMENT_CLOSE_DATE.getRoute(policyID), + [CONST.COMPANY_CARDS.STEP.SELECT_DIRECT_STATEMENT_CLOSE_DATE]: ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_DIRECT_STATEMENT_CLOSE_DATE.getRoute(policyID), + }; + + const targetRoute: AddNewCardRoute = currentStep ? stepRoutes[currentStep] : stepRoutes[defaultStep]; + if (targetRoute) { + Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.navigate(targetRoute)); + } + }, [currentStep, policyID, isStartStep, addNewCard?.data?.selectedBank, defaultStep]); +} + // eslint-disable-next-line import/prefer-default-export -export {getExportMenuItem}; +export {getExportMenuItem, useAssignCardNavigation, useAddNewCardNavigation}; diff --git a/src/pages/workspace/withPolicy.tsx b/src/pages/workspace/withPolicy.tsx index 2f5b48d990df..085186a2bd50 100644 --- a/src/pages/workspace/withPolicy.tsx +++ b/src/pages/workspace/withPolicy.tsx @@ -50,7 +50,8 @@ type PolicyRouteName = | typeof SCREENS.WORKSPACE.ACCOUNTING.CARD_RECONCILIATION | typeof SCREENS.WORKSPACE.RULES | typeof SCREENS.WORKSPACE.EXPENSIFY_CARD_ISSUE_NEW - | typeof SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD; + | typeof SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD + | typeof SCREENS.WORKSPACE.COMPANY_CARDS_ADD_NEW; type PolicyRoute = PlatformStackRouteProp; diff --git a/tests/ui/AssignCardFeedPage.tsx b/tests/ui/AssignCardFeedPage.tsx index 83bbb8f15278..6e2549f1b051 100644 --- a/tests/ui/AssignCardFeedPage.tsx +++ b/tests/ui/AssignCardFeedPage.tsx @@ -1,6 +1,6 @@ import {PortalProvider} from '@gorhom/portal'; import {NavigationContainer} from '@react-navigation/native'; -import {act, fireEvent, render, screen, waitFor} from '@testing-library/react-native'; +import {act, render, screen, waitFor} from '@testing-library/react-native'; import React from 'react'; import Onyx from 'react-native-onyx'; import ComposeProviders from '@components/ComposeProviders'; @@ -113,21 +113,19 @@ describe('AssignCardFeedPage', () => { jest.clearAllMocks(); }); - it('should navigate to the member details page as the assignee email has not changed', async () => { + it('should render loading indicator and call navigation to confirmation step', async () => { // Sign in as a test user before running the test. await TestHelper.signInWithTestUser(); - const goBack = jest.spyOn(Navigation, 'goBack'); + const navigateSpy = jest.spyOn(Navigation, 'navigate'); const policy = { ...LHNTestUtils.getFakePolicy(), role: CONST.POLICY.ROLE.ADMIN, - areTagsEnabled: true, - requiresTag: true, }; - // Add mock policy and mock the assign card details + // Set up Onyx data BEFORE rendering await act(async () => { + await Onyx.merge(ONYXKEYS.IS_LOADING_REPORT_DATA, false); await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); - await Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { data: { bankName: 'vcf', @@ -139,118 +137,79 @@ describe('AssignCardFeedPage', () => { dateOption: 'fromBeginning', startDate: '2024-12-27', }, - currentStep: 'Confirmation', + currentStep: CONST.COMPANY_CARD.STEP.CONFIRMATION, isEditing: false, }); }); + await waitForBatchedUpdatesWithAct(); - // Render the page with the specified policyID and backTo param + // Render the page const {unmount} = renderPage(SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD, { policyID: policy.id, feed: CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX, - backTo: ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policy?.id, 1234), + backTo: ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policy.id, 1234), }); - await waitForBatchedUpdatesWithAct(); - - // Verify that Assign card button is visible on the screen + // Verify the page renders with loading indicator await waitFor(() => { - expect(screen.getByTestId('assignCardButtonTestID')).toBeOnTheScreen(); + expect(screen.getByTestId('AssignCardFeedPage')).toBeOnTheScreen(); + expect(screen.getByTestId('assign-card-loading-indicator')).toBeOnTheScreen(); }); - // Click the Assign Card button - const assignCardButton = screen.getByTestId('assignCardButtonTestID'); - - // Create a mock event object that matches GestureResponderEvent. - const mockEvent = { - nativeEvent: {}, - type: 'press', - target: assignCardButton, - currentTarget: assignCardButton, - }; - fireEvent.press(assignCardButton, mockEvent); - - // Verify that we navigate to the member details page as the card assignee has not changed + // Verify navigation was called to the confirmation step await waitFor(() => { - expect(goBack).toHaveBeenCalledWith(ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policy.id, 1234)); + expect(navigateSpy).toHaveBeenCalled(); }); + // Verify the navigation call contains the correct route parts + const navigationCall = navigateSpy.mock.calls.at(0)?.[0]; + expect(navigationCall).toContain('company-cards'); + expect(navigationCall).toContain('assign-card/confirmation'); + expect(navigationCall).toContain(policy.id); + // Unmount the component after assertions to clean up. unmount(); await waitForBatchedUpdatesWithAct(); }); - it('should navigate to the company cards page as the assignee email has changed', async () => { + it('should show delegate no access message when user is acting as delegate', async () => { // Sign in as a test user before running the test. await TestHelper.signInWithTestUser(); - const navigate = jest.spyOn(Navigation, 'navigate'); const policy = { ...LHNTestUtils.getFakePolicy(), role: CONST.POLICY.ROLE.ADMIN, - areTagsEnabled: true, - requiresTag: true, }; - // Add mock policy and mock the assign card details + // Set up Onyx data with delegate access await act(async () => { + await Onyx.merge(ONYXKEYS.IS_LOADING_REPORT_DATA, false); await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, policy); - await Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false}); + await Onyx.merge(ONYXKEYS.ACCOUNT, { + delegatedAccess: { + delegate: 'delegate@example.com', + }, + }); await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { data: { bankName: 'vcf', email: 'testaccount+1@gmail.com', - cardName: "Test 1's card", - cardNumber: '490901XXXXXX1234', - // cspell:disable-next-line - encryptedCardNumber: 'v12:74E3CA3C4C0FA02FDCF754FDSFDSF', - dateOption: 'fromBeginning', - startDate: '2024-12-27', }, - currentStep: 'Confirmation', - isEditing: false, + currentStep: CONST.COMPANY_CARD.STEP.CONFIRMATION, }); }); - // Render the page with the specified policyID and backTo param + await waitForBatchedUpdatesWithAct(); + + // Render the page const {unmount} = renderPage(SCREENS.WORKSPACE.COMPANY_CARDS_ASSIGN_CARD, { policyID: policy.id, feed: CONST.COMPANY_CARD.FEED_BANK_NAME.AMEX, - backTo: ROUTES.WORKSPACE_MEMBER_DETAILS.getRoute(policy?.id, 1234), }); - await waitForBatchedUpdatesWithAct(); - - // Mock the action of changing the assignee of the card - await act(async () => { - await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { - data: { - email: 'testaccount+2@gmail.com', - }, - }); - }); - - // Verify that Assign card button is visible on the screen + // Verify the page renders await waitFor(() => { - expect(screen.getByTestId('assignCardButtonTestID')).toBeOnTheScreen(); + expect(screen.getByTestId('AssignCardFeedPage')).toBeOnTheScreen(); }); - // Click the Assign Card button - const assignCardButton = screen.getByTestId('assignCardButtonTestID'); - - // Create a mock event object that matches GestureResponderEvent - const mockEvent = { - nativeEvent: {}, - type: 'press', - target: assignCardButton, - currentTarget: assignCardButton, - }; - fireEvent.press(assignCardButton, mockEvent); - - await waitForBatchedUpdatesWithAct(); - - // Verify that we navigate to the company cards page as the card assignee has changed - await waitFor(() => { - expect(navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy.id), {forceReplace: true}); - }); // Unmount the component after assertions to clean up. unmount(); await waitForBatchedUpdatesWithAct(); diff --git a/tests/unit/CompanyCardsNavigationTest.ts b/tests/unit/CompanyCardsNavigationTest.ts new file mode 100644 index 000000000000..53cd8a795d5b --- /dev/null +++ b/tests/unit/CompanyCardsNavigationTest.ts @@ -0,0 +1,510 @@ +import {act, renderHook} from '@testing-library/react-native'; +import Onyx from 'react-native-onyx'; +import Navigation from '@libs/Navigation/Navigation'; +import {useAddNewCardNavigation, useAssignCardNavigation} from '@pages/workspace/companyCards/utils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +// Mock Navigation +jest.mock('@libs/Navigation/Navigation', () => ({ + navigate: jest.fn(), + setNavigationActionToMicrotaskQueue: jest.fn((callback: () => void) => callback()), +})); + +// Mock usePermissions hook +const mockIsBetaEnabled = jest.fn(); +jest.mock('@hooks/usePermissions', () => + jest.fn(() => ({ + isBetaEnabled: mockIsBetaEnabled, + })), +); + +const POLICY_ID = 'test-policy-id'; +const FEED = 'cdf'; + +describe('useAssignCardNavigation', () => { + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + return waitForBatchedUpdates(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + return Onyx.clear(); + }); + + it('should not navigate when step has not changed', async () => { + const currentStep = CONST.COMPANY_CARD.STEP.ASSIGNEE; + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(POLICY_ID, FEED, false)); + + // Wait for effects to run + await waitForBatchedUpdates(); + + expect(Navigation.navigate).not.toHaveBeenCalled(); + }); + + it('should navigate to ASSIGNEE step when step changes', async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.CARD, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(POLICY_ID, FEED, false)); + + // Change the step + await act(async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.ASSIGNEE, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE.getRoute(POLICY_ID, FEED)); + }); + + it('should navigate to CARD step when step changes', async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.ASSIGNEE, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(POLICY_ID, FEED, false)); + + // Change the step + await act(async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.CARD, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_SELECT.getRoute(POLICY_ID, FEED)); + }); + + it('should navigate to CONFIRMATION step with backTo parameter', async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.ASSIGNEE, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(POLICY_ID, FEED, false)); + + // Change the step + await act(async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.CONFIRMATION, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_CONFIRMATION.getRoute(POLICY_ID, FEED)); + }); + + it('should navigate to BANK_CONNECTION step', async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.ASSIGNEE, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(POLICY_ID, FEED, false)); + + // Change the step + await act(async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.BANK_CONNECTION, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith( + ROUTES.WORKSPACE_COMPANY_CARDS_BANK_CONNECTION.getRoute(POLICY_ID, FEED, ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD.getRoute(POLICY_ID, FEED)), + ); + }); + + it('should navigate to PLAID_CONNECTION step', async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.ASSIGNEE, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(POLICY_ID, FEED, false)); + + // Change the step + await act(async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.PLAID_CONNECTION, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_PLAID_CONNECTION.getRoute(POLICY_ID, FEED)); + }); + + it('should navigate immediately when isStartStep is true', async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.ASSIGNEE, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(POLICY_ID, FEED, true)); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ASSIGN_CARD_ASSIGNEE.getRoute(POLICY_ID, FEED)); + }); + + it('should not navigate when policyID is undefined', async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.ASSIGNEE, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(undefined, FEED, true)); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).not.toHaveBeenCalled(); + }); + + it('should not navigate when feed is undefined', async () => { + await Onyx.merge(ONYXKEYS.ASSIGN_CARD, { + currentStep: CONST.COMPANY_CARD.STEP.ASSIGNEE, + data: {email: 'test@example.com'}, + }); + + renderHook(() => useAssignCardNavigation(POLICY_ID, undefined, true)); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).not.toHaveBeenCalled(); + }); +}); + +describe('useAddNewCardNavigation', () => { + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + return waitForBatchedUpdates(); + }); + + beforeEach(() => { + jest.clearAllMocks(); + mockIsBetaEnabled.mockReturnValue(false); + return Onyx.clear(); + }); + + it('should not navigate when step has not changed', async () => { + const currentStep = CONST.COMPANY_CARDS.STEP.SELECT_BANK; + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep, + data: {selectedBank: 'Stripe'}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).not.toHaveBeenCalled(); + }); + + it('should navigate to SELECT_BANK step by default when PLAID beta is disabled', async () => { + mockIsBetaEnabled.mockReturnValue(false); + + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: undefined, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, true)); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_BANK.getRoute(POLICY_ID)); + }); + + it('should navigate to SELECT_COUNTRY step by default when PLAID beta is enabled', async () => { + mockIsBetaEnabled.mockReturnValue(true); + + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: undefined, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, true)); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY.getRoute(POLICY_ID)); + }); + + it('should navigate to SELECT_COUNTRY step when step changes', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_BANK, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_COUNTRY, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_COUNTRY.getRoute(POLICY_ID)); + }); + + it('should navigate to SELECT_BANK step when step changes', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_COUNTRY, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_BANK, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_BANK.getRoute(POLICY_ID)); + }); + + it('should navigate to SELECT_FEED_TYPE step', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_COUNTRY, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_FEED_TYPE.getRoute(POLICY_ID)); + }); + + it('should navigate to CARD_TYPE step', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_BANK, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_TYPE, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_TYPE.getRoute(POLICY_ID)); + }); + + it('should navigate to BANK_CONNECTION step with selected bank', async () => { + const selectedBank = 'Stripe'; + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_TYPE, + data: {selectedBank}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.BANK_CONNECTION, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith( + ROUTES.WORKSPACE_COMPANY_CARDS_BANK_CONNECTION.getRoute(POLICY_ID, selectedBank, ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW.getRoute(POLICY_ID)), + ); + }); + + it('should navigate to CARD_INSTRUCTIONS step', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_TYPE, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_INSTRUCTIONS.getRoute(POLICY_ID)); + }); + + it('should navigate to CARD_NAME step', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_INSTRUCTIONS, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_NAME, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_NAME.getRoute(POLICY_ID)); + }); + + it('should navigate to CARD_DETAILS step', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_NAME, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_DETAILS, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_CARD_DETAILS.getRoute(POLICY_ID)); + }); + + it('should navigate to AMEX_CUSTOM_FEED step', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_BANK, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.AMEX_CUSTOM_FEED, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_AMEX_CUSTOM_FEED.getRoute(POLICY_ID)); + }); + + it('should navigate to PLAID_CONNECTION step with selected bank', async () => { + const selectedBank = 'Chase'; + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_FEED_TYPE, + data: {selectedBank}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.PLAID_CONNECTION, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_PLAID_CONNECTION.getRoute(POLICY_ID, selectedBank)); + }); + + it('should navigate to SELECT_STATEMENT_CLOSE_DATE step', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_DETAILS, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_STATEMENT_CLOSE_DATE, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_STATEMENT_CLOSE_DATE.getRoute(POLICY_ID)); + }); + + it('should navigate to SELECT_DIRECT_STATEMENT_CLOSE_DATE step', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.CARD_DETAILS, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, false)); + + await act(async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_DIRECT_STATEMENT_CLOSE_DATE, + }); + }); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_DIRECT_STATEMENT_CLOSE_DATE.getRoute(POLICY_ID)); + }); + + it('should navigate immediately when isStartStep is true', async () => { + mockIsBetaEnabled.mockReturnValue(false); + + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_BANK, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(POLICY_ID, true)); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).toHaveBeenCalledWith(ROUTES.WORKSPACE_COMPANY_CARDS_ADD_NEW_SELECT_BANK.getRoute(POLICY_ID)); + }); + + it('should not navigate when policyID is undefined', async () => { + await Onyx.merge(ONYXKEYS.ADD_NEW_COMPANY_CARD, { + currentStep: CONST.COMPANY_CARDS.STEP.SELECT_BANK, + data: {}, + }); + + renderHook(() => useAddNewCardNavigation(undefined, true)); + + await waitForBatchedUpdates(); + + expect(Navigation.navigate).not.toHaveBeenCalled(); + }); +});