feat: added support for tracking the confirmation process status and its type

attempting to show this status inside of the ConversationListItem message section, show confirmation modal when leaving private chats
pull/2789/head
William Grant 2 years ago
parent cd14180b63
commit c492fad4ee

@ -100,7 +100,11 @@
"deleteMessagesQuestion": "Delete $count$ messages?",
"deleteMessageQuestion": "Delete this message?",
"deleteMessages": "Delete Messages",
"deleteMessagesConfirmation": "Permanently delete the messages in this conversation?",
"deleteConversation": "Delete Conversation",
"deleteConversationConfirmation": "Are you sure you want to delete your conversation with $name$?",
"deleteConversationFailed": "Failed to leave the Conversation!",
"leaving": "Leaving...",
"deleted": "$count$ deleted",
"messageDeletedPlaceholder": "This message has been deleted",
"from": "From:",
@ -111,7 +115,6 @@
"groupMembers": "Members",
"moreInformation": "More information",
"resend": "Resend",
"deleteConversationConfirmation": "Permanently delete the messages in this conversation?",
"clear": "Clear",
"clearAllData": "Clear All Data",
"deleteAccountWarning": "This will permanently delete your messages and contacts.",
@ -256,8 +259,11 @@
"userBanFailed": "Ban failed!",
"leaveGroup": "Leave Group",
"leaveAndRemoveForEveryone": "Leave Group and Remove for Everyone",
"leaveGroupConfirmation": "Are you sure you want to leave this group?",
"leaveGroupConfirmation": "Are you sure you want to leave $name$?",
"leaveGroupConfirmationAdmin": "As you are the admin of this group, if you leave it it will be removed for every current members. Are you sure you want to leave this group?",
"leaveGroupFailed": "Failed to leave Group!",
"leaveCommunity": "Leave Community",
"leaveCommunityFailed": "Failed to leave Community!",
"cannotRemoveCreatorFromGroup": "Cannot remove this user",
"cannotRemoveCreatorFromGroupDesc": "You cannot remove this user as they are the creator of the group.",
"noContactsForGroup": "You don't have any contacts yet",

@ -40,6 +40,7 @@ import { SessionDropdown } from '../basic/SessionDropdown';
import { SpacerLG } from '../basic/Text';
import { MediaItemType } from '../lightbox/LightboxGallery';
import { MediaGallery } from './media-gallery/MediaGallery';
import { useConversationUsername } from '../../hooks/useParamSelector';
async function getMediaGalleryProps(
conversationId: string
@ -208,6 +209,8 @@ export const SessionRightPanelWithDetails = () => {
const [media, setMedia] = useState<Array<MediaItemType>>([]);
const selectedConvoKey = useSelectedConversationKey();
// TODO we need to test what happens to the localisad string without a group name
const selectedUsername = useConversationUsername(selectedConvoKey) || selectedConvoKey;
const isShowing = useSelector(isRightPanelShowing);
const subscriberCount = useSelectedSubscriberCount();

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer';
import { updateConfirmModal } from '../../state/ducks/modalDialog';
import { SpacerLG } from '../basic/Text';
@ -10,6 +10,10 @@ import { Dispatch } from '@reduxjs/toolkit';
import { shell } from 'electron';
import { MessageInteraction } from '../../interactions';
export type ConfirmationStatus = 'loading' | 'success' | 'error';
// TODO expand support for other confirmation actions
export type ConfirmationType = 'delete-conversation';
export interface SessionConfirmDialogProps {
message?: string;
messageSub?: string;
@ -29,6 +33,7 @@ export interface SessionConfirmDialogProps {
* function to run on close click. Closes modal after execution by default
*/
onClickCancel?: () => any;
okText?: string;
cancelText?: string;
hideCancel?: boolean;
@ -38,6 +43,9 @@ export interface SessionConfirmDialogProps {
iconSize?: SessionIconSize;
shouldShowConfirm?: boolean | undefined;
showExitIcon?: boolean | undefined;
status?: ConfirmationStatus;
confirmationType?: ConfirmationType;
conversationId?: string;
}
export const SessionConfirm = (props: SessionConfirmDialogProps) => {
@ -73,8 +81,10 @@ export const SessionConfirm = (props: SessionConfirmDialogProps) => {
await onClickOk();
} catch (e) {
window.log.warn(e);
window.inboxStore?.dispatch(updateConfirmModal({ ...props, status: 'error' }));
} finally {
setIsLoading(false);
window.inboxStore?.dispatch(updateConfirmModal({ ...props, status: 'success' }));
}
}
@ -102,6 +112,12 @@ export const SessionConfirm = (props: SessionConfirmDialogProps) => {
window.inboxStore?.dispatch(updateConfirmModal(null));
};
useEffect(() => {
if (isLoading) {
window.inboxStore?.dispatch(updateConfirmModal({ ...props, status: 'loading' }));
}
}, [isLoading]);
return (
<SessionWrapperModal
title={title}

@ -3,9 +3,11 @@ import { isEmpty } from 'lodash';
import React from 'react';
import { useSelector } from 'react-redux';
import {
useConfirmModalStatusAndType,
useConversationPropsById,
useHasUnread,
useIsPrivate,
useIsPublic,
useIsTyping,
} from '../../../hooks/useParamSelector';
import { isSearching } from '../../../state/selectors/search';
@ -14,6 +16,7 @@ import { TypingAnimation } from '../../conversation/TypingAnimation';
import { MessageBody } from '../../conversation/message/message-content/MessageBody';
import { OutgoingMessageStatus } from '../../conversation/message/message-content/OutgoingMessageStatus';
import { useConvoIdFromContext } from './ConvoIdContext';
import { assertUnreachable } from '../../../types/sqlSharedTypes';
function useLastMessageFromConvo(convoId: string) {
const convoProps = useConversationPropsById(convoId);
@ -27,6 +30,7 @@ export const MessageItem = () => {
const conversationId = useConvoIdFromContext();
const lastMessage = useLastMessageFromConvo(conversationId);
const isGroup = !useIsPrivate(conversationId);
const isCommunity = useIsPublic(conversationId);
const hasUnread = useHasUnread(conversationId);
const isConvoTyping = useIsTyping(conversationId);
@ -34,10 +38,37 @@ export const MessageItem = () => {
const isSearchingMode = useSelector(isSearching);
const confirmModal = useConfirmModalStatusAndType();
if (!lastMessage && !isConvoTyping) {
return null;
}
const text = lastMessage?.text || '';
let text = lastMessage?.text || '';
if (confirmModal?.conversationId === conversationId && confirmModal?.type) {
window.log.debug(`WIP: updating status for ${confirmModal?.type} ${confirmModal.status}`);
switch (confirmModal?.type) {
case 'delete-conversation':
const failText = isCommunity
? ''
: isGroup
? window.i18n('leaveGroupFailed')
: window.i18n('deleteConversationFailed');
text =
confirmModal.status === 'error'
? failText
: confirmModal.status === 'loading'
? window.i18n('leaving')
: '';
break;
default:
assertUnreachable(
confirmModal?.type,
`MessageItem: Missing case error "${confirmModal?.type}"`
);
}
}
if (isEmpty(text)) {
return null;

@ -29,6 +29,7 @@ import {
showBanUserByConvoId,
showInviteContactByConvoId,
showLeaveGroupByConvoId,
showLeavePrivateConversationbyConvoId,
showRemoveModeratorsByConvoId,
showUnbanUserByConvoId,
showUpdateGroupNameByConvoId,
@ -174,6 +175,7 @@ export const DeleteGroupOrCommunityMenuItem = () => {
export const LeaveGroupMenuItem = () => {
const convoId = useConvoIdFromContext();
const username = useConversationUsername(convoId) || convoId;
const isPublic = useIsPublic(convoId);
const isLeft = useIsLeft(convoId);
const isKickedFromGroup = useIsKickedFromGroup(convoId);
@ -183,7 +185,7 @@ export const LeaveGroupMenuItem = () => {
return (
<Item
onClick={() => {
showLeaveGroupByConvoId(convoId);
showLeaveGroupByConvoId(convoId, username);
}}
>
{window.i18n('leaveGroup')}
@ -447,6 +449,7 @@ export const DeleteMessagesMenuItem = () => {
*/
export const DeletePrivateConversationMenuItem = () => {
const convoId = useConvoIdFromContext();
const username = useConversationUsername(convoId) || convoId;
const isRequest = useIsIncomingRequest(convoId);
const isPrivate = useIsPrivate(convoId);
@ -456,11 +459,8 @@ export const DeletePrivateConversationMenuItem = () => {
return (
<Item
onClick={async () => {
await getConversationController().delete1o1(convoId, {
fromSyncMessage: false,
justHidePrivate: true,
});
onClick={() => {
showLeavePrivateConversationbyConvoId(convoId, username);
}}
>
{window.i18n('deleteConversation')}

@ -267,3 +267,14 @@ export function useMentionedUs(conversationId?: string): boolean {
export function useIsTyping(conversationId?: string): boolean {
return useConversationPropsById(conversationId)?.isTyping || false;
}
export function useConfirmModalStatusAndType() {
return useSelector((state: StateType) => {
if (!state.modals.confirmModal) {
return null;
}
const { status, confirmationType: type, conversationId } = state.modals.confirmModal;
return { status, type, conversationId };
});
}

@ -225,48 +225,90 @@ export async function showUpdateGroupMembersByConvoId(conversationId: string) {
window.inboxStore?.dispatch(updateGroupMembersModal({ conversationId }));
}
export function showLeaveGroupByConvoId(conversationId: string) {
export function showLeavePrivateConversationbyConvoId(conversationId: string, name: string) {
const conversation = getConversationController().get(conversationId);
if (!conversation.isPrivate()) {
throw new Error('showLeavePrivateConversationDialog() called with a non private convo.');
}
const onClickClose = () => {
window?.inboxStore?.dispatch(updateConfirmModal(null));
};
const onClickOk = async () => {
await getConversationController().delete1o1(conversationId, {
fromSyncMessage: false,
justHidePrivate: true,
});
onClickClose();
};
window?.inboxStore?.dispatch(
updateConfirmModal({
title: window.i18n('deleteConversation'),
message: window.i18n('deleteConversationConfirmation', [name]),
onClickOk,
okText: window.i18n('delete'),
okTheme: SessionButtonColor.Danger,
onClickClose,
confirmationType: 'delete-conversation',
conversationId,
})
);
}
export function showLeaveGroupByConvoId(conversationId: string, name?: string) {
const conversation = getConversationController().get(conversationId);
if (!conversation.isGroup()) {
throw new Error('showLeaveGroupDialog() called with a non group convo.');
}
const title = window.i18n('leaveGroup');
const message = window.i18n('leaveGroupConfirmation');
const isClosedGroup = conversation.isClosedGroup() || false;
const isPublic = conversation.isPublic() || false;
const isAdmin = (conversation.get('groupAdmins') || []).includes(
UserUtils.getOurPubKeyStrFromCache()
);
const isClosedGroup = conversation.isClosedGroup() || false;
const isPublic = conversation.isPublic() || false;
// if this is a community, or we legacy group are not admin, we can just show a confirmation dialog
const onClickClose = () => {
window?.inboxStore?.dispatch(updateConfirmModal(null));
};
const onClickOk = async () => {
if (isPublic) {
await getConversationController().deleteCommunity(conversation.id, {
fromSyncMessage: false,
});
} else {
await getConversationController().deleteClosedGroup(conversation.id, {
fromSyncMessage: false,
sendLeaveMessage: true,
});
}
onClickClose();
};
// TODO Communities don't need confirmation modal and have different logic
if (isPublic || (isClosedGroup && !isAdmin)) {
const onClickClose = () => {
window.inboxStore?.dispatch(updateConfirmModal(null));
};
window.inboxStore?.dispatch(
updateConfirmModal({
title,
message,
onClickOk: async () => {
if (isPublic) {
await getConversationController().deleteCommunity(conversation.id, {
fromSyncMessage: false,
});
} else {
await getConversationController().deleteClosedGroup(conversation.id, {
fromSyncMessage: false,
sendLeaveMessage: true,
});
}
onClickClose();
},
title: isPublic ? window.i18n('leaveCommunity') : window.i18n('leaveGroup'),
message: window.i18n('leaveGroupConfirmation', name ? [name] : undefined),
onClickOk,
okText: window.i18n('delete'),
okTheme: SessionButtonColor.Danger,
onClickClose,
confirmationType: 'delete-conversation',
conversationId,
})
);
return;
}
// TODO use different admin modal from figma with add another admin option
window.inboxStore?.dispatch(
adminLeaveClosedGroup({
conversationId,
@ -354,7 +396,7 @@ export function deleteAllMessagesByConvoIdWithConfirmation(conversationId: strin
window?.inboxStore?.dispatch(
updateConfirmModal({
title: window.i18n('deleteMessages'),
message: window.i18n('deleteConversationConfirmation'),
message: window.i18n('deleteMessagesConfirmation'),
onClickOk,
okTheme: SessionButtonColor.Danger,
onClickClose,

@ -266,6 +266,7 @@ export class ConversationController {
conversation.set({
priority: CONVERSATION_PRIORITIES.hidden,
});
// TODO based on some sort of arg we should remove the contacts messages
// We don't remove entries from the contacts wrapper, so better keep corresponding convo volatile info for now (it will be pruned if needed)
await conversation.commit(); // this updates the wrappers content to reflect the hidden state
} else {

@ -100,7 +100,11 @@ export type LocalizerKeys =
| 'deleteMessagesQuestion'
| 'deleteMessageQuestion'
| 'deleteMessages'
| 'deleteMessagesConfirmation'
| 'deleteConversation'
| 'deleteConversationConfirmation'
| 'deleteConversationFailed'
| 'leaving'
| 'deleted'
| 'messageDeletedPlaceholder'
| 'from'
@ -111,7 +115,6 @@ export type LocalizerKeys =
| 'groupMembers'
| 'moreInformation'
| 'resend'
| 'deleteConversationConfirmation'
| 'clear'
| 'clearAllData'
| 'deleteAccountWarning'
@ -258,6 +261,9 @@ export type LocalizerKeys =
| 'leaveAndRemoveForEveryone'
| 'leaveGroupConfirmation'
| 'leaveGroupConfirmationAdmin'
| 'leaveGroupFailed'
| 'leaveCommunity'
| 'leaveCommunityFailed'
| 'cannotRemoveCreatorFromGroup'
| 'cannotRemoveCreatorFromGroupDesc'
| 'noContactsForGroup'

Loading…
Cancel
Save