diff --git a/contributingGuides/NAVIGATION.md b/contributingGuides/NAVIGATION.md index f0db6723b4e5c..20d284b8e5a71 100644 --- a/contributingGuides/NAVIGATION.md +++ b/contributingGuides/NAVIGATION.md @@ -34,7 +34,7 @@ When creating RHP flows, you have to remember a couple of things: - We use a custom `goBack` function to handle the browser and the `react-navigation` history stack. Under the hood, it resolves to either replacing the current screen with the one we navigate to (deeplinking scenario) or just going back if we reached the current page by navigating in App (pops the screen). It ensures the requested behaviors on web, which is navigating back to the place from where you deeplinked when going into the RHP flow by it. -- If you want to navigate to a certain report after completing a flow related to it, e.g. `RequestMoney` flow with a certain group/user, you should use `Navigation.dismissModal` with this `reportID` as an argument. If, in the future, we would like to navigate to something different than the report after such flows, the API should be rather easy to change. We do it like that in order to replace the RHP flow with the new report instead of pushing it, so pressing the back button does not navigate back to the ending page of the flow. If we were to navigate to the same report, we just pop the RHP modal. +- If you want to navigate to a certain report after completing a flow related to it, e.g. `RequestMoney` flow with a certain group/user, you should use `Navigation.dismissModalWithReport` with this `reportID` as an argument. If, in the future, we would like to navigate to something different than the report after such flows, the API should be rather easy to change. We do it like that in order to replace the RHP flow with the new report instead of pushing it, so pressing the back button does not navigate back to the ending page of the flow. If we were to navigate to the same report, we just pop the RHP modal. ### Example of usage diff --git a/src/libs/Navigation/AppNavigator/createRootStackNavigator/GetStateForActionHandlers.ts b/src/libs/Navigation/AppNavigator/createRootStackNavigator/GetStateForActionHandlers.ts index bb796c74eee18..1965d7a1bc0c9 100644 --- a/src/libs/Navigation/AppNavigator/createRootStackNavigator/GetStateForActionHandlers.ts +++ b/src/libs/Navigation/AppNavigator/createRootStackNavigator/GetStateForActionHandlers.ts @@ -7,7 +7,7 @@ import type {RootNavigatorParamList, State} from '@libs/Navigation/types'; import * as SearchQueryUtils from '@libs/SearchQueryUtils'; import NAVIGATORS from '@src/NAVIGATORS'; import SCREENS from '@src/SCREENS'; -import type {OpenWorkspaceSplitActionType, PushActionType, SwitchPolicyIdActionType} from './types'; +import type {OpenWorkspaceSplitActionType, PushActionType, ReplaceActionType, SwitchPolicyIdActionType} from './types'; const MODAL_ROUTES_TO_DISMISS: string[] = [ NAVIGATORS.WORKSPACE_SPLIT_NAVIGATOR, @@ -237,6 +237,29 @@ function handlePushSearchPageAction( return stackRouter.getStateForAction(state, updatedAction, configOptions); } +function handleReplaceReportsSplitNavigatorAction( + state: StackNavigationState, + action: ReplaceActionType, + configOptions: RouterConfigOptions, + stackRouter: Router, CommonActions.Action | StackActionType>, +) { + const stateWithReportsSplitNavigator = stackRouter.getStateForAction(state, action, configOptions); + + if (!stateWithReportsSplitNavigator) { + Log.hmmm('[handleReplaceReportsSplitNavigatorAction] ReportsSplitNavigator has not been found in the navigation state.'); + return null; + } + + const lastReportsSplitNavigator = stateWithReportsSplitNavigator.routes.at(-1); + + // ReportScreen should always be opened with an animation when replacing the navigator + if (lastReportsSplitNavigator?.key) { + reportsSplitsWithEnteringAnimation.add(lastReportsSplitNavigator.key); + } + + return stateWithReportsSplitNavigator; +} + /** * Handles the DISMISS_MODAL action. * If the last route is a modal route, it has to be popped from the navigation stack. @@ -275,6 +298,7 @@ export { handleDismissModalAction, handlePushReportSplitAction, handlePushSearchPageAction, + handleReplaceReportsSplitNavigatorAction, handleSwitchPolicyIDAction, handleSwitchPolicyIDFromSearchAction, handleNavigatingToModalFromModal, diff --git a/src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts b/src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts index fcaad4ae232a4..0e9b4b2c36692 100644 --- a/src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts +++ b/src/libs/Navigation/AppNavigator/createRootStackNavigator/RootStackRouter.ts @@ -14,10 +14,19 @@ import { handleOpenWorkspaceSplitAction, handlePushReportSplitAction, handlePushSearchPageAction, + handleReplaceReportsSplitNavigatorAction, handleSwitchPolicyIDAction, } from './GetStateForActionHandlers'; import syncBrowserHistory from './syncBrowserHistory'; -import type {DismissModalActionType, OpenWorkspaceSplitActionType, PushActionType, RootStackNavigatorAction, RootStackNavigatorRouterOptions, SwitchPolicyIdActionType} from './types'; +import type { + DismissModalActionType, + OpenWorkspaceSplitActionType, + PushActionType, + ReplaceActionType, + RootStackNavigatorAction, + RootStackNavigatorRouterOptions, + SwitchPolicyIdActionType, +} from './types'; function isOpenWorkspaceSplitAction(action: RootStackNavigatorAction): action is OpenWorkspaceSplitActionType { return action.type === CONST.NAVIGATION.ACTION_TYPE.OPEN_WORKSPACE_SPLIT; @@ -31,6 +40,10 @@ function isPushAction(action: RootStackNavigatorAction): action is PushActionTyp return action.type === CONST.NAVIGATION.ACTION_TYPE.PUSH; } +function isReplaceAction(action: RootStackNavigatorAction): action is ReplaceActionType { + return action.type === CONST.NAVIGATION.ACTION_TYPE.REPLACE; +} + function isDismissModalAction(action: RootStackNavigatorAction): action is DismissModalActionType { return action.type === CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL; } @@ -82,6 +95,10 @@ function RootStackRouter(options: RootStackNavigatorRouterOptions) { return handleDismissModalAction(state, configOptions, stackRouter); } + if (isReplaceAction(action) && action.payload.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR) { + return handleReplaceReportsSplitNavigatorAction(state, action, configOptions, stackRouter); + } + if (isPushAction(action)) { if (action.payload.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR) { return handlePushReportSplitAction(state, action, configOptions, stackRouter, setActiveWorkspaceID); diff --git a/src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts b/src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts index 6dae82db35b5b..67d69f90a2056 100644 --- a/src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts +++ b/src/libs/Navigation/AppNavigator/createRootStackNavigator/types.ts @@ -29,6 +29,8 @@ type SwitchPolicyIdActionType = RootStackNavigatorActionType & { type PushActionType = StackActionType & {type: typeof CONST.NAVIGATION.ACTION_TYPE.PUSH}; +type ReplaceActionType = StackActionType & {type: typeof CONST.NAVIGATION.ACTION_TYPE.REPLACE}; + type DismissModalActionType = RootStackNavigatorActionType & { type: typeof CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL; }; @@ -49,6 +51,7 @@ export type { OpenWorkspaceSplitActionType, SwitchPolicyIdActionType, PushActionType, + ReplaceActionType, DismissModalActionType, RootStackNavigatorAction, RootStackNavigatorActionType, diff --git a/src/libs/Navigation/Navigation.ts b/src/libs/Navigation/Navigation.ts index 282dda5d3c57a..f4e636ae951f1 100644 --- a/src/libs/Navigation/Navigation.ts +++ b/src/libs/Navigation/Navigation.ts @@ -5,7 +5,7 @@ import {CommonActions, getPathFromState, StackActions} from '@react-navigation/n import omit from 'lodash/omit'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import type {Writable} from 'type-fest'; +import type {MergeExclusive, Writable} from 'type-fest'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import Log from '@libs/Log'; import {shallowCompare} from '@libs/ObjectUtils'; @@ -24,6 +24,7 @@ import originalCloseRHPFlow from './helpers/closeRHPFlow'; import getPolicyIDFromState from './helpers/getPolicyIDFromState'; import getStateFromPath from './helpers/getStateFromPath'; import getTopmostReportParams from './helpers/getTopmostReportParams'; +import {isFullScreenName} from './helpers/isNavigatorName'; import isReportOpenInRHP from './helpers/isReportOpenInRHP'; import isSideModalNavigator from './helpers/isSideModalNavigator'; import linkTo from './helpers/linkTo'; @@ -469,20 +470,25 @@ function waitForProtectedRoutes() { }); } -type NavigateToReportWithPolicyCheckPayload = {report?: OnyxEntry; reportID?: string; reportActionID?: string; referrer?: string; policyIDToCheck?: string}; +// It should not be possible to pass a report and a reportID at the same time. +type NavigateToReportWithPolicyCheckPayload = MergeExclusive<{report: OnyxEntry}, {reportID: string}> & { + reportActionID?: string; + referrer?: string; + policyIDToCheck?: string; +}; /** * Navigates to a report passed as a param (as an id or report object) and checks whether the target object belongs to the currently selected workspace. * If not, the current workspace is set to global. */ -function navigateToReportWithPolicyCheck({report, reportID, reportActionID, referrer, policyIDToCheck}: NavigateToReportWithPolicyCheckPayload, ref = navigationRef) { +function navigateToReportWithPolicyCheck({report, reportID, reportActionID, referrer, policyIDToCheck}: NavigateToReportWithPolicyCheckPayload, forceReplace = false, ref = navigationRef) { const targetReport = reportID ? {reportID, ...allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]} : report; const policyID = policyIDToCheck ?? getPolicyIDFromState(navigationRef.getRootState() as State); const policyMemberAccountIDs = getPolicyEmployeeAccountIDs(policyID); const shouldOpenAllWorkspace = isEmptyObject(targetReport) ? true : !doesReportBelongToWorkspace(targetReport, policyMemberAccountIDs, policyID); if ((shouldOpenAllWorkspace && !policyID) || !shouldOpenAllWorkspace) { - linkTo(ref.current, ROUTES.REPORT_WITH_ID.getRoute(targetReport?.reportID, reportActionID, referrer)); + linkTo(ref.current, ROUTES.REPORT_WITH_ID.getRoute(targetReport?.reportID, reportActionID, referrer), {forceReplace: !!forceReplace}); return; } @@ -498,6 +504,17 @@ function navigateToReportWithPolicyCheck({report, reportID, reportActionID, refe params.referrer = referrer; } + if (forceReplace) { + ref.dispatch( + StackActions.replace(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, { + policyID: undefined, + screen: SCREENS.REPORT, + params, + }), + ); + return; + } + ref.dispatch( StackActions.push(NAVIGATORS.REPORTS_SPLIT_NAVIGATOR, { policyID: undefined, @@ -528,23 +545,31 @@ function getReportRouteByID(reportID?: string, routes: NavigationRoute[] = navig /** * Closes the modal navigator (RHP, LHP, onboarding). */ -const dismissModal = (reportID?: string, ref = navigationRef) => { +const dismissModal = (ref = navigationRef) => { isNavigationReady().then(() => { ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL}); - if (!reportID) { - return; - } - navigateToReportWithPolicyCheck({reportID}); }); }; /** * Dismisses the modal and opens the given report. */ -const dismissModalWithReport = (report: OnyxEntry, ref = navigationRef) => { +const dismissModalWithReport = (navigateToReportPayload: NavigateToReportWithPolicyCheckPayload, ref = navigationRef) => { isNavigationReady().then(() => { - ref.dispatch({type: CONST.NAVIGATION.ACTION_TYPE.DISMISS_MODAL}); - navigateToReportWithPolicyCheck({report}); + if (getIsNarrowLayout()) { + const topmostReportID = getTopmostReportId(); + const areReportsIDsDefined = !!topmostReportID && !!navigateToReportPayload.reportID; + const isReportsSplitTopmostFullScreen = ref.getRootState().routes.findLast((route) => isFullScreenName(route.name))?.name === NAVIGATORS.REPORTS_SPLIT_NAVIGATOR; + if (topmostReportID === navigateToReportPayload.reportID && areReportsIDsDefined && isReportsSplitTopmostFullScreen) { + dismissModal(); + return; + } + const forceReplace = true; + navigateToReportWithPolicyCheck(navigateToReportPayload, forceReplace); + return; + } + dismissModal(); + navigateToReportWithPolicyCheck(navigateToReportPayload); }); }; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9029f272122b9..5321834fefab5 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -679,6 +679,19 @@ Onyx.connect({ callback: (value) => (personalDetailsList = value), }); +/** + * @private + * After finishing the action in RHP from the Inbox tab, besides dismissing the modal, we should open the report. + * It is a helper function used only in this file. + */ +function dismissModalAndOpenReportInInboxTab(reportID?: string) { + if (isSearchTopmostFullScreenRoute() || !reportID) { + Navigation.dismissModal(); + return; + } + Navigation.dismissModalWithReport({reportID}); +} + /** * Find the report preview action from given chat report and iou report */ @@ -4758,7 +4771,7 @@ function requestMoney(requestMoneyInformation: RequestMoneyInformation) { } InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : activeReportID); + dismissModalAndOpenReportInInboxTab(activeReportID); const trackReport = Navigation.getReportRouteByID(linkedTrackedExpenseReportAction?.childReportID); if (trackReport?.key) { @@ -4842,7 +4855,8 @@ function submitPerDiemExpense(submitPerDiemExpenseInformation: PerDiemExpenseInf API.write(WRITE_COMMANDS.CREATE_PER_DIEM_REQUEST, parameters, onyxData); InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : activeReportID); + dismissModalAndOpenReportInInboxTab(activeReportID); + if (activeReportID) { notifyNewAction(activeReportID, payeeAccountID); } @@ -4909,7 +4923,7 @@ function sendInvoice( if (isSearchTopmostFullScreenRoute()) { Navigation.dismissModal(); } else { - Navigation.dismissModalWithReport(invoiceRoom); + Navigation.dismissModalWithReport({report: invoiceRoom}); } notifyNewAction(invoiceRoom.reportID, receiver.accountID); @@ -5135,7 +5149,7 @@ function trackExpense(params: CreateTrackExpenseParams) { } } InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : activeReportID); + dismissModalAndOpenReportInInboxTab(activeReportID); if (action === CONST.IOU.ACTION.SHARE) { if (isSearchTopmostFullScreenRoute() && activeReportID) { @@ -5718,7 +5732,8 @@ function splitBill({ API.write(WRITE_COMMANDS.SPLIT_BILL, parameters, onyxData); InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : existingSplitChatReportID); + dismissModalAndOpenReportInInboxTab(existingSplitChatReportID); + notifyNewAction(splitData.chatReportID, currentUserAccountID); } @@ -5791,7 +5806,7 @@ function splitBillAndOpenReport({ API.write(WRITE_COMMANDS.SPLIT_BILL_AND_OPEN_REPORT, parameters, onyxData); InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : splitData.chatReportID); + dismissModalAndOpenReportInInboxTab(splitData.chatReportID); notifyNewAction(splitData.chatReportID, currentUserAccountID); } @@ -6110,7 +6125,7 @@ function startSplitBill({ API.write(WRITE_COMMANDS.START_SPLIT_BILL, parameters, {optimisticData, successData, failureData}); - Navigation.dismissModalWithReport(splitChatReport); + Navigation.dismissModalWithReport({report: splitChatReport}); notifyNewAction(splitChatReport.reportID, currentUserAccountID); } @@ -6372,7 +6387,7 @@ function completeSplitBill( API.write(WRITE_COMMANDS.COMPLETE_SPLIT_BILL, parameters, {optimisticData, successData, failureData}); InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : chatReportID); + dismissModalAndOpenReportInInboxTab(chatReportID); notifyNewAction(chatReportID, sessionAccountID); } @@ -6555,7 +6570,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest API.write(WRITE_COMMANDS.CREATE_DISTANCE_REQUEST, parameters, onyxData); InteractionManager.runAfterInteractions(() => removeDraftTransaction(CONST.IOU.OPTIMISTIC_TRANSACTION_ID)); const activeReportID = isMoneyRequestReport && report?.reportID ? report.reportID : parameters.chatReportID; - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : activeReportID); + dismissModalAndOpenReportInInboxTab(activeReportID); notifyNewAction(activeReportID, userAccountID); } @@ -8256,7 +8271,7 @@ function sendMoneyElsewhere(report: OnyxEntry, amount: number, API.write(WRITE_COMMANDS.SEND_MONEY_ELSEWHERE, params, {optimisticData, successData, failureData}); - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : params.chatReportID); + dismissModalAndOpenReportInInboxTab(params.chatReportID); notifyNewAction(params.chatReportID, managerID); } @@ -8269,7 +8284,7 @@ function sendMoneyWithWallet(report: OnyxEntry, amount: number API.write(WRITE_COMMANDS.SEND_MONEY_WITH_WALLET, params, {optimisticData, successData, failureData}); - Navigation.dismissModal(isSearchTopmostFullScreenRoute() ? undefined : params.chatReportID); + dismissModalAndOpenReportInInboxTab(params.chatReportID); notifyNewAction(params.chatReportID, managerID); } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 0b7fa684de20c..cf638597ad04f 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -66,6 +66,7 @@ import type EnvironmentType from '@libs/Environment/getEnvironment/types'; import * as ErrorUtils from '@libs/ErrorUtils'; import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; import fileDownload from '@libs/fileDownload'; +import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import HttpUtils from '@libs/HttpUtils'; import isPublicScreenRoute from '@libs/isPublicScreenRoute'; import * as Localize from '@libs/Localize'; @@ -1200,6 +1201,11 @@ function navigateToAndOpenReport( // We want to pass newChat here because if anything is passed in that param (even an existing chat), we will try to create a chat on the server openReport(report?.reportID, '', userLogins, newChat, undefined, undefined, undefined, avatarFile); if (shouldDismissModal) { + if (getIsNarrowLayout()) { + Navigation.dismissModalWithReport({report}); + return; + } + Navigation.dismissModal(); } @@ -2400,7 +2406,7 @@ function navigateToConciergeChat(shouldDismissModal = false, checkIfCurrentPageA navigateToAndOpenReport([CONST.EMAIL.CONCIERGE], shouldDismissModal); }); } else if (shouldDismissModal) { - Navigation.dismissModal(conciergeChatReportID); + Navigation.dismissModalWithReport({reportID: conciergeChatReportID}); } else { Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(conciergeChatReportID), linkToOptions); } @@ -2656,7 +2662,7 @@ function addPolicyReport(policyReport: OptimisticChatReport) { }; API.write(WRITE_COMMANDS.ADD_WORKSPACE_ROOM, parameters, {optimisticData, successData, failureData}); - Navigation.dismissModalWithReport(policyReport); + Navigation.dismissModalWithReport({report: policyReport}); } /** Deletes a report, along with its reportActions, any linked reports, and any linked IOU report. */ diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 9045b11234512..a208893e89f30 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -340,7 +340,7 @@ function createTaskAndNavigate( InteractionManager.runAfterInteractions(() => { clearOutTaskInfo(); }); - Navigation.dismissModal(parentReportID); + Navigation.dismissModalWithReport({reportID: parentReportID}); } notifyNewAction(parentReportID, currentUserAccountID); } diff --git a/src/libs/actions/TeachersUnite.ts b/src/libs/actions/TeachersUnite.ts index 11cb189b20a7b..ed78a7c60ad84 100644 --- a/src/libs/actions/TeachersUnite.ts +++ b/src/libs/actions/TeachersUnite.ts @@ -74,7 +74,7 @@ function referTeachersUniteVolunteer(partnerUserID: string, firstName: string, l }; API.write(WRITE_COMMANDS.REFER_TEACHERS_UNITE_VOLUNTEER, parameters, {optimisticData}); - Navigation.dismissModal(publicRoomReportID); + Navigation.dismissModalWithReport({reportID: publicRoomReportID}); } /** @@ -206,7 +206,7 @@ function addSchoolPrincipal(firstName: string, partnerUserID: string, lastName: }; API.write(WRITE_COMMANDS.ADD_SCHOOL_PRINCIPAL, parameters, {optimisticData, successData, failureData}); - Navigation.dismissModal(expenseChatReportID); + Navigation.dismissModalWithReport({reportID: expenseChatReportID}); } export default {referTeachersUniteVolunteer, addSchoolPrincipal}; diff --git a/src/pages/AddPersonalBankAccountPage.tsx b/src/pages/AddPersonalBankAccountPage.tsx index 07da0d48c2498..0734a5749f4c9 100644 --- a/src/pages/AddPersonalBankAccountPage.tsx +++ b/src/pages/AddPersonalBankAccountPage.tsx @@ -66,7 +66,7 @@ function AddPersonalBankAccountPage() { const onSuccessFallbackRoute = personalBankAccount?.onSuccessFallbackRoute ?? ''; if (exitReportID) { - Navigation.dismissModal(exitReportID); + Navigation.dismissModalWithReport({reportID: exitReportID}); } else if (shouldContinue && onSuccessFallbackRoute) { continueSetup(onSuccessFallbackRoute); } else { diff --git a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx index a273b210efa98..5b38a557ad2c1 100644 --- a/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx +++ b/src/pages/EnablePayments/AddBankAccount/AddBankAccount.tsx @@ -8,10 +8,10 @@ import useLocalize from '@hooks/useLocalize'; import useSubStep from '@hooks/useSubStep'; import type {SubStepProps} from '@hooks/useSubStep/types'; import useThemeStyles from '@hooks/useThemeStyles'; +import {addPersonalBankAccount, clearPersonalBankAccount} from '@libs/actions/BankAccounts'; +import {continueSetup} from '@libs/actions/PaymentMethods'; +import {updateCurrentStep} from '@libs/actions/Wallet'; import Navigation from '@navigation/Navigation'; -import * as BankAccounts from '@userActions/BankAccounts'; -import * as PaymentMethods from '@userActions/PaymentMethods'; -import * as Wallet from '@userActions/Wallet'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -32,7 +32,7 @@ function AddBankAccount() { const selectedPlaidBankAccount = bankAccounts.find((bankAccount) => bankAccount.plaidAccountID === personalBankAccountDraft?.plaidAccountID); if (selectedPlaidBankAccount) { - BankAccounts.addPersonalBankAccount(selectedPlaidBankAccount); + addPersonalBankAccount(selectedPlaidBankAccount); } }, [personalBankAccountDraft?.plaidAccountID, plaidData?.bankAccounts]); @@ -45,11 +45,11 @@ function AddBankAccount() { const onSuccessFallbackRoute = personalBankAccount?.onSuccessFallbackRoute ?? ''; if (exitReportID) { - Navigation.dismissModal(exitReportID); + Navigation.dismissModalWithReport({reportID: exitReportID}); return; } if (shouldContinue && onSuccessFallbackRoute) { - PaymentMethods.continueSetup(onSuccessFallbackRoute); + continueSetup(onSuccessFallbackRoute); return; } Navigation.goBack(ROUTES.SETTINGS_WALLET); @@ -61,8 +61,8 @@ function AddBankAccount() { return; } if (screenIndex === 0) { - BankAccounts.clearPersonalBankAccount(); - Wallet.updateCurrentStep(null); + clearPersonalBankAccount(); + updateCurrentStep(null); Navigation.goBack(ROUTES.SETTINGS_WALLET); return; } diff --git a/src/pages/NewChatPage.tsx b/src/pages/NewChatPage.tsx index 6c14fcc0d2376..d3a88c13494a7 100755 --- a/src/pages/NewChatPage.tsx +++ b/src/pages/NewChatPage.tsx @@ -229,7 +229,11 @@ function NewChatPage() { const selectOption = useCallback( (option?: Option) => { if (option?.isSelfDM) { - Navigation.dismissModal(option.reportID); + if (!option.reportID) { + Navigation.dismissModal(); + return; + } + Navigation.dismissModalWithReport({reportID: option.reportID}); return; } if (selectedOptions.length && option) { diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index a212d1ab5c49b..eae5be29c9d71 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -215,7 +215,7 @@ function RoomInvitePage({ const goBack = useCallback(() => { if (role === CONST.IOU.SHARE.ROLE.ACCOUNTANT) { - Navigation.dismissModal(reportID); + Navigation.dismissModalWithReport({reportID}); return; } Navigation.goBack(backRoute); diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 805fda5c298af..e6d19a4d98157 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -66,7 +66,11 @@ function Confirmation() { const resolveDuplicates = useCallback(() => { IOU.resolveDuplicates(transactionsMergeParams); - Navigation.dismissModal(reportAction?.childReportID); + if (!reportAction?.childReportID) { + Navigation.dismissModal(); + return; + } + Navigation.dismissModalWithReport({reportID: reportAction.childReportID}); }, [transactionsMergeParams, reportAction?.childReportID]); const contextValue = useMemo( diff --git a/src/pages/TransactionReceiptPage.tsx b/src/pages/TransactionReceiptPage.tsx index 132d1607fb52c..78a35fcd163bf 100644 --- a/src/pages/TransactionReceiptPage.tsx +++ b/src/pages/TransactionReceiptPage.tsx @@ -59,7 +59,12 @@ function TransactionReceipt({route}: TransactionReceiptProps) { Navigation.dismissModal(); } else { const isOneTransactionThread = isOneTransactionThreadReportUtils(report?.reportID, report?.parentReportID, parentReportAction); - Navigation.dismissModal(isOneTransactionThread ? report?.parentReportID : report?.reportID); + const reportID = isOneTransactionThread ? report?.parentReportID : report?.reportID; + if (!reportID) { + Navigation.dismissModal(); + return; + } + Navigation.dismissModalWithReport({reportID}); } }; diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 5aaf02d8ba3e6..7611f448dd7ca 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -18,15 +18,15 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useDebouncedState from '@hooks/useDebouncedState'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as ReportActions from '@libs/actions/Report'; +import {searchInServer} from '@libs/actions/Report'; +import {canModifyTask, editTaskAssignee, setAssigneeValue} from '@libs/actions/Task'; import {READ_COMMANDS} from '@libs/API/types'; import HttpUtils from '@libs/HttpUtils'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; -import * as OptionsListUtils from '@libs/OptionsListUtils'; -import * as ReportUtils from '@libs/ReportUtils'; +import {filterAndOrderOptions, getHeaderMessage, getValidOptions, isCurrentUser} from '@libs/OptionsListUtils'; +import {isOpenTaskReport, isTaskReport} from '@libs/ReportUtils'; import type {TaskDetailsNavigatorParamList} from '@navigation/types'; -import * as TaskActions from '@userActions/Task'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -40,7 +40,7 @@ function useOptions() { const {options: optionsList, areOptionsInitialized} = useOptionsList(); const defaultOptions = useMemo(() => { - const {recentReports, personalDetails, userToInvite, currentUserOption} = OptionsListUtils.getValidOptions( + const {recentReports, personalDetails, userToInvite, currentUserOption} = getValidOptions( { reports: optionsList.reports, personalDetails: optionsList.personalDetails, @@ -51,7 +51,7 @@ function useOptions() { }, ); - const headerMessage = OptionsListUtils.getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0 || !!currentUserOption, !!userToInvite, ''); + const headerMessage = getHeaderMessage((recentReports?.length || 0) + (personalDetails?.length || 0) !== 0 || !!currentUserOption, !!userToInvite, ''); if (isLoading) { // eslint-disable-next-line react-compiler/react-compiler @@ -68,11 +68,11 @@ function useOptions() { }, [optionsList.reports, optionsList.personalDetails, betas, isLoading]); const options = useMemo(() => { - const filteredOptions = OptionsListUtils.filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { + const filteredOptions = filterAndOrderOptions(defaultOptions, debouncedSearchValue.trim(), { excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT, maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW, }); - const headerMessage = OptionsListUtils.getHeaderMessage( + const headerMessage = getHeaderMessage( (filteredOptions.recentReports?.length || 0) + (filteredOptions.personalDetails?.length || 0) !== 0 || !!filteredOptions.currentUserOption, !!filteredOptions.userToInvite, debouncedSearchValue, @@ -104,9 +104,9 @@ function TaskAssigneeSelectorModal() { return; } const reportOnyx = reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`]; - if (reportOnyx && !ReportUtils.isTaskReport(reportOnyx)) { + if (reportOnyx && !isTaskReport(reportOnyx)) { Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(reportOnyx.reportID); + Navigation.dismissModalWithReport({reportID: reportOnyx.reportID}); }); } return reports?.[`${ONYXKEYS.COLLECTION.REPORT}${route.params?.reportID}`]; @@ -167,27 +167,27 @@ function TaskAssigneeSelectorModal() { // Check to see if we're editing a task and if so, update the assignee if (report) { if (option.accountID !== report.managerID) { - const assigneeChatReport = TaskActions.setAssigneeValue( + const assigneeChatReport = setAssigneeValue( option?.login ?? '', - option?.accountID ?? -1, + option?.accountID ?? CONST.DEFAULT_NUMBER_ID, report.reportID, undefined, // passing null as report because for editing task the report will be task details report page not the actual report where task was created - OptionsListUtils.isCurrentUser({...option, accountID: option?.accountID ?? -1, login: option?.login ?? ''}), + isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? ''}), ); // Pass through the selected assignee - TaskActions.editTaskAssignee(report, session?.accountID ?? -1, option?.login ?? '', option?.accountID, assigneeChatReport); + editTaskAssignee(report, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, option?.login ?? '', option?.accountID, assigneeChatReport); } InteractionManager.runAfterInteractions(() => { - Navigation.dismissModal(report.reportID); + Navigation.dismissModalWithReport({reportID: report?.reportID}); }); // If there's no report, we're creating a new task } else if (option.accountID) { - TaskActions.setAssigneeValue( + setAssigneeValue( option?.login ?? '', - option.accountID ?? -1, + option.accountID ?? CONST.DEFAULT_NUMBER_ID, task?.shareDestination ?? '', undefined, // passing null as report is null in this condition - OptionsListUtils.isCurrentUser({...option, accountID: option?.accountID ?? -1, login: option?.login ?? undefined}), + isCurrentUser({...option, accountID: option?.accountID ?? CONST.DEFAULT_NUMBER_ID, login: option?.login ?? undefined}), ); InteractionManager.runAfterInteractions(() => { Navigation.goBack(ROUTES.NEW_TASK.getRoute(backTo)); @@ -199,12 +199,12 @@ function TaskAssigneeSelectorModal() { const handleBackButtonPress = useCallback(() => Navigation.goBack(!route.params?.reportID ? ROUTES.NEW_TASK.getRoute(backTo) : backTo), [route.params, backTo]); - const isOpen = ReportUtils.isOpenTaskReport(report); - const canModifyTask = TaskActions.canModifyTask(report, currentUserPersonalDetails.accountID); - const isTaskNonEditable = ReportUtils.isTaskReport(report) && (!canModifyTask || !isOpen); + const isOpen = isOpenTaskReport(report); + const canModifyTaskValue = canModifyTask(report, currentUserPersonalDetails.accountID); + const isTaskNonEditable = isTaskReport(report) && (!canModifyTaskValue || !isOpen); useEffect(() => { - ReportActions.searchInServer(debouncedSearchValue); + searchInServer(debouncedSearchValue); }, [debouncedSearchValue]); return ( diff --git a/src/pages/tasks/TaskDescriptionPage.tsx b/src/pages/tasks/TaskDescriptionPage.tsx index 9fdf1757ca96f..d855228e3c970 100644 --- a/src/pages/tasks/TaskDescriptionPage.tsx +++ b/src/pages/tasks/TaskDescriptionPage.tsx @@ -59,14 +59,14 @@ function TaskDescriptionPage({report, currentUserPersonalDetails}: TaskDescripti editTask(report, {description: values.description}); } - Navigation.dismissModal(report?.reportID); + Navigation.dismissModalWithReport({reportID: report?.reportID}); }, [report], ); if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(report?.reportID); + Navigation.dismissModalWithReport({reportID: report?.reportID}); }); } const inputRef = useRef(null); diff --git a/src/pages/tasks/TaskTitlePage.tsx b/src/pages/tasks/TaskTitlePage.tsx index 30beb0bd700d6..8d2f33d2cd75d 100644 --- a/src/pages/tasks/TaskTitlePage.tsx +++ b/src/pages/tasks/TaskTitlePage.tsx @@ -63,14 +63,14 @@ function TaskTitlePage({report, currentUserPersonalDetails}: TaskTitlePageProps) editTask(report, {title: values.title}); } - Navigation.dismissModal(report?.reportID); + Navigation.dismissModalWithReport({reportID: report?.reportID}); }, [report], ); if (!isTaskReport(report)) { Navigation.isNavigationReady().then(() => { - Navigation.dismissModal(report?.reportID); + Navigation.dismissModalWithReport({reportID: report?.reportID}); }); } diff --git a/src/pages/workspace/WorkspaceInviteMessagePage.tsx b/src/pages/workspace/WorkspaceInviteMessagePage.tsx index 3f48e3c7a2f96..458395e47dbf9 100644 --- a/src/pages/workspace/WorkspaceInviteMessagePage.tsx +++ b/src/pages/workspace/WorkspaceInviteMessagePage.tsx @@ -24,6 +24,7 @@ import {clearDraftValues} from '@libs/actions/FormActions'; import {openExternalLink} from '@libs/actions/Link'; import {addMembersToWorkspace} from '@libs/actions/Policy/Member'; import {setWorkspaceInviteMessageDraft} from '@libs/actions/Policy/Policy'; +import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getAvatarsForAccountIDs} from '@libs/OptionsListUtils'; @@ -112,6 +113,12 @@ function WorkspaceInviteMessagePage({policy, route, currentUserPersonalDetails}: Navigation.setNavigationActionToMicrotaskQueue(() => Navigation.dismissModal()); return; } + + if (getIsNarrowLayout()) { + Navigation.navigate(ROUTES.WORKSPACE_MEMBERS.getRoute(route.params.policyID), {forceReplace: true}); + return; + } + Navigation.setNavigationActionToMicrotaskQueue(() => { Navigation.dismissModal(); InteractionManager.runAfterInteractions(() => { diff --git a/src/pages/workspace/WorkspaceNewRoomPage.tsx b/src/pages/workspace/WorkspaceNewRoomPage.tsx index 52f687c43d81a..4b6b568b389ce 100644 --- a/src/pages/workspace/WorkspaceNewRoomPage.tsx +++ b/src/pages/workspace/WorkspaceNewRoomPage.tsx @@ -132,7 +132,11 @@ function WorkspaceNewRoomPage() { if (!(((wasLoading && !isLoading) || (isOffline && isLoading)) && isEmptyObject(errorFields))) { return; } - Navigation.dismissModal(newRoomReportID); + if (!newRoomReportID) { + Navigation.dismissModal(); + return; + } + Navigation.dismissModalWithReport({reportID: newRoomReportID}); // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps -- we just want this to update on changing the form State }, [isLoading, errorFields]); diff --git a/src/pages/workspace/WorkspaceOverviewSharePage.tsx b/src/pages/workspace/WorkspaceOverviewSharePage.tsx index 2eab58a65e78d..47ee741928682 100644 --- a/src/pages/workspace/WorkspaceOverviewSharePage.tsx +++ b/src/pages/workspace/WorkspaceOverviewSharePage.tsx @@ -20,9 +20,9 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; import Navigation from '@libs/Navigation/Navigation'; -import * as ReportUtils from '@libs/ReportUtils'; +import {getDefaultWorkspaceAvatar, getRoom} from '@libs/ReportUtils'; import shouldAllowDownloadQRCode from '@libs/shouldAllowDownloadQRCode'; -import * as Url from '@libs/Url'; +import {addTrailingForwardSlash} from '@libs/Url'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; import AccessOrNotFoundWrapper from './AccessOrNotFoundWrapper'; @@ -39,17 +39,17 @@ function WorkspaceOverviewSharePage({policy}: WithPolicyProps) { const session = useSession(); const policyName = policy?.name ?? ''; - const policyID = policy?.id ?? '-1'; + const policyID = policy?.id; const adminEmail = session?.email ?? ''; - const urlWithTrailingSlash = Url.addTrailingForwardSlash(environmentURL); + const urlWithTrailingSlash = addTrailingForwardSlash(environmentURL); - const url = `${urlWithTrailingSlash}${ROUTES.WORKSPACE_JOIN_USER.getRoute(policyID, adminEmail)}`; + const url = policyID ? `${urlWithTrailingSlash}${ROUTES.WORKSPACE_JOIN_USER.getRoute(policyID, adminEmail)}` : ''; const hasAvatar = !!policy?.avatarURL; const logo = hasAvatar ? (policy?.avatarURL as ImageSourcePropType) : undefined; - const defaultWorkspaceAvatar = ReportUtils.getDefaultWorkspaceAvatar(policyName) || Expensicons.FallbackAvatar; - const defaultWorkspaceAvatarColors = StyleUtils.getDefaultWorkspaceAvatarColor(policyID); + const defaultWorkspaceAvatar = getDefaultWorkspaceAvatar(policyName) || Expensicons.FallbackAvatar; + const defaultWorkspaceAvatarColors = policyID ? StyleUtils.getDefaultWorkspaceAvatarColor(policyID) : StyleUtils.getDefaultWorkspaceAvatarColor(''); const svgLogo = !hasAvatar ? defaultWorkspaceAvatar : undefined; const logoBackgroundColor = !hasAvatar ? defaultWorkspaceAvatarColors.backgroundColor?.toString() : undefined; @@ -59,7 +59,7 @@ function WorkspaceOverviewSharePage({policy}: WithPolicyProps) { if (!policy?.id) { return undefined; } - return ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, policy?.id); + return getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ADMINS, policy?.id); }, [policy?.id]); return ( @@ -89,7 +89,7 @@ function WorkspaceOverviewSharePage({policy}: WithPolicyProps) { if (!adminRoom?.reportID) { return; } - Navigation.dismissModal(adminRoom.reportID); + Navigation.dismissModalWithReport({reportID: adminRoom.reportID}); }} > {CONST.REPORT.WORKSPACE_CHAT_ROOMS.ADMINS}