From 7f14d6c2fba2d7a4d6e2a5a31f2461d997ee7301 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Mon, 22 Dec 2025 22:31:20 +0500 Subject: [PATCH 1/6] Add delegate access restriction checks to reject and pay actions in search and money request flows --- src/components/MoneyReportHeader.tsx | 5 ++ src/components/MoneyRequestHeader.tsx | 5 ++ .../SearchPageHeader/SearchFiltersBar.tsx | 12 +++-- .../Search/ActionCell.tsx | 12 ++++- .../Search/ExpenseReportListItem.tsx | 20 ++++++- .../Search/ReportListItemHeader.tsx | 6 ++- .../Search/TransactionListItem.tsx | 22 +++++++- src/libs/actions/Search.ts | 54 +++++++++++++++---- src/pages/Search/SearchPage.tsx | 39 +++++++++++++- src/pages/Search/SearchSelectedNarrow.tsx | 12 +++-- 10 files changed, 160 insertions(+), 27 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 75687a483653..75b20771a05c 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -1390,6 +1390,11 @@ function MoneyReportHeader({ value: CONST.REPORT.SECONDARY_ACTIONS.REJECT, sentryLabel: CONST.SENTRY_LABEL.MORE_MENU.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 ed9c56bec30b..4c8f427ee909 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -437,6 +437,11 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre icon: Expensicons.ThumbsDown, value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.REJECT, onSelected: () => { + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + if (dismissedRejectUseExplanation) { if (parentReportAction) { rejectMoneyRequestReason(parentReportAction); diff --git a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx index b49b95665424..27a30624428b 100644 --- a/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx +++ b/src/components/Search/SearchPageHeader/SearchFiltersBar.tsx @@ -7,6 +7,7 @@ import {FlatList, View} 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'; @@ -110,6 +111,7 @@ function SearchFiltersBar({ const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); const [searchResultsErrors] = useOnyx(`${ONYXKEYS.COLLECTION.SNAPSHOT}${hash}`, {canBeMissing: true, selector: searchResultsErrorSelector}); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Filter', 'Columns']); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); // Get workspace data for the filter const {sections: workspaces, shouldShowSearchInput: shouldShowWorkspaceSearchInput} = useWorkspaceList({ @@ -844,17 +846,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/components/SelectionListWithSections/Search/ActionCell.tsx b/src/components/SelectionListWithSections/Search/ActionCell.tsx index 7989c8dd93f0..916309688d53 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 type {PaymentMethod} from '@components/KYCWall/types'; import {SearchScopeProvider} from '@components/Search/SearchScopeProvider'; import SettlementButton from '@components/SettlementButton'; @@ -73,6 +74,7 @@ function ActionCell({ const StyleUtils = useStyleUtils(); const {isOffline} = useNetwork(); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Checkmark', 'Checkbox']); + 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 3c1c32c9825d..3a2a52c69c68 100644 --- a/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx +++ b/src/components/SelectionListWithSections/Search/ExpenseReportListItem.tsx @@ -1,5 +1,6 @@ -import React, {useCallback, useMemo} from 'react'; +import React, {useCallback, useContext, useMemo} from 'react'; import {View} from 'react-native'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import Icon from '@components/Icon'; import {useSearchContext} from '@components/Search/SearchContext'; import BaseListItem from '@components/SelectionListWithSections/BaseListItem'; @@ -60,6 +61,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, @@ -70,8 +73,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 dc04da9f9ddc..fd4046bca57b 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'; @@ -223,6 +224,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; @@ -237,6 +239,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 e4af0f21bb84..b0b008111d87 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'; @@ -124,6 +125,8 @@ function TransactionListItem({ ); }, [snapshotPolicy, snapshotReport, transactionItem, violations, currentUserDetails.email, currentUserDetails.accountID]); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); + const handleActionButtonPress = useCallback(() => { handleActionButtonPressUtil( currentSearchHash, @@ -134,8 +137,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 6d80f72fdfe0..9cc765e83b2a 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -81,6 +81,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. @@ -95,9 +97,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; @@ -1049,17 +1059,39 @@ 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, - 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)) { + 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 e55de31bf129..23ee6876bae8 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -6,6 +6,7 @@ import type {ValueOf} from 'type-fest'; 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'; @@ -106,6 +107,7 @@ function SearchPage({route}: SearchPageProps) { const theme = useTheme(); const {isOffline} = useNetwork(); const personalDetails = usePersonalDetails(); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const {selectedTransactions, clearSelectedTransactions, selectedReports, lastSearchType, setLastSearchType, areAllMatchingItemsSelected, selectAllMatchingItems} = useSearchContext(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const isMobileSelectionModeEnabled = useMobileSelectionMode(); @@ -269,6 +271,11 @@ function SearchPage({route}: SearchPageProps) { return; } + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + const activeRoute = Navigation.getActiveRoute(); const selectedOptions = selectedReports.length ? selectedReports : Object.values(selectedTransactions); @@ -361,7 +368,19 @@ function SearchPage({route}: SearchPageProps) { clearSelectedTransactions(); }); }, - [clearSelectedTransactions, hash, isOffline, lastPaymentMethods, selectedReports, selectedTransactions, policies, formatPhoneNumber, policyIDsWithVBBA], + [ + clearSelectedTransactions, + hash, + isOffline, + lastPaymentMethods, + selectedReports, + selectedTransactions, + policies, + formatPhoneNumber, + policyIDsWithVBBA, + isDelegateAccessRestricted, + showDelegateNoAccessModal, + ], ); const [isSorting, setIsSorting] = useState(false); @@ -506,6 +525,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) @@ -554,6 +578,12 @@ function SearchPage({route}: SearchPageProps) { return; } + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + + if (dismissedRejectUseExplanation) { Navigation.navigate(ROUTES.SEARCH_REJECT_REASON_RHP); } else { @@ -627,6 +657,11 @@ function SearchPage({route}: SearchPageProps) { return; } + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + const isDismissed = areAllTransactionsFromSubmitter ? dismissedHoldUseExplanation : dismissedRejectUseExplanation; if (isDismissed) { @@ -822,6 +857,8 @@ function SearchPage({route}: SearchPageProps) { dismissedHoldUseExplanation, dismissedRejectUseExplanation, areAllTransactionsFromSubmitter, + isDelegateAccessRestricted, + showDelegateNoAccessModal, ]); const handleDeleteExpenses = () => { diff --git a/src/pages/Search/SearchSelectedNarrow.tsx b/src/pages/Search/SearchSelectedNarrow.tsx index 790d03397597..9ad0c8a16c7e 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) { @@ -75,17 +77,19 @@ function SearchSelectedNarrow({options, itemsLength, currentSelectedPolicyID, cu shouldPopoverUseScrollView={options.length >= CONST.DROPDOWN_SCROLL_THRESHOLD} 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 a9354f4bcc05b27befe425c0fbcd87f8263dc601 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Mon, 22 Dec 2025 22:34:24 +0500 Subject: [PATCH 2/6] Remove extra blank line in SearchPage reject action flow --- 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 23ee6876bae8..88c0e3ade014 100644 --- a/src/pages/Search/SearchPage.tsx +++ b/src/pages/Search/SearchPage.tsx @@ -583,7 +583,6 @@ function SearchPage({route}: SearchPageProps) { return; } - if (dismissedRejectUseExplanation) { Navigation.navigate(ROUTES.SEARCH_REJECT_REASON_RHP); } else { From e07428f4dedee3c669b10af4796ede325bb26252 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Tue, 30 Dec 2025 11:53:15 +0500 Subject: [PATCH 3/6] Fix delegate access check to prevent all bulk payment actions when restricted --- src/libs/actions/Search.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 979b164cc74f..8b15077b2ca8 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -1092,18 +1092,17 @@ function handleBulkPayItemSelected(params: { 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)) { - 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) { return; } + + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return; + } + if (isAccountLocked) { showLockedAccountModal(); return; From dafab25eb0eeb6e9f10d609f25d25e683d7cf0b4 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Tue, 30 Dec 2025 12:04:48 +0500 Subject: [PATCH 4/6] Add delegate access restriction check to SettlementButton --- src/components/SettlementButton/index.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index 15849f323643..ae52ab8e50bd 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -9,6 +9,7 @@ import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import KYCWall from '@components/KYCWall'; import {KYCWallContext} from '@components/KYCWall/KYCWallContext'; import type {ContinueActionParams, PaymentMethod} from '@components/KYCWall/types'; +import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; @@ -154,6 +155,7 @@ function SettlementButton({ const isInvoiceReport = (!isEmptyObject(iouReport) && isInvoiceReportUtil(iouReport)) || false; const {isAccountLocked, showLockedAccountModal} = useContext(LockedAccountContext); + const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext); const kycWallRef = useContext(KYCWallContext); const shouldShowPayWithExpensifyOption = !shouldHidePaymentOptions; const shouldShowPayElsewhereOption = !shouldHidePaymentOptions && !isInvoiceReport; @@ -185,6 +187,11 @@ function SettlementButton({ } const checkForNecessaryAction = useCallback(() => { + if (isDelegateAccessRestricted) { + showDelegateNoAccessModal(); + return true; + } + if (isAccountLocked) { showLockedAccountModal(); return true; @@ -201,7 +208,7 @@ function SettlementButton({ } return false; - }, [policy, isAccountLocked, isUserValidated, chatReportID, reportID, showLockedAccountModal]); + }, [policy, isAccountLocked, isUserValidated, chatReportID, reportID, showLockedAccountModal, isDelegateAccessRestricted, showDelegateNoAccessModal]); const getPaymentSubitems = useCallback( (payAsBusiness: boolean) => { From 03a886691e1516a0747a4982126ddb8a6b342707 Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Tue, 30 Dec 2025 12:08:47 +0500 Subject: [PATCH 5/6] prettier fix --- src/components/SettlementButton/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SettlementButton/index.tsx b/src/components/SettlementButton/index.tsx index ae52ab8e50bd..0adedfcf60dc 100644 --- a/src/components/SettlementButton/index.tsx +++ b/src/components/SettlementButton/index.tsx @@ -6,10 +6,10 @@ import type {GestureResponderEvent} from 'react-native'; import type {TupleToUnion} from 'type-fest'; 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 {ContinueActionParams, PaymentMethod} from '@components/KYCWall/types'; -import {DelegateNoAccessContext} from '@components/DelegateNoAccessModalProvider'; import {LockedAccountContext} from '@components/LockedAccountModalProvider'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; From 1a84dcb36b22a13a19c5f5ba30a35795180df01c Mon Sep 17 00:00:00 2001 From: Mukhriddin Shakhriyorov Date: Tue, 30 Dec 2025 13:09:22 +0500 Subject: [PATCH 6/6] prettier fix --- src/libs/actions/Search.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 8b15077b2ca8..1b9bfd2eb2fe 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -1097,7 +1097,7 @@ function handleBulkPayItemSelected(params: { if (!isValidBulkPayOption(item) && !selectedPolicy) { return; } - + if (isDelegateAccessRestricted) { showDelegateNoAccessModal(); return;