From 107f84cde8e3b3b8be70a51f79ecf8fb8201eab7 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 2 Feb 2024 15:32:38 +0100
Subject: [PATCH 01/32] implement isPolicyOwner

---
 src/libs/PolicyUtils.ts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts
index b6ee4ab3a35..80f23fd3c9a 100644
--- a/src/libs/PolicyUtils.ts
+++ b/src/libs/PolicyUtils.ts
@@ -111,6 +111,11 @@ const isPolicyAdmin = (policy: OnyxEntry<Policy>): boolean => policy?.role === C
 
 const isPolicyMember = (policyID: string, policies: Record<string, Policy>): boolean => Object.values(policies).some((policy) => policy?.id === policyID);
 
+/**
+ * Checks if the current user is an owner (creator) of the policy.
+ */
+const isPolicyOwner = (policy: OnyxEntry<Policy>, 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.
  *
@@ -244,6 +249,7 @@ export {
     getCleanedTagName,
     isPendingDeletePolicy,
     isPolicyMember,
+    isPolicyOwner,
     isPaidGroupPolicy,
     extractPolicyIDFromPath,
     getPathWithoutPolicyID,

From 41110392095b4762c020f66bd4baebeeb57ad171 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 2 Feb 2024 15:32:46 +0100
Subject: [PATCH 02/32] implement isReportOwner

---
 src/libs/ReportUtils.ts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 5f3efcbcdbb..33ef7982af3 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -4679,6 +4679,10 @@ function canBeAutoReimbursed(report: OnyxEntry<Report>, policy: OnyxEntry<Policy
     return isAutoReimbursable;
 }
 
+function isReportOwner(report: OnyxEntry<Report>): boolean {
+    return report?.ownerAccountID === currentUserPersonalDetails?.accountID;
+}
+
 export {
     getReportParticipantsTitle,
     isReportMessageAttachment,
@@ -4865,6 +4869,7 @@ export {
     isReportFieldOfTypeTitle,
     isReportFieldDisabled,
     getAvailableReportFields,
+    isReportOwner,
 };
 
 export type {

From 52b16ceb740c3f21c4b9c3629643db1ef4b8e52b Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 2 Feb 2024 15:45:47 +0100
Subject: [PATCH 03/32] implement draft types for leave API

---
 src/libs/API/parameters/LeavePolicyExpenseChatParams.ts | 6 ++++++
 src/libs/API/parameters/LeaveWorkspaceParams.ts         | 6 ++++++
 src/libs/API/parameters/index.ts                        | 2 ++
 src/libs/API/types.ts                                   | 5 +++++
 4 files changed, 19 insertions(+)
 create mode 100644 src/libs/API/parameters/LeavePolicyExpenseChatParams.ts
 create mode 100644 src/libs/API/parameters/LeaveWorkspaceParams.ts

diff --git a/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts b/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts
new file mode 100644
index 00000000000..665b28b9e0a
--- /dev/null
+++ b/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts
@@ -0,0 +1,6 @@
+type LeavePolicyExpenseChatParams = {
+    // TODO: Clarify
+    reportID: string;
+};
+
+export default LeavePolicyExpenseChatParams;
diff --git a/src/libs/API/parameters/LeaveWorkspaceParams.ts b/src/libs/API/parameters/LeaveWorkspaceParams.ts
new file mode 100644
index 00000000000..b28fa3e5aed
--- /dev/null
+++ b/src/libs/API/parameters/LeaveWorkspaceParams.ts
@@ -0,0 +1,6 @@
+type LeaveWorkspaceParams = {
+    // TODO: Clarify
+    policyID: string;
+};
+
+export default LeaveWorkspaceParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 8c0c2fde17c..9e877fffbd2 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -89,7 +89,9 @@ export type {default as AddWorkspaceRoomParams} from './AddWorkspaceRoomParams';
 export type {default as UpdatePolicyRoomNameParams} from './UpdatePolicyRoomNameParams';
 export type {default as AddEmojiReactionParams} from './AddEmojiReactionParams';
 export type {default as RemoveEmojiReactionParams} from './RemoveEmojiReactionParams';
+export type {default as LeavePolicyExpenseChatParams} from './LeavePolicyExpenseChatParams';
 export type {default as LeaveRoomParams} from './LeaveRoomParams';
+export type {default as LeaveWorkspaceParams} from './LeaveWorkspaceParams';
 export type {default as InviteToRoomParams} from './InviteToRoomParams';
 export type {default as RemoveFromRoomParams} from './RemoveFromRoomParams';
 export type {default as FlagCommentParams} from './FlagCommentParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 05b658ee070..37ed746ea5b 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -115,6 +115,9 @@ const WRITE_COMMANDS = {
     SET_NAME_VALUE_PAIR: 'SetNameValuePair',
     SET_REPORT_FIELD: 'Report_SetFields',
     SET_REPORT_NAME: 'RenameReport',
+    // TODO: Clarify
+    LEAVE_WORKSPACE: 'LeaveWorkspace',
+    LEAVE_POLICY_EXPENSE_CHAT: 'LeaveWorkspaceExpenseChat',
 } as const;
 
 type WriteCommand = ValueOf<typeof WRITE_COMMANDS>;
@@ -227,6 +230,8 @@ type WriteCommandParameters = {
     [WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams;
     [WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams;
     [WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams;
+    [WRITE_COMMANDS.LEAVE_WORKSPACE]: Parameters.LeaveWorkspaceParams;
+    [WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT]: Parameters.LeavePolicyExpenseChatParams;
 };
 
 const READ_COMMANDS = {

From 5788e867e4ebd40cde8099d19f72332206842a92 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 2 Feb 2024 15:46:04 +0100
Subject: [PATCH 04/32] implement draft for leave API

---
 src/libs/actions/Policy.ts | 47 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 0c3a8afc157..c68bd947faf 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
 import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST';
 import ExpensiMark from 'expensify-common/lib/ExpensiMark';
 import Str from 'expensify-common/lib/str';
@@ -14,6 +15,8 @@ import type {
     DeleteMembersFromWorkspaceParams,
     DeleteWorkspaceAvatarParams,
     DeleteWorkspaceParams,
+    LeavePolicyExpenseChatParams,
+    LeaveWorkspaceParams,
     OpenDraftWorkspaceRequestParams,
     OpenWorkspaceInvitePageParams,
     OpenWorkspaceMembersPageParams,
@@ -1984,6 +1987,48 @@ function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined {
     return policyID;
 }
 
+/**
+ * TODO: Comment
+ */
+function leaveWorkspace(policyID: string) {
+    const optimisticData: OnyxUpdate[] = [];
+    const successData: OnyxUpdate[] = [];
+    const failureData: OnyxUpdate[] = [];
+
+    const parameters: LeaveWorkspaceParams = {
+        policyID,
+    };
+
+    console.group('leaveWorkspace');
+    console.log('policyID', policyID);
+    console.log('parameters', parameters);
+    console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData});
+    console.groupEnd();
+    return;
+    API.write(WRITE_COMMANDS.LEAVE_WORKSPACE, parameters, {optimisticData, successData, failureData});
+}
+
+/**
+ * TODO: Comment
+ */
+function leavePolicyExpenseChat(reportID: string) {
+    const optimisticData: OnyxUpdate[] = [];
+    const successData: OnyxUpdate[] = [];
+    const failureData: OnyxUpdate[] = [];
+
+    const parameters: LeavePolicyExpenseChatParams = {
+        reportID,
+    };
+
+    console.group('leaveWorkspace');
+    console.log('reportID', reportID);
+    console.log('parameters', parameters);
+    console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData});
+    console.groupEnd();
+    return;
+    API.write(WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT, parameters, {optimisticData, successData, failureData});
+}
+
 export {
     removeMembers,
     addMembersToWorkspace,
@@ -2020,4 +2065,6 @@ export {
     buildOptimisticPolicyRecentlyUsedTags,
     createDraftInitialWorkspace,
     setWorkspaceInviteMessageDraft,
+    leaveWorkspace,
+    leavePolicyExpenseChat,
 };

From 2114adb61d998fb729101975b9a7d765355f9d57 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 2 Feb 2024 15:46:23 +0100
Subject: [PATCH 05/32] implement draft for leave a workspace chat

---
 src/pages/home/HeaderView.js | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index ca4c90b2df5..0ea2f92d3cc 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -29,10 +29,12 @@ import {getGroupChatName} from '@libs/GroupChatUtils';
 import * as HeaderUtils from '@libs/HeaderUtils';
 import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
 import * as OptionsListUtils from '@libs/OptionsListUtils';
+import * as PolicyUtils from '@libs/PolicyUtils';
 import * as ReportActionsUtils from '@libs/ReportActionsUtils';
 import * as ReportUtils from '@libs/ReportUtils';
 import reportPropTypes from '@pages/reportPropTypes';
 import * as Link from '@userActions/Link';
+import * as Policy from '@userActions/Policy';
 import * as Report from '@userActions/Report';
 import * as Session from '@userActions/Session';
 import * as Task from '@userActions/Task';
@@ -118,6 +120,8 @@ function HeaderView(props) {
     const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(props.report);
     const isPolicyMember = useMemo(() => !_.isEmpty(props.policy), [props.policy]);
     const canLeaveRoom = ReportUtils.canLeaveRoom(props.report, isPolicyMember);
+    const canLeavePolicyExpenseChat =
+        isPolicyExpenseChat && !(PolicyUtils.isPolicyAdmin(props.policy) || PolicyUtils.isPolicyOwner(props.policy, props.session.accountID) || ReportUtils.isReportOwner(props.report));
     const isArchivedRoom = ReportUtils.isArchivedRoom(props.report);
 
     // We hide the button when we are chatting with an automated Expensify account since it's not possible to contact
@@ -156,9 +160,9 @@ function HeaderView(props) {
         ),
     );
 
-    const canJoinOrLeave = isChatThread || isUserCreatedPolicyRoom || canLeaveRoom;
+    const canJoinOrLeave = isChatThread || isUserCreatedPolicyRoom || canLeaveRoom || canLeavePolicyExpenseChat;
     const canJoin = canJoinOrLeave && !isWhisperAction && props.report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN;
-    const canLeave = canJoinOrLeave && ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom);
+    const canLeave = canJoinOrLeave && ((isChatThread && props.report.notificationPreference.length) || isUserCreatedPolicyRoom || canLeaveRoom || canLeavePolicyExpenseChat);
     if (canJoin) {
         threeDotMenuItems.push({
             icon: Expensicons.ChatBubbles,
@@ -167,10 +171,11 @@ function HeaderView(props) {
         });
     } else if (canLeave) {
         const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && lodashGet(props.report, 'visibility', '') === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyMember;
+        const action = isPolicyExpenseChat ? () => Policy.leavePolicyExpenseChat(props.reportID) : () => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom);
         threeDotMenuItems.push({
             icon: Expensicons.ChatBubbles,
             text: translate('common.leave'),
-            onSelected: Session.checkIfActionIsAllowed(() => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom)),
+            onSelected: Session.checkIfActionIsAllowed(action),
         });
     }
 
@@ -360,7 +365,7 @@ export default memo(
         },
         policy: {
             key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY}${report ? report.policyID : '0'}`,
-            selector: (policy) => _.pick(policy, ['name', 'avatar', 'pendingAction']),
+            selector: (policy) => _.pick(policy, ['name', 'avatar', 'pendingAction', 'role', 'ownerAccountID']),
         },
         rootParentReportPolicy: {
             key: ({report}) => {

From eb6299247215dff7c64c877273acb0f3949552aa Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 2 Feb 2024 15:46:31 +0100
Subject: [PATCH 06/32] implement draft for leave a workspace

---
 src/pages/workspace/WorkspacesListPage.tsx | 23 +++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx
index 9b763120b30..c8e51842955 100755
--- a/src/pages/workspace/WorkspacesListPage.tsx
+++ b/src/pages/workspace/WorkspacesListPage.tsx
@@ -28,11 +28,12 @@ import * as ReportUtils from '@libs/ReportUtils';
 import type {AvatarSource} from '@libs/UserUtils';
 import * as App from '@userActions/App';
 import * as Policy from '@userActions/Policy';
+import * as Session from '@userActions/Session';
 import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import ROUTES from '@src/ROUTES';
 import SCREENS from '@src/SCREENS';
-import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount, Report} from '@src/types/onyx';
+import type {PolicyMembers, Policy as PolicyType, ReimbursementAccount, Report, Session as SessionType} from '@src/types/onyx';
 import type * as OnyxCommon from '@src/types/onyx/OnyxCommon';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 import withPolicyAndFullscreenLoading from './withPolicyAndFullscreenLoading';
@@ -74,6 +75,9 @@ type WorkspaceListPageOnyxProps = {
 
     /** All reports shared with the user (coming from Onyx) */
     reports: OnyxCollection<Report>;
+
+    /** Session info for the currently logged in user. */
+    session: OnyxEntry<SessionType>;
 };
 
 type WorkspaceListPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceListPageOnyxProps;
@@ -109,7 +113,7 @@ function dismissWorkspaceError(policyID: string, pendingAction: OnyxCommon.Pendi
     throw new Error('Not implemented');
 }
 
-function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports}: WorkspaceListPageProps) {
+function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, reports, session}: WorkspaceListPageProps) {
     const theme = useTheme();
     const styles = useThemeStyles();
     const {translate} = useLocalize();
@@ -135,6 +139,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r
     const getMenuItem = useCallback(
         ({item, index}: GetMenuItem) => {
             const isAdmin = item.role === CONST.POLICY.ROLE.ADMIN;
+            const isOwner = item.ownerAccountID === session?.accountID;
             // Menu options to navigate to the chat report of #admins and #announce room.
             // For navigation, the chat report ids may be unavailable due to the missing chat reports in Onyx.
             // In such cases, let us use the available chat report ids from the policy.
@@ -168,6 +173,15 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r
                 });
             }
 
+            if (!(isAdmin || isOwner)) {
+                threeDotsMenuItems.push({
+                    icon: Expensicons.ChatBubbles,
+                    text: translate('common.leave'),
+                    // TODO: Integrate a handler
+                    onSelected: Session.checkIfActionIsAllowed(() => Policy.leaveWorkspace(item.policyID ?? '')),
+                });
+            }
+
             return (
                 <OfflineWithFeedback
                     key={`${item.title}_${index}`}
@@ -199,7 +213,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r
                 </OfflineWithFeedback>
             );
         },
-        [isSmallScreenWidth, styles.mb3, styles.mh5, styles.ph5, styles.hoveredComponentBG, translate],
+        [session?.accountID, styles.ph5, styles.mh5, styles.mb3, styles.hoveredComponentBG, translate, isSmallScreenWidth],
     );
 
     const listHeaderComponent = useCallback(() => {
@@ -409,5 +423,8 @@ export default withPolicyAndFullscreenLoading(
         reports: {
             key: ONYXKEYS.COLLECTION.REPORT,
         },
+        session: {
+            key: ONYXKEYS.SESSION,
+        },
     })(WorkspacesListPage),
 );

From 3775ad084b4ed838a7b4ad32793c2d9568ecc9e1 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 2 Feb 2024 17:53:42 +0100
Subject: [PATCH 07/32] integrate draft api data

---
 .../AppNavigator/ReportScreenIDSetter.ts      | 26 ++-------
 src/libs/ReportUtils.ts                       | 48 +++++++++++++++-
 src/libs/actions/Policy.ts                    | 56 +++++++++++++++----
 src/libs/actions/Report.ts                    | 42 +-------------
 4 files changed, 95 insertions(+), 77 deletions(-)

diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
index b4bb5626286..927e4c57d66 100644
--- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
+++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
@@ -7,7 +7,7 @@ import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils';
 import * as ReportUtils from '@libs/ReportUtils';
 import * as App from '@userActions/App';
 import ONYXKEYS from '@src/ONYXKEYS';
-import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/onyx';
+import type {Policy, PolicyMembers, Report} from '@src/types/onyx';
 import type {ReportScreenWrapperProps} from './ReportScreenWrapper';
 
 type ReportScreenIDSetterComponentProps = {
@@ -23,9 +23,6 @@ type ReportScreenIDSetterComponentProps = {
     /** Whether user is a new user */
     isFirstTimeNewExpensifyUser: OnyxEntry<boolean>;
 
-    /** The report metadata */
-    reportMetadata: OnyxCollection<ReportMetadata>;
-
     /** The accountID of the current user */
     accountID?: number;
 };
@@ -41,25 +38,15 @@ const getLastAccessedReportID = (
     policies: OnyxCollection<Policy>,
     isFirstTimeNewExpensifyUser: OnyxEntry<boolean>,
     openOnAdminRoom: boolean,
-    reportMetadata: OnyxCollection<ReportMetadata>,
     policyID?: string,
     policyMemberAccountIDs?: number[],
 ): string | undefined => {
-    const lastReport = ReportUtils.findLastAccessedReport(
-        reports,
-        ignoreDefaultRooms,
-        policies,
-        !!isFirstTimeNewExpensifyUser,
-        openOnAdminRoom,
-        reportMetadata,
-        policyID,
-        policyMemberAccountIDs,
-    );
+    const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, !!isFirstTimeNewExpensifyUser, openOnAdminRoom, policyID, policyMemberAccountIDs);
     return lastReport?.reportID;
 };
 
 // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params
-function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) {
+function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, accountID}: ReportScreenIDSetterProps) {
     const {canUseDefaultRooms} = usePermissions();
     const {activeWorkspaceID} = useActiveWorkspace();
 
@@ -84,7 +71,6 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav
             policies,
             isFirstTimeNewExpensifyUser,
             !!reports?.params?.openOnAdminRoom,
-            reportMetadata,
             activeWorkspaceID,
             policyMemberAccountIDs,
         );
@@ -96,7 +82,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav
         } else {
             App.confirmReadyToOpenApp();
         }
-    }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reportMetadata, activeWorkspaceID, policyMembers, accountID]);
+    }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, activeWorkspaceID, policyMembers, accountID]);
 
     // The ReportScreen without the reportID set will display a skeleton
     // until the reportID is loaded and set in the route param
@@ -122,10 +108,6 @@ export default withOnyx<ReportScreenIDSetterProps, ReportScreenIDSetterComponent
         key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER,
         initialValue: false,
     },
-    reportMetadata: {
-        key: ONYXKEYS.COLLECTION.REPORT_METADATA,
-        allowStaleData: true,
-    },
     accountID: {
         key: ONYXKEYS.SESSION,
         selector: (session) => session?.accountID,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 33ef7982af3..3f48f550a07 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -491,6 +491,13 @@ Onyx.connect({
     },
 });
 
+let reportMetadata: OnyxCollection<ReportMetadata> = {};
+Onyx.connect({
+    key: ONYXKEYS.COLLECTION.REPORT_METADATA,
+    waitForCollectionCallback: true,
+    callback: (value) => (reportMetadata = value),
+});
+
 function getChatType(report: OnyxEntry<Report>): ValueOf<typeof CONST.REPORT.CHAT_TYPE> | undefined {
     return report?.chatType;
 }
@@ -911,7 +918,7 @@ function filterReportsByPolicyIDAndMemberAccountIDs(reports: Report[], policyMem
 /**
  * Given an array of reports, return them sorted by the last read timestamp.
  */
-function sortReportsByLastRead(reports: Report[], reportMetadata: OnyxCollection<ReportMetadata>): Array<OnyxEntry<Report>> {
+function sortReportsByLastRead(reports: Report[]): Array<OnyxEntry<Report>> {
     return reports
         .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime))
         .sort((a, b) => {
@@ -991,7 +998,6 @@ function findLastAccessedReport(
     policies: OnyxCollection<Policy>,
     isFirstTimeNewExpensifyUser: boolean,
     openOnAdminRoom = false,
-    reportMetadata: OnyxCollection<ReportMetadata> = {},
     policyID?: string,
     policyMemberAccountIDs: number[] = [],
 ): OnyxEntry<Report> {
@@ -1007,7 +1013,7 @@ function findLastAccessedReport(
         reportsValues = filterReportsByPolicyIDAndMemberAccountIDs(reportsValues, policyMemberAccountIDs, policyID);
     }
 
-    let sortedReports = sortReportsByLastRead(reportsValues, reportMetadata);
+    let sortedReports = sortReportsByLastRead(reportsValues);
 
     let adminReport: OnyxEntry<Report> | undefined;
     if (openOnAdminRoom) {
@@ -4683,6 +4689,41 @@ function isReportOwner(report: OnyxEntry<Report>): boolean {
     return report?.ownerAccountID === currentUserPersonalDetails?.accountID;
 }
 
+function navigateUserOnceLeaveReport(reportID: string) {
+    const sortedReportsByLastRead = sortReportsByLastRead(Object.values(allReports ?? {}) as Report[]);
+
+    // We want to filter out the current report, hidden reports and empty chats
+    const filteredReportsByLastRead = sortedReportsByLastRead.filter(
+        (sortedReport) =>
+            sortedReport?.reportID !== reportID &&
+            sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN &&
+            shouldReportBeInOptionList({
+                report: sortedReport,
+                currentReportId: '',
+                isInGSDMode: false,
+                betas: [],
+                policies: {},
+                excludeEmptyChats: true,
+                doesReportHaveViolations: false,
+            }),
+    );
+    const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID;
+
+    if (lastAccessedReportID) {
+        // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
+        Navigation.goBack(ROUTES.HOME);
+        Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID));
+    } else {
+        const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]);
+        const chat = getChatByParticipants(participantAccountIDs);
+        if (chat?.reportID) {
+            // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
+            Navigation.goBack(ROUTES.HOME);
+            Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID));
+        }
+    }
+}
+
 export {
     getReportParticipantsTitle,
     isReportMessageAttachment,
@@ -4870,6 +4911,7 @@ export {
     isReportFieldDisabled,
     getAvailableReportFields,
     isReportOwner,
+    navigateUserOnceLeaveReport,
 };
 
 export type {
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index c68bd947faf..929318ed985 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1991,30 +1991,60 @@ function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined {
  * TODO: Comment
  */
 function leaveWorkspace(policyID: string) {
-    const optimisticData: OnyxUpdate[] = [];
-    const successData: OnyxUpdate[] = [];
-    const failureData: OnyxUpdate[] = [];
-
+    const optimisticData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                avatar: '',
+                pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+                errors: null,
+            },
+        },
+    ];
     const parameters: LeaveWorkspaceParams = {
         policyID,
     };
 
     console.group('leaveWorkspace');
     console.log('policyID', policyID);
-    console.log('parameters', parameters);
-    console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData});
+    console.log('{parameters, optimisticData}', {parameters, optimisticData});
     console.groupEnd();
-    return;
-    API.write(WRITE_COMMANDS.LEAVE_WORKSPACE, parameters, {optimisticData, successData, failureData});
+
+    API.write(WRITE_COMMANDS.LEAVE_WORKSPACE, parameters, {optimisticData});
 }
 
 /**
  * TODO: Comment
  */
 function leavePolicyExpenseChat(reportID: string) {
-    const optimisticData: OnyxUpdate[] = [];
-    const successData: OnyxUpdate[] = [];
-    const failureData: OnyxUpdate[] = [];
+    const report = ReportUtils.getReport(reportID);
+
+    const optimisticData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+            value: {
+                notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
+            },
+        },
+    ];
+    const successData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+            value: {
+                notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
+            },
+        },
+    ];
+    const failureData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
+            value: report,
+        },
+    ];
 
     const parameters: LeavePolicyExpenseChatParams = {
         reportID,
@@ -2025,8 +2055,10 @@ function leavePolicyExpenseChat(reportID: string) {
     console.log('parameters', parameters);
     console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData});
     console.groupEnd();
-    return;
+
     API.write(WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT, parameters, {optimisticData, successData, failureData});
+
+    ReportUtils.navigateUserOnceLeaveReport(reportID);
 }
 
 export {
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 782cf2b174c..27cc7a2af4a 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -66,7 +66,7 @@ import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Route} from '@src/ROUTES';
 import ROUTES from '@src/ROUTES';
-import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportMetadata, ReportUserIsTyping} from '@src/types/onyx';
+import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx';
 import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage';
 import type {NotificationPreference, WriteCapability} from '@src/types/onyx/Report';
 import type Report from '@src/types/onyx/Report';
@@ -159,13 +159,6 @@ Onyx.connect({
     },
 });
 
-let reportMetadata: OnyxCollection<ReportMetadata> = {};
-Onyx.connect({
-    key: ONYXKEYS.COLLECTION.REPORT_METADATA,
-    waitForCollectionCallback: true,
-    callback: (value) => (reportMetadata = value),
-});
-
 const allReports: OnyxCollection<Report> = {};
 let conciergeChatReportID: string | undefined;
 const typingWatchTimers: Record<string, NodeJS.Timeout> = {};
@@ -2248,38 +2241,7 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
 
     API.write(WRITE_COMMANDS.LEAVE_ROOM, parameters, {optimisticData, successData, failureData});
 
-    const sortedReportsByLastRead = ReportUtils.sortReportsByLastRead(Object.values(allReports ?? {}) as Report[], reportMetadata);
-
-    // We want to filter out the current report, hidden reports and empty chats
-    const filteredReportsByLastRead = sortedReportsByLastRead.filter(
-        (sortedReport) =>
-            sortedReport?.reportID !== reportID &&
-            sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN &&
-            ReportUtils.shouldReportBeInOptionList({
-                report: sortedReport,
-                currentReportId: '',
-                isInGSDMode: false,
-                betas: [],
-                policies: {},
-                excludeEmptyChats: true,
-                doesReportHaveViolations: false,
-            }),
-    );
-    const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID;
-
-    if (lastAccessedReportID) {
-        // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
-        Navigation.goBack(ROUTES.HOME);
-        Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID));
-    } else {
-        const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]);
-        const chat = ReportUtils.getChatByParticipants(participantAccountIDs);
-        if (chat?.reportID) {
-            // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
-            Navigation.goBack(ROUTES.HOME);
-            Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID));
-        }
-    }
+    ReportUtils.navigateUserOnceLeaveReport(reportID);
 }
 
 /** Invites people to a room */

From 95b8741eb659e4e9b83776fdc40545a523585953 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Mon, 5 Feb 2024 17:48:58 +0100
Subject: [PATCH 08/32] use real api for leaving workspaces

---
 .../LeavePolicyExpenseChatParams.ts           |   6 -
 .../API/parameters/LeaveWorkspaceParams.ts    |   6 -
 src/libs/API/parameters/index.ts              |   2 -
 src/libs/API/types.ts                         |   5 -
 src/libs/actions/Policy.ts                    | 126 ++++++------------
 src/pages/home/HeaderView.js                  |   4 +-
 src/pages/workspace/WorkspacesListPage.tsx    |   3 +-
 7 files changed, 46 insertions(+), 106 deletions(-)
 delete mode 100644 src/libs/API/parameters/LeavePolicyExpenseChatParams.ts
 delete mode 100644 src/libs/API/parameters/LeaveWorkspaceParams.ts

diff --git a/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts b/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts
deleted file mode 100644
index 665b28b9e0a..00000000000
--- a/src/libs/API/parameters/LeavePolicyExpenseChatParams.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-type LeavePolicyExpenseChatParams = {
-    // TODO: Clarify
-    reportID: string;
-};
-
-export default LeavePolicyExpenseChatParams;
diff --git a/src/libs/API/parameters/LeaveWorkspaceParams.ts b/src/libs/API/parameters/LeaveWorkspaceParams.ts
deleted file mode 100644
index b28fa3e5aed..00000000000
--- a/src/libs/API/parameters/LeaveWorkspaceParams.ts
+++ /dev/null
@@ -1,6 +0,0 @@
-type LeaveWorkspaceParams = {
-    // TODO: Clarify
-    policyID: string;
-};
-
-export default LeaveWorkspaceParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index a41227a6050..b7c3dff7c34 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -90,9 +90,7 @@ export type {default as AddWorkspaceRoomParams} from './AddWorkspaceRoomParams';
 export type {default as UpdatePolicyRoomNameParams} from './UpdatePolicyRoomNameParams';
 export type {default as AddEmojiReactionParams} from './AddEmojiReactionParams';
 export type {default as RemoveEmojiReactionParams} from './RemoveEmojiReactionParams';
-export type {default as LeavePolicyExpenseChatParams} from './LeavePolicyExpenseChatParams';
 export type {default as LeaveRoomParams} from './LeaveRoomParams';
-export type {default as LeaveWorkspaceParams} from './LeaveWorkspaceParams';
 export type {default as InviteToRoomParams} from './InviteToRoomParams';
 export type {default as RemoveFromRoomParams} from './RemoveFromRoomParams';
 export type {default as FlagCommentParams} from './FlagCommentParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index aa1f601ed57..c011fa395f0 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -116,9 +116,6 @@ const WRITE_COMMANDS = {
     SET_NAME_VALUE_PAIR: 'SetNameValuePair',
     SET_REPORT_FIELD: 'Report_SetFields',
     SET_REPORT_NAME: 'RenameReport',
-    // TODO: Clarify
-    LEAVE_WORKSPACE: 'LeaveWorkspace',
-    LEAVE_POLICY_EXPENSE_CHAT: 'LeaveWorkspaceExpenseChat',
 } as const;
 
 type WriteCommand = ValueOf<typeof WRITE_COMMANDS>;
@@ -232,8 +229,6 @@ type WriteCommandParameters = {
     [WRITE_COMMANDS.SET_NAME_VALUE_PAIR]: Parameters.SetNameValuePairParams;
     [WRITE_COMMANDS.SET_REPORT_FIELD]: Parameters.SetReportFieldParams;
     [WRITE_COMMANDS.SET_REPORT_NAME]: Parameters.SetReportNameParams;
-    [WRITE_COMMANDS.LEAVE_WORKSPACE]: Parameters.LeaveWorkspaceParams;
-    [WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT]: Parameters.LeavePolicyExpenseChatParams;
 };
 
 const READ_COMMANDS = {
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 929318ed985..d718fcd1b88 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -15,8 +15,6 @@ import type {
     DeleteMembersFromWorkspaceParams,
     DeleteWorkspaceAvatarParams,
     DeleteWorkspaceParams,
-    LeavePolicyExpenseChatParams,
-    LeaveWorkspaceParams,
     OpenDraftWorkspaceRequestParams,
     OpenWorkspaceInvitePageParams,
     OpenWorkspaceMembersPageParams,
@@ -41,6 +39,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import type {PersonalDetailsList, Policy, PolicyMember, PolicyTags, RecentlyUsedCategories, RecentlyUsedTags, ReimbursementAccount, Report, ReportAction, Transaction} from '@src/types/onyx';
 import type {Errors} from '@src/types/onyx/OnyxCommon';
 import type {CustomUnit} from '@src/types/onyx/Policy';
+import type {EmptyObject} from '@src/types/utils/EmptyObject';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 
 type AnnounceRoomMembersOnyxData = {
@@ -354,8 +353,8 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]
 /**
  * Build optimistic data for removing users from the announcement room
  */
-function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: number[]): AnnounceRoomMembersOnyxData {
-    const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID);
+function removeOptimisticAnnounceRoomMembers(policy: Policy | EmptyObject, accountIDs: number[]): AnnounceRoomMembersOnyxData {
+    const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policy.id);
     const announceRoomMembers: AnnounceRoomMembersOnyxData = {
         onyxOptimisticData: [],
         onyxFailureData: [],
@@ -367,21 +366,37 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: numbe
 
     if (announceReport?.participantAccountIDs) {
         const remainUsers = announceReport.participantAccountIDs.filter((e) => !accountIDs.includes(e));
+
         announceRoomMembers.onyxOptimisticData.push({
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`,
             value: {
                 participantAccountIDs: [...remainUsers],
                 visibleChatMemberAccountIDs: [...remainUsers],
+                ...(accountIDs.includes(sessionAccountID)
+                    ? {
+                          statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+                          stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+                          oldPolicyName: policy.name,
+                          hasDraft: false,
+                      }
+                    : {}),
             },
         });
-
         announceRoomMembers.onyxFailureData.push({
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`,
             value: {
                 participantAccountIDs: announceReport.participantAccountIDs,
                 visibleChatMemberAccountIDs: announceReport.visibleChatMemberAccountIDs,
+                ...(accountIDs.includes(sessionAccountID)
+                    ? {
+                          statusNum: announceReport.statusNum,
+                          stateNum: announceReport.stateNum,
+                          oldPolicyName: announceReport.oldPolicyName,
+                          hasDraft: announceReport.hasDraft,
+                      }
+                    : {}),
             },
         });
     }
@@ -404,7 +419,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
     const workspaceChats = ReportUtils.getWorkspaceChats(policyID, accountIDs);
     const optimisticClosedReportActions = workspaceChats.map(() => ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy.name, CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY));
 
-    const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policyID, accountIDs);
+    const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policy, accountIDs);
 
     const optimisticMembersState: OnyxCollection<PolicyMember> = {};
     const successMembersState: OnyxCollection<PolicyMember> = {};
@@ -507,11 +522,34 @@ function removeMembers(accountIDs: number[], policyID: string) {
         });
     });
 
+    if (accountIDs.includes(sessionAccountID)) {
+        optimisticData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+            },
+        });
+        failureData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
+            value: {
+                pendingAction: policy.pendingAction,
+            },
+        });
+    }
+
     const params: DeleteMembersFromWorkspaceParams = {
         emailList: accountIDs.map((accountID) => allPersonalDetails?.[accountID]?.login).join(','),
         policyID,
     };
 
+    console.log('WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData}', WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {
+        optimisticData,
+        successData,
+        failureData,
+    });
+
     API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
 }
 
@@ -1987,80 +2025,6 @@ function createWorkspaceFromIOUPayment(iouReport: Report): string | undefined {
     return policyID;
 }
 
-/**
- * TODO: Comment
- */
-function leaveWorkspace(policyID: string) {
-    const optimisticData: OnyxUpdate[] = [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
-            value: {
-                avatar: '',
-                pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
-                errors: null,
-            },
-        },
-    ];
-    const parameters: LeaveWorkspaceParams = {
-        policyID,
-    };
-
-    console.group('leaveWorkspace');
-    console.log('policyID', policyID);
-    console.log('{parameters, optimisticData}', {parameters, optimisticData});
-    console.groupEnd();
-
-    API.write(WRITE_COMMANDS.LEAVE_WORKSPACE, parameters, {optimisticData});
-}
-
-/**
- * TODO: Comment
- */
-function leavePolicyExpenseChat(reportID: string) {
-    const report = ReportUtils.getReport(reportID);
-
-    const optimisticData: OnyxUpdate[] = [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
-            value: {
-                notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
-            },
-        },
-    ];
-    const successData: OnyxUpdate[] = [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
-            value: {
-                notificationPreference: CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN,
-            },
-        },
-    ];
-    const failureData: OnyxUpdate[] = [
-        {
-            onyxMethod: Onyx.METHOD.MERGE,
-            key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`,
-            value: report,
-        },
-    ];
-
-    const parameters: LeavePolicyExpenseChatParams = {
-        reportID,
-    };
-
-    console.group('leaveWorkspace');
-    console.log('reportID', reportID);
-    console.log('parameters', parameters);
-    console.log('{optimisticData, successData, failureData}', {optimisticData, successData, failureData});
-    console.groupEnd();
-
-    API.write(WRITE_COMMANDS.LEAVE_POLICY_EXPENSE_CHAT, parameters, {optimisticData, successData, failureData});
-
-    ReportUtils.navigateUserOnceLeaveReport(reportID);
-}
-
 export {
     removeMembers,
     addMembersToWorkspace,
@@ -2097,6 +2061,4 @@ export {
     buildOptimisticPolicyRecentlyUsedTags,
     createDraftInitialWorkspace,
     setWorkspaceInviteMessageDraft,
-    leaveWorkspace,
-    leavePolicyExpenseChat,
 };
diff --git a/src/pages/home/HeaderView.js b/src/pages/home/HeaderView.js
index e957c70ed5c..9cd78b47271 100644
--- a/src/pages/home/HeaderView.js
+++ b/src/pages/home/HeaderView.js
@@ -34,7 +34,6 @@ import * as ReportActionsUtils from '@libs/ReportActionsUtils';
 import * as ReportUtils from '@libs/ReportUtils';
 import reportPropTypes from '@pages/reportPropTypes';
 import * as Link from '@userActions/Link';
-import * as Policy from '@userActions/Policy';
 import * as Report from '@userActions/Report';
 import * as Session from '@userActions/Session';
 import * as Task from '@userActions/Task';
@@ -164,11 +163,10 @@ function HeaderView(props) {
         });
     } else if (canLeave) {
         const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && lodashGet(props.report, 'visibility', '') === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyMember;
-        const action = isPolicyExpenseChat ? () => Policy.leavePolicyExpenseChat(props.reportID) : () => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom);
         threeDotMenuItems.push({
             icon: Expensicons.ChatBubbles,
             text: translate('common.leave'),
-            onSelected: Session.checkIfActionIsAllowed(action),
+            onSelected: Session.checkIfActionIsAllowed(() => Report.leaveRoom(props.reportID, isWorkspaceMemberLeavingWorkspaceRoom)),
         });
     }
 
diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx
index c8e51842955..bda81eee001 100755
--- a/src/pages/workspace/WorkspacesListPage.tsx
+++ b/src/pages/workspace/WorkspacesListPage.tsx
@@ -177,8 +177,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r
                 threeDotsMenuItems.push({
                     icon: Expensicons.ChatBubbles,
                     text: translate('common.leave'),
-                    // TODO: Integrate a handler
-                    onSelected: Session.checkIfActionIsAllowed(() => Policy.leaveWorkspace(item.policyID ?? '')),
+                    onSelected: Session.checkIfActionIsAllowed(() => Policy.removeMembers([session?.accountID ?? 0], item.policyID ?? '')),
                 });
             }
 

From af8c733a9e824b07f3accc4131994fc4a0f579fe Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Mon, 5 Feb 2024 17:51:55 +0100
Subject: [PATCH 09/32] Revert "integrate draft api data"

This reverts commit 3775ad084b4ed838a7b4ad32793c2d9568ecc9e1.
---
 .../AppNavigator/ReportScreenIDSetter.ts      | 26 ++++++++--
 src/libs/ReportUtils.ts                       | 48 ++-----------------
 src/libs/actions/Report.ts                    | 42 +++++++++++++++-
 3 files changed, 65 insertions(+), 51 deletions(-)

diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
index 927e4c57d66..b4bb5626286 100644
--- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
+++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts
@@ -7,7 +7,7 @@ import {getPolicyMembersByIdWithoutCurrentUser} from '@libs/PolicyUtils';
 import * as ReportUtils from '@libs/ReportUtils';
 import * as App from '@userActions/App';
 import ONYXKEYS from '@src/ONYXKEYS';
-import type {Policy, PolicyMembers, Report} from '@src/types/onyx';
+import type {Policy, PolicyMembers, Report, ReportMetadata} from '@src/types/onyx';
 import type {ReportScreenWrapperProps} from './ReportScreenWrapper';
 
 type ReportScreenIDSetterComponentProps = {
@@ -23,6 +23,9 @@ type ReportScreenIDSetterComponentProps = {
     /** Whether user is a new user */
     isFirstTimeNewExpensifyUser: OnyxEntry<boolean>;
 
+    /** The report metadata */
+    reportMetadata: OnyxCollection<ReportMetadata>;
+
     /** The accountID of the current user */
     accountID?: number;
 };
@@ -38,15 +41,25 @@ const getLastAccessedReportID = (
     policies: OnyxCollection<Policy>,
     isFirstTimeNewExpensifyUser: OnyxEntry<boolean>,
     openOnAdminRoom: boolean,
+    reportMetadata: OnyxCollection<ReportMetadata>,
     policyID?: string,
     policyMemberAccountIDs?: number[],
 ): string | undefined => {
-    const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, !!isFirstTimeNewExpensifyUser, openOnAdminRoom, policyID, policyMemberAccountIDs);
+    const lastReport = ReportUtils.findLastAccessedReport(
+        reports,
+        ignoreDefaultRooms,
+        policies,
+        !!isFirstTimeNewExpensifyUser,
+        openOnAdminRoom,
+        reportMetadata,
+        policyID,
+        policyMemberAccountIDs,
+    );
     return lastReport?.reportID;
 };
 
 // This wrapper is reponsible for opening the last accessed report if there is no reportID specified in the route params
-function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, accountID}: ReportScreenIDSetterProps) {
+function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, navigation, isFirstTimeNewExpensifyUser = false, reportMetadata, accountID}: ReportScreenIDSetterProps) {
     const {canUseDefaultRooms} = usePermissions();
     const {activeWorkspaceID} = useActiveWorkspace();
 
@@ -71,6 +84,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav
             policies,
             isFirstTimeNewExpensifyUser,
             !!reports?.params?.openOnAdminRoom,
+            reportMetadata,
             activeWorkspaceID,
             policyMemberAccountIDs,
         );
@@ -82,7 +96,7 @@ function ReportScreenIDSetter({route, reports, policies, policyMembers = {}, nav
         } else {
             App.confirmReadyToOpenApp();
         }
-    }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, activeWorkspaceID, policyMembers, accountID]);
+    }, [route, navigation, reports, canUseDefaultRooms, policies, isFirstTimeNewExpensifyUser, reportMetadata, activeWorkspaceID, policyMembers, accountID]);
 
     // The ReportScreen without the reportID set will display a skeleton
     // until the reportID is loaded and set in the route param
@@ -108,6 +122,10 @@ export default withOnyx<ReportScreenIDSetterProps, ReportScreenIDSetterComponent
         key: ONYXKEYS.NVP_IS_FIRST_TIME_NEW_EXPENSIFY_USER,
         initialValue: false,
     },
+    reportMetadata: {
+        key: ONYXKEYS.COLLECTION.REPORT_METADATA,
+        allowStaleData: true,
+    },
     accountID: {
         key: ONYXKEYS.SESSION,
         selector: (session) => session?.accountID,
diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 045ed1d6a1e..7594d9db0fd 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -503,13 +503,6 @@ Onyx.connect({
     },
 });
 
-let reportMetadata: OnyxCollection<ReportMetadata> = {};
-Onyx.connect({
-    key: ONYXKEYS.COLLECTION.REPORT_METADATA,
-    waitForCollectionCallback: true,
-    callback: (value) => (reportMetadata = value),
-});
-
 function getChatType(report: OnyxEntry<Report>): ValueOf<typeof CONST.REPORT.CHAT_TYPE> | undefined {
     return report?.chatType;
 }
@@ -930,7 +923,7 @@ function filterReportsByPolicyIDAndMemberAccountIDs(reports: Report[], policyMem
 /**
  * Given an array of reports, return them sorted by the last read timestamp.
  */
-function sortReportsByLastRead(reports: Report[]): Array<OnyxEntry<Report>> {
+function sortReportsByLastRead(reports: Report[], reportMetadata: OnyxCollection<ReportMetadata>): Array<OnyxEntry<Report>> {
     return reports
         .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime))
         .sort((a, b) => {
@@ -1010,6 +1003,7 @@ function findLastAccessedReport(
     policies: OnyxCollection<Policy>,
     isFirstTimeNewExpensifyUser: boolean,
     openOnAdminRoom = false,
+    reportMetadata: OnyxCollection<ReportMetadata> = {},
     policyID?: string,
     policyMemberAccountIDs: number[] = [],
 ): OnyxEntry<Report> {
@@ -1025,7 +1019,7 @@ function findLastAccessedReport(
         reportsValues = filterReportsByPolicyIDAndMemberAccountIDs(reportsValues, policyMemberAccountIDs, policyID);
     }
 
-    let sortedReports = sortReportsByLastRead(reportsValues);
+    let sortedReports = sortReportsByLastRead(reportsValues, reportMetadata);
 
     let adminReport: OnyxEntry<Report> | undefined;
     if (openOnAdminRoom) {
@@ -4771,41 +4765,6 @@ function isReportOwner(report: OnyxEntry<Report>): boolean {
     return report?.ownerAccountID === currentUserPersonalDetails?.accountID;
 }
 
-function navigateUserOnceLeaveReport(reportID: string) {
-    const sortedReportsByLastRead = sortReportsByLastRead(Object.values(allReports ?? {}) as Report[]);
-
-    // We want to filter out the current report, hidden reports and empty chats
-    const filteredReportsByLastRead = sortedReportsByLastRead.filter(
-        (sortedReport) =>
-            sortedReport?.reportID !== reportID &&
-            sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN &&
-            shouldReportBeInOptionList({
-                report: sortedReport,
-                currentReportId: '',
-                isInGSDMode: false,
-                betas: [],
-                policies: {},
-                excludeEmptyChats: true,
-                doesReportHaveViolations: false,
-            }),
-    );
-    const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID;
-
-    if (lastAccessedReportID) {
-        // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
-        Navigation.goBack(ROUTES.HOME);
-        Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID));
-    } else {
-        const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]);
-        const chat = getChatByParticipants(participantAccountIDs);
-        if (chat?.reportID) {
-            // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
-            Navigation.goBack(ROUTES.HOME);
-            Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID));
-        }
-    }
-}
-
 export {
     getReportParticipantsTitle,
     isReportMessageAttachment,
@@ -4994,7 +4953,6 @@ export {
     isReportFieldDisabled,
     getAvailableReportFields,
     isReportOwner,
-    navigateUserOnceLeaveReport,
     getAllAncestorReportActionIDs,
 };
 
diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index c07b330b5a6..4bff826ceb3 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -66,7 +66,7 @@ import CONST from '@src/CONST';
 import ONYXKEYS from '@src/ONYXKEYS';
 import type {Route} from '@src/ROUTES';
 import ROUTES from '@src/ROUTES';
-import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportUserIsTyping} from '@src/types/onyx';
+import type {PersonalDetails, PersonalDetailsList, PolicyReportField, RecentlyUsedReportFields, ReportActionReactions, ReportMetadata, ReportUserIsTyping} from '@src/types/onyx';
 import type {Decision, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage';
 import type {NotificationPreference, WriteCapability} from '@src/types/onyx/Report';
 import type Report from '@src/types/onyx/Report';
@@ -159,6 +159,13 @@ Onyx.connect({
     },
 });
 
+let reportMetadata: OnyxCollection<ReportMetadata> = {};
+Onyx.connect({
+    key: ONYXKEYS.COLLECTION.REPORT_METADATA,
+    waitForCollectionCallback: true,
+    callback: (value) => (reportMetadata = value),
+});
+
 const allReports: OnyxCollection<Report> = {};
 let conciergeChatReportID: string | undefined;
 const typingWatchTimers: Record<string, NodeJS.Timeout> = {};
@@ -2241,7 +2248,38 @@ function leaveRoom(reportID: string, isWorkspaceMemberLeavingWorkspaceRoom = fal
 
     API.write(WRITE_COMMANDS.LEAVE_ROOM, parameters, {optimisticData, successData, failureData});
 
-    ReportUtils.navigateUserOnceLeaveReport(reportID);
+    const sortedReportsByLastRead = ReportUtils.sortReportsByLastRead(Object.values(allReports ?? {}) as Report[], reportMetadata);
+
+    // We want to filter out the current report, hidden reports and empty chats
+    const filteredReportsByLastRead = sortedReportsByLastRead.filter(
+        (sortedReport) =>
+            sortedReport?.reportID !== reportID &&
+            sortedReport?.notificationPreference !== CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN &&
+            ReportUtils.shouldReportBeInOptionList({
+                report: sortedReport,
+                currentReportId: '',
+                isInGSDMode: false,
+                betas: [],
+                policies: {},
+                excludeEmptyChats: true,
+                doesReportHaveViolations: false,
+            }),
+    );
+    const lastAccessedReportID = filteredReportsByLastRead.at(-1)?.reportID;
+
+    if (lastAccessedReportID) {
+        // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
+        Navigation.goBack(ROUTES.HOME);
+        Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(lastAccessedReportID));
+    } else {
+        const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]);
+        const chat = ReportUtils.getChatByParticipants(participantAccountIDs);
+        if (chat?.reportID) {
+            // We should call Navigation.goBack to pop the current route first before navigating to Concierge.
+            Navigation.goBack(ROUTES.HOME);
+            Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(chat.reportID));
+        }
+    }
 }
 
 /** Invites people to a room */

From 8a3473f35c8ed37cef7ab45dbeed45467d32c04e Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Mon, 5 Feb 2024 17:54:11 +0100
Subject: [PATCH 10/32] minor fix

---
 src/libs/actions/Policy.ts | 16 ++++------------
 1 file changed, 4 insertions(+), 12 deletions(-)

diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index d718fcd1b88..d012fba90ef 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1,4 +1,3 @@
-/* eslint-disable no-console */
 import {PUBLIC_DOMAINS} from 'expensify-common/lib/CONST';
 import ExpensiMark from 'expensify-common/lib/ExpensiMark';
 import Str from 'expensify-common/lib/str';
@@ -39,7 +38,6 @@ import ONYXKEYS from '@src/ONYXKEYS';
 import type {PersonalDetailsList, Policy, PolicyMember, PolicyTags, RecentlyUsedCategories, RecentlyUsedTags, ReimbursementAccount, Report, ReportAction, Transaction} from '@src/types/onyx';
 import type {Errors} from '@src/types/onyx/OnyxCommon';
 import type {CustomUnit} from '@src/types/onyx/Policy';
-import type {EmptyObject} from '@src/types/utils/EmptyObject';
 import {isEmptyObject} from '@src/types/utils/EmptyObject';
 
 type AnnounceRoomMembersOnyxData = {
@@ -353,8 +351,8 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[]
 /**
  * Build optimistic data for removing users from the announcement room
  */
-function removeOptimisticAnnounceRoomMembers(policy: Policy | EmptyObject, accountIDs: number[]): AnnounceRoomMembersOnyxData {
-    const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policy.id);
+function removeOptimisticAnnounceRoomMembers(policyID: string, policyName: string, accountIDs: number[]): AnnounceRoomMembersOnyxData {
+    const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID);
     const announceRoomMembers: AnnounceRoomMembersOnyxData = {
         onyxOptimisticData: [],
         onyxFailureData: [],
@@ -377,7 +375,7 @@ function removeOptimisticAnnounceRoomMembers(policy: Policy | EmptyObject, accou
                     ? {
                           statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
                           stateNum: CONST.REPORT.STATE_NUM.APPROVED,
-                          oldPolicyName: policy.name,
+                          oldPolicyName: policyName,
                           hasDraft: false,
                       }
                     : {}),
@@ -419,7 +417,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
     const workspaceChats = ReportUtils.getWorkspaceChats(policyID, accountIDs);
     const optimisticClosedReportActions = workspaceChats.map(() => ReportUtils.buildOptimisticClosedReportAction(sessionEmail, policy.name, CONST.REPORT.ARCHIVE_REASON.REMOVED_FROM_POLICY));
 
-    const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policy, accountIDs);
+    const announceRoomMembers = removeOptimisticAnnounceRoomMembers(policy.id, policy.name, accountIDs);
 
     const optimisticMembersState: OnyxCollection<PolicyMember> = {};
     const successMembersState: OnyxCollection<PolicyMember> = {};
@@ -544,12 +542,6 @@ function removeMembers(accountIDs: number[], policyID: string) {
         policyID,
     };
 
-    console.log('WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData}', WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {
-        optimisticData,
-        successData,
-        failureData,
-    });
-
     API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
 }
 

From 0ce1db13f56356cba7434de0603a1e0601c7b156 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Tue, 6 Feb 2024 15:28:32 +0100
Subject: [PATCH 11/32] move button to top

---
 src/pages/workspace/WorkspacesListPage.tsx | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx
index bda81eee001..b51e14adbe9 100755
--- a/src/pages/workspace/WorkspacesListPage.tsx
+++ b/src/pages/workspace/WorkspacesListPage.tsx
@@ -157,6 +157,14 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r
                 });
             }
 
+            if (!(isAdmin || isOwner)) {
+                threeDotsMenuItems.push({
+                    icon: Expensicons.ChatBubbles,
+                    text: translate('common.leave'),
+                    onSelected: Session.checkIfActionIsAllowed(() => Policy.removeMembers([session?.accountID ?? 0], item.policyID ?? '')),
+                });
+            }
+
             if (isAdmin && item.adminRoom) {
                 threeDotsMenuItems.push({
                     icon: Expensicons.Hashtag,
@@ -173,14 +181,6 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r
                 });
             }
 
-            if (!(isAdmin || isOwner)) {
-                threeDotsMenuItems.push({
-                    icon: Expensicons.ChatBubbles,
-                    text: translate('common.leave'),
-                    onSelected: Session.checkIfActionIsAllowed(() => Policy.removeMembers([session?.accountID ?? 0], item.policyID ?? '')),
-                });
-            }
-
             return (
                 <OfflineWithFeedback
                     key={`${item.title}_${index}`}

From ea0362330104183037148e50cdfc9ab9a7ab51c4 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Tue, 26 Mar 2024 12:51:25 +0100
Subject: [PATCH 12/32] integrate leave policy command

---
 src/libs/API/parameters/LeavePolicyParams.ts |  7 +++++++
 src/libs/API/parameters/index.ts             |  1 +
 src/libs/API/types.ts                        |  2 ++
 src/libs/actions/Policy.ts                   | 15 +++++++++++++++
 4 files changed, 25 insertions(+)
 create mode 100644 src/libs/API/parameters/LeavePolicyParams.ts

diff --git a/src/libs/API/parameters/LeavePolicyParams.ts b/src/libs/API/parameters/LeavePolicyParams.ts
new file mode 100644
index 00000000000..bc2962eebb6
--- /dev/null
+++ b/src/libs/API/parameters/LeavePolicyParams.ts
@@ -0,0 +1,7 @@
+type LeavePolicyParams = {
+    authToken: string;
+    policyID: string;
+    email: string;
+};
+
+export default LeavePolicyParams;
diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts
index 1895c2426e1..a3a7fc3cd83 100644
--- a/src/libs/API/parameters/index.ts
+++ b/src/libs/API/parameters/index.ts
@@ -196,3 +196,4 @@ export type {default as SetPolicyCustomTaxNameParams} from './SetPolicyCustomTax
 export type {default as SetPolicyForeignCurrencyDefaultParams} from './SetPolicyForeignCurrencyDefaultParams';
 export type {default as SetPolicyCurrencyDefaultParams} from './SetPolicyCurrencyDefaultParams';
 export type {default as RenamePolicyTaxParams} from './RenamePolicyTaxParams';
+export type {default as LeavePolicyParams} from './LeavePolicyParams';
diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts
index 9d6e6b3929b..f4945821a14 100644
--- a/src/libs/API/types.ts
+++ b/src/libs/API/types.ts
@@ -193,6 +193,7 @@ const WRITE_COMMANDS = {
     UPDATE_POLICY_DISTANCE_RATE_VALUE: 'UpdatePolicyDistanceRateValue',
     SET_POLICY_DISTANCE_RATES_ENABLED: 'SetPolicyDistanceRatesEnabled',
     DELETE_POLICY_DISTANCE_RATES: 'DeletePolicyDistanceRates',
+    LEAVE_POLICY: 'LeavePolicy',
 } as const;
 
 type WriteCommand = ValueOf<typeof WRITE_COMMANDS>;
@@ -384,6 +385,7 @@ type WriteCommandParameters = {
     [WRITE_COMMANDS.UPDATE_POLICY_DISTANCE_RATE_VALUE]: Parameters.UpdatePolicyDistanceRateValueParams;
     [WRITE_COMMANDS.SET_POLICY_DISTANCE_RATES_ENABLED]: Parameters.SetPolicyDistanceRatesEnabledParams;
     [WRITE_COMMANDS.DELETE_POLICY_DISTANCE_RATES]: Parameters.DeletePolicyDistanceRatesParams;
+    [WRITE_COMMANDS.LEAVE_POLICY]: Parameters.LeavePolicyParams;
 };
 
 const READ_COMMANDS = {
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 4b3dea92ba2..fd4108bf0e6 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -24,6 +24,7 @@ import type {
     EnablePolicyTagsParams,
     EnablePolicyTaxesParams,
     EnablePolicyWorkflowsParams,
+    LeavePolicyParams,
     OpenDraftWorkspaceRequestParams,
     OpenPolicyCategoriesPageParams,
     OpenPolicyDistanceRatesPageParams,
@@ -194,11 +195,13 @@ Onyx.connect({
 
 let sessionEmail = '';
 let sessionAccountID = 0;
+let sessionAuthToken = '';
 Onyx.connect({
     key: ONYXKEYS.SESSION,
     callback: (val) => {
         sessionEmail = val?.email ?? '';
         sessionAccountID = val?.accountID ?? -1;
+        sessionAuthToken = val?.authToken ?? '';
     },
 });
 
@@ -961,6 +964,17 @@ function removeMembers(accountIDs: number[], policyID: string) {
                 pendingAction: policy.pendingAction,
             },
         });
+
+        const params: LeavePolicyParams = {
+            policyID,
+            email: sessionEmail,
+            authToken: sessionAuthToken,
+        };
+
+        // TODO: Extract into a distinct function
+        API.write(WRITE_COMMANDS.LEAVE_POLICY, params, {optimisticData, successData, failureData});
+
+        return;
     }
 
     const params: DeleteMembersFromWorkspaceParams = {
@@ -968,6 +982,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
         policyID,
     };
 
+    // eslint-disable-next-line rulesdir/no-multiple-api-calls
     API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
 }
 

From 5ca68a6e77f9fabb67431c7347593414247d722f Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Wed, 27 Mar 2024 11:39:37 +0100
Subject: [PATCH 13/32] Add leave policy change log const

---
 src/CONST.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/CONST.ts b/src/CONST.ts
index 47caa5e64a9..660bb994b04 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -729,6 +729,7 @@ const CONST = {
                     UPDATE_TAG_NAME: 'POLICYCHANGELOG_UPDATE_TAG_NAME',
                     UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED',
                     UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE',
+                    LEAVE_POLICY: 'POLICYCHANGELOG_LEAVE_POLICY',
                 },
                 ROOMCHANGELOG: {
                     INVITE_TO_ROOM: 'INVITETOROOM',

From f8c839534ab10d9f3a5f1c468ff420a101190ed8 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 29 Mar 2024 10:18:58 +0100
Subject: [PATCH 14/32] Revert "Add leave policy change log const"

This reverts commit 5ca68a6e77f9fabb67431c7347593414247d722f.
---
 src/CONST.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/CONST.ts b/src/CONST.ts
index 74fb802a965..0fa1c64be44 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -735,7 +735,6 @@ const CONST = {
                     UPDATE_TAG_NAME: 'POLICYCHANGELOG_UPDATE_TAG_NAME',
                     UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED',
                     UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE',
-                    LEAVE_POLICY: 'POLICYCHANGELOG_LEAVE_POLICY',
                 },
                 ROOMCHANGELOG: {
                     INVITE_TO_ROOM: 'INVITETOROOM',

From 80caecdba561492ca3b336864c00ba10bf219320 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 29 Mar 2024 13:49:06 +0100
Subject: [PATCH 15/32] integrate leave policy function

---
 src/libs/actions/Policy.ts                 | 100 +++++++++++++++++----
 src/pages/workspace/WorkspacesListPage.tsx |   2 +-
 2 files changed, 82 insertions(+), 20 deletions(-)

diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index ddcf21b86f6..84fdac33e5f 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -971,41 +971,102 @@ function removeMembers(accountIDs: number[], policyID: string) {
         });
     });
 
-    if (accountIDs.includes(sessionAccountID)) {
-        optimisticData.push({
+    const params: DeleteMembersFromWorkspaceParams = {
+        emailList: accountIDs.map((accountID) => allPersonalDetails?.[accountID]?.login).join(','),
+        policyID,
+    };
+
+    API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
+}
+
+function leaveWorkspace(policyID: string) {
+    const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const;
+    const policy = ReportUtils.getPolicy(policyID);
+    const workspaceChats = ReportUtils.getWorkspaceChats(policyID, [sessionAccountID]);
+
+    const optimisticData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: membersListKey,
+            value: {
+                [sessionAccountID]: {
+                    pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
+                },
+            },
+        },
+        {
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
             value: {
                 pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE,
             },
-        });
-        failureData.push({
+        },
+    ];
+    const successData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: membersListKey,
+            value: {
+                [sessionAccountID]: null,
+            },
+        },
+    ];
+    const failureData: OnyxUpdate[] = [
+        {
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: membersListKey,
+            value: {
+                [sessionAccountID]: {
+                    errors: ErrorUtils.getMicroSecondOnyxError('workspace.people.error.genericRemove'),
+                },
+            },
+        },
+        {
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
             value: {
                 pendingAction: policy.pendingAction,
             },
-        });
-
-        const params: LeavePolicyParams = {
-            policyID,
-            email: sessionEmail,
-            authToken: sessionAuthToken,
-        };
+        },
+    ];
 
-        // TODO: Extract into a distinct function
-        API.write(WRITE_COMMANDS.LEAVE_POLICY, params, {optimisticData, successData, failureData});
+    const pendingChatMembers = ReportUtils.getPendingChatMembers([sessionAccountID], [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
 
-        return;
-    }
+    workspaceChats.forEach((report) => {
+        optimisticData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`,
+            value: {
+                statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
+                stateNum: CONST.REPORT.STATE_NUM.APPROVED,
+                oldPolicyName: policy.name,
+                hasDraft: false,
+                pendingChatMembers,
+            },
+        });
+        successData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`,
+            value: {
+                pendingChatMembers: null,
+            },
+        });
+        failureData.push({
+            onyxMethod: Onyx.METHOD.MERGE,
+            key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`,
+            value: {
+                pendingChatMembers: null,
+            },
+        });
+    });
 
-    const params: DeleteMembersFromWorkspaceParams = {
-        emailList: accountIDs.map((accountID) => allPersonalDetails?.[accountID]?.login).join(','),
+    const params: LeavePolicyParams = {
         policyID,
+        email: sessionEmail,
+        authToken: sessionAuthToken,
     };
 
-    // eslint-disable-next-line rulesdir/no-multiple-api-calls
-    API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
+    API.write(WRITE_COMMANDS.LEAVE_POLICY, params, {optimisticData, successData, failureData});
 }
 
 function updateWorkspaceMembersRole(policyID: string, accountIDs: number[], newRole: typeof CONST.POLICY.ROLE.ADMIN | typeof CONST.POLICY.ROLE.USER) {
@@ -4830,6 +4891,7 @@ function setForeignCurrencyDefault(policyID: string, taxCode: string) {
 
 export {
     removeMembers,
+    leaveWorkspace,
     updateWorkspaceMembersRole,
     requestWorkspaceOwnerChange,
     clearWorkspaceOwnerChangeFlow,
diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx
index 320ea137868..d28f3ceef75 100755
--- a/src/pages/workspace/WorkspacesListPage.tsx
+++ b/src/pages/workspace/WorkspacesListPage.tsx
@@ -174,7 +174,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r
                 threeDotsMenuItems.push({
                     icon: Expensicons.ChatBubbles,
                     text: translate('common.leave'),
-                    onSelected: Session.checkIfActionIsAllowed(() => Policy.removeMembers([session?.accountID ?? 0], item.policyID ?? '')),
+                    onSelected: Session.checkIfActionIsAllowed(() => Policy.leaveWorkspace(item.policyID ?? '')),
                 });
             }
 

From 44bcc093a1eaab6d488f834eec099f0c011d38a3 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Wed, 3 Apr 2024 16:10:14 +0200
Subject: [PATCH 16/32] add comments & use allPolicies

---
 src/libs/actions/Policy.ts | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index eff44bcb705..80bd0d5c4e4 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -974,9 +974,10 @@ function removeMembers(accountIDs: number[], policyID: string) {
     API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
 }
 
+/** Leave a workspace */
 function leaveWorkspace(policyID: string) {
     const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const;
-    const policy = ReportUtils.getPolicy(policyID);
+    const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
     const workspaceChats = ReportUtils.getWorkspaceChats(policyID, [sessionAccountID]);
 
     const optimisticData: OnyxUpdate[] = [
@@ -1020,7 +1021,7 @@ function leaveWorkspace(policyID: string) {
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`,
             value: {
-                pendingAction: policy.pendingAction,
+                pendingAction: policy?.pendingAction,
             },
         },
     ];
@@ -1034,7 +1035,7 @@ function leaveWorkspace(policyID: string) {
             value: {
                 statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
                 stateNum: CONST.REPORT.STATE_NUM.APPROVED,
-                oldPolicyName: policy.name,
+                oldPolicyName: policy?.name ?? '',
                 hasDraft: false,
                 pendingChatMembers,
             },

From 4b48baff8f9bba768a34af91ecc941e598315a6c Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Wed, 3 Apr 2024 16:11:34 +0200
Subject: [PATCH 17/32] add comments & create canLeavePolicyExpenseChat

---
 src/libs/ReportUtils.ts       | 6 ++++++
 src/pages/home/HeaderView.tsx | 4 +---
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 6906f6d80ee..16421a7890a 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -5642,6 +5642,7 @@ function canBeAutoReimbursed(report: OnyxEntry<Report>, policy: OnyxEntry<Policy
     return isAutoReimbursable;
 }
 
+/** Check if the current user is an owner of the report */
 function isReportOwner(report: OnyxEntry<Report>): boolean {
     return report?.ownerAccountID === currentUserPersonalDetails?.accountID;
 }
@@ -5705,6 +5706,10 @@ function hasActionsWithErrors(reportID: string): boolean {
     return Object.values(reportActions ?? {}).some((action) => !isEmptyObject(action.errors));
 }
 
+function canLeavePolicyExpenseChat(report: OnyxEntry<Report>, policy: OnyxEntry<Policy>): boolean {
+    return isPolicyExpenseChat(report) && !(PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPolicyOwner(policy, currentUserAccountID ?? -1) || isReportOwner(report));
+}
+
 export {
     getReportParticipantsTitle,
     isReportMessageAttachment,
@@ -5932,6 +5937,7 @@ export {
     isTrackExpenseReport,
     hasActionsWithErrors,
     getGroupChatName,
+    canLeavePolicyExpenseChat,
 };
 
 export type {
diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index 0576738609a..c27420288e5 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -26,7 +26,6 @@ import Navigation from '@libs/Navigation/Navigation';
 import type {ReportWithoutHasDraft} from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
 import reportWithoutHasDraftSelector from '@libs/OnyxSelectors/reportWithoutHasDraftSelector';
 import * as OptionsListUtils from '@libs/OptionsListUtils';
-import * as PolicyUtils from '@libs/PolicyUtils';
 import * as ReportActionsUtils from '@libs/ReportActionsUtils';
 import * as ReportUtils from '@libs/ReportUtils';
 import * as Link from '@userActions/Link';
@@ -99,8 +98,7 @@ function HeaderView({report, personalDetails, parentReport, parentReportAction,
     const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(report);
     const isPolicyMember = useMemo(() => !isEmptyObject(policy), [policy]);
     const canLeaveRoom = ReportUtils.canLeaveRoom(report, isPolicyMember);
-    const canLeavePolicyExpenseChat =
-        isPolicyExpenseChat && !(PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPolicyOwner(policy, session?.accountID ?? -1) || ReportUtils.isReportOwner(report));
+    const canLeavePolicyExpenseChat = ReportUtils.canLeavePolicyExpenseChat(report, policy);
     const reportDescription = ReportUtils.getReportDescriptionText(report);
     const policyName = ReportUtils.getPolicyName(report, true);
     const policyDescription = ReportUtils.getPolicyDescriptionText(policy);

From 9e9f7bfead305a29a744d12c6dbdae3b2d266284 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Thu, 4 Apr 2024 14:56:17 +0200
Subject: [PATCH 18/32] integrate getAllWorkspaceReports

---
 src/libs/ReportUtils.ts    | 10 ++++++++++
 src/libs/actions/Policy.ts |  2 +-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index 242a63117f5..f37c73ab8da 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -5036,6 +5036,15 @@ function getWorkspaceChats(policyID: string, accountIDs: number[]): Array<OnyxEn
     return Object.values(allReports ?? {}).filter((report) => isPolicyExpenseChat(report) && (report?.policyID ?? '') === policyID && accountIDs.includes(report?.ownerAccountID ?? -1));
 }
 
+/**
+ * Gets all reports that relate to the policy
+ * 
+ * @param policyID - the workspace ID to get all associated reports
+ */
+function getAllWorkspaceReports(policyID: string): Array<OnyxEntry<Report>> {
+    return Object.values(allReports ?? {}).filter((report) => (report?.policyID ?? '') === policyID);
+}
+
 /**
  * @param policy - the workspace the report is on, null if the user isn't a member of the workspace
  */
@@ -5860,6 +5869,7 @@ export {
     isDM,
     isSelfDM,
     getWorkspaceChats,
+    getAllWorkspaceReports,
     shouldDisableRename,
     hasSingleParticipant,
     getReportRecipientAccountIDs,
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 89101988ee5..c1b07dd6099 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -986,7 +986,7 @@ function removeMembers(accountIDs: number[], policyID: string) {
 function leaveWorkspace(policyID: string) {
     const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const;
     const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
-    const workspaceChats = ReportUtils.getWorkspaceChats(policyID, [sessionAccountID]);
+    const workspaceChats = ReportUtils.getAllWorkspaceReports(policyID);
 
     const optimisticData: OnyxUpdate[] = [
         {

From d40d9ffa0cb746e1bcfe264c691e97603f3be767 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Thu, 4 Apr 2024 18:46:23 +0200
Subject: [PATCH 19/32] prettify

---
 src/libs/ReportUtils.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index f37c73ab8da..63cd586e241 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -5038,7 +5038,7 @@ function getWorkspaceChats(policyID: string, accountIDs: number[]): Array<OnyxEn
 
 /**
  * Gets all reports that relate to the policy
- * 
+ *
  * @param policyID - the workspace ID to get all associated reports
  */
 function getAllWorkspaceReports(policyID: string): Array<OnyxEntry<Report>> {

From c55cd1329205524ac908a31077ea1f9949ac00f8 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 5 Apr 2024 16:26:35 +0200
Subject: [PATCH 20/32] fix ts

---
 src/libs/actions/Policy.ts | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 02613eeb05f..7ac620d924a 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1039,7 +1039,6 @@ function leaveWorkspace(policyID: string) {
                 statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
                 stateNum: CONST.REPORT.STATE_NUM.APPROVED,
                 oldPolicyName: policy?.name ?? '',
-                hasDraft: false,
                 pendingChatMembers,
             },
         });

From 2b955b3d4eed7b29c6d726733454c21abe4509d6 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 5 Apr 2024 16:28:56 +0200
Subject: [PATCH 21/32] fix ts

---
 src/libs/actions/Policy.ts | 2 --
 1 file changed, 2 deletions(-)

diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 7ac620d924a..ba203d91200 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -799,7 +799,6 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, policyName: strin
                           statusNum: CONST.REPORT.STATUS_NUM.CLOSED,
                           stateNum: CONST.REPORT.STATE_NUM.APPROVED,
                           oldPolicyName: policyName,
-                          hasDraft: false,
                       }
                     : {}),
             },
@@ -814,7 +813,6 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, policyName: strin
                           statusNum: announceReport.statusNum,
                           stateNum: announceReport.stateNum,
                           oldPolicyName: announceReport.oldPolicyName,
-                          hasDraft: announceReport.hasDraft,
                       }
                     : {}),
             },

From 2c2ae9f6f9f6633f705fbed06d0962d2d82d87a8 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Mon, 8 Apr 2024 16:27:33 +0200
Subject: [PATCH 22/32] check if expense chat

---
 src/pages/home/HeaderView.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx
index d3f04b74817..aacf8ebaa33 100644
--- a/src/pages/home/HeaderView.tsx
+++ b/src/pages/home/HeaderView.tsx
@@ -153,7 +153,7 @@ function HeaderView({report, personalDetails, parentReport, parentReportAction,
             onSelected: join,
         });
     } else if (canLeave) {
-        const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED && isPolicyMember;
+        const isWorkspaceMemberLeavingWorkspaceRoom = !isChatThread && (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || isPolicyExpenseChat) && isPolicyMember;
         threeDotMenuItems.push({
             icon: Expensicons.ChatBubbles,
             text: translate('common.leave'),

From 858a872fcdb7b6e9cff1e625096909ea74e40a8c Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Tue, 9 Apr 2024 12:50:50 +0200
Subject: [PATCH 23/32] fix finding a chat by participants

---
 src/libs/ReportUtils.ts | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts
index f0cad072de2..27ad6cede41 100644
--- a/src/libs/ReportUtils.ts
+++ b/src/libs/ReportUtils.ts
@@ -4573,9 +4573,9 @@ function getChatByParticipantsAndPolicy(newParticipantList: number[], policyID:
             if (!report?.participantAccountIDs) {
                 return false;
             }
-            const sortedParticipanctsAccountIDs = report.participantAccountIDs?.sort();
+            const sortedParticipantsAccountIDs = report.participantAccountIDs?.sort();
             // Only return the room if it has all the participants and is not a policy room
-            return report.policyID === policyID && lodashIsEqual(newParticipantList, sortedParticipanctsAccountIDs);
+            return report.policyID === policyID && newParticipantList.every((newParticipant) => sortedParticipantsAccountIDs.includes(newParticipant));
         }) ?? null
     );
 }

From 0b12883e71655d71e2dd545c92842b0ea54c2601 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Tue, 9 Apr 2024 15:40:26 +0200
Subject: [PATCH 24/32] check if the user should leave a chat

---
 src/libs/actions/Policy.ts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 1bb152eec91..154a519fff1 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1030,6 +1030,10 @@ function leaveWorkspace(policyID: string) {
     const pendingChatMembers = ReportUtils.getPendingChatMembers([sessionAccountID], [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
 
     workspaceChats.forEach((report) => {
+        if (ReportUtils.isPolicyExpenseChat(report) && !ReportUtils.isReportOwner(report)) {
+            return;
+        }
+
         optimisticData.push({
             onyxMethod: Onyx.METHOD.MERGE,
             key: `${ONYXKEYS.COLLECTION.REPORT}${report?.reportID}`,
@@ -1061,7 +1065,6 @@ function leaveWorkspace(policyID: string) {
         email: sessionEmail,
         authToken: sessionAuthToken,
     };
-
     API.write(WRITE_COMMANDS.LEAVE_POLICY, params, {optimisticData, successData, failureData});
 }
 

From 4de342aa75a84b327aa42e0ada7266e1e1426b68 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Tue, 9 Apr 2024 16:35:50 +0200
Subject: [PATCH 25/32] integrate clearReportNotFoundErrors

---
 src/libs/actions/Report.ts      | 13 +++++++++++++
 src/pages/home/ReportScreen.tsx |  6 +++++-
 2 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts
index 91128ac8917..24b635d9f8b 100644
--- a/src/libs/actions/Report.ts
+++ b/src/libs/actions/Report.ts
@@ -2052,6 +2052,18 @@ function clearPolicyRoomNameErrors(reportID: string) {
     });
 }
 
+/**
+ * @param reportID The reportID of the report.
+ */
+// eslint-disable-next-line rulesdir/no-negated-variables
+function clearReportNotFoundErrors(reportID: string) {
+    Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, {
+        errorFields: {
+            notFound: null,
+        },
+    });
+}
+
 function setIsComposerFullSize(reportID: string, isComposerFullSize: boolean) {
     Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_IS_COMPOSER_FULL_SIZE}${reportID}`, isComposerFullSize);
 }
@@ -3053,6 +3065,7 @@ export {
     toggleSubscribeToChildReport,
     updatePolicyRoomNameAndNavigate,
     clearPolicyRoomNameErrors,
+    clearReportNotFoundErrors,
     clearIOUError,
     subscribeToNewActionEvent,
     notifyNewAction,
diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index 940cba181db..be28db991e6 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -387,7 +387,11 @@ function ReportScreen({
             return;
         }
         Report.updateLastVisitTime(report.reportID);
-    }, [report.reportID, isFocused]);
+
+        if (report?.errorFields?.notFound) {
+            Report.clearReportNotFoundErrors(report.reportID);
+        }
+    }, [report.reportID, isFocused, report?.errorFields?.notFound]);
 
     const fetchReportIfNeeded = useCallback(() => {
         // Report ID will be empty when the reports collection is empty.

From e63bb9e409cace2b8f8b7ce4da8b4ff3f36b76e4 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Wed, 10 Apr 2024 13:59:09 +0200
Subject: [PATCH 26/32] add left a workspace action

---
 src/CONST.ts                   |  1 +
 src/languages/en.ts            |  1 +
 src/languages/es.ts            |  1 +
 src/libs/ReportActionsUtils.ts | 18 ++++++++++++++++--
 4 files changed, 19 insertions(+), 2 deletions(-)

diff --git a/src/CONST.ts b/src/CONST.ts
index b07b622cec0..87462ff1b21 100755
--- a/src/CONST.ts
+++ b/src/CONST.ts
@@ -748,6 +748,7 @@ const CONST = {
                     UPDATE_TAG_NAME: 'POLICYCHANGELOG_UPDATE_TAG_NAME',
                     UPDATE_TIME_ENABLED: 'POLICYCHANGELOG_UPDATE_TIME_ENABLED',
                     UPDATE_TIME_RATE: 'POLICYCHANGELOG_UPDATE_TIME_RATE',
+                    LEAVE_POLICY: 'POLICYCHANGELOG_LEAVE_POLICY',
                 },
                 ROOMCHANGELOG: {
                     INVITE_TO_ROOM: 'INVITETOROOM',
diff --git a/src/languages/en.ts b/src/languages/en.ts
index 3b670f7b6eb..4571f1b4e32 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -2144,6 +2144,7 @@ export default {
             users: 'users',
             invited: 'invited',
             removed: 'removed',
+            leftWorkspace: 'left the workspace',
             to: 'to',
             from: 'from',
         },
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 5027174b292..523654b57e7 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2172,6 +2172,7 @@ export default {
             users: 'usuarios',
             invited: 'invitó',
             removed: 'eliminó',
+            leftWorkspace: 'dejó el espacio de trabajo',
             to: 'a',
             from: 'de',
         },
diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index b09f58b969f..4e9c8c199d9 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -157,7 +157,8 @@ function isMemberChangeAction(reportAction: OnyxEntry<ReportAction>) {
         reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM ||
         reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM ||
         reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM ||
-        reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.REMOVE_FROM_ROOM
+        reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.REMOVE_FROM_ROOM ||
+        reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.LEAVE_POLICY
     );
 }
 
@@ -165,6 +166,10 @@ function isInviteMemberAction(reportAction: OnyxEntry<ReportAction>) {
     return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM;
 }
 
+function isLeaveMemberAction(reportAction: OnyxEntry<ReportAction>) {
+    return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.LEAVE_POLICY;
+}
+
 function isReimbursementDeQueuedAction(reportAction: OnyxEntry<ReportAction>): reportAction is ReportActionBase & OriginalMessageReimbursementDequeued {
     return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED;
 }
@@ -839,9 +844,18 @@ function isNotifiableReportAction(reportAction: OnyxEntry<ReportAction>): boolea
 
 function getMemberChangeMessageElements(reportAction: OnyxEntry<ReportAction>): readonly MemberChangeMessageElement[] {
     const isInviteAction = isInviteMemberAction(reportAction);
+    const isLeaveAction = isLeaveMemberAction(reportAction);
 
     // Currently, we only render messages when members are invited
-    const verb = isInviteAction ? Localize.translateLocal('workspace.invite.invited') : Localize.translateLocal('workspace.invite.removed');
+    let verb = Localize.translateLocal('workspace.invite.removed');
+
+    if (isInviteAction) {
+        verb = Localize.translateLocal('workspace.invite.invited');
+    }
+
+    if (isLeaveAction) {
+        verb = Localize.translateLocal('workspace.invite.leftWorkspace');
+    }
 
     const originalMessage = reportAction?.originalMessage as ChangeLog;
     const targetAccountIDs: number[] = originalMessage?.targetAccountIDs ?? [];

From 6b2efac933e21125b87ce83bf0af1c322cfa8b0c Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Wed, 10 Apr 2024 14:16:18 +0200
Subject: [PATCH 27/32] use separate useEffect

---
 src/pages/home/ReportScreen.tsx | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx
index b28dc974e2b..307bd7f02d5 100644
--- a/src/pages/home/ReportScreen.tsx
+++ b/src/pages/home/ReportScreen.tsx
@@ -383,15 +383,21 @@ function ReportScreen({
     }, [reportIDFromRoute, reportActionIDFromRoute]);
 
     useEffect(() => {
-        if (!report.reportID || !isFocused) {
+        if (!report.reportID) {
             return;
         }
-        Report.updateLastVisitTime(report.reportID);
 
         if (report?.errorFields?.notFound) {
             Report.clearReportNotFoundErrors(report.reportID);
         }
-    }, [report.reportID, isFocused, report?.errorFields?.notFound]);
+    }, [report?.errorFields?.notFound, report.reportID]);
+
+    useEffect(() => {
+        if (!report.reportID || !isFocused) {
+            return;
+        }
+        Report.updateLastVisitTime(report.reportID);
+    }, [report.reportID, isFocused]);
 
     const fetchReportIfNeeded = useCallback(() => {
         // Report ID will be empty when the reports collection is empty.

From 8ab074eb9bbb7be31984106bc27bba783e173b05 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Wed, 10 Apr 2024 14:43:51 +0200
Subject: [PATCH 28/32] show in sidebar

---
 src/libs/SidebarUtils.ts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts
index c5439d68708..1981aa60301 100644
--- a/src/libs/SidebarUtils.ts
+++ b/src/libs/SidebarUtils.ts
@@ -351,6 +351,8 @@ function getOptionData({
                         : ` ${Localize.translate(preferredLocale, 'workspace.invite.from')}`;
                 result.alternateText += `${preposition} ${roomName}`;
             }
+        } else if (lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.LEAVE_POLICY) {
+            result.alternateText = Localize.translateLocal('workspace.invite.leftWorkspace');
         } else if (lastAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW && lastActorDisplayName && lastMessageTextFromReport) {
             result.alternateText = `${lastActorDisplayName}: ${lastMessageText}`;
         } else {

From 97cab94419b1d4245351ae851ee5f4f7b58022a0 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Thu, 11 Apr 2024 12:47:13 +0200
Subject: [PATCH 29/32] omit authToken

---
 src/libs/API/parameters/LeavePolicyParams.ts | 1 -
 src/libs/actions/Policy.ts                   | 3 ---
 2 files changed, 4 deletions(-)

diff --git a/src/libs/API/parameters/LeavePolicyParams.ts b/src/libs/API/parameters/LeavePolicyParams.ts
index bc2962eebb6..ad728f06e6e 100644
--- a/src/libs/API/parameters/LeavePolicyParams.ts
+++ b/src/libs/API/parameters/LeavePolicyParams.ts
@@ -1,5 +1,4 @@
 type LeavePolicyParams = {
-    authToken: string;
     policyID: string;
     email: string;
 };
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 1cffe033edd..c0310d6e597 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -199,13 +199,11 @@ Onyx.connect({
 
 let sessionEmail = '';
 let sessionAccountID = 0;
-let sessionAuthToken = '';
 Onyx.connect({
     key: ONYXKEYS.SESSION,
     callback: (val) => {
         sessionEmail = val?.email ?? '';
         sessionAccountID = val?.accountID ?? -1;
-        sessionAuthToken = val?.authToken ?? '';
     },
 });
 
@@ -1064,7 +1062,6 @@ function leaveWorkspace(policyID: string) {
     const params: LeavePolicyParams = {
         policyID,
         email: sessionEmail,
-        authToken: sessionAuthToken,
     };
     API.write(WRITE_COMMANDS.LEAVE_POLICY, params, {optimisticData, successData, failureData});
 }

From 53139004e68ca1a8499f1d9fd161b09c6febd7f5 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Thu, 11 Apr 2024 13:06:34 +0200
Subject: [PATCH 30/32] leave a nested report

---
 src/libs/actions/Policy.ts | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index c0310d6e597..70dc4bcef73 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -1029,7 +1029,10 @@ function leaveWorkspace(policyID: string) {
     const pendingChatMembers = ReportUtils.getPendingChatMembers([sessionAccountID], [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE);
 
     workspaceChats.forEach((report) => {
-        if (ReportUtils.isPolicyExpenseChat(report) && !ReportUtils.isReportOwner(report)) {
+        const parentReport = ReportUtils.getRootParentReport(report);
+        const reportToCheckOwner = isEmptyObject(parentReport) ? report : parentReport;
+
+        if (ReportUtils.isPolicyExpenseChat(report) && !ReportUtils.isReportOwner(reportToCheckOwner)) {
             return;
         }
 

From b028a6f54f0fbc7a43d0de62eba13841ca664fa7 Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Fri, 12 Apr 2024 17:22:19 +0200
Subject: [PATCH 31/32] fix spanish string

---
 src/languages/es.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/languages/es.ts b/src/languages/es.ts
index 56e3210e16d..d251f6bfe5e 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -2172,7 +2172,7 @@ export default {
             users: 'usuarios',
             invited: 'invitó',
             removed: 'eliminó',
-            leftWorkspace: 'dejó el espacio de trabajo',
+            leftWorkspace: 'salió del espacio de trabajo',
             to: 'a',
             from: 'de',
         },

From 7321ad2800873756b03f841ac10beff07f7ad63d Mon Sep 17 00:00:00 2001
From: Mykhailo Kravchenko <rezkiy37@gmail.com>
Date: Mon, 15 Apr 2024 09:27:11 +0200
Subject: [PATCH 32/32] address comments

---
 src/libs/ReportActionsUtils.ts | 5 ++---
 src/libs/actions/Policy.ts     | 1 -
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts
index 61ac0f22ee4..e339dce5b81 100644
--- a/src/libs/ReportActionsUtils.ts
+++ b/src/libs/ReportActionsUtils.ts
@@ -170,7 +170,7 @@ function isInviteMemberAction(reportAction: OnyxEntry<ReportAction>) {
     return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM;
 }
 
-function isLeaveMemberAction(reportAction: OnyxEntry<ReportAction>) {
+function isLeavePolicyAction(reportAction: OnyxEntry<ReportAction>) {
     return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.LEAVE_POLICY;
 }
 
@@ -866,11 +866,10 @@ function isNotifiableReportAction(reportAction: OnyxEntry<ReportAction>): boolea
 
 function getMemberChangeMessageElements(reportAction: OnyxEntry<ReportAction>): readonly MemberChangeMessageElement[] {
     const isInviteAction = isInviteMemberAction(reportAction);
-    const isLeaveAction = isLeaveMemberAction(reportAction);
+    const isLeaveAction = isLeavePolicyAction(reportAction);
 
     // Currently, we only render messages when members are invited
     let verb = Localize.translateLocal('workspace.invite.removed');
-
     if (isInviteAction) {
         verb = Localize.translateLocal('workspace.invite.invited');
     }
diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts
index 7152b204879..74965bcaca1 100644
--- a/src/libs/actions/Policy.ts
+++ b/src/libs/actions/Policy.ts
@@ -976,7 +976,6 @@ function removeMembers(accountIDs: number[], policyID: string) {
     API.write(WRITE_COMMANDS.DELETE_MEMBERS_FROM_WORKSPACE, params, {optimisticData, successData, failureData});
 }
 
-/** Leave a workspace */
 function leaveWorkspace(policyID: string) {
     const membersListKey = `${ONYXKEYS.COLLECTION.POLICY_MEMBERS}${policyID}` as const;
     const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`];
