diff --git a/src/CONST.ts b/src/CONST.ts
index 310bb3959300..9b4d3a820359 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -2026,7 +2026,6 @@ const CONST = {
INFO: 'info',
},
REPORT_DETAILS_MENU_ITEM: {
- SHARE_CODE: 'shareCode',
MEMBERS: 'member',
INVITE: 'invite',
SETTINGS: 'settings',
diff --git a/src/components/ChatDetailsQuickActionsBar.tsx b/src/components/ChatDetailsQuickActionsBar.tsx
index f15fc31aec45..d289587ce953 100644
--- a/src/components/ChatDetailsQuickActionsBar.tsx
+++ b/src/components/ChatDetailsQuickActionsBar.tsx
@@ -1,11 +1,12 @@
-import React, {useState} from 'react';
+import React from 'react';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
+import Navigation from '@libs/Navigation/Navigation';
import * as Report from '@userActions/Report';
+import ROUTES from '@src/ROUTES';
import type {Report as OnyxReportType} from '@src/types/onyx';
import Button from './Button';
-import ConfirmModal from './ConfirmModal';
import * as Expensicons from './Icon/Expensicons';
type ChatDetailsQuickActionsBarProps = {
@@ -14,45 +15,26 @@ type ChatDetailsQuickActionsBarProps = {
function ChatDetailsQuickActionsBar({report}: ChatDetailsQuickActionsBarProps) {
const styles = useThemeStyles();
- const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false);
const {translate} = useLocalize();
const isPinned = !!report.isPinned;
return (
- {
- setIsLastMemberLeavingGroupModalVisible(false);
- Report.leaveGroupChat(report.reportID);
- }}
- onCancel={() => setIsLastMemberLeavingGroupModalVisible(false)}
- prompt={translate('groupChat.lastMemberWarning')}
- confirmText={translate('common.leave')}
- cancelText={translate('common.cancel')}
- />
diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index d24b249df086..f886558c54f6 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -143,7 +143,7 @@ const isPolicyEmployee = (policyID: string, policies: OnyxCollection): b
/**
* Checks if the current user is an owner (creator) of the policy.
*/
-const isPolicyOwner = (policy: OnyxEntry, currentUserAccountID: number): boolean => policy?.ownerAccountID === currentUserAccountID;
+const isPolicyOwner = (policy: OnyxEntry | EmptyObject, currentUserAccountID: number): boolean => policy?.ownerAccountID === currentUserAccountID;
/**
* Create an object mapping member emails to their accountIDs. Filter for members without errors, and get the login email from the personalDetail object using the accountID.
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 9c5e437a874e..8dd04d168717 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -6344,7 +6344,7 @@ function hasActionsWithErrors(reportID: string): boolean {
return Object.values(reportActions).some((action) => !isEmptyObject(action.errors));
}
-function canLeavePolicyExpenseChat(report: OnyxEntry, policy: OnyxEntry): boolean {
+function canLeavePolicyExpenseChat(report: OnyxEntry, policy: OnyxEntry | EmptyObject): boolean {
return isPolicyExpenseChat(report) && !(PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPolicyOwner(policy, currentUserAccountID ?? -1) || isReportOwner(report));
}
diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx
index fa939be4e63d..4fb85123df8a 100644
--- a/src/pages/ReportDetailsPage.tsx
+++ b/src/pages/ReportDetailsPage.tsx
@@ -1,6 +1,6 @@
import {useRoute} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
-import React, {useEffect, useMemo} from 'react';
+import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
@@ -29,6 +29,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as Report from '@userActions/Report';
+import ConfirmModal from '@src/components/ConfirmModal';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -49,6 +50,7 @@ type ReportDetailsPageMenuItem = {
action: () => void;
brickRoadIndicator?: ValueOf;
subtitle?: number;
+ shouldShowRightIcon?: boolean;
};
type ReportDetailsPageOnyxProps = {
@@ -65,10 +67,12 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
const {isOffline} = useNetwork();
const styles = useThemeStyles();
const route = useRoute();
+ const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false);
const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`], [policies, report?.policyID]);
const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy ?? null), [policy]);
const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID ?? '', policies), [report?.policyID, policies]);
const shouldUseFullTitle = useMemo(() => ReportUtils.shouldUseFullTitleToDisplay(report), [report]);
+ const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isChatRoom = useMemo(() => ReportUtils.isChatRoom(report), [report]);
const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(report), [report]);
const isDefaultRoom = useMemo(() => ReportUtils.isDefaultRoom(report), [report]);
@@ -78,6 +82,8 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
const isInvoiceReport = useMemo(() => ReportUtils.isInvoiceReport(report), [report]);
const canEditReportDescription = useMemo(() => ReportUtils.canEditReportDescription(report, policy), [report, policy]);
const shouldShowReportDescription = isChatRoom && (canEditReportDescription || report.description !== '');
+ const canLeaveRoom = ReportUtils.canLeaveRoom(report, isPolicyEmployee);
+ const canLeavePolicyExpenseChat = ReportUtils.canLeavePolicyExpenseChat(report, policy ?? {});
// eslint-disable-next-line react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx
const chatRoomSubtitle = useMemo(() => ReportUtils.getChatRoomSubtitle(report), [report, policy]);
@@ -98,10 +104,11 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
return !pendingMember || pendingMember.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? accountID : [];
});
- const isGroupDMChat = useMemo(() => ReportUtils.isDM(report) && participants.length > 1, [report, participants.length]);
const isPrivateNotesFetchTriggered = report?.isLoadingPrivateNotes !== undefined;
const isSelfDM = useMemo(() => ReportUtils.isSelfDM(report), [report]);
+ const canLeave =
+ !isSelfDM && (isChatThread || isUserCreatedPolicyRoom || canLeaveRoom || canLeavePolicyExpenseChat) && report.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
useEffect(() => {
// Do not fetch private notes if isLoadingPrivateNotes is already defined, or if the network is offline, or if the report is a self DM.
@@ -112,6 +119,15 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
Report.getReportPrivateNote(report?.reportID ?? '');
}, [report?.reportID, isOffline, isPrivateNotesFetchTriggered, isSelfDM]);
+ const leaveChat = useCallback(() => {
+ if (isChatRoom) {
+ const isWorkspaceMemberLeavingWorkspaceRoom = (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || isPolicyExpenseChat) && isPolicyEmployee;
+ Report.leaveRoom(report.reportID, isWorkspaceMemberLeavingWorkspaceRoom);
+ return;
+ }
+ Report.leaveGroupChat(report.reportID);
+ }, [isChatRoom, isPolicyEmployee, isPolicyExpenseChat, report.reportID, report.visibility]);
+
const menuItems: ReportDetailsPageMenuItem[] = useMemo(() => {
const items: ReportDetailsPageMenuItem[] = [];
@@ -119,16 +135,6 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
return [];
}
- if (!isGroupDMChat) {
- items.push({
- key: CONST.REPORT_DETAILS_MENU_ITEM.SHARE_CODE,
- translationKey: 'common.shareCode',
- icon: Expensicons.QrCode,
- isAnonymousAction: true,
- action: () => Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE.getRoute(report?.reportID ?? '')),
- });
- }
-
if (isArchivedRoom) {
return items;
}
@@ -195,10 +201,27 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
});
}
+ if (isGroupChat || (isChatRoom && canLeave)) {
+ items.push({
+ key: CONST.REPORT_DETAILS_MENU_ITEM.LEAVE_ROOM,
+ translationKey: 'common.leave',
+ icon: Expensicons.Exit,
+ isAnonymousAction: true,
+ shouldShowRightIcon: false,
+ action: () => {
+ if (Object.keys(report?.participants ?? {}).length === 1 && isGroupChat) {
+ setIsLastMemberLeavingGroupModalVisible(true);
+ return;
+ }
+
+ leaveChat();
+ },
+ });
+ }
+
return items;
}, [
isSelfDM,
- isGroupDMChat,
isArchivedRoom,
isGroupChat,
isDefaultRoom,
@@ -208,9 +231,12 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
participants.length,
report,
isMoneyRequestReport,
- isInvoiceReport,
+ isChatRoom,
+ canLeave,
activeChatMembers.length,
session,
+ leaveChat,
+ isInvoiceReport,
]);
const displayNamesWithTooltips = useMemo(() => {
@@ -319,7 +345,10 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
{shouldShowReportDescription && (
-
+
)}
- {isGroupChat && }
+ {(isGroupChat || isChatRoom) && }
{menuItems.map((item) => {
const brickRoadIndicator =
ReportUtils.hasReportNameError(report) && item.key === CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined;
@@ -343,12 +372,25 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
icon={item.icon}
onPress={item.action}
isAnonymousAction={item.isAnonymousAction}
- shouldShowRightIcon
+ shouldShowRightIcon={item.shouldShowRightIcon ?? true}
brickRoadIndicator={brickRoadIndicator ?? item.brickRoadIndicator}
/>
);
})}
+ {
+ setIsLastMemberLeavingGroupModalVisible(false);
+ Report.leaveGroupChat(report.reportID);
+ }}
+ onCancel={() => setIsLastMemberLeavingGroupModalVisible(false)}
+ prompt={translate('groupChat.lastMemberWarning')}
+ confirmText={translate('common.leave')}
+ cancelText={translate('common.cancel')}
+ />
);