mirror of
https://github.com/lux-tel/session-desktop.git
synced 2026-10-30 20:33:24 +00:00
234 lines
7.7 KiB
TypeScript
234 lines
7.7 KiB
TypeScript
import { useState } from 'react';
|
|
import useKey from 'react-use/lib/useKey';
|
|
import { clone } from 'lodash';
|
|
|
|
import { PubkeyType } from 'libsession_util_nodejs';
|
|
import { getAppDispatch } from '../../state/dispatch';
|
|
import { ConvoHub } from '../../session/conversations';
|
|
import { updateGroupMembersModal, updateInviteContactModal } from '../../state/ducks/modalDialog';
|
|
import { SpacerLG } from '../basic/Text';
|
|
|
|
import { useIsPrivate, useIsPublic } from '../../hooks/useParamSelector';
|
|
import { useSet } from '../../hooks/useSet';
|
|
import { PubKey } from '../../session/types';
|
|
import { SessionUtilUserGroups } from '../../session/utils/libsession/libsession_utils_user_groups';
|
|
import { groupInfoActions } from '../../state/ducks/metaGroups';
|
|
import { useSelectedIsGroupV2 } from '../../state/selectors/selectedConversation';
|
|
import { MemberListItem } from '../MemberListItem';
|
|
import { SessionButton, SessionButtonColor, SessionButtonType } from '../basic/SessionButton';
|
|
import { SessionToggle } from '../basic/SessionToggle';
|
|
import { ConversationTypeEnum } from '../../models/types';
|
|
import { Localizer } from '../basic/Localizer';
|
|
import { tr } from '../../localization/localeTools';
|
|
import { useContactsToInviteTo } from '../../hooks/useContactsToInviteToGroup';
|
|
import { SessionSearchInput } from '../SessionSearchInput';
|
|
import { NoResultsForSearch } from '../search/NoResults';
|
|
import {
|
|
ModalBasicHeader,
|
|
ModalActionsContainer,
|
|
SessionWrapperModal,
|
|
} from '../SessionWrapperModal';
|
|
import { searchActions } from '../../state/ducks/search';
|
|
import { ToastUtils } from '../../session/utils';
|
|
import { StyledContactListInModal } from '../list/StyledContactList';
|
|
import { getFeatureFlag } from '../../state/ducks/types/releasedFeaturesReduxTypes';
|
|
|
|
type Props = {
|
|
conversationId: string;
|
|
};
|
|
|
|
async function submitForOpenGroup(convoId: string, pubkeys: Array<string>) {
|
|
const convo = ConvoHub.use().get(convoId);
|
|
if (!convo || !convo.isOpenGroupV2()) {
|
|
throw new Error('submitForOpenGroup group not found');
|
|
}
|
|
try {
|
|
const roomDetails = await SessionUtilUserGroups.getCommunityByConvoIdNotCached(convo.id);
|
|
if (!roomDetails) {
|
|
throw new Error(`getCommunityByFullUrl returned no result for ${convo.id}`);
|
|
}
|
|
const groupInvitation = {
|
|
url: roomDetails?.fullUrlWithPubkey,
|
|
name: convo.getNicknameOrRealUsernameOrPlaceholder(),
|
|
};
|
|
ToastUtils.pushToastInfo('sendingInvites', tr('groupInviteSending', { count: pubkeys.length }));
|
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
|
pubkeys.forEach(async pubkeyStr => {
|
|
const privateConvo = await ConvoHub.use().getOrCreateAndWait(
|
|
pubkeyStr,
|
|
ConversationTypeEnum.PRIVATE
|
|
);
|
|
|
|
if (privateConvo) {
|
|
void privateConvo.sendMessage({
|
|
conversationId: convoId,
|
|
body: '',
|
|
attachments: undefined,
|
|
groupInvitation,
|
|
preview: undefined,
|
|
quote: undefined,
|
|
});
|
|
}
|
|
});
|
|
} catch (e) {
|
|
window.log.warn('submitForOpenGroup failed with:', e.message);
|
|
}
|
|
}
|
|
|
|
function ContactsToInvite({
|
|
validContactsForInvite,
|
|
selectedContacts,
|
|
selectContact,
|
|
unselectContact,
|
|
}: {
|
|
validContactsForInvite: Array<PubkeyType>;
|
|
selectedContacts: Array<string>;
|
|
selectContact: (member: string) => void;
|
|
unselectContact: (member: string) => void;
|
|
}) {
|
|
const hasContacts = validContactsForInvite.length > 0;
|
|
|
|
return hasContacts ? (
|
|
validContactsForInvite.map((member: string) => (
|
|
<MemberListItem
|
|
key={`contacts-list-${member}`}
|
|
pubkey={member}
|
|
isSelected={selectedContacts.includes(member)}
|
|
onSelect={selectContact}
|
|
onUnselect={unselectContact}
|
|
disableBg={true}
|
|
/>
|
|
))
|
|
) : (
|
|
<>
|
|
<SpacerLG />
|
|
<p className="no-contacts">
|
|
<Localizer token="contactNone" />
|
|
</p>
|
|
<SpacerLG />
|
|
</>
|
|
);
|
|
}
|
|
|
|
const InviteContactsDialogInner = (props: Props) => {
|
|
const { conversationId } = props;
|
|
const dispatch = getAppDispatch();
|
|
|
|
const { contactsToInvite, isSearch, searchTerm, hasSearchResults } = useContactsToInviteTo(
|
|
'invite-contact-to',
|
|
conversationId
|
|
);
|
|
const isPrivate = useIsPrivate(conversationId);
|
|
const isPublic = useIsPublic(conversationId);
|
|
const isGroupV2 = useSelectedIsGroupV2();
|
|
const [shareHistory, setShareHistory] = useState(false);
|
|
|
|
const { uniqueValues: selectedContacts, addTo, removeFrom, empty } = useSet<string>();
|
|
|
|
if (isPrivate) {
|
|
throw new Error('InviteContactsDialogInner must be a group');
|
|
}
|
|
|
|
const closeDialog = () => {
|
|
dispatch(updateInviteContactModal(null));
|
|
dispatch(searchActions.clearSearch());
|
|
};
|
|
|
|
const onClickOK = () => {
|
|
if (selectedContacts.length <= 0) {
|
|
closeDialog();
|
|
return;
|
|
}
|
|
if (isPublic) {
|
|
void submitForOpenGroup(conversationId, clone(selectedContacts));
|
|
empty();
|
|
// for a community, we want to keep that dialog open until the user is done with his invitations
|
|
|
|
return;
|
|
}
|
|
if (!PubKey.is03Pubkey(conversationId)) {
|
|
throw new Error('Only communities and 03-groups are allowed here');
|
|
}
|
|
const forcedAsPubkeys = selectedContacts as Array<PubkeyType>;
|
|
const action = groupInfoActions.currentDeviceGroupMembersChange({
|
|
addMembersWithoutHistory: shareHistory ? [] : forcedAsPubkeys,
|
|
addMembersWithHistory: shareHistory ? forcedAsPubkeys : [],
|
|
removeMembers: [],
|
|
groupPk: conversationId,
|
|
alsoRemoveMessages: false,
|
|
});
|
|
dispatch(action as any);
|
|
empty();
|
|
// We want to show the dialog where "invite sending" is visible (i.e. the current group members) instead of this one
|
|
// once we hit "invite"
|
|
closeDialog();
|
|
dispatch(updateGroupMembersModal({ conversationId }));
|
|
};
|
|
|
|
useKey((event: KeyboardEvent) => {
|
|
return event.key === 'Enter';
|
|
}, onClickOK);
|
|
|
|
const hasContacts = contactsToInvite.length > 0;
|
|
|
|
return (
|
|
<SessionWrapperModal
|
|
modalId="inviteContactModal"
|
|
onClose={closeDialog}
|
|
headerChildren={<ModalBasicHeader title={tr('membersInvite')} showExitIcon={true} />}
|
|
modalDataTestId="invite-contacts-dialog"
|
|
buttonChildren={
|
|
<ModalActionsContainer buttonType={SessionButtonType.Simple}>
|
|
<SessionButton
|
|
text={tr('membersInviteTitle')}
|
|
buttonType={SessionButtonType.Simple}
|
|
disabled={!hasContacts}
|
|
onClick={onClickOK}
|
|
dataTestId="session-confirm-ok-button"
|
|
/>
|
|
<SessionButton
|
|
text={tr('cancel')}
|
|
buttonColor={SessionButtonColor.Danger}
|
|
buttonType={SessionButtonType.Simple}
|
|
onClick={closeDialog}
|
|
dataTestId="session-confirm-cancel-button"
|
|
/>
|
|
</ModalActionsContainer>
|
|
}
|
|
>
|
|
<SpacerLG />
|
|
|
|
{isGroupV2 && getFeatureFlag('useClosedGroupV2QAButtons') && (
|
|
<>
|
|
<span
|
|
style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-evenly',
|
|
alignItems: 'center',
|
|
width: '400px',
|
|
}}
|
|
>
|
|
Share History?{' '}
|
|
<SessionToggle active={shareHistory} onClick={() => setShareHistory(!shareHistory)} />
|
|
</span>
|
|
</>
|
|
)}
|
|
<SessionSearchInput searchType="invite-contact-to" />
|
|
{isSearch && !hasSearchResults ? (
|
|
<NoResultsForSearch searchTerm={searchTerm || ''} />
|
|
) : (
|
|
<StyledContactListInModal>
|
|
<ContactsToInvite
|
|
validContactsForInvite={contactsToInvite}
|
|
selectedContacts={selectedContacts}
|
|
selectContact={addTo}
|
|
unselectContact={removeFrom}
|
|
/>
|
|
</StyledContactListInModal>
|
|
)}
|
|
</SessionWrapperModal>
|
|
);
|
|
};
|
|
|
|
export const InviteContactsDialog = InviteContactsDialogInner;
|