diff --git a/.changeset/deprecate-community-saml-ldap.md b/.changeset/deprecate-community-saml-ldap.md new file mode 100644 index 0000000000000..860c464dc56f0 --- /dev/null +++ b/.changeset/deprecate-community-saml-ldap.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Deprecates LDAP and SAML authentication on workspaces without a Premium plan. Both keep working as they are today, but the admin settings now warn that version 9.0.0 will require a license including the `ldap-enterprise` or `saml-enterprise` module, and a warning is logged when an unlicensed workspace authenticates a user through either of them. diff --git a/.changeset/gold-jobs-help.md b/.changeset/gold-jobs-help.md new file mode 100644 index 0000000000000..f526c8471d142 --- /dev/null +++ b/.changeset/gold-jobs-help.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/core-typings": minor +"@rocket.chat/media-calls": minor +--- + +Adds support for call diversion handling in voice calls diff --git a/.changeset/pink-dolls-flash.md b/.changeset/pink-dolls-flash.md new file mode 100644 index 0000000000000..ddef59a5a327e --- /dev/null +++ b/.changeset/pink-dolls-flash.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Replace http with serverFetch in downloadPublicImportFile to add SSRF protection diff --git a/apps/meteor/client/components/Wizard/WizardActions.tsx b/apps/meteor/client/components/Wizard/WizardActions.tsx deleted file mode 100644 index 13a525ace8b3f..0000000000000 --- a/apps/meteor/client/components/Wizard/WizardActions.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Box, ButtonGroup } from '@rocket.chat/fuselage'; -import type { ReactNode } from 'react'; - -export type WizardActionsProps = { - children: ReactNode; -}; - -const WizardActions = ({ children }: WizardActionsProps) => { - return ( - - {children} - - ); -}; - -export default WizardActions; diff --git a/apps/meteor/client/components/message/notification/AllMentionNotification.tsx b/apps/meteor/client/components/message/notification/AllMentionNotification.tsx deleted file mode 100644 index 7f4e1562ea077..0000000000000 --- a/apps/meteor/client/components/message/notification/AllMentionNotification.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import MessageNotification from './MessageNotification'; - -const AllMentionNotification = function All() { - return ; -}; - -export default AllMentionNotification; diff --git a/apps/meteor/client/components/message/notification/MeMentionNotification.tsx b/apps/meteor/client/components/message/notification/MeMentionNotification.tsx deleted file mode 100644 index 27611d87c9b3b..0000000000000 --- a/apps/meteor/client/components/message/notification/MeMentionNotification.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import MessageNotification from './MessageNotification'; - -const MeMentionNotification = () => { - return ; -}; - -export default MeMentionNotification; diff --git a/apps/meteor/client/components/message/notification/MessageNotification.tsx b/apps/meteor/client/components/message/notification/MessageNotification.tsx deleted file mode 100644 index 249dd15c98a67..0000000000000 --- a/apps/meteor/client/components/message/notification/MessageNotification.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { Palette } from '@rocket.chat/fuselage'; -import { Box } from '@rocket.chat/fuselage'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { useTranslation } from 'react-i18next'; - -export type MessageNotificationProps = { - label: TranslationKey; - backgroundColor: keyof (typeof Palette)['badge']; -}; - -const MessageNotification = ({ label, backgroundColor }: MessageNotificationProps) => { - const { t } = useTranslation(); - return ; -}; - -export default MessageNotification; diff --git a/apps/meteor/client/components/message/notification/UnreadMessagesNotification.tsx b/apps/meteor/client/components/message/notification/UnreadMessagesNotification.tsx deleted file mode 100644 index b7a61af1134b7..0000000000000 --- a/apps/meteor/client/components/message/notification/UnreadMessagesNotification.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import MessageNotification from './MessageNotification'; - -const UnreadMessagesNotification = () => { - return ; -}; - -export default UnreadMessagesNotification; diff --git a/apps/meteor/client/components/message/toolbar/items/actions/Timestamp/TimestampPicker/index.ts b/apps/meteor/client/components/message/toolbar/items/actions/Timestamp/TimestampPicker/index.ts deleted file mode 100644 index a497f65b3bf6c..0000000000000 --- a/apps/meteor/client/components/message/toolbar/items/actions/Timestamp/TimestampPicker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TimestampPickerModal'; diff --git a/apps/meteor/client/lib/errors/VisitorDoesNotExistError.ts b/apps/meteor/client/lib/errors/VisitorDoesNotExistError.ts deleted file mode 100644 index 646e17b17ff9f..0000000000000 --- a/apps/meteor/client/lib/errors/VisitorDoesNotExistError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { RocketChatError } from './RocketChatError'; - -export class VisitorDoesNotExistError extends RocketChatError<'visitor-does-not-exist'> { - constructor(message = 'Visitor does not exist', details?: string) { - super('visitor-does-not-exist', message, details); - } -} diff --git a/apps/meteor/client/lib/utils/waitForElement.spec.ts b/apps/meteor/client/lib/utils/waitForElement.spec.ts deleted file mode 100644 index ca1ebb67e4d97..0000000000000 --- a/apps/meteor/client/lib/utils/waitForElement.spec.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { waitForElement } from './waitForElement'; - -beforeEach(() => { - document.body.innerHTML = ``; -}); - -it('should return the element when it is already in the dom', async () => { - expect(await waitForElement('.ready')).toBe(document.querySelector('.ready')); -}); - -it('should await until the element be in the dom and return it', async () => { - setTimeout(() => { - const element = document.createElement('div'); - element.setAttribute('class', 'not-ready'); - document.body.appendChild(element); - }, 5); - expect(await waitForElement('.not-ready')).toBe(document.querySelector('.not-ready')); -}); diff --git a/apps/meteor/client/lib/utils/waitForElement.ts b/apps/meteor/client/lib/utils/waitForElement.ts deleted file mode 100644 index 4f4aa1438881d..0000000000000 --- a/apps/meteor/client/lib/utils/waitForElement.ts +++ /dev/null @@ -1,27 +0,0 @@ -export const waitForElement = async ( - selector: string, - { parent = document.documentElement, signal }: { parent?: Element; signal?: AbortSignal } = {}, -): Promise => { - const element = parent.querySelector(selector); - return new Promise((resolve, reject) => { - if (element) { - return resolve(element); - } - const observer = new MutationObserver((_, obs) => { - const element = parent.querySelector(selector); - if (element) { - obs.disconnect(); // stop observing - return resolve(element); - } - }); - observer.observe(parent, { - childList: true, - subtree: true, - }); - - signal?.addEventListener('abort', () => { - observer.disconnect(); - reject(new DOMException('Aborted', 'AbortError')); - }); - }); -}; diff --git a/apps/meteor/client/navbar/NavBarPagesGroup/hooks/useMatrixFederationItems.ts b/apps/meteor/client/navbar/NavBarPagesGroup/hooks/useMatrixFederationItems.ts deleted file mode 100644 index f0851180ef6fd..0000000000000 --- a/apps/meteor/client/navbar/NavBarPagesGroup/hooks/useMatrixFederationItems.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { GenericMenuItemProps } from '@rocket.chat/ui-client'; -import { useTranslation } from 'react-i18next'; - -import { useCreateRoomModal } from './useCreateRoomModal'; -import MatrixFederationSearch from '../../../sidebar/header/MatrixFederationSearch'; - -export const useMatrixFederationItems = ({ - isMatrixEnabled, -}: { - isMatrixEnabled: string | number | boolean | null | undefined; -}): GenericMenuItemProps[] => { - const { t } = useTranslation(); - - const searchFederatedRooms = useCreateRoomModal(MatrixFederationSearch); - - const matrixFederationSearchItem: GenericMenuItemProps = { - id: 'matrix-federation-search', - content: t('Federation_Search_federated_rooms'), - icon: 'magnifier', - onClick: () => { - searchFederatedRooms(); - }, - }; - - return [...(isMatrixEnabled ? [matrixFederationSearchItem] : [])]; -}; diff --git a/apps/meteor/client/sidebar/SidebarPortal.tsx b/apps/meteor/client/sidebar/SidebarPortal.tsx deleted file mode 100644 index aa6da64c97fc5..0000000000000 --- a/apps/meteor/client/sidebar/SidebarPortal.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import type { ReactNode } from 'react'; -import { memo } from 'react'; -import { createPortal } from 'react-dom'; - -export type SidebarPortalProps = { children?: ReactNode }; - -const SidebarPortal = ({ children }: SidebarPortalProps) => { - const sidebarRoot = document.getElementById('sidebar-region'); - - if (!sidebarRoot) { - return null; - } - - return <>{createPortal({children}, sidebarRoot)}; -}; - -export default memo(SidebarPortal); diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomList.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomList.tsx deleted file mode 100644 index e20822d6589bf..0000000000000 --- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomList.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import { Throbber, Box } from '@rocket.chat/fuselage'; -import type { IFederationPublicRooms } from '@rocket.chat/rest-typings'; -import { VirtualizedScrollbars } from '@rocket.chat/ui-client'; -import { useSetModal, useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; -import { useMutation } from '@tanstack/react-query'; -import { useTranslation } from 'react-i18next'; -import { Virtuoso } from 'react-virtuoso'; - -import FederatedRoomListEmptyPlaceholder from './FederatedRoomListEmptyPlaceholder'; -import FederatedRoomListItem from './FederatedRoomListItem'; -import { useInfiniteFederationSearchPublicRooms } from './useInfiniteFederationSearchPublicRooms'; -import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; - -export type FederatedRoomListProps = { - serverName: string; - roomName?: string; - pageToken?: string; - count?: number; -}; - -const FederatedRoomList = ({ serverName, roomName, count }: FederatedRoomListProps) => { - const joinExternalPublicRoom = useEndpoint('POST', '/v1/federation/joinExternalPublicRoom'); - - const setModal = useSetModal(); - const { t } = useTranslation(); - const dispatchToastMessage = useToastMessageDispatch(); - const { data, isPending, isFetchingNextPage, fetchNextPage } = useInfiniteFederationSearchPublicRooms(serverName, roomName, count); - - const { mutate: onClickJoin, isPending: isLoadingMutation } = useMutation({ - mutationKey: ['federation/joinExternalPublicRoom'], - - mutationFn: async ({ id, pageToken }: IFederationPublicRooms) => { - return joinExternalPublicRoom({ externalRoomId: id as `!${string}:${string}`, roomName, pageToken }); - }, - - onSuccess: (_, data) => { - dispatchToastMessage({ - type: 'success', - message: t('Your_request_to_join__roomName__has_been_made_it_could_take_up_to_15_minutes_to_be_processed', { - roomName: data.name, - }), - }); - setModal(null); - }, - - onError: (error, { id }) => { - if (error instanceof Error && error.message === 'already-joined') { - setModal(null); - roomCoordinator.openRouteLink('c', { rid: id }); - return; - } - - dispatchToastMessage({ type: 'error', message: error }); - }, - }); - - if (isPending) { - return ; - } - - const flattenedData = data?.pages.flatMap((page) => page.rooms); - return ( - - - room?.id || index} - overscan={4} - components={{ - // eslint-disable-next-line react/no-multi-comp - Footer: () => (isFetchingNextPage ? : null), - EmptyPlaceholder: FederatedRoomListEmptyPlaceholder, - }} - endReached={isPending || isFetchingNextPage ? () => undefined : () => fetchNextPage()} - itemContent={(_, room) => ( - onClickJoin(room)} {...room} disabled={isLoadingMutation} key={room.id} /> - )} - /> - - - ); -}; - -export default FederatedRoomList; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListEmptyPlaceholder.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListEmptyPlaceholder.tsx deleted file mode 100644 index aa3b5c9409345..0000000000000 --- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListEmptyPlaceholder.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Box } from '@rocket.chat/fuselage'; -import { useTranslation } from 'react-i18next'; - -import GenericNoResults from '../../../components/GenericNoResults'; - -const FederatedRoomListEmptyPlaceholder = () => { - const { t } = useTranslation(); - - return ( - - - - ); -}; - -export default FederatedRoomListEmptyPlaceholder; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListErrorBoundary.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListErrorBoundary.tsx deleted file mode 100644 index 19890e00a466b..0000000000000 --- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListErrorBoundary.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { States, StatesIcon, StatesTitle, StatesSubtitle, StatesActions, StatesAction, Icon } from '@rocket.chat/fuselage'; -import { QueryErrorResetBoundary } from '@tanstack/react-query'; -import type { ReactNode } from 'react'; -import { ErrorBoundary } from 'react-error-boundary'; -import { useTranslation } from 'react-i18next'; - -export type FederatedRoomListErrorBoundaryProps = { - children?: ReactNode; - resetKeys?: unknown[]; -}; - -const FederatedRoomListErrorBoundary = ({ children, resetKeys }: FederatedRoomListErrorBoundaryProps) => { - const { t } = useTranslation(); - - return ( - - {({ reset }) => ( - ( - - - {t('Error')} - {t('Error_something_went_wrong')} - - { - reset(); - resetErrorBoundary(); - }} - > - {t('Reload')} - - - - )} - > - {children} - - )} - - ); -}; - -export default FederatedRoomListErrorBoundary; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListItem.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListItem.tsx deleted file mode 100644 index 4f11d9b3fc88d..0000000000000 --- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/FederatedRoomListItem.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Box, Button, Icon } from '@rocket.chat/fuselage'; -import type { IFederationPublicRooms } from '@rocket.chat/rest-typings'; -import { useTranslation } from 'react-i18next'; - -export type FederatedRoomListItemProps = IFederationPublicRooms & { - disabled: boolean; - onClickJoin: () => void; -}; - -const clampLine = css` - line-clamp: 6; -`; - -const FederatedRoomListItem = ({ - name, - topic, - canonicalAlias, - joinedMembers, - onClickJoin, - canJoin, - disabled, -}: FederatedRoomListItemProps) => { - const { t } = useTranslation(); - - return ( - - - - {name} - - {canJoin && ( - - )} - {/* Currently canJoin is only false when the ammount of members is too big. This property will be used in the future - in case the matrix room is knock only. When that happens, the check for this should be based on the limit setting. */} - {!canJoin && ( - - {t('Cant_join')} - - )} - - - {topic && ( - - {topic} - - )} - - - {canonicalAlias}{' '} - - - {joinedMembers} - - - - ); -}; - -export default FederatedRoomListItem; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationManageServerModal.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationManageServerModal.tsx deleted file mode 100644 index 2a0c567a4e608..0000000000000 --- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationManageServerModal.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { - Divider, - Modal, - ButtonGroup, - Button, - Field, - TextInput, - FieldLabel, - FieldRow, - FieldError, - FieldHint, - ModalHeader, - ModalTitle, - ModalClose, - ModalContent, - ModalFooter, -} from '@rocket.chat/fuselage'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { useSetModal, useTranslation, useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import type { ChangeEvent } from 'react'; -import { useState } from 'react'; - -import MatrixFederationRemoveServerList from './MatrixFederationRemoveServerList'; -import MatrixFederationSearch from './MatrixFederationSearch'; -import { useMatrixServerList } from './useMatrixServerList'; - -export type MatrixFederationAddServerModalProps = { - onClickClose: () => void; -}; - -const getErrorKey = (error: any): TranslationKey | undefined => { - if (!error) { - return; - } - if (error.error === 'invalid-server-name') { - return 'Server_doesnt_exist'; - } - if (error.error === 'invalid-server-name') { - return 'Server_already_added'; - } -}; - -const MatrixFederationAddServerModal = ({ onClickClose }: MatrixFederationAddServerModalProps) => { - const t = useTranslation(); - const addMatrixServer = useEndpoint('POST', '/v1/federation/addServerByUser'); - const [serverName, setServerName] = useState(''); - const [errorKey, setErrorKey] = useState(); - const setModal = useSetModal(); - const queryClient = useQueryClient(); - const dispatchToastMessage = useToastMessageDispatch(); - - const { - mutate: addServer, - isPending, - isError, - } = useMutation({ - mutationKey: ['v1/federation/addServerByUser', serverName], - mutationFn: () => addMatrixServer({ serverName }), - - onSuccess: async () => { - await queryClient.invalidateQueries({ - queryKey: ['federation/listServersByUsers'], - }); - setModal(); - }, - - onError: (error) => { - const errorKey = getErrorKey(error); - if (!errorKey) { - dispatchToastMessage({ type: 'error', message: error }); - return; - } - setErrorKey(errorKey); - }, - }); - - const { data, isPending: isLoadingServerList } = useMatrixServerList(); - - return ( - - - {t('Manage_servers')} - - - - - {t('Server_name')} - - ) => { - setServerName(e.currentTarget.value); - if (errorKey) { - setErrorKey(undefined); - } - }} - marginInlineEnd={4} - /> - - - {isError && errorKey && {t(errorKey)}} - {t('Federation_Example_matrix_server')} - - - {!isLoadingServerList && data?.servers && } - - - - - - - - ); -}; - -export default MatrixFederationAddServerModal; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationRemoveServerList.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationRemoveServerList.tsx deleted file mode 100644 index 3b23aac42973d..0000000000000 --- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationRemoveServerList.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Box, Option, Icon } from '@rocket.chat/fuselage'; -import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; - -export type MatrixFederationRemoveServerListProps = { - servers: Array<{ name: string; default: boolean; local: boolean }>; -}; - -const style = css` - i { - visibility: hidden; - } - li { - cursor: default; - } - li:hover { - i { - cursor: pointer; - visibility: visible; - } - } -`; - -const MatrixFederationRemoveServerList = ({ servers }: MatrixFederationRemoveServerListProps) => { - const removeMatrixServer = useEndpoint('POST', '/v1/federation/removeServerByUser'); - - const queryClient = useQueryClient(); - - const { mutate: removeServer, isPending: isRemovingServer } = useMutation({ - mutationKey: ['federation/removeServerByUser'], - mutationFn: (serverName: string) => removeMatrixServer({ serverName }), - - onSuccess: () => - queryClient.invalidateQueries({ - queryKey: ['federation/listServersByUsers'], - }), - }); - - const t = useTranslation(); - - return ( - - - {t('Servers')} - - {servers.map(({ name, default: isDefault }) => ( - - ))} - - ); -}; - -export default MatrixFederationRemoveServerList; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearch.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearch.tsx deleted file mode 100644 index d1b72a3e07f5a..0000000000000 --- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearch.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Modal, ModalClose, ModalContent, ModalFooter, ModalHeader, ModalTitle, Skeleton } from '@rocket.chat/fuselage'; -import { useTranslation } from 'react-i18next'; - -import MatrixFederationSearchModalContent from './MatrixFederationSearchModalContent'; -import { useMatrixServerList } from './useMatrixServerList'; - -export type MatrixFederationSearchProps = { - onClose: () => void; - defaultSelectedServer?: string; -}; - -const MatrixFederationSearch = ({ onClose, defaultSelectedServer }: MatrixFederationSearchProps) => { - const { t } = useTranslation(); - const { data, isLoading } = useMatrixServerList(); - - return ( - - - {t('Federation_Federated_room_search')} - - - - {isLoading && ( - <> - - - - - - )} - {!isLoading && data?.servers && ( - - )} - - - - ); -}; - -export default MatrixFederationSearch; diff --git a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx b/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx deleted file mode 100644 index 98c9563e2301d..0000000000000 --- a/apps/meteor/client/sidebar/header/MatrixFederationSearch/MatrixFederationSearchModalContent.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import type { SelectOption } from '@rocket.chat/fuselage'; -import { Box, Select, TextInput } from '@rocket.chat/fuselage'; -import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import { useSetModal } from '@rocket.chat/ui-contexts'; -import type { ChangeEvent } from 'react'; -import { useCallback, useState, useMemo } from 'react'; -import { useTranslation } from 'react-i18next'; - -import FederatedRoomList from './FederatedRoomList'; -import FederatedRoomListErrorBoundary from './FederatedRoomListErrorBoundary'; -import MatrixFederationManageServersModal from './MatrixFederationManageServerModal'; -import MatrixFederationSearch from './MatrixFederationSearch'; - -export type MatrixFederationSearchModalContentProps = { - servers: Array<{ - name: string; - default: boolean; - local: boolean; - }>; - defaultSelectedServer?: string; -}; - -const MatrixFederationSearchModalContent = ({ defaultSelectedServer, servers }: MatrixFederationSearchModalContentProps) => { - const [serverName, setServerName] = useState(() => { - const defaultServer = servers.find((server) => server.name === defaultSelectedServer); - return defaultServer?.name ?? servers[0].name; - }); - - const [roomName, setRoomName] = useState(''); - - const setModal = useSetModal(); - - const debouncedRoomName = useDebouncedValue(roomName, 400); - - const { t } = useTranslation(); - - const serverOptions = useMemo>(() => servers.map((server): SelectOption => [server.name, server.name]), [servers]); - - const manageServers = useCallback(() => { - setModal( - setModal( setModal(null)} />)} />, - ); - }, [setModal]); - - return ( - <> - - -