From 56855aad3a72691823976d55547f08bae72bac44 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Sat, 22 Nov 2025 23:04:12 +0500 Subject: [PATCH 1/6] Add delegate access restriction checks to search page actions --- src/components/MoneyReportHeader.tsx | 5 +++ src/components/MoneyRequestHeader.tsx | 5 +++ .../Search/ActionCell.tsx | 12 +++++-- .../Search/ExpenseReportListItem.tsx | 20 ++++++++++-- .../Search/ReportListItemHeader.tsx | 6 +++- .../Search/TransactionListItem.tsx | 22 +++++++++++-- src/libs/actions/Search.ts | 10 ++++++ src/pages/Search/SearchPage.tsx | 32 ++++++++++++++++++- 8 files changed, 104 insertions(+), 8 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 6a8d122e581e6..a8740eb7e621e 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1229,6 +1229,11 @@ function MoneyReportHeader({ icon: expensifyIcons.ThumbsDown, value: CONST.REPORT.SECONDARY_ACTIONS.REJECT, onSelected: () => { + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + if (dismissedRejectUseExplanation) { if (requestParentReportAction) { rejectMoneyRequestReason(requestParentReportAction); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 73542310819bc..9f685727346cd 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -385,6 +385,11 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre icon: expensifyIcons.ThumbsDown, value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT, onSelected: () => { + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + if (dismissedRejectUseExplanation) { if (parentReportAction) { rejectMoneyRequestReason(parentReportAction); diff --git a/src/components/SelectionListWithSections/Search/ActionCell.tsx b/src/components/SelectionListWithSections/Search/ActionCell.tsx index 223a419f2f523..b86017fd6b357 100644 --- a/src/components/SelectionListWithSections/Search/ActionCell.tsx +++ b/src/components/SelectionListWithSections/Search/ActionCell.tsx @@ -1,8 +1,9 @@ -import React, {useCallback} from 'react'; +import React, {useCallback, useContext} from 'react'; import {View} from 'react-native'; import type {ValueOf} from 'type-fest'; import Badge from '@components/Badge'; import Button from '@components/Button'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import * as Expensicons from '@components/Icon/Expensicons'; import type {PaymentMethod} from '@components/KYCWall/types'; import {SearchScopeProvider} from '@components/Search/SearchScopeProvider'; @@ -73,6 +74,7 @@ function ActionCell({ const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {isOffline} = useNetwork(); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const [iouReport, transactions] = useReportWithTransactionsAndViolations(reportID); const policy = usePolicy(policyID); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`, {canBeMissing: true}); @@ -88,10 +90,16 @@ function ActionCell({ if (!type || !reportID || !hash || !amount) { return; } + + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + const invoiceParams = getPayMoneyOnSearchInvoiceParams(policyID, payAsBusiness, methodID, paymentMethod); payMoneyRequestOnSearch(hash, [{amount, paymentType: type, reportID, ...(isInvoiceReport(iouReport) ? invoiceParams : {})}]); }, - [reportID, hash, amount, policyID, iouReport], + [reportID, hash, amount, policyID, iouReport, isDelegateAccessRestricted, showDelegateNoAccessModal], ); if (!isChildListItem && ((parentAction !== CONST.SEARCH.ACTION_TYPES.PAID && action === CONST.SEARCH.ACTION_TYPES.PAID) || action === CONST.SEARCH.ACTION_TYPES.DONE)) { diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx index 8e5886d9ccdc1..e1918b64278a1 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx @@ -1,4 +1,5 @@ -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback, useContext, useMemo} from 'react'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import {useSearchContext} from '@components/Search/SearchContext'; import BaseListItem from '@components/SelectionListWithSections/BaseListItem'; import type {ExpenseReportListItemProps, ExpenseReportListItemType, ListItem} from '@components/SelectionListWithSections/types'; @@ -54,6 +55,8 @@ function ExpenseReportListItem({ return isEmpty ?? reportItem.isDisabled ?? reportItem.isDisabledCheckbox; }, [reportItem.isDisabled, reportItem.isDisabledCheckbox, reportItem.transactions.length]); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); + const handleOnButtonPress = useCallback(() => { handleActionButtonPress( currentSearchHash, @@ -64,8 +67,21 @@ function ExpenseReportListItem({ lastPaymentMethod, currentSearchKey, onDEWModalOpen, + isDelegateAccessRestricted, + showDelegateNoAccessModal, ); - }, [currentSearchHash, reportItem, onSelectRow, snapshotReport, snapshotPolicy, lastPaymentMethod, currentSearchKey, onDEWModalOpen]); + }, [ + currentSearchHash, + reportItem, + onSelectRow, + snapshotReport, + snapshotPolicy, + lastPaymentMethod, + currentSearchKey, + onDEWModalOpen, + isDelegateAccessRestricted, + showDelegateNoAccessModal, + ]); const handleCheckboxPress = useCallback(() => { onCheckboxPress?.(reportItem as unknown as TItem); diff --git a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx index 2307830ec3a90..19459815756a9 100644 --- a/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx +++ b/src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx @@ -1,7 +1,8 @@ -import React, {useMemo} from 'react'; +import React, {useContext, useMemo} from 'react'; import {View} from 'react-native'; import type {ColorValue} from 'react-native'; import Checkbox from '@components/Checkbox'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import {PressableWithFeedback} from '@components/Pressable'; @@ -225,6 +226,7 @@ function ReportListItemHeader({ const snapshotPolicy = useMemo(() => { return (snapshot?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${reportItem.policyID}`] ?? {}) as Policy; }, [snapshot, reportItem.policyID]); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const avatarBorderColor = StyleUtils.getItemBackgroundColorStyle(!!reportItem.isSelected, !!isFocused || !!isHovered, !!isDisabled, theme.activeComponentBG, theme.hoverComponentBG)?.backgroundColor ?? theme.highlightBG; @@ -239,6 +241,8 @@ function ReportListItemHeader({ lastPaymentMethod, currentSearchKey, onDEWModalOpen, + isDelegateAccessRestricted, + showDelegateNoAccessModal, ); }; return !isLargeScreenWidth ? ( diff --git a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx index 159c83b73522f..8c2b248ab7b86 100644 --- a/src/components/SelectionListWithSections/Search/TransactionListItem.tsx +++ b/src/components/SelectionListWithSections/Search/TransactionListItem.tsx @@ -1,10 +1,11 @@ -import React, {useCallback, useMemo, useRef} from 'react'; +import React, {useCallback, useContext, useMemo, useRef} from 'react'; import type {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; // Use the original useOnyx hook to get the real-time data from Onyx and not from the snapshot // eslint-disable-next-line no-restricted-imports import {useOnyx as originalUseOnyx} from 'react-native-onyx'; import {getButtonRole} from '@components/Button/utils'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import {useSearchContext} from '@components/Search/SearchContext'; @@ -112,6 +113,8 @@ function TransactionListItem({ ); }, [snapshotPolicy, snapshotReport, transactionItem, violations, currentUserDetails.email]); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); + const handleActionButtonPress = useCallback(() => { handleActionButtonPressUtil( currentSearchHash, @@ -122,8 +125,23 @@ function TransactionListItem({ lastPaymentMethod, currentSearchKey, onDEWModalOpen, + isDelegateAccessRestricted, + showDelegateNoAccessModal, ); - }, [currentSearchHash, transactionItem, transactionPreviewData, snapshotReport, snapshotPolicy, lastPaymentMethod, currentSearchKey, onSelectRow, item, onDEWModalOpen]); + }, [ + currentSearchHash, + transactionItem, + transactionPreviewData, + snapshotReport, + snapshotPolicy, + lastPaymentMethod, + currentSearchKey, + onSelectRow, + item, + onDEWModalOpen, + isDelegateAccessRestricted, + showDelegateNoAccessModal, + ]); const handleCheckboxPress = useCallback(() => { onCheckboxPress?.(item); diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 99c66ed98754c..cb2147213ed4b 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -77,6 +77,8 @@ function handleActionButtonPress( lastPaymentMethod: OnyxEntry, currentSearchKey?: SearchKey, onDEWModalOpen?: () => void, + isDelegateAccessRestricted?: boolean, + onDelegateAccessRestricted?: () => void, ) { // The transactionIDList is needed to handle actions taken on `status:""` where transactions on single expense reports can be approved/paid. // We need the transactionID to display the loading indicator for that list item's action. @@ -91,9 +93,17 @@ function handleActionButtonPress( switch (item.action) { case CONST.SEARCH.ACTION_TYPES.PAY: + if (isDelegateAccessRestricted) { + onDelegateAccessRestricted?.(); + return; + } getPayActionCallback(hash, item, goToItem, snapshotReport, snapshotPolicy, lastPaymentMethod, currentSearchKey); return; case CONST.SEARCH.ACTION_TYPES.APPROVE: + if (isDelegateAccessRestricted) { + onDelegateAccessRestricted?.(); + return; + } if (hasDynamicExternalWorkflow(snapshotPolicy)) { onDEWModalOpen?.(); return; diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 4df55d8b9efe3..ab209dc3158a2 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -5,6 +5,7 @@ import FullPageNotFoundView from '@components/BlockingViews/FullPageNotFoundView import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import ConfirmModal from '@components/ConfirmModal'; import DecisionModal from '@components/DecisionModal'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import DragAndDropConsumer from '@components/DragAndDrop/Consumer'; import DragAndDropProvider from '@components/DragAndDrop/Provider'; import DropZoneUI from '@components/DropZone/DropZoneUI'; @@ -94,6 +95,7 @@ function SearchPage({route}: SearchPageProps) { const styles = useThemeStyles(); const theme = useTheme(); const {isOffline} = useNetwork(); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const {selectedTransactions, clearSelectedTransactions, selectedReports, lastSearchType, setLastSearchType, areAllMatchingItemsSelected, selectAllMatchingItems} = useSearchContext(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isMobileSelectionModeEnabled = useMobileSelectionMode(); @@ -214,6 +216,11 @@ function SearchPage({route}: SearchPageProps) { return; } + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + const activeRoute = Navigation.getActiveRoute(); const selectedOptions = selectedReports.length ? selectedReports : Object.values(selectedTransactions); @@ -305,7 +312,18 @@ function SearchPage({route}: SearchPageProps) { clearSelectedTransactions(); }); }, - [clearSelectedTransactions, hash, isOffline, lastPaymentMethods, selectedReports, selectedTransactions, policies, formatPhoneNumber], + [ + clearSelectedTransactions, + hash, + isOffline, + isDelegateAccessRestricted, + showDelegateNoAccessModal, + lastPaymentMethods, + selectedReports, + selectedTransactions, + policies, + formatPhoneNumber, + ], ); const headerButtonsOptions = useMemo(() => { @@ -419,6 +437,11 @@ function SearchPage({route}: SearchPageProps) { return; } + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + // Check if any of the selected items have DEW enabled const selectedPolicyIDList = selectedReports.length ? selectedReports.map((report) => report.policyID) @@ -509,6 +532,11 @@ function SearchPage({route}: SearchPageProps) { return; } + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + Navigation.navigate(ROUTES.TRANSACTION_HOLD_REASON_RHP); }, }); @@ -631,6 +659,8 @@ function SearchPage({route}: SearchPageProps) { selectedReportIDs, selectedTransactionReportIDs, expensifyIcons, + isDelegateAccessRestricted, + showDelegateNoAccessModal, ]); const handleDeleteExpenses = () => { From 8c8c7d72bf980a46e0399260cba864536cd3852e Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Tue, 2 Dec 2025 23:15:31 +0500 Subject: [PATCH 2/6] fix: Prevent delegate users from bulk paying with business bank account or elsewhere --- src/components/PopoverMenu.tsx | 2 ++ .../Search/SearchPageHeader/SearchFiltersBar.tsx | 4 ++++ src/libs/actions/Search.ts | 8 ++++++++ src/pages/Search/SearchPage.tsx | 5 +++++ src/pages/Search/SearchSelectedNarrow.tsx | 4 ++++ 5 files changed, 23 insertions(+) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 8d20f9261a546..9dfb1b2ea2dff 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -62,6 +62,8 @@ type PopoverMenuItem = MenuItemProps & { key?: string; + value?: string; + /** Whether to keep the modal open after clicking on the menu item */ shouldKeepModalOpen?: boolean; diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx index 50450b0c75889..46e37141ba95f 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx @@ -10,6 +10,7 @@ import type {ScrollView as RNScrollView} from 'react-native'; import Button from '@components/Button'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import KYCWall from '@components/KYCWall'; import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; import type {PaymentMethodType} from '@components/KYCWall/types'; @@ -115,6 +116,7 @@ function SearchFiltersBar({ const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); const [searchResultsErrors] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: true, selector: searchResultsErrorSelector}); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Filter'] as const); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const taxRates = getAllTaxRates(allPolicies); @@ -776,6 +778,8 @@ function SearchFiltersBar({ latestBankItems, activeAdminPolicies, isUserValidated, + isDelegateAccessRestricted, + showDelegateNoAccessModal, confirmPayment, ) } diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 9f052ccd68836..5fe565c4631db 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -1043,8 +1043,16 @@ function handleBulkPayItemSelected( latestBankItems: BankAccountMenuItem[] | undefined, activeAdminPolicies: Policy[], isUserValidated: boolean | undefined, + isDelegateAccessRestricted: boolean, + showDelegateNoAccessModal: () => void, confirmPayment?: (paymentType: PaymentMethodType | undefined, additionalData?: Record) => void, ) { + // If delegate access is restricted, we should not allow bulk pay with business bank account or bulk pay + if (isDelegateAccessRestricted && (item.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE || item.value === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT)) { + showDelegateNoAccessModal(); + return; + } + const {paymentType, selectedPolicy, shouldSelectPaymentMethod} = getActivePaymentType(item.key, activeAdminPolicies, latestBankItems); // Policy id is also a last payment method so we shouldn't early return here for that case. if (!isValidBulkPayOption(item) && !selectedPolicy) { diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 3f2a0507f71b5..8ed9f4ecb75c4 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -604,6 +604,11 @@ function SearchPage({route}: SearchPageProps) { return; } + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + if (!areItemsHydratedForReject) { return; } diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx index 900fe506fa2ec..805bf7b0243ae 100644 --- a/src/pages/Search/SearchSelectedNarrow.tsx +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -3,6 +3,7 @@ import React, {useContext, useRef} from 'react'; import {View} from 'react-native'; import ButtonWithDropdownMenu from '@components/ButtonWithDropdownMenu'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import KYCWall from '@components/KYCWall'; import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; import type {PaymentMethodType} from '@components/KYCWall/types'; @@ -45,6 +46,7 @@ function SearchSelectedNarrow({options, itemsLength, currentSelectedPolicyID, cu const selectedOptionRef = useRef | null>(null); const {accountID} = useCurrentUserPersonalDetails(); const activeAdminPolicies = getActiveAdminWorkspaces(allPolicies, accountID.toString()).sort((a, b) => localeCompare(a.name || '', b.name || '')); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const handleOnMenuItemPress = (option: DropdownOption) => { if (option?.shouldCloseModalOnSelect) { @@ -83,6 +85,8 @@ function SearchSelectedNarrow({options, itemsLength, currentSelectedPolicyID, cu latestBankItems, activeAdminPolicies, isUserValidated, + isDelegateAccessRestricted, + showDelegateNoAccessModal, confirmPayment, ) } From a23048cc6157f0a4942e76c55f7aa58529a4938b Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Tue, 2 Dec 2025 23:46:53 +0500 Subject: [PATCH 3/6] fix: Add type guard for value property in bulk pay item check --- src/components/PopoverMenu.tsx | 2 -- src/libs/actions/Search.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx index 9dfb1b2ea2dff..8d20f9261a546 100644 --- a/src/components/PopoverMenu.tsx +++ b/src/components/PopoverMenu.tsx @@ -62,8 +62,6 @@ type PopoverMenuItem = MenuItemProps & { key?: string; - value?: string; - /** Whether to keep the modal open after clicking on the menu item */ shouldKeepModalOpen?: boolean; diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 5fe565c4631db..bb5cfaa5343ea 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -1048,7 +1048,7 @@ function handleBulkPayItemSelected( confirmPayment?: (paymentType: PaymentMethodType | undefined, additionalData?: Record) => void, ) { // If delegate access is restricted, we should not allow bulk pay with business bank account or bulk pay - if (isDelegateAccessRestricted && (item.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE || item.value === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT)) { + if (isDelegateAccessRestricted && ('value' in item && (item.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE || item.value === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT))) { showDelegateNoAccessModal(); return; } From a005ee67c8d6e906cd1ab00ff1fba06ba656f768 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Tue, 2 Dec 2025 23:51:26 +0500 Subject: [PATCH 4/6] refactor: Convert handleBulkPayItemSelected parameters to object destructuring --- .../SearchPageHeader/SearchFiltersBar.tsx | 8 ++-- src/libs/actions/Search.ts | 42 ++++++++++++------- src/pages/Search/SearchSelectedNarrow.tsx | 8 ++-- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx index 46e37141ba95f..a62843bdaeb0e 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx @@ -769,19 +769,19 @@ function SearchFiltersBar({ customText={selectionButtonText} options={headerButtonsOptions} onSubItemSelected={(subItem) => - handleBulkPayItemSelected( - subItem, + handleBulkPayItemSelected({ + item: subItem, triggerKYCFlow, isAccountLocked, showLockedAccountModal, - currentPolicy, + policy: currentPolicy, latestBankItems, activeAdminPolicies, isUserValidated, isDelegateAccessRestricted, showDelegateNoAccessModal, confirmPayment, - ) + }) } isSplitButton={false} buttonRef={buttonRef} diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index bb5cfaa5343ea..b4ed6d5c96719 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -1034,21 +1034,35 @@ function isValidBulkPayOption(item: PopoverMenuItem) { /** * Handles the click event when user selects bulk pay action. */ -function handleBulkPayItemSelected( - item: PopoverMenuItem, - triggerKYCFlow: (params: ContinueActionParams) => void, - isAccountLocked: boolean, - showLockedAccountModal: () => void, - policy: OnyxEntry, - latestBankItems: BankAccountMenuItem[] | undefined, - activeAdminPolicies: Policy[], - isUserValidated: boolean | undefined, - isDelegateAccessRestricted: boolean, - showDelegateNoAccessModal: () => void, - confirmPayment?: (paymentType: PaymentMethodType | undefined, additionalData?: Record) => void, -) { +function handleBulkPayItemSelected(params: { + item: PopoverMenuItem; + triggerKYCFlow: (params: ContinueActionParams) => void; + isAccountLocked: boolean; + showLockedAccountModal: () => void; + policy: OnyxEntry; + latestBankItems: BankAccountMenuItem[] | undefined; + activeAdminPolicies: Policy[]; + isUserValidated: boolean | undefined; + isDelegateAccessRestricted: boolean; + showDelegateNoAccessModal: () => void; + confirmPayment?: (paymentType: PaymentMethodType | undefined, additionalData?: Record) => void; +}) { + const { + item, + triggerKYCFlow, + isAccountLocked, + showLockedAccountModal, + policy, + latestBankItems, + activeAdminPolicies, + isUserValidated, + isDelegateAccessRestricted, + showDelegateNoAccessModal, + confirmPayment, + } = params; + // If delegate access is restricted, we should not allow bulk pay with business bank account or bulk pay - if (isDelegateAccessRestricted && ('value' in item && (item.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE || item.value === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT))) { + if (isDelegateAccessRestricted && 'value' in item && (item.value === CONST.IOU.PAYMENT_TYPE.ELSEWHERE || item.value === CONST.PAYMENT_METHODS.BUSINESS_BANK_ACCOUNT)) { showDelegateNoAccessModal(); return; } diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx index 805bf7b0243ae..e7896830b95f4 100644 --- a/src/pages/Search/SearchSelectedNarrow.tsx +++ b/src/pages/Search/SearchSelectedNarrow.tsx @@ -76,19 +76,19 @@ function SearchSelectedNarrow({options, itemsLength, currentSelectedPolicyID, cu onPress={() => null} onOptionSelected={(item) => handleOnMenuItemPress(item)} onSubItemSelected={(subItem) => - handleBulkPayItemSelected( - subItem, + handleBulkPayItemSelected({ + item: subItem, triggerKYCFlow, isAccountLocked, showLockedAccountModal, - currentPolicy, + policy: currentPolicy, latestBankItems, activeAdminPolicies, isUserValidated, isDelegateAccessRestricted, showDelegateNoAccessModal, confirmPayment, - ) + }) } success isSplitButton={false} From d5c858178390de38415dd8c79f4527d95f1db0c8 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Thu, 4 Dec 2025 21:23:05 +0500 Subject: [PATCH 5/6] fix: Remove duplicate navigation call for transaction hold reason --- src/pages/Search/SearchPage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/Search/SearchPage.tsx b/src/pages/Search/SearchPage.tsx index 8ed9f4ecb75c4..4e55c49e04ac7 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -691,7 +691,6 @@ function SearchPage({route}: SearchPageProps) { return; } - Navigation.navigate(ROUTES.TRANSACTION_HOLD_REASON_RHP); const isDismissed = areAllTransactionsFromSubmitter ? dismissedHoldUseExplanation : dismissedRejectUseExplanation; if (isDismissed) { From b24193298ad4427c7e435e5b029b4250adc083c3 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Thu, 4 Dec 2025 21:33:27 +0500 Subject: [PATCH 6/6] fix: Reorder imports to follow alphabetical convention --- .../Search/ExpenseReportListItem.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx index 6b94e2eb6f2ed..f7acce9fe90a9 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx @@ -1,7 +1,7 @@ -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback, useContext, useMemo} from 'react'; import {View} from 'react-native'; -import Icon from '@components/Icon'; import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; +import Icon from '@components/Icon'; import {useSearchContext} from '@components/Search/SearchContext'; import BaseListItem from '@components/SelectionListWithSections/BaseListItem'; import type {ExpenseReportListItemProps, ExpenseReportListItemType, ListItem} from '@components/SelectionListWithSections/types';