Skip to content

Commit

Permalink
Merge pull request #41823 from ZhenjaHorbach/move-leave-button-into-r…
Browse files Browse the repository at this point in the history
…ow-of-the-report-details-page-update

Move Leave button into a row of the Report Details page
  • Loading branch information
marcaaron authored May 30, 2024
2 parents b4c7110 + 9f957f5 commit ec495a3
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 84 deletions.
1 change: 0 additions & 1 deletion src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2094,7 +2094,6 @@ const CONST = {
INFO: 'info',
},
REPORT_DETAILS_MENU_ITEM: {
SHARE_CODE: 'shareCode',
MEMBERS: 'member',
INVITE: 'invite',
SETTINGS: 'settings',
Expand Down
63 changes: 7 additions & 56 deletions src/components/PromotedActionsBar.tsx
Original file line number Diff line number Diff line change
@@ -1,97 +1,48 @@
import React, {useState} from 'react';
import React from 'react';
import type {StyleProp, ViewStyle} from 'react-native';
import {View} from 'react-native';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import * as HeaderUtils from '@libs/HeaderUtils';
import * as ReportActions from '@userActions/Report';
import type OnyxReport from '@src/types/onyx/Report';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
import type {ThreeDotsMenuItem} from './HeaderWithBackButton/types';
import * as Expensicons from './Icon/Expensicons';

type PromotedAction = {
key: string;
} & ThreeDotsMenuItem;

type ReportPromotedAction = (report: OnyxReport) => PromotedAction;

type PromotedActionsType = {
pin: ReportPromotedAction;
};
type PromotedActionsType = Record<'pin' | 'share', (report: OnyxReport) => PromotedAction>;

const PromotedActions = {
pin: (report) => ({
key: 'pin',
...HeaderUtils.getPinMenuItem(report),
}),
share: (report) => ({
key: 'share',
...HeaderUtils.getShareMenuItem(report),
}),
} satisfies PromotedActionsType;

type PromotedActionsBarProps = {
/** The report of actions */
report?: OnyxReport;

/** The list of actions to show */
promotedActions: PromotedAction[];

/** The style of the container */
containerStyle?: StyleProp<ViewStyle>;

/**
* Whether to show the `Leave` button.
* @deprecated Remove this prop when @src/pages/ReportDetailsPage.tsx is updated
*/
shouldShowLeaveButton?: boolean;
};

function PromotedActionsBar({report, promotedActions, containerStyle, shouldShowLeaveButton}: PromotedActionsBarProps) {
function PromotedActionsBar({promotedActions, containerStyle}: PromotedActionsBarProps) {
const theme = useTheme();
const styles = useThemeStyles();
const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false);
const {translate} = useLocalize();

if (promotedActions.length === 0) {
return null;
}

return (
<View style={[styles.flexRow, styles.ph5, styles.mb5, styles.gap2, styles.mw100, styles.w100, styles.justifyContentCenter, containerStyle]}>
{/* TODO: Remove the `Leave` button when @src/pages/ReportDetailsPage.tsx is updated */}
{shouldShowLeaveButton && report && (
// The `Leave` button is left to make the component backward compatible with the existing code.
// After the `Leave` button is moved to the `MenuItem` list, this block can be removed.
<View style={[styles.flex1]}>
<ConfirmModal
danger
title={translate('groupChat.lastMemberTitle')}
isVisible={isLastMemberLeavingGroupModalVisible}
onConfirm={() => {
setIsLastMemberLeavingGroupModalVisible(false);
ReportActions.leaveGroupChat(report.reportID);
}}
onCancel={() => setIsLastMemberLeavingGroupModalVisible(false)}
prompt={translate('groupChat.lastMemberWarning')}
confirmText={translate('common.leave')}
cancelText={translate('common.cancel')}
/>
<Button
onPress={() => {
if (Object.keys(report?.participants ?? {}).length === 1) {
setIsLastMemberLeavingGroupModalVisible(true);
return;
}

ReportActions.leaveGroupChat(report.reportID);
}}
icon={Expensicons.Exit}
style={styles.flex1}
medium
text={translate('common.leave')}
/>
</View>
)}
{promotedActions.map(({key, onSelected, ...props}) => (
<View
style={[styles.flex1, styles.mw50]}
Expand Down
11 changes: 11 additions & 0 deletions src/libs/HeaderUtils.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import type {ThreeDotsMenuItem} from '@components/HeaderWithBackButton/types';
import * as Expensicons from '@components/Icon/Expensicons';
import ROUTES from '@src/ROUTES';
import type OnyxReport from '@src/types/onyx/Report';
import * as Report from './actions/Report';
import * as Session from './actions/Session';
import * as Localize from './Localize';
import Navigation from './Navigation/Navigation';

function getPinMenuItem(report: OnyxReport): ThreeDotsMenuItem {
const isPinned = !!report.isPinned;
Expand All @@ -15,7 +17,16 @@ function getPinMenuItem(report: OnyxReport): ThreeDotsMenuItem {
};
}

function getShareMenuItem(report: OnyxReport): ThreeDotsMenuItem {
return {
icon: Expensicons.QrCode,
text: Localize.translateLocal('common.share'),
onSelected: () => Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE.getRoute(report?.reportID ?? '')),
};
}

export {
// eslint-disable-next-line import/prefer-default-export
getPinMenuItem,
getShareMenuItem,
};
2 changes: 1 addition & 1 deletion src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ const isPolicyEmployee = (policyID: string, policies: OnyxCollection<Policy>): b
/**
* Checks if the current user is an owner (creator) of the policy.
*/
const isPolicyOwner = (policy: OnyxEntry<Policy>, currentUserAccountID: number): boolean => policy?.ownerAccountID === currentUserAccountID;
const isPolicyOwner = (policy: OnyxEntry<Policy> | EmptyObject, currentUserAccountID: number): boolean => policy?.ownerAccountID === currentUserAccountID;

/**
* Create an object mapping member emails to their accountIDs. Filter for members without errors if includeMemberWithErrors is false, and get the login email from the personalDetail object using the accountID.
Expand Down
85 changes: 59 additions & 26 deletions src/pages/ReportDetailsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {useRoute} from '@react-navigation/native';
import type {StackScreenProps} from '@react-navigation/stack';
import React, {useEffect, useMemo} from 'react';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import {View} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
Expand Down Expand Up @@ -29,6 +29,7 @@ import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PolicyUtils from '@libs/PolicyUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as Report from '@userActions/Report';
import ConfirmModal from '@src/components/ConfirmModal';
import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -49,6 +50,7 @@ type ReportDetailsPageMenuItem = {
action: () => void;
brickRoadIndicator?: ValueOf<typeof CONST.BRICK_ROAD_INDICATOR_STATUS>;
subtitle?: number;
shouldShowRightIcon?: boolean;
};

type ReportDetailsPageOnyxProps = {
Expand All @@ -65,10 +67,11 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
const {isOffline} = useNetwork();
const styles = useThemeStyles();
const route = useRoute();
const [isLastMemberLeavingGroupModalVisible, setIsLastMemberLeavingGroupModalVisible] = useState(false);
const policy = useMemo(() => policies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID ?? ''}`], [policies, report?.policyID]);
const isPolicyAdmin = useMemo(() => PolicyUtils.isPolicyAdmin(policy ?? null), [policy]);
const isPolicyEmployee = useMemo(() => PolicyUtils.isPolicyEmployee(report?.policyID ?? '', policies), [report?.policyID, policies]);
const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report);
const isPolicyExpenseChat = useMemo(() => ReportUtils.isPolicyExpenseChat(report), [report]);
const shouldUseFullTitle = useMemo(() => ReportUtils.shouldUseFullTitleToDisplay(report), [report]);
const isChatRoom = useMemo(() => ReportUtils.isChatRoom(report), [report]);
const isUserCreatedPolicyRoom = useMemo(() => ReportUtils.isUserCreatedPolicyRoom(report), [report]);
Expand Down Expand Up @@ -101,6 +104,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
});

const isGroupDMChat = useMemo(() => ReportUtils.isDM(report) && participants.length > 1, [report, participants.length]);

const isPrivateNotesFetchTriggered = report?.isLoadingPrivateNotes !== undefined;

const isSelfDM = useMemo(() => ReportUtils.isSelfDM(report), [report]);
Expand All @@ -114,23 +118,22 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
Report.getReportPrivateNote(report?.reportID ?? '');
}, [report?.reportID, isOffline, isPrivateNotesFetchTriggered, isSelfDM]);

const leaveChat = useCallback(() => {
if (isChatRoom) {
const isWorkspaceMemberLeavingWorkspaceRoom = (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || isPolicyExpenseChat) && isPolicyEmployee;
Report.leaveRoom(report.reportID, isWorkspaceMemberLeavingWorkspaceRoom);
return;
}
Report.leaveGroupChat(report.reportID);
}, [isChatRoom, isPolicyEmployee, isPolicyExpenseChat, report.reportID, report.visibility]);

const menuItems: ReportDetailsPageMenuItem[] = useMemo(() => {
const items: ReportDetailsPageMenuItem[] = [];

if (isSelfDM) {
return [];
}

if (!isGroupDMChat) {
items.push({
key: CONST.REPORT_DETAILS_MENU_ITEM.SHARE_CODE,
translationKey: 'common.shareCode',
icon: Expensicons.QrCode,
isAnonymousAction: true,
action: () => Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS_SHARE_CODE.getRoute(report?.reportID ?? '')),
});
}

if (isArchivedRoom) {
return items;
}
Expand All @@ -152,6 +155,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
icon: Expensicons.Users,
subtitle: activeChatMembers.length,
isAnonymousAction: false,
shouldShowRightIcon: true,
action: () => {
if (isUserCreatedPolicyRoom || isChatThread || isPolicyExpenseChat) {
Navigation.navigate(ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? ''));
Expand All @@ -166,6 +170,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
translationKey: 'common.invite',
icon: Expensicons.Users,
isAnonymousAction: false,
shouldShowRightIcon: true,
action: () => {
Navigation.navigate(ROUTES.ROOM_INVITE.getRoute(report?.reportID ?? ''));
},
Expand All @@ -177,6 +182,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
translationKey: 'common.settings',
icon: Expensicons.Gear,
isAnonymousAction: false,
shouldShowRightIcon: true,
action: () => {
Navigation.navigate(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? ''));
},
Expand All @@ -189,15 +195,32 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
translationKey: 'privateNotes.title',
icon: Expensicons.Pencil,
isAnonymousAction: false,
shouldShowRightIcon: true,
action: () => ReportUtils.navigateToPrivateNotes(report, session),
brickRoadIndicator: Report.hasErrorInPrivateNotes(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined,
});
}

if (isGroupChat || (isChatRoom && ReportUtils.canLeaveChat(report, policy ?? null))) {
items.push({
key: CONST.REPORT_DETAILS_MENU_ITEM.LEAVE_ROOM,
translationKey: 'common.leave',
icon: Expensicons.Exit,
isAnonymousAction: true,
action: () => {
if (Object.keys(report?.participants ?? {}).length === 1 && isGroupChat) {
setIsLastMemberLeavingGroupModalVisible(true);
return;
}

leaveChat();
},
});
}

return items;
}, [
isSelfDM,
isGroupDMChat,
isArchivedRoom,
isGroupChat,
isDefaultRoom,
Expand All @@ -209,8 +232,11 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
report,
isMoneyRequestReport,
isInvoiceReport,
isChatRoom,
policy,
activeChatMembers.length,
session,
leaveChat,
]);

const displayNamesWithTooltips = useMemo(() => {
Expand Down Expand Up @@ -276,10 +302,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
);
}, [report, icons, isMoneyRequestReport, isInvoiceReport, isGroupChat, isThread, styles]);

const reportName =
ReportUtils.isDeprecatedGroupDM(report) || ReportUtils.isGroupChat(report)
? ReportUtils.getGroupChatName(undefined, false, report.reportID ?? '')
: ReportUtils.getReportName(report);
const reportName = ReportUtils.isDeprecatedGroupDM(report) || isGroupChat ? ReportUtils.getGroupChatName(undefined, false, report.reportID ?? '') : ReportUtils.getReportName(report);
return (
<ScreenWrapper testID={ReportDetailsPage.displayName}>
<FullPageNotFoundView shouldShow={isEmptyObject(report)}>
Expand Down Expand Up @@ -329,7 +352,10 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
</View>
</View>
{shouldShowReportDescription && (
<OfflineWithFeedback pendingAction={report.pendingFields?.description}>
<OfflineWithFeedback
pendingAction={report.pendingFields?.description}
style={styles.mb5}
>
<MenuItemWithTopDescription
shouldShowRightIcon={canEditReportDescription}
interactive={canEditReportDescription}
Expand All @@ -341,13 +367,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
/>
</OfflineWithFeedback>
)}
{isGroupChat && (
<PromotedActionsBar
report={report}
promotedActions={[PromotedActions.pin(report)]}
shouldShowLeaveButton
/>
)}
<PromotedActionsBar promotedActions={[PromotedActions.pin(report), ...(isGroupDMChat ? [] : [PromotedActions.share(report)])]} />
{menuItems.map((item) => {
const brickRoadIndicator =
ReportUtils.hasReportNameError(report) && item.key === CONST.REPORT_DETAILS_MENU_ITEM.SETTINGS ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : undefined;
Expand All @@ -359,12 +379,25 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
icon={item.icon}
onPress={item.action}
isAnonymousAction={item.isAnonymousAction}
shouldShowRightIcon
shouldShowRightIcon={item.shouldShowRightIcon}
brickRoadIndicator={brickRoadIndicator ?? item.brickRoadIndicator}
/>
);
})}
</ScrollView>
<ConfirmModal
danger
title={translate('groupChat.lastMemberTitle')}
isVisible={isLastMemberLeavingGroupModalVisible}
onConfirm={() => {
setIsLastMemberLeavingGroupModalVisible(false);
Report.leaveGroupChat(report.reportID);
}}
onCancel={() => setIsLastMemberLeavingGroupModalVisible(false)}
prompt={translate('groupChat.lastMemberWarning')}
confirmText={translate('common.leave')}
cancelText={translate('common.cancel')}
/>
</FullPageNotFoundView>
</ScreenWrapper>
);
Expand Down

0 comments on commit ec495a3

Please sign in to comment.