diff --git a/src/CONST.ts b/src/CONST.ts index b7e90b070066..74e722cdba59 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -750,6 +750,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 be5120a2c7de..54a7be84829c 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -2151,6 +2151,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 ffb92ace77d8..2a02340ad383 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -2179,6 +2179,7 @@ export default { users: 'usuarios', invited: 'invitó', removed: 'eliminó', + leftWorkspace: 'salió del espacio de trabajo', to: 'a', from: 'de', }, diff --git a/src/libs/API/parameters/LeavePolicyParams.ts b/src/libs/API/parameters/LeavePolicyParams.ts new file mode 100644 index 000000000000..ad728f06e6ee --- /dev/null +++ b/src/libs/API/parameters/LeavePolicyParams.ts @@ -0,0 +1,6 @@ +type LeavePolicyParams = { + policyID: string; + email: string; +}; + +export default LeavePolicyParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 2636d8f393c6..385aabf8acff 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -205,3 +205,4 @@ export type {default as SetPolicyForeignCurrencyDefaultParams} from './SetPolicy export type {default as SetPolicyCurrencyDefaultParams} from './SetPolicyCurrencyDefaultParams'; export type {default as UpdatePolicyConnectionConfigParams} from './UpdatePolicyConnectionConfigParams'; 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 d82a021c4670..91b95dd6327e 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -205,6 +205,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; @@ -409,6 +410,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/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 1b9423c70ee7..b162a7c6df93 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -130,6 +130,11 @@ const isFreeGroupPolicy = (policy: OnyxEntry | EmptyObject): boolean => const isPolicyMember = (policyID: string, policies: OnyxCollection): 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, 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. * @@ -341,6 +346,7 @@ export { getCountOfEnabledTagsOfList, isPendingDeletePolicy, isPolicyMember, + isPolicyOwner, isPaidGroupPolicy, extractPolicyIDFromPath, getPathWithoutPolicyID, diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 20d5ab0905e7..e339dce5b81b 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -161,7 +161,8 @@ function isMemberChangeAction(reportAction: OnyxEntry) { 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 ); } @@ -169,6 +170,10 @@ function isInviteMemberAction(reportAction: OnyxEntry) { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.INVITE_TO_ROOM; } +function isLeavePolicyAction(reportAction: OnyxEntry) { + return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.POLICYCHANGELOG.LEAVE_POLICY; +} + function isReimbursementDeQueuedAction(reportAction: OnyxEntry): reportAction is ReportActionBase & OriginalMessageReimbursementDequeued { return reportAction?.actionName === CONST.REPORT.ACTIONS.TYPE.REIMBURSEMENTDEQUEUED; } @@ -861,9 +866,17 @@ function isNotifiableReportAction(reportAction: OnyxEntry): boolea function getMemberChangeMessageElements(reportAction: OnyxEntry): readonly MemberChangeMessageElement[] { const isInviteAction = isInviteMemberAction(reportAction); + const isLeaveAction = isLeavePolicyAction(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 ?? []; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b03abbcdf9bb..1b60cd611d57 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4687,9 +4687,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 ); } @@ -5177,6 +5177,15 @@ function getWorkspaceChats(policyID: string, accountIDs: number[]): Array 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> { + 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 */ @@ -5761,6 +5770,11 @@ function canBeAutoReimbursed(report: OnyxEntry, policy: OnyxEntry): boolean { + return report?.ownerAccountID === currentUserPersonalDetails?.accountID; +} + function isAllowedToApproveExpenseReport(report: OnyxEntry, approverAccountID?: number): boolean { const policy = getPolicy(report?.policyID); const {preventSelfApproval} = policy; @@ -5820,6 +5834,10 @@ function hasActionsWithErrors(reportID: string): boolean { return Object.values(reportActions).some((action) => !isEmptyObject(action.errors)); } +function canLeavePolicyExpenseChat(report: OnyxEntry, policy: OnyxEntry): boolean { + return isPolicyExpenseChat(report) && !(PolicyUtils.isPolicyAdmin(policy) || PolicyUtils.isPolicyOwner(policy, currentUserAccountID ?? -1) || isReportOwner(report)); +} + function getReportActionActorAccountID(reportAction: OnyxEntry, iouReport: OnyxEntry | undefined): number | undefined { switch (reportAction?.actionName) { case CONST.REPORT.ACTIONS.TYPE.REPORTPREVIEW: @@ -6014,6 +6032,7 @@ export { isDM, isSelfDM, getWorkspaceChats, + getAllWorkspaceReports, shouldDisableRename, hasSingleParticipant, getReportRecipientAccountIDs, @@ -6072,6 +6091,7 @@ export { hasUpdatedTotal, isReportFieldDisabled, getAvailableReportFields, + isReportOwner, getReportFieldKey, reportFieldsEnabled, getAllAncestorReportActionIDs, @@ -6093,6 +6113,7 @@ export { hasActionsWithErrors, getReportActionActorAccountID, getGroupChatName, + canLeavePolicyExpenseChat, getOutstandingChildRequest, getVisibleChatMemberAccountIDs, getParticipantAccountIDs, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 69ce2cb90ba4..3f1b354d70fc 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -354,6 +354,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 { diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index c212ad8e5ef8..74965bcaca13 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -25,6 +25,7 @@ import type { EnablePolicyTagsParams, EnablePolicyTaxesParams, EnablePolicyWorkflowsParams, + LeavePolicyParams, OpenDraftWorkspaceRequestParams, OpenPolicyCategoriesPageParams, OpenPolicyDistanceRatesPageParams, @@ -774,7 +775,7 @@ function clearWorkspaceReimbursementErrors(policyID: string) { /** * Build optimistic data for removing users from the announcement room */ -function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: number[]): AnnounceRoomMembersOnyxData { +function removeOptimisticAnnounceRoomMembers(policyID: string, policyName: string, accountIDs: number[]): AnnounceRoomMembersOnyxData { const announceReport = ReportUtils.getRoom(CONST.REPORT.CHAT_TYPE.POLICY_ANNOUNCE, policyID); const announceRoomMembers: AnnounceRoomMembersOnyxData = { onyxOptimisticData: [], @@ -794,14 +795,27 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, accountIDs: numbe key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, value: { pendingChatMembers, + ...(accountIDs.includes(sessionAccountID) + ? { + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + oldPolicyName: policyName, + } + : {}), }, }); - announceRoomMembers.onyxFailureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, value: { pendingChatMembers: announceReport?.pendingChatMembers ?? null, + ...(accountIDs.includes(sessionAccountID) + ? { + statusNum: announceReport.statusNum, + stateNum: announceReport.stateNum, + oldPolicyName: announceReport.oldPolicyName, + } + : {}), }, }); announceRoomMembers.onyxSuccessData.push({ @@ -833,7 +847,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.id, policy.name, accountIDs); const optimisticMembersState: OnyxCollection = {}; const successMembersState: OnyxCollection = {}; @@ -962,6 +976,100 @@ function removeMembers(accountIDs: number[], policyID: string) { 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 = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + const workspaceChats = ReportUtils.getAllWorkspaceReports(policyID); + + 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, + }, + }, + ]; + 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 pendingChatMembers = ReportUtils.getPendingChatMembers([sessionAccountID], [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + + workspaceChats.forEach((report) => { + const parentReport = ReportUtils.getRootParentReport(report); + const reportToCheckOwner = isEmptyObject(parentReport) ? report : parentReport; + + if (ReportUtils.isPolicyExpenseChat(report) && !ReportUtils.isReportOwner(reportToCheckOwner)) { + return; + } + + 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 ?? '', + 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: LeavePolicyParams = { + policyID, + email: sessionEmail, + }; + 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) { const previousPolicyMembers = {...allPolicyMembers}; const memberRoles: WorkspaceMembersRoleData[] = accountIDs.reduce((result: WorkspaceMembersRoleData[], accountID: number) => { @@ -4982,6 +5090,7 @@ function setForeignCurrencyDefault(policyID: string, taxCode: string) { export { removeMembers, + leaveWorkspace, updateWorkspaceMembersRole, requestWorkspaceOwnerChange, clearWorkspaceOwnerChangeFlow, diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index ac7edc38980d..1e59cf21f542 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2084,6 +2084,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); } @@ -3202,6 +3214,7 @@ export { toggleSubscribeToChildReport, updatePolicyRoomNameAndNavigate, clearPolicyRoomNameErrors, + clearReportNotFoundErrors, clearIOUError, subscribeToNewActionEvent, notifyNewAction, diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index a89c8591ab88..1a88719c2649 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -96,6 +96,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 = ReportUtils.canLeavePolicyExpenseChat(report, policy); const reportDescription = ReportUtils.getReportDescriptionText(report); const policyName = ReportUtils.getPolicyName(report, true); const policyDescription = ReportUtils.getPolicyDescriptionText(policy); @@ -142,9 +143,9 @@ function HeaderView({report, personalDetails, parentReport, parentReportAction, Report.updateNotificationPreference(reportID, report.notificationPreference, CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, false, report.parentReportID, report.parentReportActionID), ); - const canJoinOrLeave = !isSelfDM && !isGroupChat && (isChatThread || isUserCreatedPolicyRoom || canLeaveRoom); + const canJoinOrLeave = !isSelfDM && !isGroupChat && (isChatThread || isUserCreatedPolicyRoom || canLeaveRoom || canLeavePolicyExpenseChat); const canJoin = canJoinOrLeave && !isWhisperAction && report.notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN; - const canLeave = canJoinOrLeave && ((isChatThread && !!report.notificationPreference?.length) || isUserCreatedPolicyRoom || canLeaveRoom); + const canLeave = canJoinOrLeave && ((isChatThread && !!report.notificationPreference?.length) || isUserCreatedPolicyRoom || canLeaveRoom || canLeavePolicyExpenseChat); if (canJoin) { threeDotMenuItems.push({ icon: Expensicons.ChatBubbles, @@ -152,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'), diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index d3d1a43a30b7..332e9b080558 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -391,6 +391,16 @@ function ReportScreen({ Report.openReport(reportIDFromRoute, reportActionIDFromRoute); }, [reportIDFromRoute, reportActionIDFromRoute]); + useEffect(() => { + if (!report.reportID) { + return; + } + + if (report?.errorFields?.notFound) { + Report.clearReportNotFoundErrors(report.reportID); + } + }, [report?.errorFields?.notFound, report.reportID]); + useEffect(() => { if (!report.reportID || !isFocused) { return; diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 3a24f22996c0..3f4dc7297b15 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -33,10 +33,11 @@ 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 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'; @@ -79,6 +80,9 @@ type WorkspaceListPageOnyxProps = { /** All reports shared with the user (coming from Onyx) */ reports: OnyxCollection; + + /** Session info for the currently logged in user. */ + session: OnyxEntry; }; type WorkspaceListPageProps = WithPolicyAndFullscreenLoadingProps & WorkspaceListPageOnyxProps; @@ -114,7 +118,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(); @@ -148,6 +152,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. @@ -165,6 +170,14 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r }); } + if (!(isAdmin || isOwner)) { + threeDotsMenuItems.push({ + icon: Expensicons.ChatBubbles, + text: translate('common.leave'), + onSelected: Session.checkIfActionIsAllowed(() => Policy.leaveWorkspace(item.policyID ?? '')), + }); + } + if (isAdmin && item.adminRoom) { threeDotsMenuItems.push({ icon: Expensicons.Hashtag, @@ -216,7 +229,7 @@ function WorkspacesListPage({policies, allPolicyMembers, reimbursementAccount, r ); }, - [isLessThanMediumScreen, styles.mb3, styles.mh5, styles.ph5, styles.hoveredComponentBG, translate, styles.offlineFeedback.deleted], + [isLessThanMediumScreen, styles.mb3, styles.mh5, styles.ph5, styles.hoveredComponentBG, translate, styles.offlineFeedback.deleted, session?.accountID], ); const listHeaderComponent = useCallback(() => { @@ -446,5 +459,8 @@ export default withPolicyAndFullscreenLoading( reports: { key: ONYXKEYS.COLLECTION.REPORT, }, + session: { + key: ONYXKEYS.SESSION, + }, })(WorkspacesListPage), );