// Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only /* eslint-disable react/no-array-index-key */ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { noop } from 'lodash'; import { Avatar, AvatarSize } from './Avatar'; import { ContactName } from './conversation/ContactName'; import { InContactsIcon } from './InContactsIcon'; import type { LocalizerType } from '../types/Util'; import type { ConversationType } from '../state/ducks/conversations'; import { isInSystemContacts } from '../util/isInSystemContacts'; import type { BatchUserActionPayloadType, PendingUserActionPayloadType, } from '../state/ducks/calling'; import { Button, ButtonVariant } from './Button'; import type { ServiceIdString } from '../types/ServiceId'; import { handleOutsideClick } from '../util/handleOutsideClick'; import { Theme } from '../util/theme'; import { ConfirmationDialog } from './ConfirmationDialog'; enum ConfirmDialogState { None = 'None', ApproveAll = 'ApproveAll', DenyAll = 'DenyAll', } export type PropsType = { readonly i18n: LocalizerType; readonly participants: Array; // For storybook readonly defaultIsExpanded?: boolean; readonly approveUser: (payload: PendingUserActionPayloadType) => void; readonly batchUserAction: (payload: BatchUserActionPayloadType) => void; readonly denyUser: (payload: PendingUserActionPayloadType) => void; }; export function CallingPendingParticipants({ defaultIsExpanded, i18n, participants, approveUser, batchUserAction, denyUser, }: PropsType): JSX.Element | null { const [isExpanded, setIsExpanded] = useState(defaultIsExpanded ?? false); const [confirmDialogState, setConfirmDialogState] = React.useState(ConfirmDialogState.None); const [serviceIdsStagedForAction, setServiceIdsStagedForAction] = React.useState>([]); const expandedListRef = useRef(null); const handleHideAllRequests = useCallback(() => { setIsExpanded(false); }, [setIsExpanded]); // When opening the "Approve all" confirm dialog, save the current list of participants // to ensure we only approve users who the admin has checked. If additional people // request to join while the dialog is open, we don't auto approve those. const stageServiceIdsForAction = useCallback(() => { const serviceIds: Array = []; participants.forEach(participant => { if (participant.serviceId) { serviceIds.push(participant.serviceId); } }); setServiceIdsStagedForAction(serviceIds); }, [participants, setServiceIdsStagedForAction]); const hideConfirmDialog = useCallback(() => { setConfirmDialogState(ConfirmDialogState.None); setServiceIdsStagedForAction([]); }, [setConfirmDialogState]); const handleApprove = useCallback( (participant: ConversationType) => { const { serviceId } = participant; if (!serviceId) { return; } approveUser({ serviceId }); }, [approveUser] ); const handleDeny = useCallback( (participant: ConversationType) => { const { serviceId } = participant; if (!serviceId) { return; } denyUser({ serviceId }); }, [denyUser] ); const handleApproveAll = useCallback(() => { batchUserAction({ action: 'approve', serviceIds: serviceIdsStagedForAction, }); hideConfirmDialog(); }, [serviceIdsStagedForAction, batchUserAction, hideConfirmDialog]); const handleDenyAll = useCallback(() => { batchUserAction({ action: 'deny', serviceIds: serviceIdsStagedForAction, }); hideConfirmDialog(); }, [serviceIdsStagedForAction, batchUserAction, hideConfirmDialog]); const renderApprovalButtons = useCallback( (participant: ConversationType) => { if (participant.serviceId == null) { return null; } return ( <> ); }, [i18n, handleApprove, handleDeny] ); useEffect(() => { if (!isExpanded) { return noop; } return handleOutsideClick( () => { handleHideAllRequests(); return true; }, { containerElements: [expandedListRef], name: 'CallingPendingParticipantsList.expandedList', } ); }, [isExpanded, handleHideAllRequests]); if (confirmDialogState === ConfirmDialogState.ApproveAll) { return ( {i18n('icu:CallingPendingParticipants__ConfirmDialogBody--ApproveAll', { count: serviceIdsStagedForAction.length, })} ); } if (confirmDialogState === ConfirmDialogState.DenyAll) { return ( {i18n('icu:CallingPendingParticipants__ConfirmDialogBody--DenyAll', { count: serviceIdsStagedForAction.length, })} ); } if (isExpanded) { return (
{i18n('icu:CallingPendingParticipants__RequestsToJoin', { count: participants.length, })}
    {participants.map((participant: ConversationType, index: number) => (
  • {isInSystemContacts(participant) ? ( {' '} ) : null}
    {renderApprovalButtons(participant)}
  • ))}
); } const participant = participants[0]; return (
{isInSystemContacts(participant) ? ( ) : null}
{i18n('icu:CallingPendingParticipants__WouldLikeToJoin')}
{renderApprovalButtons(participant)}
{participants.length > 1 && (
)}
); }