// Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import { pick } from 'lodash'; import React, { useCallback } from 'react'; import type { ListRowProps } from 'react-virtualized'; import type { ConversationType } from '../state/ducks/conversations'; import type { LocalizerType, ThemeType } from '../types/Util'; import { ToastType } from '../types/Toast'; import { filterAndSortConversationsByRecent } from '../util/filterAndSortConversations'; import { ConfirmationDialog } from './ConfirmationDialog'; import type { GroupListItemConversationType } from './conversationList/GroupListItem'; import { DisabledReason, GroupListItem, } from './conversationList/GroupListItem'; import { Modal } from './Modal'; import { SearchInput } from './SearchInput'; import { useRestoreFocus } from '../hooks/useRestoreFocus'; import { ListView } from './ListView'; import { ListTile } from './ListTile'; import type { ShowToastAction } from '../state/ducks/toast'; import { SizeObserver } from '../hooks/useSizeObserver'; type OwnProps = { i18n: LocalizerType; theme: ThemeType; contact: Pick; candidateConversations: ReadonlyArray; regionCode: string | undefined; }; type DispatchProps = { toggleAddUserToAnotherGroupModal: (contactId?: string) => void; addMembersToGroup: ( conversationId: string, contactIds: Array, opts: { onSuccess?: () => unknown; onFailure?: () => unknown; } ) => void; showToast: ShowToastAction; }; export type Props = OwnProps & DispatchProps; export function AddUserToAnotherGroupModal({ i18n, contact, toggleAddUserToAnotherGroupModal, addMembersToGroup, 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): GroupListItemConversationType => { 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.aci === contact.serviceId)) { disabledReason = DisabledReason.AlreadyMember; } else if ( pendingApprovalMemberships.some(c => c.aci === contact.serviceId) || pendingMemberships.some(c => c.serviceId === contact.serviceId) || pendingMemberships.some(c => c.serviceId === contact.pni) ) { disabledReason = DisabledReason.Pending; } return { ...pick(convo, 'id', 'avatarPath', 'title', 'unblurredAvatarPath'), memberships, membersCount, disabledReason, }; }, [filteredConversations, contact] ); const renderGroupListItem = useCallback( ({ key, index, style }: ListRowProps) => { const group = handleGetRow(index); return (
); }, [i18n, handleGetRow] ); const handleCalculateRowHeight = useCallback( () => ListTile.heightCompact, [] ); return ( <> {!selectedGroup && (
{(ref, size) => { return (
{size != null && ( )}
); }}
)} {selectedGroupId && selectedGroup && ( setSelectedGroupId(undefined)} actions={[ { text: i18n('icu:AddUserToAnotherGroupModal__confirm-add'), style: 'affirmative', action: () => { showToast({ toastType: ToastType.AddingUserToGroup, parameters: { contact: contact.title, }, }); addMembersToGroup(selectedGroupId, [contact.id], { onSuccess: () => showToast({ toastType: ToastType.UserAddedToGroup, parameters: { contact: contact.title, group: selectedGroup.title, }, }), }); toggleAddUserToAnotherGroupModal(undefined); }, }, ]} > {i18n('icu:AddUserToAnotherGroupModal__confirm-message', { contact: contact.title, group: selectedGroup.title, })} )} ); }