From 84896d0fbbb702cd8f703250444f73b17ada117c Mon Sep 17 00:00:00 2001 From: ayumi-signal <143036029+ayumi-signal@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:30:50 -0700 Subject: [PATCH] Contact info modal for call link join requests --- _locales/en/messages.json | 8 + .../CallLinkPendingParticipantModal.scss | 106 +++++++++++++ .../CallingPendingParticipants.scss | 25 ++++ stylesheets/manifest.scss | 1 + ...allLinkPendingParticipantModal.stories.tsx | 69 +++++++++ .../CallLinkPendingParticipantModal.tsx | 140 ++++++++++++++++++ ts/components/CallManager.stories.tsx | 3 + ts/components/CallManager.tsx | 9 ++ ts/components/CallScreen.stories.tsx | 3 + ts/components/CallScreen.tsx | 5 + .../CallingPendingParticipants.stories.tsx | 3 + ts/components/CallingPendingParticipants.tsx | 29 +++- ts/components/GlobalModalContainer.tsx | 12 ++ .../AboutContactModal.stories.tsx | 16 ++ ts/state/ducks/globalModals.ts | 27 ++++ ts/state/selectors/globalModals.ts | 6 + .../smart/CallLinkPendingParticipantModal.tsx | 41 +++++ ts/state/smart/CallManager.tsx | 10 +- ts/state/smart/GlobalModalContainer.tsx | 12 ++ 19 files changed, 519 insertions(+), 6 deletions(-) create mode 100644 stylesheets/components/CallLinkPendingParticipantModal.scss create mode 100644 ts/components/CallLinkPendingParticipantModal.stories.tsx create mode 100644 ts/components/CallLinkPendingParticipantModal.tsx create mode 100644 ts/state/smart/CallLinkPendingParticipantModal.tsx diff --git a/_locales/en/messages.json b/_locales/en/messages.json index db10067ad91c..dad048e1e8e9 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -7437,6 +7437,14 @@ "messageformat": "Require admin approval", "description": "Call Link Edit Modal > Require admin approval checkbox > Label" }, + "icu:CallLinkPendingParticipantModal__ApproveButtonLabel": { + "messageformat": "Approve entry", + "description": "Call Link Pending Participant Modal > Approve Join Request Button Label" + }, + "icu:CallLinkPendingParticipantModal__DenyButtonLabel": { + "messageformat": "Deny entry", + "description": "Call Link Pending Participant Modal > Deny Join Request Button Label" + }, "icu:CallLinkRestrictionsSelect__Option--Off": { "messageformat": "Off", "description": "Call Link Edit Modal > Require admin approval checkbox > Option > Off" diff --git a/stylesheets/components/CallLinkPendingParticipantModal.scss b/stylesheets/components/CallLinkPendingParticipantModal.scss new file mode 100644 index 000000000000..48dca7defe73 --- /dev/null +++ b/stylesheets/components/CallLinkPendingParticipantModal.scss @@ -0,0 +1,106 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +// Overriding default style +.module-Modal__body_inner.CallLinkPendingParticipantModal__body_inner { + align-items: center; + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 4px; + margin-bottom: 14px; + padding-inline: 8px; +} + +.CallLinkPendingParticipantModal__NameButton { + @include button-reset(); + @include font-title-1; + display: flex; + flex-direction: row; + align-items: baseline; + max-width: 100%; + margin-top: 12px; + font-weight: 500; + color: $color-gray-05; + cursor: pointer; +} + +.CallLinkPendingParticipantModal__InContactsIcon { + height: 22px; + width: 22px; + @include any-theme { + background-color: currentColor; + } +} + +.CallLinkPendingParticipantModal__AboutIcon { + display: inline-block; + height: 20px; + width: 20px; + + position: relative; + inset-block-start: 2px; + + @include color-svg( + '../images/icons/v3/chevron/chevron-right-bold.svg', + $color-gray-25 + ); +} + +.CallLinkPendingParticipantModal__SharedGroupInfo { + margin-top: 10px; + color: $color-gray-25; + text-align: center; +} + +.CallLinkPendingParticipantModal__Hr { + width: 100%; + margin-block: 24px; + border: none; + height: 1px; + background: $color-gray-65; +} + +.CallLinkPendingParticipantModal__ActionButton { + @include button-reset; + + display: flex; + padding-block: 6px; + width: 100%; + align-items: center; + color: $color-gray-05; +} + +.CallLinkPendingParticipantModal__ActionButton:last-child { + margin-bottom: 0; +} + +.CallLinkPendingParticipantModal__ActionButton:focus { + @include keyboard-mode { + background: $color-gray-65; + } +} + +.CallLinkPendingParticipantModal__ButtonIcon { + display: flex; + justify-content: center; + align-items: center; + margin-inline-end: 12px; + width: 20px; +} + +.CallLinkPendingParticipantModal__ButtonIconContent { + width: 20px; + height: 20px; +} + +.CallLinkPendingParticipantModal__ButtonIconContent--approve { + @include color-svg( + '../images/icons/v3/check/check-circle.svg', + $color-gray-25 + ); +} + +.CallLinkPendingParticipantModal__ButtonIconContent--deny { + @include color-svg('../images/icons/v3/x/x-circle.svg', $color-gray-25); +} diff --git a/stylesheets/components/CallingPendingParticipants.scss b/stylesheets/components/CallingPendingParticipants.scss index 91631d0d6ef0..245fbbaabf92 100644 --- a/stylesheets/components/CallingPendingParticipants.scss +++ b/stylesheets/components/CallingPendingParticipants.scss @@ -34,11 +34,36 @@ margin-inline-start: 8px; } +.CallingPendingParticipants__ParticipantButton { + @include button-reset(); +} + +.CallingPendingParticipants__ParticipantButton:focus { + @include keyboard-mode { + outline: 3px solid $color-ultramarine; + outline-offset: 1px; + } +} + .CallingPendingParticipants__ParticipantName { @include font-body-1-bold; color: $color-gray-15; } +.CallingPendingParticipants__ParticipantAboutIcon { + display: inline-block; + height: 14px; + width: 14px; + + position: relative; + inset-block-start: 3px; + + @include color-svg( + '../images/icons/v3/chevron/chevron-right-bold.svg', + $color-gray-05 + ); +} + .CallingPendingParticipants__WouldLikeToJoin { @include font-body-2; color: $color-gray-20; diff --git a/stylesheets/manifest.scss b/stylesheets/manifest.scss index 7c0b8657f191..b07d243c0ad6 100644 --- a/stylesheets/manifest.scss +++ b/stylesheets/manifest.scss @@ -54,6 +54,7 @@ @import './components/CallLinkAddNameModal.scss'; @import './components/CallLinkDetails.scss'; @import './components/CallLinkEditModal.scss'; +@import './components/CallLinkPendingParticipantModal.scss'; @import './components/CallLinkRestrictionsSelect.scss'; @import './components/CallingRaisedHandsList.scss'; @import './components/CallingRaisedHandsToasts.scss'; diff --git a/ts/components/CallLinkPendingParticipantModal.stories.tsx b/ts/components/CallLinkPendingParticipantModal.stories.tsx new file mode 100644 index 000000000000..35ae9f01def3 --- /dev/null +++ b/ts/components/CallLinkPendingParticipantModal.stories.tsx @@ -0,0 +1,69 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React from 'react'; +import { action } from '@storybook/addon-actions'; +import { setupI18n } from '../util/setupI18n'; +import enMessages from '../../_locales/en/messages.json'; +import type { CallLinkPendingParticipantModalProps } from './CallLinkPendingParticipantModal'; +import { CallLinkPendingParticipantModal } from './CallLinkPendingParticipantModal'; +import type { ComponentMeta } from '../storybook/types'; +import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; + +const i18n = setupI18n('en', enMessages); + +const conversation = getDefaultConversation({ + acceptedMessageRequest: true, + hasMessages: true, +}); +const conversationWithSharedGroups = getDefaultConversation({ + acceptedMessageRequest: true, + aboutText: 'likes to chat', + hasMessages: true, + sharedGroupNames: ['Axolotl lovers'], +}); +const systemContact = getDefaultConversation({ + acceptedMessageRequest: true, + systemGivenName: 'Alice', + phoneNumber: '+1 555 123-4567', + hasMessages: true, +}); + +export default { + title: 'Components/CallLinkPendingParticipantModal', + component: CallLinkPendingParticipantModal, + args: { + i18n, + conversation, + approveUser: action('approveUser'), + denyUser: action('denyUser'), + toggleAboutContactModal: action('toggleAboutContactModal'), + onClose: action('onClose'), + updateSharedGroups: action('updateSharedGroups'), + }, +} satisfies ComponentMeta; + +export function Default( + args: CallLinkPendingParticipantModalProps +): JSX.Element { + return ; +} + +export function SystemContact( + args: CallLinkPendingParticipantModalProps +): JSX.Element { + return ( + + ); +} + +export function WithSharedGroups( + args: CallLinkPendingParticipantModalProps +): JSX.Element { + return ( + + ); +} diff --git a/ts/components/CallLinkPendingParticipantModal.tsx b/ts/components/CallLinkPendingParticipantModal.tsx new file mode 100644 index 000000000000..2ab161a0bbe7 --- /dev/null +++ b/ts/components/CallLinkPendingParticipantModal.tsx @@ -0,0 +1,140 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import React, { useCallback, useEffect, useMemo } from 'react'; +import { Modal } from './Modal'; +import type { LocalizerType } from '../types/I18N'; +import { Avatar, AvatarSize } from './Avatar'; +import type { PendingUserActionPayloadType } from '../state/ducks/calling'; +import type { ConversationType } from '../state/ducks/conversations'; +import { InContactsIcon } from './InContactsIcon'; +import { isInSystemContacts } from '../util/isInSystemContacts'; +import { ThemeType } from '../types/Util'; +import { Theme } from '../util/theme'; +import { UserText } from './UserText'; +import { SharedGroupNames } from './SharedGroupNames'; + +export type CallLinkPendingParticipantModalProps = { + readonly i18n: LocalizerType; + readonly conversation: ConversationType; + readonly approveUser: (payload: PendingUserActionPayloadType) => void; + readonly denyUser: (payload: PendingUserActionPayloadType) => void; + readonly onClose: () => void; + readonly toggleAboutContactModal: (conversationId: string) => void; + readonly updateSharedGroups: (conversationId: string) => void; +}; + +export function CallLinkPendingParticipantModal({ + i18n, + conversation, + approveUser, + denyUser, + onClose, + toggleAboutContactModal, + updateSharedGroups, +}: CallLinkPendingParticipantModalProps): JSX.Element { + useEffect(() => { + // Kick off the expensive hydration of the current sharedGroupNames + updateSharedGroups(conversation.id); + }, [conversation.id, updateSharedGroups]); + + const serviceId = useMemo(() => { + return conversation.serviceId; + }, [conversation]); + + const handleApprove = useCallback(() => { + approveUser({ serviceId }); + onClose(); + }, [approveUser, onClose, serviceId]); + + const handleDeny = useCallback(() => { + denyUser({ serviceId }); + onClose(); + }, [denyUser, onClose, serviceId]); + + return ( + + + + + +
+ {conversation.sharedGroupNames?.length ? ( + + ) : ( + i18n('icu:no-groups-in-common-warning') + )} +
+ +
+ + + + + + ); +} diff --git a/ts/components/CallManager.stories.tsx b/ts/components/CallManager.stories.tsx index 6c5826d481f2..bab6952fd2b3 100644 --- a/ts/components/CallManager.stories.tsx +++ b/ts/components/CallManager.stories.tsx @@ -139,6 +139,9 @@ const createProps = (storyProps: Partial = {}): PropsType => ({ stopRingtone: action('stop-ringtone'), switchToPresentationView: action('switch-to-presentation-view'), switchFromPresentationView: action('switch-from-presentation-view'), + toggleCallLinkPendingParticipantModal: action( + 'toggle-call-link-pending-participant-modal' + ), toggleParticipants: action('toggle-participants'), togglePip: action('toggle-pip'), toggleScreenRecordingPermissionsDialog: action( diff --git a/ts/components/CallManager.tsx b/ts/components/CallManager.tsx index d1247e28b916..d5d4ba9ebb2c 100644 --- a/ts/components/CallManager.tsx +++ b/ts/components/CallManager.tsx @@ -139,6 +139,7 @@ export type PropsType = { switchFromPresentationView: () => void; hangUpActiveCall: (reason: string) => void; togglePip: () => void; + toggleCallLinkPendingParticipantModal: (contactId: string) => void; toggleScreenRecordingPermissionsDialog: () => unknown; toggleSettings: () => void; isConversationTooBigToRing: boolean; @@ -199,6 +200,7 @@ function ActiveCallManager({ startCall, switchToPresentationView, switchFromPresentationView, + toggleCallLinkPendingParticipantModal, toggleParticipants, togglePip, toggleScreenRecordingPermissionsDialog, @@ -475,6 +477,9 @@ function ActiveCallManager({ stickyControls={showParticipantsList} switchToPresentationView={switchToPresentationView} switchFromPresentationView={switchFromPresentationView} + toggleCallLinkPendingParticipantModal={ + toggleCallLinkPendingParticipantModal + } toggleScreenRecordingPermissionsDialog={ toggleScreenRecordingPermissionsDialog } @@ -571,6 +576,7 @@ export function CallManager({ switchToPresentationView, toggleParticipants, togglePip, + toggleCallLinkPendingParticipantModal, toggleScreenRecordingPermissionsDialog, toggleSettings, }: PropsType): JSX.Element | null { @@ -661,6 +667,9 @@ export function CallManager({ startCall={startCall} switchFromPresentationView={switchFromPresentationView} switchToPresentationView={switchToPresentationView} + toggleCallLinkPendingParticipantModal={ + toggleCallLinkPendingParticipantModal + } toggleParticipants={toggleParticipants} togglePip={togglePip} toggleScreenRecordingPermissionsDialog={ diff --git a/ts/components/CallScreen.stories.tsx b/ts/components/CallScreen.stories.tsx index d0f76427c4dd..e0a3648c54e2 100644 --- a/ts/components/CallScreen.stories.tsx +++ b/ts/components/CallScreen.stories.tsx @@ -217,6 +217,9 @@ const createProps = ( stickyControls: false, switchToPresentationView: action('switch-to-presentation-view'), switchFromPresentationView: action('switch-from-presentation-view'), + toggleCallLinkPendingParticipantModal: action( + 'toggle-call-link-pending-participant-modal' + ), toggleParticipants: action('toggle-participants'), togglePip: action('toggle-pip'), toggleScreenRecordingPermissionsDialog: action( diff --git a/ts/components/CallScreen.tsx b/ts/components/CallScreen.tsx index 4e98d011c3ce..18113fb9ac7d 100644 --- a/ts/components/CallScreen.tsx +++ b/ts/components/CallScreen.tsx @@ -125,6 +125,7 @@ export type PropsType = { stickyControls: boolean; switchToPresentationView: () => void; switchFromPresentationView: () => void; + toggleCallLinkPendingParticipantModal: (contactId: string) => void; toggleParticipants: () => void; togglePip: () => void; toggleScreenRecordingPermissionsDialog: () => unknown; @@ -214,6 +215,7 @@ export function CallScreen({ stickyControls, switchToPresentationView, switchFromPresentationView, + toggleCallLinkPendingParticipantModal, toggleParticipants, togglePip, toggleScreenRecordingPermissionsDialog, @@ -864,6 +866,9 @@ export function CallScreen({ approveUser={approveUser} batchUserAction={batchUserAction} denyUser={denyUser} + toggleCallLinkPendingParticipantModal={ + toggleCallLinkPendingParticipantModal + } /> ) : null} {/* We render the local preview first and set the footer flex direction to row-reverse diff --git a/ts/components/CallingPendingParticipants.stories.tsx b/ts/components/CallingPendingParticipants.stories.tsx index 3d664632847d..1e53c566d643 100644 --- a/ts/components/CallingPendingParticipants.stories.tsx +++ b/ts/components/CallingPendingParticipants.stories.tsx @@ -18,6 +18,9 @@ const createProps = (storyProps: Partial = {}): PropsType => ({ approveUser: action('approve-user'), batchUserAction: action('batch-user-action'), denyUser: action('deny-user'), + toggleCallLinkPendingParticipantModal: action( + 'toggle-call-link-pending-participant-modal' + ), ...storyProps, }); diff --git a/ts/components/CallingPendingParticipants.tsx b/ts/components/CallingPendingParticipants.tsx index 7cd9facf4926..c7e0d5060296 100644 --- a/ts/components/CallingPendingParticipants.tsx +++ b/ts/components/CallingPendingParticipants.tsx @@ -35,6 +35,9 @@ export type PropsType = { readonly approveUser: (payload: PendingUserActionPayloadType) => void; readonly batchUserAction: (payload: BatchUserActionPayloadType) => void; readonly denyUser: (payload: PendingUserActionPayloadType) => void; + readonly toggleCallLinkPendingParticipantModal: ( + conversationId: string + ) => void; }; export function CallingPendingParticipants({ @@ -44,6 +47,7 @@ export function CallingPendingParticipants({ approveUser, batchUserAction, denyUser, + toggleCallLinkPendingParticipantModal, }: PropsType): JSX.Element | null { const [isExpanded, setIsExpanded] = useState(defaultIsExpanded ?? false); const [confirmDialogState, setConfirmDialogState] = @@ -241,7 +245,15 @@ export function CallingPendingParticipants({ className="module-calling-participants-list__contact" key={index} > -
+
+ {renderApprovalButtons(participant)} ))} @@ -303,7 +315,15 @@ export function CallingPendingParticipants({ return (
-
+
{i18n('icu:CallingPendingParticipants__WouldLikeToJoin')}
-
+ {renderApprovalButtons(participant)}
{participants.length > 1 && ( diff --git a/ts/components/GlobalModalContainer.tsx b/ts/components/GlobalModalContainer.tsx index 79dbab547462..0f5a3b235206 100644 --- a/ts/components/GlobalModalContainer.tsx +++ b/ts/components/GlobalModalContainer.tsx @@ -36,6 +36,9 @@ export type PropsType = { // CallLinkEditModal callLinkEditModalRoomId: string | null; renderCallLinkEditModal: () => JSX.Element; + // CallLinkPendingParticipantModal + callLinkPendingParticipantContactId: string | undefined; + renderCallLinkPendingParticipantModal: () => JSX.Element; // ConfirmLeaveCallModal confirmLeaveCallModalState: StartCallData | null; renderConfirmLeaveCallModal: () => JSX.Element; @@ -118,6 +121,9 @@ export function GlobalModalContainer({ // CallLinkEditModal callLinkEditModalRoomId, renderCallLinkEditModal, + // CallLinkPendingParticipantModal + callLinkPendingParticipantContactId, + renderCallLinkPendingParticipantModal, // ConfirmLeaveCallModal confirmLeaveCallModalState, renderConfirmLeaveCallModal, @@ -268,6 +274,12 @@ export function GlobalModalContainer({ return renderContactModal(); } + // This needs to be after the about contact modal because the pending participant modal + // opens the about contact modal + if (callLinkPendingParticipantContactId) { + return renderCallLinkPendingParticipantModal(); + } + if (isStoriesSettingsVisible) { return renderStoriesSettings(); } diff --git a/ts/components/conversation/AboutContactModal.stories.tsx b/ts/components/conversation/AboutContactModal.stories.tsx index 3cb6a201c9b6..d9492cdd1c23 100644 --- a/ts/components/conversation/AboutContactModal.stories.tsx +++ b/ts/components/conversation/AboutContactModal.stories.tsx @@ -38,6 +38,12 @@ const conversationWithAbout = getDefaultConversation({ aboutText: '😀 About Me', hasMessages: true, }); +const conversationWithSharedGroups = getDefaultConversation({ + acceptedMessageRequest: true, + aboutText: 'likes to chat', + hasMessages: true, + sharedGroupNames: ['Axolotl lovers'], +}); const systemContact = getDefaultConversation({ acceptedMessageRequest: true, systemGivenName: 'Alice', @@ -110,3 +116,13 @@ export function SystemContact(args: PropsType): JSX.Element { /> ); } + +export function WithSharedGroups(args: PropsType): JSX.Element { + return ( + + ); +} diff --git a/ts/state/ducks/globalModals.ts b/ts/state/ducks/globalModals.ts index 70bbc2377175..a9b75a544700 100644 --- a/ts/state/ducks/globalModals.ts +++ b/ts/state/ducks/globalModals.ts @@ -94,6 +94,7 @@ export type GlobalModalsStateType = ReadonlyDeep<{ aboutContactModalContactId?: string; callLinkAddNameModalRoomId: string | null; callLinkEditModalRoomId: string | null; + callLinkPendingParticipantContactId: string | undefined; confirmLeaveCallModalState: StartCallData | null; contactModalState?: ContactModalStateType; deleteMessagesProps?: DeleteMessagesPropsType; @@ -149,6 +150,8 @@ const TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL = const TOGGLE_CALL_LINK_ADD_NAME_MODAL = 'globalModals/TOGGLE_CALL_LINK_ADD_NAME_MODAL'; const TOGGLE_CALL_LINK_EDIT_MODAL = 'globalModals/TOGGLE_CALL_LINK_EDIT_MODAL'; +const TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL = + 'globalModals/TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL'; const TOGGLE_ABOUT_MODAL = 'globalModals/TOGGLE_ABOUT_MODAL'; const TOGGLE_SIGNAL_CONNECTIONS_MODAL = 'globalModals/TOGGLE_SIGNAL_CONNECTIONS_MODAL'; @@ -266,6 +269,11 @@ type ToggleCallLinkEditModalActionType = ReadonlyDeep<{ payload: string | null; }>; +type ToggleCallLinkPendingParticipantModalActionType = ReadonlyDeep<{ + type: typeof TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL; + payload: string | undefined; +}>; + type ToggleAboutContactModalActionType = ReadonlyDeep<{ type: typeof TOGGLE_ABOUT_MODAL; payload: string | undefined; @@ -393,6 +401,7 @@ export type GlobalModalsActionType = ReadonlyDeep< | ToggleAddUserToAnotherGroupModalActionType | ToggleCallLinkAddNameModalActionType | ToggleCallLinkEditModalActionType + | ToggleCallLinkPendingParticipantModalActionType | ToggleConfirmationModalActionType | ToggleConfirmLeaveCallModalActionType | ToggleDeleteMessagesModalActionType @@ -435,6 +444,7 @@ export const actions = { toggleAddUserToAnotherGroupModal, toggleCallLinkAddNameModal, toggleCallLinkEditModal, + toggleCallLinkPendingParticipantModal, toggleConfirmationModal, toggleConfirmLeaveCallModal, toggleDeleteMessagesModal, @@ -757,6 +767,15 @@ function toggleCallLinkEditModal( }; } +function toggleCallLinkPendingParticipantModal( + contactId?: string +): ToggleCallLinkPendingParticipantModalActionType { + return { + type: TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL, + payload: contactId, + }; +} + function toggleAboutContactModal( contactId?: string ): ToggleAboutContactModalActionType { @@ -974,6 +993,7 @@ export function getEmptyState(): GlobalModalsStateType { hasConfirmationModal: false, callLinkAddNameModalRoomId: null, callLinkEditModalRoomId: null, + callLinkPendingParticipantContactId: undefined, confirmLeaveCallModalState: null, editNicknameAndNoteModalProps: null, isProfileEditorVisible: false, @@ -1109,6 +1129,13 @@ export function reducer( }; } + if (action.type === TOGGLE_CALL_LINK_PENDING_PARTICIPANT_MODAL) { + return { + ...state, + callLinkPendingParticipantContactId: action.payload, + }; + } + if (action.type === TOGGLE_DELETE_MESSAGES_MODAL) { return { ...state, diff --git a/ts/state/selectors/globalModals.ts b/ts/state/selectors/globalModals.ts index ce281e8735b0..63b70c29c590 100644 --- a/ts/state/selectors/globalModals.ts +++ b/ts/state/selectors/globalModals.ts @@ -32,6 +32,12 @@ export const getCallLinkAddNameModalRoomId = createSelector( ({ callLinkAddNameModalRoomId }) => callLinkAddNameModalRoomId ); +export const getCallLinkPendingParticipantContactId = createSelector( + getGlobalModalsState, + ({ callLinkPendingParticipantContactId }) => + callLinkPendingParticipantContactId +); + export const getConfirmLeaveCallModalState = createSelector( getGlobalModalsState, ({ confirmLeaveCallModalState }) => confirmLeaveCallModalState diff --git a/ts/state/smart/CallLinkPendingParticipantModal.tsx b/ts/state/smart/CallLinkPendingParticipantModal.tsx new file mode 100644 index 000000000000..5c1ad11b1da6 --- /dev/null +++ b/ts/state/smart/CallLinkPendingParticipantModal.tsx @@ -0,0 +1,41 @@ +// Copyright 2024 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only +import React, { memo } from 'react'; +import { useSelector } from 'react-redux'; +import { CallLinkPendingParticipantModal } from '../../components/CallLinkPendingParticipantModal'; +import { useCallingActions } from '../ducks/calling'; +import { getIntl } from '../selectors/user'; +import { useConversationsActions } from '../ducks/conversations'; +import { useGlobalModalActions } from '../ducks/globalModals'; +import { getConversationSelector } from '../selectors/conversations'; +import { getCallLinkPendingParticipantContactId } from '../selectors/globalModals'; +import { strictAssert } from '../../util/assert'; + +export const SmartCallLinkPendingParticipantModal = memo( + function SmartCallLinkPendingParticipantModal(): JSX.Element | null { + const contactId = useSelector(getCallLinkPendingParticipantContactId); + strictAssert(contactId, 'Expected contactId to be set'); + + const i18n = useSelector(getIntl); + const getConversation = useSelector(getConversationSelector); + + const { updateSharedGroups } = useConversationsActions(); + const { approveUser, denyUser } = useCallingActions(); + const { toggleAboutContactModal, toggleCallLinkPendingParticipantModal } = + useGlobalModalActions(); + + const conversation = getConversation(contactId); + + return ( + + ); + } +); diff --git a/ts/state/smart/CallManager.tsx b/ts/state/smart/CallManager.tsx index b14a93040709..383499b6e1f1 100644 --- a/ts/state/smart/CallManager.tsx +++ b/ts/state/smart/CallManager.tsx @@ -458,8 +458,11 @@ export const SmartCallManager = memo(function SmartCallManager() { toggleSettings, } = useCallingActions(); const { pauseVoiceNotePlayer } = useAudioPlayerActions(); - const { showContactModal, showShareCallLinkViaSignal } = - useGlobalModalActions(); + const { + showContactModal, + showShareCallLinkViaSignal, + toggleCallLinkPendingParticipantModal, + } = useGlobalModalActions(); return ( ; @@ -38,6 +39,10 @@ function renderCallLinkEditModal(): JSX.Element { return ; } +function renderCallLinkPendingParticipantModal(): JSX.Element { + return ; +} + function renderConfirmLeaveCallModal(): JSX.Element { return ; } @@ -107,6 +112,7 @@ export const SmartGlobalModalContainer = memo( addUserToAnotherGroupModalContactId, callLinkAddNameModalRoomId, callLinkEditModalRoomId, + callLinkPendingParticipantContactId, confirmLeaveCallModalState, contactModalState, deleteMessagesProps, @@ -188,6 +194,9 @@ export const SmartGlobalModalContainer = memo( } callLinkAddNameModalRoomId={callLinkAddNameModalRoomId} callLinkEditModalRoomId={callLinkEditModalRoomId} + callLinkPendingParticipantContactId={ + callLinkPendingParticipantContactId + } confirmLeaveCallModalState={confirmLeaveCallModalState} contactModalState={contactModalState} editHistoryMessages={editHistoryMessages} @@ -213,6 +222,9 @@ export const SmartGlobalModalContainer = memo( renderAddUserToAnotherGroup={renderAddUserToAnotherGroup} renderCallLinkAddNameModal={renderCallLinkAddNameModal} renderCallLinkEditModal={renderCallLinkEditModal} + renderCallLinkPendingParticipantModal={ + renderCallLinkPendingParticipantModal + } renderConfirmLeaveCallModal={renderConfirmLeaveCallModal} renderContactModal={renderContactModal} renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}