// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { noop, pick } from 'lodash'; import React from 'react'; import type { MeasuredComponentProps } from 'react-measure'; import Measure from 'react-measure'; import type { ConversationType } from '../state/ducks/conversations'; import type { LocalizerType, ReplacementValuesType, ThemeType, } from '../types/Util'; import { ToastType } from '../state/ducks/toast'; import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations'; import { ConfirmationDialog } from './ConfirmationDialog'; import type { Row } from './ConversationList'; import { ConversationList, RowType } from './ConversationList'; import { DisabledReason } from './conversationList/GroupListItem'; import { Modal } from './Modal'; import { SearchInput } from './SearchInput'; import { useRestoreFocus } from '../hooks/useRestoreFocus'; type OwnProps = { i18n: LocalizerType; theme: ThemeType; contact: Pick; candidateConversations: ReadonlyArray; regionCode: string | undefined; }; type DispatchProps = { toggleAddUserToAnotherGroupModal: (contactId?: string) => void; addMemberToGroup: ( conversationId: string, contactId: string, onComplete: () => void ) => void; showToast: (toastType: ToastType, parameters?: ReplacementValuesType) => void; }; export type Props = OwnProps & DispatchProps; export const AddUserToAnotherGroupModal = ({ i18n, theme, contact, toggleAddUserToAnotherGroupModal, addMemberToGroup, showToast, candidateConversations, regionCode, }: Props): JSX.Element | null => { const [searchTerm, setSearchTerm] = React.useState(''); const [filteredConversations, setFilteredConversations] = React.useState( filterAndSortConversationsByRecent(candidateConversations, '', undefined) ); const [selectedGroupId, setSelectedGroupId] = React.useState< undefined | string >(undefined); const groupLookup: Map = React.useMemo(() => { const map = new Map(); candidateConversations.forEach(conversation => { map.set(conversation.id, conversation); }); return map; }, [candidateConversations]); const [inputRef] = useRestoreFocus(); const normalizedSearchTerm = searchTerm.trim(); React.useEffect(() => { const timeout = setTimeout(() => { setFilteredConversations( filterAndSortConversationsByRecent( candidateConversations, normalizedSearchTerm, regionCode ) ); }, 200); return () => { clearTimeout(timeout); }; }, [ candidateConversations, normalizedSearchTerm, setFilteredConversations, regionCode, ]); const selectedGroup = selectedGroupId ? groupLookup.get(selectedGroupId) : undefined; const handleSearchInputChange = React.useCallback( (event: React.ChangeEvent) => { setSearchTerm(event.target.value); }, [setSearchTerm] ); const handleGetRow = React.useCallback( (idx: number): Row | undefined => { const convo = filteredConversations[idx]; // these are always populated in the case of a group const memberships = convo.memberships ?? []; const pendingApprovalMemberships = convo.pendingApprovalMemberships ?? []; const pendingMemberships = convo.pendingMemberships ?? []; const membersCount = convo.membersCount ?? 0; let disabledReason; if (memberships.some(c => c.uuid === contact.uuid)) { disabledReason = DisabledReason.AlreadyMember; } else if ( pendingApprovalMemberships.some(c => c.uuid === contact.uuid) || pendingMemberships.some(c => c.uuid === contact.uuid) ) { disabledReason = DisabledReason.Pending; } return { type: RowType.SelectSingleGroup, group: { ...pick(convo, 'id', 'avatarPath', 'title', 'unblurredAvatarPath'), memberships, membersCount, disabledReason, }, }; }, [filteredConversations, contact] ); return ( <> {!selectedGroup && (
{({ contentRect, measureRef }: MeasuredComponentProps) => (
undefined} i18n={i18n} theme={theme} onClickArchiveButton={noop} onClickContactCheckbox={noop} onSelectConversation={setSelectedGroupId} renderMessageSearchResult={_ => <>} showChooseGroupMembers={noop} lookupConversationWithoutUuid={async _ => undefined} showUserNotFoundModal={noop} setIsFetchingUUID={noop} />
)}
)} {selectedGroupId && selectedGroup && ( setSelectedGroupId(undefined)} actions={[ { text: i18n('AddUserToAnotherGroupModal__confirm-add'), style: 'affirmative', action: () => { showToast(ToastType.AddingUserToGroup, { contact: contact.title, }); addMemberToGroup(selectedGroupId, contact.id, () => showToast(ToastType.UserAddedToGroup, { contact: contact.title, group: selectedGroup.title, }) ); toggleAddUserToAnotherGroupModal(undefined); }, }, ]} > {i18n('AddUserToAnotherGroupModal__confirm-message', { contact: contact.title, group: selectedGroup.title, })} )} ); };