From 2ce6c26b242e35825cf3fc96defac2838f980f4a Mon Sep 17 00:00:00 2001 From: annaweber Date: Fri, 6 Feb 2026 10:35:01 -0800 Subject: [PATCH 1/4] fix: init --- src/libs/actions/Workflow.ts | 31 +++++++++++++++---- src/pages/workspace/WorkspaceMembersPage.tsx | 24 ++++++++------ .../members/WorkspaceMemberDetailsPage.tsx | 25 ++++++++------- 3 files changed, 53 insertions(+), 27 deletions(-) diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index bb86449286a4b..8361a2510edea 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -39,7 +39,7 @@ function createApprovalWorkflow({approvalWorkflow, policy, addExpenseApprovalsTa return; } - const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}])); + const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value}])); const previousApprovalMode = policy.approvalMode; const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({previousEmployeeList, approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE}); @@ -80,7 +80,14 @@ function createApprovalWorkflow({approvalWorkflow, policy, addExpenseApprovalsTa onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, value: { - employeeList: Object.fromEntries(Object.keys(updatedEmployees).map((key) => [key, {pendingAction: null, pendingFields: null}])), + employeeList: Object.fromEntries( + Object.keys(updatedEmployees).map((key) => [ + key, + previousEmployeeList[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE + ? {pendingFields: null} + : {pendingAction: null, pendingFields: null}, + ]), + ), }, }, ]; @@ -103,7 +110,7 @@ function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRem const previousDefaultApprover = getDefaultApprover(policy); const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers.at(0)?.email : undefined; - const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}])); + const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value}])); const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({ previousEmployeeList, approvalWorkflow, @@ -151,7 +158,14 @@ function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRem onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, value: { - employeeList: Object.fromEntries(Object.keys(updatedEmployees).map((key) => [key, {pendingAction: null, pendingFields: null}])), + employeeList: Object.fromEntries( + Object.keys(updatedEmployees).map((key) => [ + key, + previousEmployeeList[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE + ? {pendingFields: null} + : {pendingAction: null, pendingFields: null}, + ]), + ), }, }, ]; @@ -169,7 +183,7 @@ function removeApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: Onyx return; } - const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}])); + const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value}])); const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({previousEmployeeList, approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.REMOVE}); const updatedEmployeeList = {...previousEmployeeList, ...updatedEmployees}; @@ -209,7 +223,12 @@ function removeApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: Onyx onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.POLICY}${policy.id}`, value: { - employeeList: Object.fromEntries(Object.keys(updatedEmployees).map((key) => [key, {pendingAction: null}])), + employeeList: Object.fromEntries( + Object.keys(updatedEmployees).map((key) => [ + key, + previousEmployeeList[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? {pendingFields: null} : {pendingAction: null, pendingFields: null}, + ]), + ), }, }, ]; diff --git a/src/pages/workspace/WorkspaceMembersPage.tsx b/src/pages/workspace/WorkspaceMembersPage.tsx index 313430a14b469..846d95d8f1763 100644 --- a/src/pages/workspace/WorkspaceMembersPage.tsx +++ b/src/pages/workspace/WorkspaceMembersPage.tsx @@ -226,12 +226,22 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers * Please see https://github.com/Expensify/App/blob/main/README.md#Security for more details */ const removeUsers = () => { - // Check if any of the members are approvers - const hasApprovers = selectedEmployees.some((email) => isApprover(policy, email)); + const selectedEmployeesToRemove = [...selectedEmployees]; + + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(() => { + setSelectedEmployees([]); + removeMembers(policy, selectedEmployeesToRemove, policyMemberEmailsToAccountIDs); + + // Check if any of the members are approvers + const hasApprovers = selectedEmployeesToRemove.some((email) => isApprover(policy, email)); + + if (!hasApprovers) { + return; + } - if (hasApprovers) { const ownerEmail = ownerDetails.login; - for (const login of selectedEmployees) { + for (const login of selectedEmployeesToRemove) { if (!isApprover(policy, login)) { continue; } @@ -255,12 +265,6 @@ function WorkspaceMembersPage({personalDetails, route, policy}: WorkspaceMembers } } } - } - - // eslint-disable-next-line @typescript-eslint/no-deprecated - InteractionManager.runAfterInteractions(() => { - setSelectedEmployees([]); - removeMembers(policy, selectedEmployees, policyMemberEmailsToAccountIDs); }); }; diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index e21bf06dc6508..236c96f0ebfa7 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -1,6 +1,6 @@ import {Str} from 'expensify-common'; import React, {useContext, useEffect} from 'react'; -import {View} from 'react-native'; +import {InteractionManager, View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import Avatar from '@components/Avatar'; @@ -190,6 +190,9 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM return; } + // Remove the member first so they disappear immediately from the list + removeMemberAndCloseModal(); + // Update approval workflows after approver removal const updatedWorkflows = updateWorkflowDataOnApproverRemoval({ approvalWorkflows, @@ -197,18 +200,18 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM ownerDetails, }); - for (const workflow of updatedWorkflows) { - if (workflow?.removeApprovalWorkflow) { - const {removeApprovalWorkflow, ...updatedWorkflow} = workflow; + // eslint-disable-next-line @typescript-eslint/no-deprecated + InteractionManager.runAfterInteractions(() => { + for (const workflow of updatedWorkflows) { + if (workflow?.removeApprovalWorkflow) { + const {removeApprovalWorkflow, ...updatedWorkflow} = workflow; - removeApprovalWorkflowAction(updatedWorkflow, policy); - } else { - updateApprovalWorkflow(workflow, [], [], policy); + removeApprovalWorkflowAction(updatedWorkflow, policy); + } else { + updateApprovalWorkflow(workflow, [], [], policy); + } } - } - - // Remove the member and close the modal - removeMemberAndCloseModal(); + }); }; const showRemoveMemberModal = async () => { From 9fcd2e1fca7dff85a4a9eeae3b1c801b9d178227 Mon Sep 17 00:00:00 2001 From: annaweber Date: Fri, 6 Feb 2026 10:46:01 -0800 Subject: [PATCH 2/4] fix: prettier --- src/libs/actions/Workflow.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index 8361a2510edea..d25253082d2ab 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -83,9 +83,7 @@ function createApprovalWorkflow({approvalWorkflow, policy, addExpenseApprovalsTa employeeList: Object.fromEntries( Object.keys(updatedEmployees).map((key) => [ key, - previousEmployeeList[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE - ? {pendingFields: null} - : {pendingAction: null, pendingFields: null}, + previousEmployeeList[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? {pendingFields: null} : {pendingAction: null, pendingFields: null}, ]), ), }, @@ -161,9 +159,7 @@ function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRem employeeList: Object.fromEntries( Object.keys(updatedEmployees).map((key) => [ key, - previousEmployeeList[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE - ? {pendingFields: null} - : {pendingAction: null, pendingFields: null}, + previousEmployeeList[key]?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE ? {pendingFields: null} : {pendingAction: null, pendingFields: null}, ]), ), }, From 5d4d733a7e2f1226d8b263dd11ae7af3eeb54425 Mon Sep 17 00:00:00 2001 From: annaweber Date: Sun, 8 Feb 2026 08:41:09 -0800 Subject: [PATCH 3/4] fix: remove fix --- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 236c96f0ebfa7..05c1b8539de4c 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -190,9 +190,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM return; } - // Remove the member first so they disappear immediately from the list - removeMemberAndCloseModal(); - // Update approval workflows after approver removal const updatedWorkflows = updateWorkflowDataOnApproverRemoval({ approvalWorkflows, @@ -212,6 +209,9 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM } } }); + + // Remove the member first so they disappear immediately from the list + removeMemberAndCloseModal(); }; const showRemoveMemberModal = async () => { From bff1fce0aed2087ef1901908ff8aec42ee032150 Mon Sep 17 00:00:00 2001 From: annaweber Date: Sun, 8 Feb 2026 12:53:57 -0800 Subject: [PATCH 4/4] fix: comment fix --- src/libs/actions/Workflow.ts | 6 +++--- src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libs/actions/Workflow.ts b/src/libs/actions/Workflow.ts index d25253082d2ab..6b53b785ae1ef 100644 --- a/src/libs/actions/Workflow.ts +++ b/src/libs/actions/Workflow.ts @@ -39,7 +39,7 @@ function createApprovalWorkflow({approvalWorkflow, policy, addExpenseApprovalsTa return; } - const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value}])); + const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}])); const previousApprovalMode = policy.approvalMode; const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({previousEmployeeList, approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.CREATE}); @@ -108,7 +108,7 @@ function updateApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, membersToRem const previousDefaultApprover = getDefaultApprover(policy); const newDefaultApprover = approvalWorkflow.isDefault ? approvalWorkflow.approvers.at(0)?.email : undefined; - const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value}])); + const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}])); const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({ previousEmployeeList, approvalWorkflow, @@ -179,7 +179,7 @@ function removeApprovalWorkflow(approvalWorkflow: ApprovalWorkflow, policy: Onyx return; } - const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value}])); + const previousEmployeeList = Object.fromEntries(Object.entries(policy.employeeList ?? {}).map(([key, value]) => [key, {...value, pendingAction: null}])); const updatedEmployees = convertApprovalWorkflowToPolicyEmployees({previousEmployeeList, approvalWorkflow, type: CONST.APPROVAL_WORKFLOW.TYPE.REMOVE}); const updatedEmployeeList = {...previousEmployeeList, ...updatedEmployees}; diff --git a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx index 05c1b8539de4c..0944f5abc8660 100644 --- a/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx +++ b/src/pages/workspace/members/WorkspaceMemberDetailsPage.tsx @@ -190,6 +190,9 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM return; } + // Remove the member and close the modal + removeMemberAndCloseModal(); + // Update approval workflows after approver removal const updatedWorkflows = updateWorkflowDataOnApproverRemoval({ approvalWorkflows, @@ -209,9 +212,6 @@ function WorkspaceMemberDetailsPage({personalDetails, policy, route}: WorkspaceM } } }); - - // Remove the member first so they disappear immediately from the list - removeMemberAndCloseModal(); }; const showRemoveMemberModal = async () => {