Skip to content
5 changes: 5 additions & 0 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1417,6 +1417,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);
Expand Down
5 changes: 5 additions & 0 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -440,6 +440,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);
Expand Down
12 changes: 8 additions & 4 deletions src/components/Search/SearchPageHeader/SearchFiltersBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -112,6 +113,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({
Expand Down Expand Up @@ -850,17 +852,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}
Expand Down
12 changes: 10 additions & 2 deletions src/components/SelectionListWithSections/Search/ActionCell.tsx
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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});
Expand All @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -60,6 +61,8 @@ function ExpenseReportListItem<TItem extends ListItem>({
return isEmpty ?? reportItem.isDisabled ?? reportItem.isDisabledCheckbox;
}, [reportItem.isDisabled, reportItem.isDisabledCheckbox, reportItem.transactions.length]);

const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext);

const handleOnButtonPress = useCallback(() => {
handleActionButtonPress(
currentSearchHash,
Expand All @@ -70,8 +73,21 @@ function ExpenseReportListItem<TItem extends ListItem>({
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);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
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';

Check warning on line 7 in src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used by a pattern. Direct imports from Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details

Check warning on line 7 in src/components/SelectionListWithSections/Search/ReportListItemHeader.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'@components/Icon/Expensicons' import is restricted from being used. Direct imports from @components/Icon/Expensicons are deprecated. Please use lazy loading hooks instead. Use `useMemoizedLazyExpensifyIcons` from @hooks/useLazyAsset. See docs/LAZY_ICONS_AND_ILLUSTRATIONS.md for details
import {PressableWithFeedback} from '@components/Pressable';
import ReportSearchHeader from '@components/ReportSearchHeader';
import {useSearchContext} from '@components/Search/SearchContext';
Expand Down Expand Up @@ -223,6 +224,7 @@
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;
Expand All @@ -237,6 +239,8 @@
lastPaymentMethod,
currentSearchKey,
onDEWModalOpen,
isDelegateAccessRestricted,
showDelegateNoAccessModal,
);
};
return !isLargeScreenWidth ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -124,6 +125,8 @@ function TransactionListItem<TItem extends ListItem>({
);
}, [snapshotPolicy, snapshotReport, transactionItem, violations, currentUserDetails.email, currentUserDetails.accountID]);

const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext);

const handleActionButtonPress = useCallback(() => {
handleActionButtonPressUtil(
currentSearchHash,
Expand All @@ -134,8 +137,23 @@ function TransactionListItem<TItem extends ListItem>({
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);
Expand Down
9 changes: 8 additions & 1 deletion src/components/SettlementButton/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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';
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -185,6 +187,11 @@ function SettlementButton({
}

const checkForNecessaryAction = useCallback(() => {
if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
return true;
}

if (isAccountLocked) {
showLockedAccountModal();
return true;
Expand All @@ -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) => {
Expand Down
53 changes: 42 additions & 11 deletions src/libs/actions/Search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ function handleActionButtonPress(
lastPaymentMethod: OnyxEntry<LastPaymentMethod>,
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.
Expand All @@ -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;
Expand Down Expand Up @@ -1054,22 +1064,43 @@ 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<Policy>,
latestBankItems: BankAccountMenuItem[] | undefined,
activeAdminPolicies: Policy[],
isUserValidated: boolean | undefined,
confirmPayment?: (paymentType: PaymentMethodType | undefined, additionalData?: Record<string, unknown>) => void,
) {
function handleBulkPayItemSelected(params: {
item: PopoverMenuItem;
triggerKYCFlow: (params: ContinueActionParams) => void;
isAccountLocked: boolean;
showLockedAccountModal: () => void;
policy: OnyxEntry<Policy>;
latestBankItems: BankAccountMenuItem[] | undefined;
activeAdminPolicies: Policy[];
isUserValidated: boolean | undefined;
isDelegateAccessRestricted: boolean;
showDelegateNoAccessModal: () => void;
confirmPayment?: (paymentType: PaymentMethodType | undefined, additionalData?: Record<string, unknown>) => void;
}) {
const {
item,
triggerKYCFlow,
isAccountLocked,
showLockedAccountModal,
policy,
latestBankItems,
activeAdminPolicies,
isUserValidated,
isDelegateAccessRestricted,
showDelegateNoAccessModal,
confirmPayment,
} = params;
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;
Expand Down
Loading
Loading