fix: clean up delete/leave group&communities button/menuitems

pull/3281/head
Audric Ackermann 6 months ago
parent 3ee69437b0
commit c3cfd37950
No known key found for this signature in database

@ -46,7 +46,6 @@ import { Reactions } from '../../../../util/reactions';
import { SessionContextMenuContainer } from '../../../SessionContextMenuContainer';
import { SessionEmojiPanel, StyledEmojiPanel } from '../../SessionEmojiPanel';
import { MessageReactBar } from './MessageReactBar';
import { showCopyAccountIdAction } from '../../../menu/items/CopyAccountId';
import { CopyAccountIdMenuItem } from '../../../menu/items/CopyAccountId/CopyAccountIdMenuItem';
import { Localizer } from '../../../basic/Localizer';
import { ItemWithDataTestId } from '../../../menu/items/MenuItemWithDataTestId';
@ -54,6 +53,7 @@ import { getMenuAnimation } from '../../../menu/MenuAnimation';
import { WithMessageId } from '../../../../session/types/with';
import { DeleteItem } from '../../../menu/items/DeleteMessage/DeleteMessageMenuItem';
import { RetryItem } from '../../../menu/items/RetrySend/RetrySendMenuItem';
import { showCopyAccountIdAction } from '../../../menu/items/CopyAccountId/guard';
export type MessageContextMenuSelectorProps = Pick<
MessageRenderingProps,

@ -1,7 +1,7 @@
import { compact, flatten, isEqual } from 'lodash';
import { SessionDataTestId, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import useInterval from 'react-use/lib/useInterval';
import styled from 'styled-components';
import { Data } from '../../../../data/data';
@ -10,6 +10,10 @@ import { SessionIconButton } from '../../../icon';
import {
useConversationUsername,
useDisappearingMessageSettingText,
useIsClosedGroup,
useIsKickedFromGroup,
useIsPublic,
useLastMessageIsLeaveError,
} from '../../../../hooks/useParamSelector';
import { useIsRightPanelShowing } from '../../../../hooks/useUI';
import {
@ -36,7 +40,6 @@ import {
useSelectedIsGroupV2,
useSelectedIsKickedFromGroup,
useSelectedIsPublic,
useSelectedLastMessage,
useSelectedSubscriberCount,
useSelectedWeAreAdmin,
} from '../../../../state/selectors/selectedConversation';
@ -49,11 +52,13 @@ import { PanelButtonGroup, PanelIconButton } from '../../../buttons';
import { MediaItemType } from '../../../lightbox/LightboxGallery';
import { MediaGallery } from '../../media-gallery/MediaGallery';
import { Header, StyledScrollContainer } from './components';
import {
ConversationInteractionStatus,
ConversationInteractionType,
} from '../../../../interactions/types';
import { Localizer } from '../../../basic/Localizer';
import {
showDeleteGroupItem,
showLeaveGroupItem,
} from '../../../menu/items/LeaveAndDeleteGroup/guard';
import { getIsMessageRequestOverlayShown } from '../../../../state/selectors/section';
import { showLeaveCommunityItem } from '../../../menu/items/LeaveCommunity/guard';
async function getMediaGalleryProps(conversationId: string): Promise<{
documents: Array<MediaItemType>;
@ -199,12 +204,94 @@ const StyledName = styled.h4`
font-size: var(--font-size-md);
`;
const LeaveCommunityPanelButton = () => {
const selectedConvoKey = useSelectedConversationKey();
const selectedUsername = useConversationUsername(selectedConvoKey) || selectedConvoKey;
const isPublic = useIsPublic(selectedConvoKey);
const showItem = showLeaveCommunityItem({ isPublic });
if (!selectedConvoKey || !showItem) {
return null;
}
return (
<PanelIconButton
text={window.i18n('communityLeave')}
dataTestId="leave-group-button"
onClick={() => void showLeaveGroupByConvoId(selectedConvoKey, selectedUsername)}
color={'var(--danger-color)'}
iconType={'delete'}
/>
);
};
const DeleteGroupPanelButton = () => {
const convoId = useSelectedConversationKey();
const isGroup = useIsClosedGroup(convoId);
const isMessageRequestShown = useSelector(getIsMessageRequestOverlayShown);
const isKickedFromGroup = useIsKickedFromGroup(convoId) || false;
const lastMessageIsLeaveError = useLastMessageIsLeaveError(convoId);
const selectedUsername = useConversationUsername(convoId) || convoId;
const showItem = showDeleteGroupItem({
isGroup,
isKickedFromGroup,
isMessageRequestShown,
lastMessageIsLeaveError,
});
if (!showItem || !convoId) {
return null;
}
const token = PubKey.is03Pubkey(convoId) ? 'groupDelete' : 'conversationsDelete';
return (
<PanelIconButton
text={window.i18n(token)}
dataTestId="leave-group-button"
onClick={() => void showLeaveGroupByConvoId(convoId, selectedUsername)}
color={'var(--danger-color)'}
iconType={'delete'}
/>
);
};
const LeaveGroupPanelButton = () => {
const selectedConvoKey = useSelectedConversationKey();
const isGroup = useIsClosedGroup(selectedConvoKey);
const username = useConversationUsername(selectedConvoKey) || selectedConvoKey;
const isMessageRequestShown = useSelector(getIsMessageRequestOverlayShown);
const isKickedFromGroup = useIsKickedFromGroup(selectedConvoKey) || false;
const lastMessageIsLeaveError = useLastMessageIsLeaveError(selectedConvoKey);
const showItem = showLeaveGroupItem({
isGroup,
isKickedFromGroup,
isMessageRequestShown,
lastMessageIsLeaveError,
});
if (!selectedConvoKey || !showItem) {
return null;
}
return (
<PanelIconButton
text={window.i18n('groupLeave')}
dataTestId="leave-group-button"
onClick={() => void showLeaveGroupByConvoId(selectedConvoKey, username)}
color={'var(--danger-color)'}
iconType={'delete'}
/>
);
};
export const OverlayRightPanelSettings = () => {
const [documents, setDocuments] = useState<Array<MediaItemType>>([]);
const [media, setMedia] = useState<Array<MediaItemType>>([]);
const selectedConvoKey = useSelectedConversationKey();
const selectedUsername = useConversationUsername(selectedConvoKey) || selectedConvoKey;
const isShowing = useIsRightPanelShowing();
const dispatch = useDispatch();
@ -219,7 +306,6 @@ export const OverlayRightPanelSettings = () => {
const disappearingMessagesSubtitle = useDisappearingMessageSettingText({
convoId: selectedConvoKey,
});
const lastMessage = useSelectedLastMessage();
useEffect(() => {
let isCancelled = false;
@ -269,23 +355,11 @@ export const OverlayRightPanelSettings = () => {
const commonNoShow = isKickedFromGroup || isBlocked || !isActive;
const hasDisappearingMessages = !isPublic && !commonNoShow;
const leaveGroupString = isPublic
? window.i18n('communityLeave')
: lastMessage?.interactionType === ConversationInteractionType.Leave &&
lastMessage?.interactionStatus === ConversationInteractionStatus.Error
? window.i18n('conversationsDelete')
: isKickedFromGroup
? window.i18n('groupDelete')
: window.i18n('groupLeave');
const showUpdateGroupNameButton = isGroup && weAreAdmin && !commonNoShow; // legacy groups non-admin cannot change groupname anymore
const showAddRemoveModeratorsButton = weAreAdmin && !commonNoShow && isPublic;
const showUpdateGroupMembersButton = !isPublic && isGroup && !commonNoShow;
const deleteConvoAction = () => {
void showLeaveGroupByConvoId(selectedConvoKey, selectedUsername);
};
return (
<StyledScrollContainer>
<Flex container={true} flexDirection={'column'} alignItems={'center'}>
@ -399,14 +473,11 @@ export const OverlayRightPanelSettings = () => {
<MediaGallery documents={documents} media={media} />
{isGroup && (
<PanelIconButton
text={leaveGroupString}
dataTestId="leave-group-button"
disabled={isKickedFromGroup}
onClick={() => void deleteConvoAction()}
color={'var(--danger-color)'}
iconType={'delete'}
/>
<>
<LeaveGroupPanelButton />
<DeleteGroupPanelButton />
<LeaveCommunityPanelButton />
</>
)}
</PanelButtonGroup>
<SpacerLG />

@ -51,7 +51,7 @@ import { Message } from '../../../message/message-item/Message';
import { AttachmentInfo, MessageInfo } from './components';
import { AttachmentCarousel } from './components/AttachmentCarousel';
import { ToastUtils } from '../../../../../session/utils';
import { showCopyAccountIdAction } from '../../../../menu/items/CopyAccountId';
import { showCopyAccountIdAction } from '../../../../menu/items/CopyAccountId/guard';
// NOTE we override the default max-widths when in the detail isDetailView
const StyledMessageBody = styled.div`

@ -21,7 +21,6 @@ import {
DeleteMessagesMenuItem,
DeletePrivateConversationMenuItem,
InviteContactMenuItem,
LeaveGroupOrCommunityMenuItem,
MarkAllReadMenuItem,
MarkConversationUnreadMenuItem,
NotificationForConvoMenuItem,
@ -32,6 +31,9 @@ import { CopyCommunityUrlMenuItem } from './items/CopyCommunityUrl/CopyCommunity
import { CopyAccountIdMenuItem } from './items/CopyAccountId/CopyAccountIdMenuItem';
import { ItemWithDataTestId } from './items/MenuItemWithDataTestId';
import { getMenuAnimation } from './MenuAnimation';
import { LeaveCommunityMenuItem } from './items/LeaveCommunity/LeaveCommunityMenuItem';
import { LeaveGroupMenuItem } from './items/LeaveAndDeleteGroup/LeaveGroupMenuItem';
import { DeleteGroupMenuItem } from './items/LeaveAndDeleteGroup/DeleteGroupMenuItem';
export type PropsContextConversationItem = {
triggerId: string;
@ -57,7 +59,6 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
{/* Generic actions */}
<PinConversationMenuItem />
<NotificationForConvoMenuItem />
<BlockMenuItem />
<CopyCommunityUrlMenuItem convoId={convoIdFromContext} />
<CopyAccountIdMenuItem pubkey={convoIdFromContext} />
@ -73,7 +74,9 @@ const ConversationListItemContextMenu = (props: PropsContextConversationItem) =>
<InviteContactMenuItem />
<DeleteMessagesMenuItem />
<DeletePrivateConversationMenuItem />
<LeaveGroupOrCommunityMenuItem />
<LeaveCommunityMenuItem />
<LeaveGroupMenuItem />
<DeleteGroupMenuItem />
<ShowUserDetailsMenuItem />
</Menu>
</SessionContextMenuContainer>

@ -15,7 +15,6 @@ import {
useIsPrivate,
useIsPrivateAndFriend,
useIsPublic,
useLastMessage,
useNicknameOrProfileNameOrShortenedPubkey,
useNotificationSetting,
useWeAreAdmin,
@ -31,8 +30,7 @@ import {
showAddModeratorsByConvoId,
showBanUserByConvoId,
showInviteContactByConvoId,
showLeaveGroupByConvoId,
showLeavePrivateConversationByConvoId,
showDeletePrivateConversationByConvoId,
showRemoveModeratorsByConvoId,
showUnbanUserByConvoId,
showUpdateGroupNameByConvoId,
@ -58,10 +56,6 @@ import { useSelectedConversationKey } from '../../state/selectors/selectedConver
import type { LocalizerToken } from '../../types/localizer';
import { SessionButtonColor } from '../basic/SessionButton';
import { ItemWithDataTestId } from './items/MenuItemWithDataTestId';
import {
ConversationInteractionStatus,
ConversationInteractionType,
} from '../../interactions/types';
import { useLibGroupDestroyed } from '../../state/selectors/userGroups';
import { NetworkTime } from '../../util/NetworkTime';
@ -155,34 +149,6 @@ export const DeletePrivateContactMenuItem = () => {
return null;
};
export const LeaveGroupOrCommunityMenuItem = () => {
const convoId = useConvoIdFromContext();
const username = useConversationUsername(convoId) || convoId;
const isPrivate = useIsPrivate(convoId);
const isPublic = useIsPublic(convoId);
const lastMessage = useLastMessage(convoId);
const isMessageRequestShown = useSelector(getIsMessageRequestOverlayShown);
if (!isPrivate && !isMessageRequestShown) {
return (
<ItemWithDataTestId
onClick={() => {
void showLeaveGroupByConvoId(convoId, username);
}}
>
{isPublic
? window.i18n('communityLeave')
: lastMessage?.interactionType === ConversationInteractionType.Leave &&
lastMessage?.interactionStatus === ConversationInteractionStatus.Error
? window.i18n('conversationsDelete')
: window.i18n('groupLeave')}
</ItemWithDataTestId>
);
}
return null;
};
export const ShowUserDetailsMenuItem = () => {
const dispatch = useDispatch();
const convoId = useConvoIdFromContext();
@ -427,7 +393,7 @@ export const DeletePrivateConversationMenuItem = () => {
return (
<ItemWithDataTestId
onClick={() => {
showLeavePrivateConversationByConvoId(convoId);
showDeletePrivateConversationByConvoId(convoId);
}}
>
{isMe ? window.i18n('noteToSelfHide') : window.i18n('conversationsDelete')}

@ -1,8 +1,8 @@
import { useIsPrivate } from '../../../../hooks/useParamSelector';
import { copyPublicKeyByConvoId } from '../../../../interactions/conversationInteractions';
import { Localizer } from '../../../basic/Localizer';
import { showCopyAccountIdAction } from '.';
import { ItemWithDataTestId } from '../MenuItemWithDataTestId';
import { showCopyAccountIdAction } from './guard';
/**
* Can be used to copy the conversation AccountID or the message's author sender'id.
@ -11,8 +11,6 @@ import { ItemWithDataTestId } from '../MenuItemWithDataTestId';
export const CopyAccountIdMenuItem = ({ pubkey }: { pubkey: string }): JSX.Element | null => {
const isPrivate = useIsPrivate(pubkey);
// we want to show the copyId for communities only
if (showCopyAccountIdAction({ isPrivate, pubkey })) {
return (
<ItemWithDataTestId

@ -1,5 +1,8 @@
import { PubKey } from '../../../../session/types';
/**
* We want to show the copyId for private and not blinded chats only
*/
export function showCopyAccountIdAction({
isPrivate,
pubkey,

@ -1,8 +1,8 @@
import { showCopyCommunityUrlMenuItem } from '.';
import { useIsPublic } from '../../../../hooks/useParamSelector';
import { copyPublicKeyByConvoId } from '../../../../interactions/conversationInteractions';
import { Localizer } from '../../../basic/Localizer';
import { ItemWithDataTestId } from '../MenuItemWithDataTestId';
import { showCopyCommunityUrlMenuItem } from './guard';
export const CopyCommunityUrlMenuItem = ({ convoId }: { convoId: string }): JSX.Element | null => {
const isPublic = useIsPublic(convoId);

@ -0,0 +1,46 @@
import { useSelector } from 'react-redux';
import { useConvoIdFromContext } from '../../../../contexts/ConvoIdContext';
import {
useConversationUsername,
useIsKickedFromGroup,
useIsClosedGroup,
useLastMessageIsLeaveError,
} from '../../../../hooks/useParamSelector';
import { showLeaveGroupByConvoId } from '../../../../interactions/conversationInteractions';
import { PubKey } from '../../../../session/types';
import { getIsMessageRequestOverlayShown } from '../../../../state/selectors/section';
import { ItemWithDataTestId } from '../MenuItemWithDataTestId';
import { showDeleteGroupItem } from './guard';
import { Localizer } from '../../../basic/Localizer';
export const DeleteGroupMenuItem = () => {
const convoId = useConvoIdFromContext();
const username = useConversationUsername(convoId) || convoId;
const isGroup = useIsClosedGroup(convoId);
const isMessageRequestShown = useSelector(getIsMessageRequestOverlayShown);
const isKickedFromGroup = useIsKickedFromGroup(convoId) || false;
const lastMessageIsLeaveError = useLastMessageIsLeaveError(convoId);
const showLeave = showDeleteGroupItem({
isGroup,
isKickedFromGroup,
isMessageRequestShown,
lastMessageIsLeaveError,
});
if (!showLeave) {
return null;
}
const token = PubKey.is03Pubkey(convoId) ? 'groupDelete' : 'conversationsDelete';
return (
<ItemWithDataTestId
onClick={() => {
void showLeaveGroupByConvoId(convoId, username);
}}
>
<Localizer token={token} />
</ItemWithDataTestId>
);
};

@ -0,0 +1,43 @@
import { useSelector } from 'react-redux';
import { useConvoIdFromContext } from '../../../../contexts/ConvoIdContext';
import {
useConversationUsername,
useIsKickedFromGroup,
useIsClosedGroup,
useLastMessageIsLeaveError,
} from '../../../../hooks/useParamSelector';
import { showLeaveGroupByConvoId } from '../../../../interactions/conversationInteractions';
import { getIsMessageRequestOverlayShown } from '../../../../state/selectors/section';
import { ItemWithDataTestId } from '../MenuItemWithDataTestId';
import { showLeaveGroupItem } from './guard';
import { Localizer } from '../../../basic/Localizer';
export const LeaveGroupMenuItem = () => {
const convoId = useConvoIdFromContext();
const isGroup = useIsClosedGroup(convoId);
const username = useConversationUsername(convoId) || convoId;
const isMessageRequestShown = useSelector(getIsMessageRequestOverlayShown);
const isKickedFromGroup = useIsKickedFromGroup(convoId) || false;
const lastMessageIsLeaveError = useLastMessageIsLeaveError(convoId);
const showLeave = showLeaveGroupItem({
isGroup,
isMessageRequestShown,
isKickedFromGroup,
lastMessageIsLeaveError,
});
if (!showLeave) {
return null;
}
return (
<ItemWithDataTestId
onClick={() => {
void showLeaveGroupByConvoId(convoId, username);
}}
>
<Localizer token="groupLeave" />
</ItemWithDataTestId>
);
};

@ -0,0 +1,42 @@
function sharedEnabled({
isGroup,
isMessageRequestShown,
}: Pick<Parameters<typeof showLeaveGroupItem>[0], 'isGroup' | 'isMessageRequestShown'>) {
return isGroup && !isMessageRequestShown;
}
export function showLeaveGroupItem({
isGroup,
isKickedFromGroup,
isMessageRequestShown,
lastMessageIsLeaveError,
}: {
isGroup: boolean;
isMessageRequestShown: boolean;
lastMessageIsLeaveError: boolean;
isKickedFromGroup: boolean;
}) {
// we can't try to leave the group if we were kicked from it, or if we've already tried to (lastMessageIsLeaveError is true)
return (
sharedEnabled({ isGroup, isMessageRequestShown }) &&
!isKickedFromGroup &&
!lastMessageIsLeaveError
);
}
export function showDeleteGroupItem({
isGroup,
isKickedFromGroup,
isMessageRequestShown,
lastMessageIsLeaveError,
}: {
isGroup: boolean;
isMessageRequestShown: boolean;
lastMessageIsLeaveError: boolean;
isKickedFromGroup: boolean;
}) {
return (
sharedEnabled({ isGroup, isMessageRequestShown }) &&
(isKickedFromGroup || lastMessageIsLeaveError)
);
}

@ -0,0 +1,26 @@
import { useConvoIdFromContext } from '../../../../contexts/ConvoIdContext';
import { useConversationUsername, useIsPublic } from '../../../../hooks/useParamSelector';
import { showLeaveGroupByConvoId } from '../../../../interactions/conversationInteractions';
import { Localizer } from '../../../basic/Localizer';
import { ItemWithDataTestId } from '../MenuItemWithDataTestId';
import { showLeaveCommunityItem } from './guard';
export const LeaveCommunityMenuItem = () => {
const convoId = useConvoIdFromContext();
const username = useConversationUsername(convoId) || convoId;
const isPublic = useIsPublic(convoId);
if (!showLeaveCommunityItem({ isPublic })) {
return null;
}
return (
<ItemWithDataTestId
onClick={() => {
void showLeaveGroupByConvoId(convoId, username);
}}
>
<Localizer token="communityLeave" />
</ItemWithDataTestId>
);
};

@ -0,0 +1,3 @@
export const showLeaveCommunityItem = ({ isPublic }: { isPublic: boolean }) => {
return isPublic;
};

@ -26,6 +26,7 @@ import {
useLibGroupInvitePending,
useLibGroupKicked,
} from '../state/selectors/userGroups';
import { ConversationInteractionStatus, ConversationInteractionType } from '../interactions/types';
export function useAvatarPath(convoId: string | undefined) {
const convoProps = useConversationPropsById(convoId);
@ -536,3 +537,12 @@ export function useLastMessage(convoId?: string) {
return convoProps.lastMessage;
}
export function useLastMessageIsLeaveError(convoId?: string) {
const lastMessage = useLastMessage(convoId);
return (
lastMessage?.interactionType === ConversationInteractionType.Leave &&
lastMessage?.interactionStatus === ConversationInteractionStatus.Error
);
}

@ -340,7 +340,7 @@ export async function showUpdateGroupMembersByConvoId(conversationId: string) {
window.inboxStore?.dispatch(updateGroupMembersModal({ conversationId }));
}
export function showLeavePrivateConversationByConvoId(conversationId: string) {
export function showDeletePrivateConversationByConvoId(conversationId: string) {
const conversation = ConvoHub.use().get(conversationId);
const isMe = conversation.isMe();
@ -367,7 +367,7 @@ export function showLeavePrivateConversationByConvoId(conversationId: string) {
});
await clearConversationInteractionState({ conversationId });
} catch (err) {
window.log.warn(`showLeavePrivateConversationByConvoId error: ${err}`);
window.log.warn(`showDeletePrivateConversationByConvoId error: ${err}`);
await saveConversationInteractionErrorAsMessage({
conversationId,
interactionType: isMe
@ -452,6 +452,21 @@ async function leaveGroupOrCommunityByConvoId({
}
}
/**
* Returns true if we the convo is a 03 group and if we can try to send a leave message.
*/
async function hasLeavingDetails(convoId: string) {
if (!PubKey.is03Pubkey(convoId)) {
return true;
}
const group = await UserGroupsWrapperActions.getGroup(convoId);
// we need the authData or the secretKey to be able to attempt to leave,
// otherwise we won't be able to even try
return group && (!isEmpty(group.authData) || !isEmpty(group.secretKey));
}
export async function showLeaveGroupByConvoId(conversationId: string, name: string | undefined) {
const conversation = ConvoHub.use().get(conversationId);
@ -465,15 +480,19 @@ export async function showLeaveGroupByConvoId(conversationId: string, name: stri
const isAdmin = admins.includes(UserUtils.getOurPubKeyStrFromCache());
const showOnlyGroupAdminWarning = isClosedGroup && isAdmin;
const weAreLastAdmin =
(PubKey.is05Pubkey(conversationId) && isAdmin && admins.length === 1) ||
(PubKey.is03Pubkey(conversationId) && isAdmin && admins.length === 1);
(PubKey.is05Pubkey(conversationId) || PubKey.is03Pubkey(conversationId)) &&
isAdmin &&
admins.length === 1;
const lastMessageInteractionType = conversation.get('lastMessageInteractionType');
const lastMessageInteractionStatus = conversation.get('lastMessageInteractionStatus');
const canTryToLeave = await hasLeavingDetails(conversationId);
if (
!isPublic &&
lastMessageInteractionType === ConversationInteractionType.Leave &&
lastMessageInteractionStatus === ConversationInteractionStatus.Error
((lastMessageInteractionType === ConversationInteractionType.Leave &&
lastMessageInteractionStatus === ConversationInteractionStatus.Error) ||
!canTryToLeave) // if we don't have any key to send our leave message, no need to try
) {
await leaveGroupOrCommunityByConvoId({ conversationId, isPublic, sendLeaveMessage: false });
return;

Loading…
Cancel
Save