import { SessionDataTestId } from 'react'; import { useSelector } from 'react-redux'; import styled from 'styled-components'; import { useIsIncomingRequest, useIsOutgoingRequest, useNicknameOrProfileNameOrShortenedPubkey, } from '../../hooks/useParamSelector'; import { PubKey } from '../../session/types'; import { SessionUtilContact } from '../../session/utils/libsession/libsession_utils_contacts'; import { hasSelectedConversationIncomingMessages, hasSelectedConversationOutgoingMessages, useSelectedHasMessages, } from '../../state/selectors/conversations'; import { getSelectedCanWrite, useSelectedConversationIdOrigin, useSelectedConversationKey, useSelectedHasDisabledBlindedMsgRequests, useSelectedIsApproved, useSelectedIsGroupOrCommunity, useSelectedIsGroupV2, useSelectedIsNoteToSelf, useSelectedIsPrivate, useSelectedIsPublic, useSelectedNicknameOrProfileNameOrShortenedPubkey, } from '../../state/selectors/selectedConversation'; import { useLibGroupDestroyed, useLibGroupInviteGroupName, useLibGroupInvitePending, useLibGroupKicked, useLibGroupWeHaveSecretKey, } from '../../state/selectors/userGroups'; import { localize } from '../../localization/localeTools'; import { SessionHtmlRenderer } from '../basic/SessionHTMLRenderer'; const Container = styled.div<{ noExtraPadding: boolean }>` display: flex; flex-direction: row; justify-content: center; background-color: var(--background-secondary-color); padding: ${props => (props.noExtraPadding ? '' : 'var(--margins-lg)')}; `; const TextInner = styled.div` color: var(--text-secondary-color); text-align: center; max-width: 390px; `; function TextNotification({ html, dataTestId, noExtraPadding, }: { html: string; dataTestId: SessionDataTestId; noExtraPadding: boolean; }) { return ( ); } /** * This component is used to display a warning when the user is sending a message request. * */ export const ConversationOutgoingRequestExplanation = () => { const selectedConversation = useSelectedConversationKey(); const isOutgoingMessageRequest = useIsOutgoingRequest(selectedConversation); // FIXME: we shouldn't need to rely on incoming messages being present (they can be deleted, expire, etc) const hasIncomingMessages = useSelector(hasSelectedConversationIncomingMessages); const showMsgRequestUI = selectedConversation && isOutgoingMessageRequest; const selectedIsPrivate = useSelectedIsPrivate(); if (!showMsgRequestUI || hasIncomingMessages || !selectedIsPrivate) { return null; } const contactFromLibsession = SessionUtilContact.getContactCached(selectedConversation); // Note: we want to display this description when the conversation is private (or blinded) AND // - the conversation is brand new (and not saved yet in libsession: transient conversation), // - the conversation exists in libsession but we are not approved yet. // This works because a blinded conversation is not saved in libsession currently, and will only be once approved_me is true if (!contactFromLibsession || !contactFromLibsession.approvedMe) { return ( {window.i18n('messageRequestPendingDescription')} ); } return null; }; /** * This component is used to display a warning when the user is responding to a message request. * */ export const ConversationIncomingRequestExplanation = () => { const selectedConversation = useSelectedConversationKey(); const isIncomingMessageRequest = useIsIncomingRequest(selectedConversation); const showMsgRequestUI = selectedConversation && isIncomingMessageRequest; const hasOutgoingMessages = useSelector(hasSelectedConversationOutgoingMessages); const isGroupV2 = useSelectedIsGroupV2(); if (isGroupV2) { return ; } if (!showMsgRequestUI || hasOutgoingMessages) { return null; } return ( ); }; const GroupRequestExplanation = () => { const selectedConversation = useSelectedConversationKey(); const isIncomingMessageRequest = useIsIncomingRequest(selectedConversation); const isGroupV2 = useSelectedIsGroupV2(); const showMsgRequestUI = selectedConversation && isIncomingMessageRequest; // isApproved in DB is tracking the pending state for a group const isApproved = useSelectedIsApproved(); const isGroupPendingInvite = useLibGroupInvitePending(selectedConversation); if (!showMsgRequestUI || isApproved || !isGroupV2 || !isGroupPendingInvite) { return null; } return ( ); }; const InvitedToGroupControlMessage = () => { const selectedConversation = useSelectedConversationKey(); const isGroupV2 = useSelectedIsGroupV2(); const hasMessages = useSelectedHasMessages(); const isApproved = useSelectedIsApproved(); const groupName = useLibGroupInviteGroupName(selectedConversation) || window.i18n('unknown'); const conversationOrigin = useSelectedConversationIdOrigin(); const adminNameInvitedUs = useNicknameOrProfileNameOrShortenedPubkey(conversationOrigin) || window.i18n('unknown'); const isGroupPendingInvite = useLibGroupInvitePending(selectedConversation); const weHaveSecretKey = useLibGroupWeHaveSecretKey(selectedConversation); if ( !selectedConversation || isApproved || hasMessages || // we don't want to display that "xx invited you" message if there are already other messages (incoming or outgoing) !isGroupV2 || (conversationOrigin && !PubKey.is05Pubkey(conversationOrigin)) || !isGroupPendingInvite ) { return null; } // when restoring from seed we might not have the pubkey of who invited us, in that case, we just use a fallback const html = conversationOrigin ? weHaveSecretKey ? window.i18n('groupInviteReinvite', { group_name: groupName, name: adminNameInvitedUs, }) : window.i18n('messageRequestGroupInvite', { group_name: groupName, name: adminNameInvitedUs, }) : weHaveSecretKey ? window.i18n('groupInviteReinviteYou', { group_name: groupName }) : window.i18n('groupInviteYou'); return ( ); }; export const InvitedToGroup = () => { return ( ); }; export const NoMessageInConversation = () => { const selectedConversation = useSelectedConversationKey(); const hasMessages = useSelectedHasMessages(); const isGroupV2 = useSelectedIsGroupV2(); const isGroupOrCommunity = useSelectedIsGroupOrCommunity(); const isInvitePending = useLibGroupInvitePending(selectedConversation); const isMe = useSelectedIsNoteToSelf(); const canWrite = useSelector(getSelectedCanWrite); const privateBlindedAndBlockingMsgReqs = useSelectedHasDisabledBlindedMsgRequests(); const isPrivate = useSelectedIsPrivate(); const isIncomingRequest = useIsIncomingRequest(selectedConversation); const isKickedFromGroup = useLibGroupKicked(selectedConversation); const isGroupDestroyed = useLibGroupDestroyed(selectedConversation); const name = useSelectedNicknameOrProfileNameOrShortenedPubkey(); const isPublic = useSelectedIsPublic(); const getHtmlToRender = () => { // First, handle the "noteToSelf and various "private" cases if (isMe) { return localize('noteToSelfEmpty').toString(); } if (privateBlindedAndBlockingMsgReqs) { return localize('messageRequestsTurnedOff').withArgs({ name }).toString(); } if (isPrivate) { // "You have no messages from X. Send a message to start the conversation!" return localize('groupNoMessages').withArgs({ group_name: name }).toString(); } if (isPublic) { return localize('conversationsEmpty').withArgs({ conversation_name: name }).toString(); } // a "group but not public" is a legacy or a groupv2 (isPublic is handled just above) if (isGroupOrCommunity) { if (isGroupDestroyed) { return localize('groupDeletedMemberDescription').withArgs({ group_name: name }).toString(); } if (isKickedFromGroup) { return localize('groupRemovedYou').withArgs({ group_name: name }).toString(); } if (canWrite) { // "You have no messages from X. Send a message to start the conversation!" return localize('groupNoMessages').withArgs({ group_name: name }).toString(); } // if we cannot write for some reason, don't show the "send a message" part return localize('conversationsEmpty').withArgs({ conversation_name: name }).toString(); } return localize('conversationsEmpty').withArgs({ conversation_name: name }).toString(); }; // groupV2 use its own invite logic as part of if ( !selectedConversation || hasMessages || (isGroupV2 && isInvitePending) || (isPrivate && isIncomingRequest) ) { return null; } return ( ); };