Update call link edit/add name modals

This commit is contained in:
Jamie Kyle 2024-06-25 11:56:28 -07:00 committed by GitHub
parent ba77ef7563
commit b691e24d5c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 526 additions and 232 deletions

View file

@ -0,0 +1,28 @@
// 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 { CallLinkAddNameModalProps } from './CallLinkAddNameModal';
import { CallLinkAddNameModal } from './CallLinkAddNameModal';
import type { ComponentMeta } from '../storybook/types';
import { FAKE_CALL_LINK_WITH_ADMIN_KEY } from '../test-both/helpers/fakeCallLink';
const i18n = setupI18n('en', enMessages);
export default {
title: 'Components/CallLinkAddNameModal',
component: CallLinkAddNameModal,
args: {
i18n,
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
onClose: action('onClose'),
onUpdateCallLinkName: action('onUpdateCallLinkName'),
},
} satisfies ComponentMeta<CallLinkAddNameModalProps>;
export function Basic(args: CallLinkAddNameModalProps): JSX.Element {
return <CallLinkAddNameModal {...args} />;
}

View file

@ -0,0 +1,98 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback, useState } from 'react';
import { v4 as generateUuid } from 'uuid';
import { Modal } from './Modal';
import type { LocalizerType } from '../types/I18N';
import { Button, ButtonVariant } from './Button';
import { Avatar, AvatarSize } from './Avatar';
import { Input } from './Input';
import type { CallLinkType } from '../types/CallLink';
export type CallLinkAddNameModalProps = Readonly<{
i18n: LocalizerType;
callLink: CallLinkType;
onClose: () => void;
onUpdateCallLinkName: (name: string) => void;
}>;
export function CallLinkAddNameModal({
i18n,
callLink,
onClose,
onUpdateCallLinkName,
}: CallLinkAddNameModalProps): JSX.Element {
const [formId] = useState(() => generateUuid());
const [nameId] = useState(() => generateUuid());
const [nameInput, setNameInput] = useState(callLink.name);
const handleNameInputChange = useCallback((nextNameInput: string) => {
setNameInput(nextNameInput);
}, []);
const handleSubmit = useCallback(() => {
const nameValue = nameInput.trim();
if (nameValue === callLink.name) {
return;
}
onUpdateCallLinkName(nameValue);
onClose();
}, [nameInput, callLink, onUpdateCallLinkName, onClose]);
return (
<Modal
modalName="CallLinkAddNameModal"
i18n={i18n}
hasXButton
noEscapeClose
noMouseClose
title={i18n('icu:CallLinkAddNameModal__Title')}
onClose={onClose}
moduleClassName="CallLinkAddNameModal"
modalFooter={
<>
<Button onClick={onClose} variant={ButtonVariant.Secondary}>
{i18n('icu:cancel')}
</Button>
<Button type="submit" form={formId} variant={ButtonVariant.Primary}>
{i18n('icu:save')}
</Button>
</>
}
>
<form
id={formId}
onSubmit={handleSubmit}
className="CallLinkAddNameModal__Row"
>
<Avatar
i18n={i18n}
badge={undefined}
conversationType="callLink"
size={AvatarSize.SIXTY_FOUR}
acceptedMessageRequest
isMe={false}
sharedGroupNames={[]}
title={
callLink.name === ''
? i18n('icu:calling__call-link-default-title')
: callLink.name
}
/>
<label htmlFor={nameId} className="CallLinkAddNameModal__SrOnly">
{i18n('icu:CallLinkAddNameModal__NameLabel')}
</label>
<Input
i18n={i18n}
id={nameId}
value={nameInput}
placeholder={i18n('icu:CallLinkAddNameModal__NameLabel')}
autoFocus
onChange={handleNameInputChange}
moduleClassName="CallLinkAddNameModal__Input"
/>
</form>
</Modal>
);
}

View file

@ -20,7 +20,7 @@ export default {
callLink: FAKE_CALL_LINK_WITH_ADMIN_KEY,
onClose: action('onClose'),
onCopyCallLink: action('onCopyCallLink'),
onUpdateCallLinkName: action('onUpdateCallLinkName'),
onOpenCallLinkAddNameModal: action('onOpenCallLinkAddNameModal'),
onUpdateCallLinkRestrictions: action('onUpdateCallLinkRestrictions'),
onShareCallLinkViaSignal: action('onShareCallLinkViaSignal'),
onStartCallLinkLobby: action('onStartCallLinkLobby'),

View file

@ -1,7 +1,8 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { useCallback, useMemo, useState } from 'react';
import type { ReactNode } from 'react';
import React, { useMemo, useState } from 'react';
import { v4 as generateUuid } from 'uuid';
import { Modal } from './Modal';
import type { LocalizerType } from '../types/I18N';
@ -10,19 +11,67 @@ import {
toCallLinkRestrictions,
type CallLinkType,
} from '../types/CallLink';
import { Input } from './Input';
import { Select } from './Select';
import { linkCallRoute } from '../util/signalRoutes';
import { Button, ButtonSize, ButtonVariant } from './Button';
import { Avatar, AvatarSize } from './Avatar';
import { formatUrlWithoutProtocol } from '../util/url';
const CallLinkEditModalRowIconClasses = {
Edit: 'CallLinkEditModal__RowIcon--Edit',
Approve: 'CallLinkEditModal__RowIcon--Approve',
Copy: 'CallLinkEditModal__RowIcon--Copy',
Share: 'CallLinkEditModal__RowIcon--Share',
} as const;
function RowIcon({
icon,
}: {
icon: keyof typeof CallLinkEditModalRowIconClasses;
}) {
return (
<i
role="presentation"
className={`CallLinkEditModal__RowIcon ${CallLinkEditModalRowIconClasses[icon]}`}
/>
);
}
function RowText({ children }: { children: ReactNode }) {
return <div className="CallLinkEditModal__RowLabel">{children}</div>;
}
function Row({ children }: { children: ReactNode }) {
return <div className="CallLinkEditModal__Row">{children}</div>;
}
function RowButton({
onClick,
children,
}: {
onClick: () => void;
children: ReactNode;
}) {
return (
<button
className="CallLinkEditModal__RowButton"
type="button"
onClick={onClick}
>
{children}
</button>
);
}
function Hr() {
return <hr className="CallLinkEditModal__Hr" />;
}
export type CallLinkEditModalProps = {
i18n: LocalizerType;
callLink: CallLinkType;
onClose: () => void;
onCopyCallLink: () => void;
onUpdateCallLinkName: (name: string) => void;
onOpenCallLinkAddNameModal: () => void;
onUpdateCallLinkRestrictions: (restrictions: CallLinkRestrictions) => void;
onShareCallLinkViaSignal: () => void;
onStartCallLinkLobby: () => void;
@ -33,64 +82,32 @@ export function CallLinkEditModal({
callLink,
onClose,
onCopyCallLink,
onUpdateCallLinkName,
onOpenCallLinkAddNameModal,
onUpdateCallLinkRestrictions,
onShareCallLinkViaSignal,
onStartCallLinkLobby,
}: CallLinkEditModalProps): JSX.Element {
const { name: savedName, restrictions: savedRestrictions } = callLink;
const [nameId] = useState(() => generateUuid());
const [restrictionsId] = useState(() => generateUuid());
const [nameInput, setNameInput] = useState(savedName);
const [restrictionsInput, setRestrictionsInput] = useState(savedRestrictions);
// We only want to use the default name "Signal Call" as a value if the user
// modified the input and then chose that name. Doesn't revert when saved.
const [nameTouched, setNameTouched] = useState(false);
const callLinkWebUrl = useMemo(() => {
return formatUrlWithoutProtocol(
linkCallRoute.toWebUrl({ key: callLink.rootKey })
);
return linkCallRoute.toWebUrl({ key: callLink.rootKey }).toString();
}, [callLink.rootKey]);
const onSaveName = useCallback(
(newName: string) => {
if (!nameTouched) {
return;
}
if (newName === savedName) {
return;
}
onUpdateCallLinkName(newName);
},
[nameTouched, savedName, onUpdateCallLinkName]
);
const onSaveRestrictions = useCallback(
(newRestrictions: CallLinkRestrictions) => {
if (newRestrictions === savedRestrictions) {
return;
}
onUpdateCallLinkRestrictions(newRestrictions);
},
[savedRestrictions, onUpdateCallLinkRestrictions]
);
return (
<Modal
i18n={i18n}
modalName="CallLinkEditModal"
moduleClassName="CallLinkEditModal"
title={i18n('icu:CallLinkEditModal__Title')}
hasXButton
onClose={() => {
// Save the modal in case the user hits escape
onSaveName(nameInput);
onClose();
}}
noEscapeClose
noMouseClose
padded={false}
modalFooter={
<Button type="submit" variant={ButtonVariant.Primary} onClick={onClose}>
{i18n('icu:done')}
</Button>
}
onClose={onClose}
>
<div className="CallLinkEditModal__Header">
<Avatar
@ -101,114 +118,96 @@ export function CallLinkEditModal({
acceptedMessageRequest
isMe={false}
sharedGroupNames={[]}
title={callLink.name ?? i18n('icu:calling__call-link-default-title')}
title={
callLink.name === ''
? i18n('icu:calling__call-link-default-title')
: callLink.name
}
/>
<div className="CallLinkEditModal__Header__Details">
<label htmlFor={nameId} className="CallLinkEditModal__SrOnly">
{i18n('icu:CallLinkEditModal__InputLabel--Name--SrOnly')}
</label>
<Input
moduleClassName="CallLinkEditModal__Input--Name"
i18n={i18n}
value={
nameInput === '' && !nameTouched
? i18n('icu:calling__call-link-default-title')
: nameInput
}
maxByteCount={120}
onChange={value => {
setNameTouched(true);
setNameInput(value);
}}
onBlur={() => {
onSaveName(nameInput);
}}
onEnter={() => {
onSaveName(nameInput);
}}
placeholder={i18n('icu:calling__call-link-default-title')}
/>
<div className="CallLinkEditModal__CallLinkAndJoinButton">
<button
className="CallLinkEditModal__CopyUrlTextButton"
type="button"
onClick={onCopyCallLink}
aria-label={i18n('icu:CallLinkDetails__CopyLink')}
>
{callLinkWebUrl}
</button>
<Button
onClick={onStartCallLinkLobby}
size={ButtonSize.Small}
variant={ButtonVariant.SecondaryAffirmative}
className="CallLinkEditModal__JoinButton"
>
{i18n('icu:CallLinkEditModal__JoinButtonLabel')}
</Button>
<div className="CallLinkEditModal__Header__Title">
{callLink.name === ''
? i18n('icu:calling__call-link-default-title')
: callLink.name}
</div>
<button
className="CallLinkEditModal__Header__CallLinkButton"
type="button"
onClick={onCopyCallLink}
aria-label={i18n('icu:CallLinkDetails__CopyLink')}
>
<div className="CallLinkEditModal__Header__CallLinkButton__Text">
{callLinkWebUrl}
</div>
</button>
</div>
<div className="CallLinkEditModal__Header__Actions">
<Button
onClick={onStartCallLinkLobby}
size={ButtonSize.Small}
variant={ButtonVariant.SecondaryAffirmative}
className="CallLinkEditModal__JoinButton"
>
{i18n('icu:CallLinkEditModal__JoinButtonLabel')}
</Button>
</div>
</div>
<div
className="CallLinkEditModal__ApproveAllMembers__Row"
// For testing, to easily check the restrictions saved
data-restrictions={savedRestrictions}
>
<label
htmlFor={restrictionsId}
className="CallLinkEditModal__ApproveAllMembers__Label"
>
{i18n('icu:CallLinkEditModal__InputLabel--ApproveAllMembers')}
</label>
<Hr />
<RowButton onClick={onOpenCallLinkAddNameModal}>
<Row>
<RowIcon icon="Edit" />
<RowText>{i18n('icu:CallLinkEditModal__AddCallNameLabel')}</RowText>
</Row>
</RowButton>
<Row>
<RowIcon icon="Approve" />
<RowText>
<label htmlFor={restrictionsId}>
{i18n('icu:CallLinkEditModal__InputLabel--ApproveAllMembers')}
</label>
</RowText>
<Select
id={restrictionsId}
value={restrictionsInput}
value={String(callLink.restrictions)}
moduleClassName="CallLinkEditModal__RowSelect"
options={[
{
value: CallLinkRestrictions.None,
value: String(CallLinkRestrictions.None),
text: i18n(
'icu:CallLinkEditModal__ApproveAllMembers__Option--Off'
),
},
{
value: CallLinkRestrictions.AdminApproval,
value: String(CallLinkRestrictions.AdminApproval),
text: i18n(
'icu:CallLinkEditModal__ApproveAllMembers__Option--On'
),
},
]}
onChange={value => {
const newRestrictions = toCallLinkRestrictions(value);
setRestrictionsInput(newRestrictions);
onSaveRestrictions(newRestrictions);
onUpdateCallLinkRestrictions(toCallLinkRestrictions(value));
}}
/>
</div>
</Row>
<button
type="button"
className="CallLinkEditModal__ActionButton"
onClick={onCopyCallLink}
>
<i
role="presentation"
className="CallLinkEditModal__ActionButton__Icon CallLinkEditModal__ActionButton__Icon--Copy"
/>
{i18n('icu:CallLinkDetails__CopyLink')}
</button>
<Hr />
<button
type="button"
className="CallLinkEditModal__ActionButton"
onClick={onShareCallLinkViaSignal}
>
<i
role="presentation"
className="CallLinkEditModal__ActionButton__Icon CallLinkEditModal__ActionButton__Icon--Share"
/>
{i18n('icu:CallLinkDetails__ShareLinkViaSignal')}
</button>
<RowButton onClick={onCopyCallLink}>
<Row>
<RowIcon icon="Copy" />
<RowText>{i18n('icu:CallLinkDetails__CopyLink')}</RowText>
</Row>
</RowButton>
<RowButton onClick={onShareCallLinkViaSignal}>
<Row>
<RowIcon icon="Share" />
<RowText>{i18n('icu:CallLinkDetails__ShareLinkViaSignal')}</RowText>
</Row>
</RowButton>
</Modal>
);
}

View file

@ -76,7 +76,7 @@ export function EditNicknameAndNoteModal({
}, [givenName, familyName, note]);
const handleSubmit = useCallback(
(event: MouseEvent | FormEvent) => {
(event: FormEvent) => {
event.preventDefault();
if (formResult.success) {
onSave(formResult.data);
@ -104,7 +104,6 @@ export function EditNicknameAndNoteModal({
type="submit"
form={formId}
aria-disabled={!formResult.success}
onClick={handleSubmit}
>
{i18n('icu:save')}
</Button>
@ -124,7 +123,7 @@ export function EditNicknameAndNoteModal({
theme={undefined}
/>
</div>
<form onSubmit={handleSubmit}>
<form id={formId} onSubmit={handleSubmit}>
<label
htmlFor={givenNameId}
className="EditNicknameAndNoteModal__Label"

View file

@ -29,6 +29,9 @@ export type PropsType = {
// AddUserToAnotherGroupModal
addUserToAnotherGroupModalContactId: string | undefined;
renderAddUserToAnotherGroup: () => JSX.Element;
// CallLinkAddNameModal
callLinkAddNameModalRoomId: string | null;
renderCallLinkAddNameModal: () => JSX.Element;
// CallLinkEditModal
callLinkEditModalRoomId: string | null;
renderCallLinkEditModal: () => JSX.Element;
@ -105,6 +108,9 @@ export function GlobalModalContainer({
// AddUserToAnotherGroupModal
addUserToAnotherGroupModalContactId,
renderAddUserToAnotherGroup,
// CallLinkAddNameModal
callLinkAddNameModalRoomId,
renderCallLinkAddNameModal,
// CallLinkEditModal
callLinkEditModalRoomId,
renderCallLinkEditModal,
@ -194,6 +200,10 @@ export function GlobalModalContainer({
return renderAddUserToAnotherGroup();
}
if (callLinkAddNameModalRoomId) {
return renderCallLinkAddNameModal();
}
if (callLinkEditModalRoomId) {
return renderCallLinkEditModal();
}

View file

@ -18,6 +18,7 @@ import { useRefMerger } from '../hooks/useRefMerger';
import { byteLength } from '../Bytes';
export type PropsType = {
autoFocus?: boolean;
countBytes?: (value: string) => number;
countLength?: (value: string) => number;
disabled?: boolean;
@ -63,6 +64,7 @@ export const Input = forwardRef<
PropsType
>(function InputInner(
{
autoFocus,
countBytes = byteLength,
countLength = grapheme.count,
disabled,
@ -206,6 +208,7 @@ export const Input = forwardRef<
const isTextarea = expandable || forceTextarea;
const inputProps = {
autoFocus,
className: classNames(
getClassName('__input'),
icon && getClassName('__input--with-icon'),

View file

@ -43,6 +43,7 @@ type PropsType = {
export type ModalPropsType = PropsType & {
noTransform?: boolean;
noEscapeClose?: boolean;
noMouseClose?: boolean;
theme?: Theme;
};
@ -57,6 +58,7 @@ export function Modal({
modalFooter,
modalHeaderChildren,
moduleClassName,
noEscapeClose,
noMouseClose,
onBackButtonClick,
onClose = noop,
@ -114,6 +116,7 @@ export function Modal({
<ModalHost
modalName={modalName}
moduleClassName={moduleClassName}
noEscapeClose={noEscapeClose}
noMouseClose={noMouseClose}
onClose={close}
onEscape={onBackButtonClick}

View file

@ -27,6 +27,7 @@ export type PropsType = Readonly<{
children: React.ReactElement;
modalName: string;
moduleClassName?: string;
noEscapeClose?: boolean;
noMouseClose?: boolean;
onClose: () => unknown;
onEscape?: () => unknown;
@ -40,6 +41,7 @@ export const ModalHost = React.memo(function ModalHostInner({
children,
modalName,
moduleClassName,
noEscapeClose,
noMouseClose,
onClose,
onEscape,
@ -72,7 +74,7 @@ export const ModalHost = React.memo(function ModalHostInner({
};
}, [modalContainer]);
useEscapeHandling(onEscape || onClose);
useEscapeHandling(noEscapeClose ? noop : onEscape || onClose);
useEffect(() => {
if (noMouseClose) {
return noop;

View file

@ -91,6 +91,7 @@ type MigrateToGV2PropsType = ReadonlyDeep<{
export type GlobalModalsStateType = ReadonlyDeep<{
addUserToAnotherGroupModalContactId?: string;
aboutContactModalContactId?: string;
callLinkAddNameModalRoomId: string | null;
callLinkEditModalRoomId: string | null;
contactModalState?: ContactModalStateType;
deleteMessagesProps?: DeleteMessagesPropsType;
@ -143,6 +144,8 @@ export const TOGGLE_PROFILE_EDITOR_ERROR =
const TOGGLE_SAFETY_NUMBER_MODAL = 'globalModals/TOGGLE_SAFETY_NUMBER_MODAL';
const TOGGLE_ADD_USER_TO_ANOTHER_GROUP_MODAL =
'globalModals/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_ABOUT_MODAL = 'globalModals/TOGGLE_ABOUT_MODAL';
const TOGGLE_SIGNAL_CONNECTIONS_MODAL =
@ -244,6 +247,11 @@ type ToggleAddUserToAnotherGroupModalActionType = ReadonlyDeep<{
payload: string | undefined;
}>;
type ToggleCallLinkAddNameModalActionType = ReadonlyDeep<{
type: typeof TOGGLE_CALL_LINK_ADD_NAME_MODAL;
payload: string | null;
}>;
type ToggleCallLinkEditModalActionType = ReadonlyDeep<{
type: typeof TOGGLE_CALL_LINK_EDIT_MODAL;
payload: string | null;
@ -374,6 +382,7 @@ export type GlobalModalsActionType = ReadonlyDeep<
| StartMigrationToGV2ActionType
| ToggleAboutContactModalActionType
| ToggleAddUserToAnotherGroupModalActionType
| ToggleCallLinkAddNameModalActionType
| ToggleCallLinkEditModalActionType
| ToggleConfirmationModalActionType
| ToggleDeleteMessagesModalActionType
@ -414,6 +423,7 @@ export const actions = {
showWhatsNewModal,
toggleAboutContactModal,
toggleAddUserToAnotherGroupModal,
toggleCallLinkAddNameModal,
toggleCallLinkEditModal,
toggleConfirmationModal,
toggleDeleteMessagesModal,
@ -711,6 +721,15 @@ function toggleAddUserToAnotherGroupModal(
};
}
function toggleCallLinkAddNameModal(
roomId: string | null
): ToggleCallLinkAddNameModalActionType {
return {
type: TOGGLE_CALL_LINK_ADD_NAME_MODAL,
payload: roomId,
};
}
function toggleCallLinkEditModal(
roomId: string | null
): ToggleCallLinkEditModalActionType {
@ -935,6 +954,7 @@ function copyOverMessageAttributesIntoForwardMessages(
export function getEmptyState(): GlobalModalsStateType {
return {
hasConfirmationModal: false,
callLinkAddNameModalRoomId: null,
callLinkEditModalRoomId: null,
editNicknameAndNoteModalProps: null,
isProfileEditorVisible: false,
@ -1049,6 +1069,13 @@ export function reducer(
};
}
if (action.type === TOGGLE_CALL_LINK_ADD_NAME_MODAL) {
return {
...state,
callLinkAddNameModalRoomId: action.payload,
};
}
if (action.type === TOGGLE_CALL_LINK_EDIT_MODAL) {
return {
...state,

View file

@ -27,6 +27,11 @@ export const getCallLinkEditModalRoomId = createSelector(
({ callLinkEditModalRoomId }) => callLinkEditModalRoomId
);
export const getCallLinkAddNameModalRoomId = createSelector(
getGlobalModalsState,
({ callLinkAddNameModalRoomId }) => callLinkAddNameModalRoomId
);
export const getContactModalState = createSelector(
getGlobalModalsState,
({ contactModalState }) => contactModalState

View file

@ -0,0 +1,60 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import React, { memo, useCallback, useMemo } from 'react';
import { useSelector } from 'react-redux';
import { useCallingActions } from '../ducks/calling';
import { getCallLinkSelector } from '../selectors/calling';
import * as log from '../../logging/log';
import { getIntl } from '../selectors/user';
import { useGlobalModalActions } from '../ducks/globalModals';
import { getCallLinkAddNameModalRoomId } from '../selectors/globalModals';
import { strictAssert } from '../../util/assert';
import { isCallLinksCreateEnabled } from '../../util/callLinks';
import { CallLinkAddNameModal } from '../../components/CallLinkAddNameModal';
export const SmartCallLinkAddNameModal = memo(
function SmartCallLinkAddNameModal(): JSX.Element | null {
strictAssert(isCallLinksCreateEnabled(), 'Call links creation is disabled');
const roomId = useSelector(getCallLinkAddNameModalRoomId);
strictAssert(roomId, 'Expected roomId to be set');
const i18n = useSelector(getIntl);
const callLinkSelector = useSelector(getCallLinkSelector);
const { updateCallLinkName } = useCallingActions();
const { toggleCallLinkAddNameModal } = useGlobalModalActions();
const callLink = useMemo(() => {
return callLinkSelector(roomId);
}, [callLinkSelector, roomId]);
const handleClose = useCallback(() => {
toggleCallLinkAddNameModal(null);
}, [toggleCallLinkAddNameModal]);
const handleUpdateCallLinkName = useCallback(
(newName: string) => {
updateCallLinkName(roomId, newName);
},
[roomId, updateCallLinkName]
);
if (!callLink) {
log.error(
'SmartCallLinkEditModal: No call link found for roomId',
roomId
);
return null;
}
return (
<CallLinkAddNameModal
i18n={i18n}
callLink={callLink}
onClose={handleClose}
onUpdateCallLinkName={handleUpdateCallLinkName}
/>
);
}
);

View file

@ -26,13 +26,13 @@ export const SmartCallLinkEditModal = memo(
const i18n = useSelector(getIntl);
const callLinkSelector = useSelector(getCallLinkSelector);
const { updateCallLinkRestrictions, startCallLinkLobby } =
useCallingActions();
const {
updateCallLinkName,
updateCallLinkRestrictions,
startCallLinkLobby,
} = useCallingActions();
const { toggleCallLinkEditModal, showShareCallLinkViaSignal } =
useGlobalModalActions();
toggleCallLinkAddNameModal,
toggleCallLinkEditModal,
showShareCallLinkViaSignal,
} = useGlobalModalActions();
const callLink = useMemo(() => {
return callLinkSelector(roomId);
@ -52,12 +52,9 @@ export const SmartCallLinkEditModal = memo(
drop(copyCallLink(callLinkWebUrl));
}, [callLink]);
const handleUpdateCallLinkName = useCallback(
(newName: string) => {
updateCallLinkName(roomId, newName);
},
[roomId, updateCallLinkName]
);
const handleOpenCallLinkAddNameModal = useCallback(() => {
toggleCallLinkAddNameModal(roomId);
}, [roomId, toggleCallLinkAddNameModal]);
const handleUpdateCallLinkRestrictions = useCallback(
(newRestrictions: CallLinkRestrictions) => {
@ -91,7 +88,7 @@ export const SmartCallLinkEditModal = memo(
callLink={callLink}
onClose={handleClose}
onCopyCallLink={handleCopyCallLink}
onUpdateCallLinkName={handleUpdateCallLinkName}
onOpenCallLinkAddNameModal={handleOpenCallLinkAddNameModal}
onUpdateCallLinkRestrictions={handleUpdateCallLinkRestrictions}
onShareCallLinkViaSignal={handleShareCallLinkViaSignal}
onStartCallLinkLobby={handleStartCallLinkLobby}

View file

@ -27,6 +27,11 @@ import { getGlobalModalsState } from '../selectors/globalModals';
import { SmartEditNicknameAndNoteModal } from './EditNicknameAndNoteModal';
import { SmartNotePreviewModal } from './NotePreviewModal';
import { SmartCallLinkEditModal } from './CallLinkEditModal';
import { SmartCallLinkAddNameModal } from './CallLinkAddNameModal';
function renderCallLinkAddNameModal(): JSX.Element {
return <SmartCallLinkAddNameModal />;
}
function renderCallLinkEditModal(): JSX.Element {
return <SmartCallLinkEditModal />;
@ -95,6 +100,7 @@ export const SmartGlobalModalContainer = memo(
const {
aboutContactModalContactId,
addUserToAnotherGroupModalContactId,
callLinkAddNameModalRoomId,
callLinkEditModalRoomId,
contactModalState,
deleteMessagesProps,
@ -174,6 +180,7 @@ export const SmartGlobalModalContainer = memo(
addUserToAnotherGroupModalContactId={
addUserToAnotherGroupModalContactId
}
callLinkAddNameModalRoomId={callLinkAddNameModalRoomId}
callLinkEditModalRoomId={callLinkEditModalRoomId}
contactModalState={contactModalState}
editHistoryMessages={editHistoryMessages}
@ -197,6 +204,7 @@ export const SmartGlobalModalContainer = memo(
isWhatsNewVisible={isWhatsNewVisible}
renderAboutContactModal={renderAboutContactModal}
renderAddUserToAnotherGroup={renderAddUserToAnotherGroup}
renderCallLinkAddNameModal={renderCallLinkAddNameModal}
renderCallLinkEditModal={renderCallLinkEditModal}
renderContactModal={renderContactModal}
renderEditHistoryMessagesModal={renderEditHistoryMessagesModal}

View file

@ -42,23 +42,29 @@ describe('calling/callLinkAdmin', function (this: Mocha.Suite) {
.getByText('Create a Call Link')
.click();
const callLinkItem = window.locator('.CallsList__Item[data-type="Adhoc"]');
const editModal = window.locator('.CallLinkEditModal');
await editModal.waitFor();
const modal = window.locator('.CallLinkEditModal');
await modal.waitFor();
const restrictionsInput = editModal.getByLabel('Approve all members');
const row = modal.locator('.CallLinkEditModal__ApproveAllMembers__Row');
await expect(restrictionsInput).toHaveJSProperty('value', '0');
await restrictionsInput.selectOption({ label: 'On' });
await expect(restrictionsInput).toHaveJSProperty('value', '1');
await expect(row).toHaveAttribute('data-restrictions', '0');
await editModal.locator('button', { hasText: 'Add call name' }).click();
const select = modal.locator('select');
await select.selectOption({ label: 'On' });
await expect(row).toHaveAttribute('data-restrictions', '1');
const addNameModal = window.locator('.CallLinkAddNameModal');
await addNameModal.waitFor();
const nameInput = modal.locator('.CallLinkEditModal__Input--Name__input');
const nameInput = addNameModal.getByLabel('Call name');
await nameInput.fill('New Name');
await nameInput.blur();
await expect(callLinkItem).toContainText('New Name');
const saveBtn = addNameModal.getByText('Save');
await saveBtn.click();
await editModal.waitFor();
const title = editModal.locator('.CallLinkEditModal__Header__Title');
await expect(title).toContainText('New Name');
});
});

View file

@ -37,7 +37,3 @@ export function urlPathFromComponents(
): string {
return `/${components.filter(Boolean).map(encodeURIComponent).join('/')}`;
}
export function formatUrlWithoutProtocol(url: Readonly<URL>): string {
return `${url.hostname}${url.pathname}${url.search}${url.hash}`;
}