Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,11 @@ function MoneyReportHeader({
icon: expensifyIcons.ThumbsDown,
value: CONST.REPORT.SECONDARY_ACTIONS.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 @@ -59,7 +59,7 @@
import HoldOrRejectEducationalModal from './HoldOrRejectEducationalModal';
import HoldSubmitterEducationalModal from './HoldSubmitterEducationalModal';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';

Check warning on line 62 in src/components/MoneyRequestHeader.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

'./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
import LoadingBar from './LoadingBar';
import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar';
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
Expand Down Expand Up @@ -372,6 +372,11 @@
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 @@ -8,6 +8,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 @@ -113,6 +114,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);

Expand Down Expand Up @@ -811,17 +813,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 @@ -72,6 +73,7 @@ function ActionCell({
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const {isOffline} = useNetwork();
const {isDelegateAccessRestricted, showDelegateNoAccessModal} = useContext(DelegateNoAccessContext);
const expensifyIcons = useMemoizedLazyExpensifyIcons(['Checkmark', 'Checkbox'] as const);
const [iouReport, transactions] = useReportWithTransactionsAndViolations(reportID);
const policy = usePolicy(policyID);
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 @@ -59,6 +60,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 @@ -69,8 +72,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 @@ -113,6 +114,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 @@ -123,8 +126,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
54 changes: 43 additions & 11 deletions src/libs/actions/Search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,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 @@ -92,9 +94,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 @@ -1014,17 +1024,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<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;

// 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)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mukhrr Why do we need to check item.value?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is triggered by every option in dropdown

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will prevent if this is a pay action, don't need to care about what kind of pay is used

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) {
Expand Down
33 changes: 31 additions & 2 deletions src/pages/Search/SearchPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -96,6 +97,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();
Expand Down Expand Up @@ -227,6 +229,11 @@ function SearchPage({route}: SearchPageProps) {
return;
}

if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
return;
}

const activeRoute = Navigation.getActiveRoute();
const selectedOptions = selectedReports.length ? selectedReports : Object.values(selectedTransactions);

Expand Down Expand Up @@ -318,7 +325,18 @@ function SearchPage({route}: SearchPageProps) {
clearSelectedTransactions();
});
},
[clearSelectedTransactions, hash, isOffline, lastPaymentMethods, selectedReports, selectedTransactions, policies, formatPhoneNumber],
[
clearSelectedTransactions,
hash,
isOffline,
isDelegateAccessRestricted,
showDelegateNoAccessModal,
lastPaymentMethods,
selectedReports,
selectedTransactions,
policies,
formatPhoneNumber,
],
);

// Check if all selected transactions are from the submitter
Expand Down Expand Up @@ -454,6 +472,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)
Expand Down Expand Up @@ -544,6 +567,11 @@ function SearchPage({route}: SearchPageProps) {
return;
}

if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
return;
}

const isDismissed = areAllTransactionsFromSubmitter ? dismissedHoldUseExplanation : dismissedRejectUseExplanation;

if (isDismissed) {
Expand Down Expand Up @@ -679,10 +707,11 @@ function SearchPage({route}: SearchPageProps) {
styles.fontWeightNormal,
styles.textWrap,
expensifyIcons,
isDelegateAccessRestricted,
showDelegateNoAccessModal,
dismissedHoldUseExplanation,
dismissedRejectUseExplanation,
areAllTransactionsFromSubmitter,
currentUserPersonalDetails?.login,
]);

const handleDeleteExpenses = () => {
Expand Down
Loading
Loading