From c0fa567246209cc0b714c3dad67b28c6d14d43b8 Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Sat, 24 Jun 2023 16:40:41 -0300 Subject: [PATCH 01/79] feat(fuselage-ui-kit): Add `i18n` parser to render surfaces and blocks (#29413) Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com> --- .changeset/rude-insects-speak.md | 6 ++++ apps/meteor/ee/client/apps/i18n.js | 6 +++- .../src/elements/MarkdownTextElement.tsx | 30 +++++++++++++++++++ .../src/elements/PlainTextElement.tsx | 25 ++++++++++++++++ .../src/surfaces/FuselageSurfaceRenderer.tsx | 21 ++++++------- yarn.lock | 6 ++-- 6 files changed, 78 insertions(+), 16 deletions(-) create mode 100644 .changeset/rude-insects-speak.md create mode 100644 packages/fuselage-ui-kit/src/elements/MarkdownTextElement.tsx create mode 100644 packages/fuselage-ui-kit/src/elements/PlainTextElement.tsx diff --git a/.changeset/rude-insects-speak.md b/.changeset/rude-insects-speak.md new file mode 100644 index 0000000000000..13b06ab68957b --- /dev/null +++ b/.changeset/rude-insects-speak.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/fuselage-ui-kit': minor +'@rocket.chat/meteor': minor +--- + +Introducing i18n to UiKit text renderers diff --git a/apps/meteor/ee/client/apps/i18n.js b/apps/meteor/ee/client/apps/i18n.js index c383ed8041104..9b2c6ffe2cb78 100644 --- a/apps/meteor/ee/client/apps/i18n.js +++ b/apps/meteor/ee/client/apps/i18n.js @@ -6,13 +6,17 @@ import { Apps } from './orchestrator'; const loadAppI18nResources = (appId, languages) => { Object.entries(languages).forEach(([language, translations]) => { try { + const regex = /([a-z]{2,3})-([a-z]{2,4})/; + const match = regex.exec(language); + const normalizedLanguage = match ? `${match[1]}-${match[2].toUpperCase()}` : language; + // Translations keys must be scoped under app id const scopedTranslations = Object.entries(translations).reduce((translations, [key, value]) => { translations[Utilities.getI18nKeyForApp(key, appId)] = value; return translations; }, {}); - i18n.addResourceBundle(language, 'core', scopedTranslations); + i18n.addResourceBundle(normalizedLanguage, 'core', scopedTranslations); } catch (error) { Apps.handleError(error); } diff --git a/packages/fuselage-ui-kit/src/elements/MarkdownTextElement.tsx b/packages/fuselage-ui-kit/src/elements/MarkdownTextElement.tsx new file mode 100644 index 0000000000000..b4d516bc8c77e --- /dev/null +++ b/packages/fuselage-ui-kit/src/elements/MarkdownTextElement.tsx @@ -0,0 +1,30 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; +import { parse } from '@rocket.chat/message-parser'; +import { Markup } from '@rocket.chat/gazzodown'; +import type { TextObject } from '@rocket.chat/ui-kit'; + +import { useUiKitContext } from '../contexts/kitContext'; + +const MarkdownTextElement = ({ textObject }: { textObject: TextObject }) => { + const t = useTranslation() as ( + key: string, + args: { [key: string]: string | number } + ) => string; + const { appId } = useUiKitContext(); + + const { i18n } = textObject; + + if (i18n) { + return ( + + ); + } + + return ; +}; + +export default MarkdownTextElement; diff --git a/packages/fuselage-ui-kit/src/elements/PlainTextElement.tsx b/packages/fuselage-ui-kit/src/elements/PlainTextElement.tsx new file mode 100644 index 0000000000000..3178caedd263d --- /dev/null +++ b/packages/fuselage-ui-kit/src/elements/PlainTextElement.tsx @@ -0,0 +1,25 @@ +import type { TextObject } from '@rocket.chat/ui-kit'; +import { Fragment } from 'react'; +import { useTranslation } from '@rocket.chat/ui-contexts'; + +import { useUiKitContext } from '../contexts/kitContext'; + +const PlainTextElement = ({ textObject }: { textObject: TextObject }) => { + const t = useTranslation() as ( + key: string, + args: { [key: string]: string | number } + ) => string; + const { appId } = useUiKitContext(); + + const { i18n } = textObject; + + if (i18n) { + return ( + {t(`apps-${appId}-${i18n.key}`, { ...i18n.args })} + ); + } + + return {textObject.text}; +}; + +export default PlainTextElement; diff --git a/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx b/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx index b2f4d1f9764f3..c808878a9fb1e 100644 --- a/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx +++ b/packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx @@ -1,8 +1,5 @@ import * as UiKit from '@rocket.chat/ui-kit'; -import { parse } from '@rocket.chat/message-parser'; import type { ReactElement } from 'react'; -import { Fragment } from 'react'; -import { Markup } from '@rocket.chat/gazzodown'; import ActionsBlock from '../blocks/ActionsBlock'; import ContextBlock from '../blocks/ContextBlock'; @@ -19,6 +16,8 @@ import MultiStaticSelectElement from '../elements/MultiStaticSelectElement'; import OverflowElement from '../elements/OverflowElement'; import PlainTextInputElement from '../elements/PlainTextInputElement'; import StaticSelectElement from '../elements/StaticSelectElement'; +import MarkdownTextElement from '../elements/MarkdownTextElement'; +import PlainTextElement from '../elements/PlainTextElement'; export type FuselageSurfaceRendererProps = ConstructorParameters< typeof UiKit.SurfaceRenderer @@ -40,7 +39,7 @@ export class FuselageSurfaceRenderer extends UiKit.SurfaceRenderer } public plain_text( - { text = '' }: UiKit.PlainText, + textObject: UiKit.TextObject, context: UiKit.BlockContext, index: number ): ReactElement | null { @@ -48,11 +47,11 @@ export class FuselageSurfaceRenderer extends UiKit.SurfaceRenderer return null; } - return text ? {text} : null; + return ; } public mrkdwn( - { text = '' }: UiKit.Markdown, + textObject: UiKit.TextObject, context: UiKit.BlockContext, index: number ): ReactElement | null { @@ -60,9 +59,7 @@ export class FuselageSurfaceRenderer extends UiKit.SurfaceRenderer return null; } - return text ? ( - - ) : null; + return ; } public text( @@ -70,11 +67,11 @@ export class FuselageSurfaceRenderer extends UiKit.SurfaceRenderer context: UiKit.BlockContext, index: number ): ReactElement | null { - if (textObject.type !== 'mrkdwn') { - return this.plain_text(textObject, context, index); + if (textObject.type === 'mrkdwn') { + return this.mrkdwn(textObject, context, index); } - return this.mrkdwn(textObject, context, index); + return this.plain_text(textObject, context, index); } actions( diff --git a/yarn.lock b/yarn.lock index ce4fd467ba94b..0b573475ee3ce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11008,9 +11008,9 @@ __metadata: linkType: soft "@rocket.chat/ui-kit@npm:next": - version: 0.32.0-dev.264 - resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.264" - checksum: f856ac22a422c862b84f5a550ca1358c313f0f42d74c1780eeb781394a9aca27a12572873c6d69edea92fdd092dcd4dd34dcfc8e45ecc91a34feec4220a80905 + version: 0.32.0-dev.294 + resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.294" + checksum: cf58890f3fbcfdff3df9436a33c4aa333de34369f89902107dee74c50c11b8af793c9c80a8df5046ffcabf0fd90111f31a6fc2030876e838e43ec4c6a5703fc4 languageName: node linkType: hard From 2ddb56618a4939d6e12a177b90f918b874e82d86 Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto Date: Mon, 26 Jun 2023 12:13:21 -0300 Subject: [PATCH 02/79] refactor: `AppsProvider` (#29264) Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com> --- .../marketplace => contexts}/AppsContext.tsx | 17 +++------ .../client/contexts/hooks/useAppsReload.ts | 8 +++++ .../client/contexts/hooks/useAppsResult.ts | 6 ++++ .../AppsProvider.tsx | 30 +++++++--------- .../AppDetailsPage/AppDetailsPage.tsx | 4 +-- .../tabs/AppRequests/AppRequests.tsx | 2 +- .../views/marketplace/AppInstallPage.js | 6 ++-- .../marketplace/AppsPage/AppsPageContent.tsx | 28 +++++++-------- .../AppsPage/AppsPageContentBody.tsx | 13 +++---- .../client/views/marketplace/AppsRoute.tsx | 2 +- .../views/marketplace/helpers/installApp.ts | 4 +-- .../views/marketplace/helpers/updateApp.ts | 4 +-- .../views/marketplace/hooks/useAppInfo.ts | 8 ++--- .../hooks/useAppInstallationHandler.tsx | 6 ++-- .../views/marketplace/hooks/useCategories.ts | 4 +-- .../marketplace/hooks/useFilteredApps.ts | 2 +- .../hooks/useOpenIncompatibleModal.tsx | 8 +++-- apps/meteor/ee/client/apps/i18n.js | 12 +++---- apps/meteor/ee/client/apps/index.ts | 2 +- apps/meteor/ee/client/apps/orchestrator.ts | 35 +++++++------------ .../fragments/home-flextab-members.ts | 2 +- .../fragments/home-flextab-members.ts | 2 +- 22 files changed, 99 insertions(+), 106 deletions(-) rename apps/meteor/client/{views/marketplace => contexts}/AppsContext.tsx (57%) create mode 100644 apps/meteor/client/contexts/hooks/useAppsReload.ts create mode 100644 apps/meteor/client/contexts/hooks/useAppsResult.ts rename apps/meteor/client/{views/marketplace => providers}/AppsProvider.tsx (83%) diff --git a/apps/meteor/client/views/marketplace/AppsContext.tsx b/apps/meteor/client/contexts/AppsContext.tsx similarity index 57% rename from apps/meteor/client/views/marketplace/AppsContext.tsx rename to apps/meteor/client/contexts/AppsContext.tsx index d816d32f97dee..769609c733b09 100644 --- a/apps/meteor/client/views/marketplace/AppsContext.tsx +++ b/apps/meteor/client/contexts/AppsContext.tsx @@ -1,10 +1,10 @@ -import { createContext, useContext } from 'react'; +import { createContext } from 'react'; -import type { AsyncState } from '../../lib/asyncState'; -import { AsyncStatePhase } from '../../lib/asyncState'; -import type { App } from './types'; +import type { AsyncState } from '../lib/asyncState'; +import { AsyncStatePhase } from '../lib/asyncState'; +import type { App } from '../views/marketplace/types'; -type AppsContextValue = { +export type AppsContextValue = { installedApps: AsyncState<{ apps: App[] }>; marketplaceApps: AsyncState<{ apps: App[] }>; privateApps: AsyncState<{ apps: App[] }>; @@ -29,10 +29,3 @@ export const AppsContext = createContext({ }, reload: () => Promise.resolve(), }); - -export const useAppsReload = (): (() => void) => { - const { reload } = useContext(AppsContext); - return reload; -}; - -export const useAppsResult = (): AppsContextValue => useContext(AppsContext); diff --git a/apps/meteor/client/contexts/hooks/useAppsReload.ts b/apps/meteor/client/contexts/hooks/useAppsReload.ts new file mode 100644 index 0000000000000..276c30fe5a158 --- /dev/null +++ b/apps/meteor/client/contexts/hooks/useAppsReload.ts @@ -0,0 +1,8 @@ +import { useContext } from 'react'; + +import { AppsContext } from '../AppsContext'; + +export const useAppsReload = (): (() => void) => { + const { reload } = useContext(AppsContext); + return reload; +}; diff --git a/apps/meteor/client/contexts/hooks/useAppsResult.ts b/apps/meteor/client/contexts/hooks/useAppsResult.ts new file mode 100644 index 0000000000000..fb8deed2a9cbe --- /dev/null +++ b/apps/meteor/client/contexts/hooks/useAppsResult.ts @@ -0,0 +1,6 @@ +import { useContext } from 'react'; + +import type { AppsContextValue } from '../AppsContext'; +import { AppsContext } from '../AppsContext'; + +export const useAppsResult = (): AppsContextValue => useContext(AppsContext); diff --git a/apps/meteor/client/views/marketplace/AppsProvider.tsx b/apps/meteor/client/providers/AppsProvider.tsx similarity index 83% rename from apps/meteor/client/views/marketplace/AppsProvider.tsx rename to apps/meteor/client/providers/AppsProvider.tsx index 0f1868ba375c4..2b9e2c82cd74c 100644 --- a/apps/meteor/client/views/marketplace/AppsProvider.tsx +++ b/apps/meteor/client/providers/AppsProvider.tsx @@ -3,13 +3,13 @@ import { useQuery, useQueryClient } from '@tanstack/react-query'; import type { FC } from 'react'; import React, { useEffect } from 'react'; -import { AppEvents } from '../../../ee/client/apps/communication'; -import { Apps } from '../../../ee/client/apps/orchestrator'; -import PageSkeleton from '../../components/PageSkeleton'; -import { AsyncStatePhase } from '../../lib/asyncState'; -import { AppsContext } from './AppsContext'; -import { useInvalidateAppsCountQueryCallback } from './hooks/useAppsCountQuery'; -import type { App } from './types'; +import { AppEvents } from '../../ee/client/apps/communication'; +import { AppClientOrchestratorInstance } from '../../ee/client/apps/orchestrator'; +import PageSkeleton from '../components/PageSkeleton'; +import { AppsContext } from '../contexts/AppsContext'; +import { AsyncStatePhase } from '../lib/asyncState'; +import { useInvalidateAppsCountQueryCallback } from '../views/marketplace/hooks/useAppsCountQuery'; +import type { App } from '../views/marketplace/types'; type ListenersMapping = { readonly [P in keyof typeof AppEvents]?: (...args: any[]) => void; @@ -23,11 +23,11 @@ const registerListeners = (listeners: ListenersMapping): (() => void) => { undefined >[]; for (const [event, callback] of entries) { - Apps.getWsListener()?.registerListener(AppEvents[event], callback); + AppClientOrchestratorInstance.getWsListener()?.registerListener(AppEvents[event], callback); } return (): void => { for (const [event, callback] of entries) { - Apps.getWsListener()?.unregisterListener(AppEvents[event], callback); + AppClientOrchestratorInstance.getWsListener()?.unregisterListener(AppEvents[event], callback); } }; }; @@ -68,7 +68,7 @@ const AppsProvider: FC = ({ children }) => { const marketplace = useQuery( ['marketplace', 'apps-marketplace', isAdminUser], () => { - const result = Apps.getAppsFromMarketplace(isAdminUser ? 'true' : 'false'); + const result = AppClientOrchestratorInstance.getAppsFromMarketplace(isAdminUser); queryClient.invalidateQueries(['marketplace', 'apps-stored']); return result; }, @@ -83,7 +83,7 @@ const AppsProvider: FC = ({ children }) => { const instance = useQuery( ['marketplace', 'apps-instance', isAdminUser], async () => { - const result = await Apps.getInstalledApps().then((result: App[]) => + const result = await AppClientOrchestratorInstance.getInstalledApps().then((result: App[]) => result.map((current: App) => ({ ...current, installed: true, @@ -109,7 +109,6 @@ const AppsProvider: FC = ({ children }) => { const marketplaceApps: App[] = []; const installedApps: App[] = []; const privateApps: App[] = []; - const clonedData = [...instance.data]; sortByName(marketplace.data).forEach((app) => { @@ -130,13 +129,10 @@ const AppsProvider: FC = ({ children }) => { marketplaceVersion: app.version, }; - if (installedApp?.private) { - privateApps.push(record); - } - - if (installedApp && !installedApp.private) { + if (installedApp) { installedApps.push(record); } + marketplaceApps.push(record); }); diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx index 83374c8f981af..55d01c63a0c52 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx @@ -14,7 +14,7 @@ import type { ReactElement } from 'react'; import React, { useState, useCallback, useRef } from 'react'; import type { ISettings } from '../../../../ee/client/apps/@types/IOrchestrator'; -import { Apps } from '../../../../ee/client/apps/orchestrator'; +import { AppClientOrchestratorInstance } from '../../../../ee/client/apps/orchestrator'; import Page from '../../../components/Page'; import { handleAPIError } from '../helpers/handleAPIError'; import { useAppInfo } from '../hooks/useAppInfo'; @@ -61,7 +61,7 @@ const AppDetailsPage = ({ id }: { id: App['id'] }): ReactElement => { const { current } = settingsRef; setIsSaving(true); try { - await Apps.setAppSettings( + await AppClientOrchestratorInstance.setAppSettings( id, (Object.values(settings || {}) as ISetting[]).map((value) => ({ ...value, diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppRequests/AppRequests.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppRequests/AppRequests.tsx index 1698d60290c47..228c625cf2df6 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppRequests/AppRequests.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/tabs/AppRequests/AppRequests.tsx @@ -5,8 +5,8 @@ import { useMutation } from '@tanstack/react-query'; import type { ReactElement, SetStateAction } from 'react'; import React, { useState, useEffect } from 'react'; +import { useAppsReload } from '../../../../../contexts/hooks/useAppsReload'; import { queryClient } from '../../../../../lib/queryClient'; -import { useAppsReload } from '../../../AppsContext'; import { useAppRequests } from '../../../hooks/useAppRequests'; import AppRequestItem from './AppRequestItem'; import AppRequestsLoading from './AppRequestsLoading'; diff --git a/apps/meteor/client/views/marketplace/AppInstallPage.js b/apps/meteor/client/views/marketplace/AppInstallPage.js index 896be9061912c..41c64ca0479c6 100644 --- a/apps/meteor/client/views/marketplace/AppInstallPage.js +++ b/apps/meteor/client/views/marketplace/AppInstallPage.js @@ -11,13 +11,13 @@ import { } from '@rocket.chat/ui-contexts'; import React, { useCallback, useEffect, useState } from 'react'; -import { Apps } from '../../../ee/client/apps/orchestrator'; +import { AppClientOrchestratorInstance } from '../../../ee/client/apps/orchestrator'; import Page from '../../components/Page'; +import { useAppsReload } from '../../contexts/hooks/useAppsReload'; import { useFileInput } from '../../hooks/useFileInput'; import { useForm } from '../../hooks/useForm'; import AppPermissionsReviewModal from './AppPermissionsReviewModal'; import AppUpdateModal from './AppUpdateModal'; -import { useAppsReload } from './AppsContext'; import AppInstallModal from './components/AppInstallModal/AppInstallModal'; import { handleAPIError, handleInstallError } from './helpers'; import { useAppsCountQuery } from './hooks/useAppsCountQuery'; @@ -102,7 +102,7 @@ function AppInstallPage() { const isAppInstalled = async (appId) => { try { - const app = await Apps.getApp(appId); + const app = await AppClientOrchestratorInstance.getApp(appId); return !!app || false; } catch (e) { return false; diff --git a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx index f0173ea20450f..1e2c79fe40f1b 100644 --- a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx +++ b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx @@ -4,8 +4,8 @@ import type { ReactElement } from 'react'; import React, { useEffect, useMemo, useState, useCallback } from 'react'; import { usePagination } from '../../../components/GenericTable/hooks/usePagination'; +import { useAppsResult } from '../../../contexts/hooks/useAppsResult'; import { AsyncStatePhase } from '../../../lib/asyncState'; -import { useAppsReload, useAppsResult } from '../AppsContext'; import type { RadioDropDownGroup } from '../definitions/RadioDropDownDefinitions'; import { useCategories } from '../hooks/useCategories'; import type { appsDataType } from '../hooks/useFilteredApps'; @@ -23,9 +23,8 @@ import PrivateEmptyState from './PrivateEmptyState'; const AppsPageContent = (): ReactElement => { const t = useTranslation(); - const { marketplaceApps, installedApps, privateApps } = useAppsResult(); + const { marketplaceApps, installedApps, privateApps, reload } = useAppsResult(); const [text, setText] = useDebouncedState('', 500); - const reload = useAppsReload(); const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); const [currentRouteName] = useCurrentRoute(); @@ -36,10 +35,8 @@ const AppsPageContent = (): ReactElement => { const context = useRouteParameter('context'); - const isEnterprise = context === 'enterprise'; const isMarketplace = context === 'explore'; const isRequested = context === 'requested'; - const isPrivate = context === 'private'; const [freePaidFilterStructure, setFreePaidFilterStructure] = useState({ label: t('Filter_By_Price'), @@ -76,16 +73,17 @@ const AppsPageContent = (): ReactElement => { const sortFilterOnSelected = useRadioToggle(setSortFilterStructure); const getAppsData = useCallback((): appsDataType => { - if (isMarketplace || isEnterprise || isRequested) { - return marketplaceApps; + switch (context) { + case 'enterprise': + case 'explore': + case 'requested': + return marketplaceApps; + case 'private': + return privateApps; + default: + return installedApps; } - - if (isPrivate) { - return privateApps; - } - - return installedApps; - }, [isMarketplace, isEnterprise, isRequested, isPrivate, installedApps, marketplaceApps, privateApps]); + }, [context, marketplaceApps, installedApps, privateApps]); const [categories, selectedCategories, categoryTagList, onSelected] = useCategories(); const appsResult = useFilteredApps({ @@ -172,7 +170,7 @@ const AppsPageContent = (): ReactElement => { ; + appsResult: { items: App[] } & { shouldShowSearchText: boolean } & PaginatedResult & { allApps: App[] } & { totalAppsLength: number }; itemsPerPage: 25 | 50 | 100; current: number; onSetItemsPerPage: React.Dispatch>; @@ -44,17 +41,17 @@ const AppsPageContentBody = ({ {noErrorsOcurred && ( - {isMarketplace && !isFiltered && } - + {isMarketplace && !isFiltered && } + )} - {Boolean(appsResult?.value?.count) && ( + {Boolean(appsResult?.count) && ( { onSetCurrent(value); diff --git a/apps/meteor/client/views/marketplace/AppsRoute.tsx b/apps/meteor/client/views/marketplace/AppsRoute.tsx index f7e930a86d457..0b0ca342e0ad1 100644 --- a/apps/meteor/client/views/marketplace/AppsRoute.tsx +++ b/apps/meteor/client/views/marketplace/AppsRoute.tsx @@ -3,11 +3,11 @@ import type { ReactElement } from 'react'; import React, { useState, useEffect } from 'react'; import PageSkeleton from '../../components/PageSkeleton'; +import AppsProvider from '../../providers/AppsProvider'; import NotAuthorizedPage from '../notAuthorized/NotAuthorizedPage'; import AppDetailsPage from './AppDetailsPage'; import AppInstallPage from './AppInstallPage'; import AppsPage from './AppsPage'; -import AppsProvider from './AppsProvider'; import BannerEnterpriseTrialEnded from './components/BannerEnterpriseTrialEnded'; const AppsRoute = (): ReactElement => { diff --git a/apps/meteor/client/views/marketplace/helpers/installApp.ts b/apps/meteor/client/views/marketplace/helpers/installApp.ts index 6d51c4813b188..ed909b571a21d 100644 --- a/apps/meteor/client/views/marketplace/helpers/installApp.ts +++ b/apps/meteor/client/views/marketplace/helpers/installApp.ts @@ -1,6 +1,6 @@ import type { App, AppPermission } from '@rocket.chat/core-typings'; -import { Apps } from '../../../../ee/client/apps/orchestrator'; +import { AppClientOrchestratorInstance } from '../../../../ee/client/apps/orchestrator'; import { handleAPIError } from './handleAPIError'; import { warnAppInstall } from './warnAppInstall'; @@ -10,7 +10,7 @@ type installAppProps = App & { export const installApp = async ({ id, name, marketplaceVersion, permissionsGranted }: installAppProps): Promise => { try { - const { status } = await Apps.installApp(id, marketplaceVersion, permissionsGranted); + const { status } = await AppClientOrchestratorInstance.installApp(id, marketplaceVersion, permissionsGranted); if (status) { warnAppInstall(name, status); } diff --git a/apps/meteor/client/views/marketplace/helpers/updateApp.ts b/apps/meteor/client/views/marketplace/helpers/updateApp.ts index a7b2e31be2962..fbcc3a2d5fa3d 100644 --- a/apps/meteor/client/views/marketplace/helpers/updateApp.ts +++ b/apps/meteor/client/views/marketplace/helpers/updateApp.ts @@ -1,6 +1,6 @@ import type { App, AppPermission } from '@rocket.chat/core-typings'; -import { Apps } from '../../../../ee/client/apps/orchestrator'; +import { AppClientOrchestratorInstance } from '../../../../ee/client/apps/orchestrator'; import { handleAPIError } from './handleAPIError'; import { warnStatusChange } from './warnStatusChange'; @@ -10,7 +10,7 @@ type updateAppProps = App & { export const updateApp = async ({ id, name, marketplaceVersion, permissionsGranted }: updateAppProps): Promise => { try { - const { status } = await Apps.updateApp(id, marketplaceVersion, permissionsGranted); + const { status } = await AppClientOrchestratorInstance.updateApp(id, marketplaceVersion, permissionsGranted); if (status) { warnStatusChange(name, status); } diff --git a/apps/meteor/client/views/marketplace/hooks/useAppInfo.ts b/apps/meteor/client/views/marketplace/hooks/useAppInfo.ts index de95080cd4f8c..7e69cdeef97c1 100644 --- a/apps/meteor/client/views/marketplace/hooks/useAppInfo.ts +++ b/apps/meteor/client/views/marketplace/hooks/useAppInfo.ts @@ -3,8 +3,8 @@ import { useEndpoint } from '@rocket.chat/ui-contexts'; import { useState, useEffect, useContext } from 'react'; import type { ISettings } from '../../../../ee/client/apps/@types/IOrchestrator'; -import { Apps } from '../../../../ee/client/apps/orchestrator'; -import { AppsContext } from '../AppsContext'; +import { AppClientOrchestratorInstance } from '../../../../ee/client/apps/orchestrator'; +import { AppsContext } from '../../../contexts/AppsContext'; import type { AppInfo } from '../definitions/AppInfo'; const getBundledInApp = async (app: App): Promise => { @@ -12,7 +12,7 @@ const getBundledInApp = async (app: App): Promise => { return Promise.all( bundledIn.map(async (bundle) => { - const apps = await Apps.getAppsOnBundle(bundle.bundleId); + const apps = await AppClientOrchestratorInstance.getAppsOnBundle(bundle.bundleId); bundle.apps = apps.slice(0, 4); return bundle; }), @@ -83,7 +83,7 @@ export const useAppInfo = (appId: string, context: string): AppInfo | undefined }; fetchAppInfo(); - }, [appId, context, getApis, getBundledIn, getScreenshots, getSettings, installedApps, marketplaceApps, privateApps.value]); + }, [appId, context, getApis, getBundledIn, getScreenshots, getSettings, installedApps, marketplaceApps, privateApps.value?.apps]); return appData; }; diff --git a/apps/meteor/client/views/marketplace/hooks/useAppInstallationHandler.tsx b/apps/meteor/client/views/marketplace/hooks/useAppInstallationHandler.tsx index af0b04469cd5f..666ea7bf7ae0a 100644 --- a/apps/meteor/client/views/marketplace/hooks/useAppInstallationHandler.tsx +++ b/apps/meteor/client/views/marketplace/hooks/useAppInstallationHandler.tsx @@ -2,7 +2,7 @@ import type { App } from '@rocket.chat/core-typings'; import { useEndpoint, useRoute, useRouteParameter, useSetModal, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import { Apps } from '../../../../ee/client/apps/orchestrator'; +import { AppClientOrchestratorInstance } from '../../../../ee/client/apps/orchestrator'; import IframeModal from '../IframeModal'; import AppInstallModal from '../components/AppInstallModal/AppInstallModal'; import type { Actions } from '../helpers'; @@ -51,7 +51,7 @@ export function useAppInstallationHandler({ app, action, isAppPurchased, onDismi const acquireApp = useCallback(async () => { if (action === 'purchase' && !isAppPurchased) { try { - const data = await Apps.buildExternalUrl(app.id, app.purchaseType, false); + const data = await AppClientOrchestratorInstance.buildExternalUrl(app.id, app.purchaseType, false); setModal(); } catch (error) { handleAPIError(error); @@ -84,7 +84,7 @@ export function useAppInstallationHandler({ app, action, isAppPurchased, onDismi }; try { - const data = await Apps.buildExternalAppRequest(app.id); + const data = await AppClientOrchestratorInstance.buildExternalAppRequest(app.id); setModal(); } catch (error) { handleAPIError(error); diff --git a/apps/meteor/client/views/marketplace/hooks/useCategories.ts b/apps/meteor/client/views/marketplace/hooks/useCategories.ts index 27f01f91e04b1..4a457a1460461 100644 --- a/apps/meteor/client/views/marketplace/hooks/useCategories.ts +++ b/apps/meteor/client/views/marketplace/hooks/useCategories.ts @@ -1,7 +1,7 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Apps } from '../../../../ee/client/apps/orchestrator'; +import { AppClientOrchestratorInstance } from '../../../../ee/client/apps/orchestrator'; import type { CategoryDropDownGroups, CategoryDropdownItem, @@ -19,7 +19,7 @@ export const useCategories = (): [CategoryDropDownGroups, selectedCategoriesList const fetchCategories = useCallback(async (): Promise => { try { - const fetchedCategories = await Apps.getCategories(); + const fetchedCategories = await AppClientOrchestratorInstance.getCategories(); const mappedCategories = fetchedCategories.map((currentCategory) => ({ id: currentCategory.id, diff --git a/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts b/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts index 1d14c34c7be4c..19664cd4b6937 100644 --- a/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts +++ b/apps/meteor/client/views/marketplace/hooks/useFilteredApps.ts @@ -2,9 +2,9 @@ import type { PaginatedResult } from '@rocket.chat/rest-typings'; import type { ContextType } from 'react'; import { useMemo } from 'react'; +import type { AppsContext } from '../../../contexts/AppsContext'; import type { AsyncState } from '../../../lib/asyncState'; import { AsyncStatePhase } from '../../../lib/asyncState'; -import type { AppsContext } from '../AppsContext'; import { filterAppsByCategories } from '../helpers/filterAppsByCategories'; import { filterAppsByDisabled } from '../helpers/filterAppsByDisabled'; import { filterAppsByEnabled } from '../helpers/filterAppsByEnabled'; diff --git a/apps/meteor/client/views/marketplace/hooks/useOpenIncompatibleModal.tsx b/apps/meteor/client/views/marketplace/hooks/useOpenIncompatibleModal.tsx index 41bf7716b1eb6..b80bdb9a61a7a 100644 --- a/apps/meteor/client/views/marketplace/hooks/useOpenIncompatibleModal.tsx +++ b/apps/meteor/client/views/marketplace/hooks/useOpenIncompatibleModal.tsx @@ -1,7 +1,7 @@ import { useSetModal } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import { Apps } from '../../../../ee/client/apps/orchestrator'; +import { AppClientOrchestratorInstance } from '../../../../ee/client/apps/orchestrator'; import IframeModal from '../IframeModal'; import { handleAPIError } from '../helpers/handleAPIError'; @@ -21,7 +21,11 @@ export const useOpenIncompatibleModal = () => { }; try { - const incompatibleData = await Apps.buildIncompatibleExternalUrl(app.id, app.marketplaceVersion, actionName); + const incompatibleData = await AppClientOrchestratorInstance.buildIncompatibleExternalUrl( + app.id, + app.marketplaceVersion, + actionName, + ); setModal(); } catch (e) { handleAPIError(e); diff --git a/apps/meteor/ee/client/apps/i18n.js b/apps/meteor/ee/client/apps/i18n.js index 9b2c6ffe2cb78..bdece5e690616 100644 --- a/apps/meteor/ee/client/apps/i18n.js +++ b/apps/meteor/ee/client/apps/i18n.js @@ -1,7 +1,7 @@ import { i18n } from '../../../app/utils/lib/i18n'; import { Utilities } from '../../lib/misc/Utilities'; import { AppEvents } from './communication'; -import { Apps } from './orchestrator'; +import { AppClientOrchestratorInstance } from './orchestrator'; const loadAppI18nResources = (appId, languages) => { Object.entries(languages).forEach(([language, translations]) => { @@ -18,22 +18,22 @@ const loadAppI18nResources = (appId, languages) => { i18n.addResourceBundle(normalizedLanguage, 'core', scopedTranslations); } catch (error) { - Apps.handleError(error); + AppClientOrchestratorInstance.handleError(error); } }); }; const handleAppAdded = async (appId) => { - const languages = await Apps.getAppLanguages(appId); + const languages = await AppClientOrchestratorInstance.getAppLanguages(appId); loadAppI18nResources(appId, languages); }; export const handleI18nResources = async () => { - const apps = await Apps.getAppsLanguages(); + const apps = await AppClientOrchestratorInstance.getAppsLanguages(); apps.forEach(({ id, languages }) => { loadAppI18nResources(id, languages); }); - Apps.getWsListener().unregisterListener(AppEvents.APP_ADDED, handleAppAdded); - Apps.getWsListener().registerListener(AppEvents.APP_ADDED, handleAppAdded); + AppClientOrchestratorInstance.getWsListener().unregisterListener(AppEvents.APP_ADDED, handleAppAdded); + AppClientOrchestratorInstance.getWsListener().registerListener(AppEvents.APP_ADDED, handleAppAdded); }; diff --git a/apps/meteor/ee/client/apps/index.ts b/apps/meteor/ee/client/apps/index.ts index 2e909abf81e5e..2c81f48ecdd6f 100644 --- a/apps/meteor/ee/client/apps/index.ts +++ b/apps/meteor/ee/client/apps/index.ts @@ -1,3 +1,3 @@ import './gameCenter/tabBar'; -export { Apps } from './orchestrator'; +export { AppClientOrchestratorInstance as Apps } from './orchestrator'; diff --git a/apps/meteor/ee/client/apps/orchestrator.ts b/apps/meteor/ee/client/apps/orchestrator.ts index c005222796d1f..80a8e2cbf6ef4 100644 --- a/apps/meteor/ee/client/apps/orchestrator.ts +++ b/apps/meteor/ee/client/apps/orchestrator.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-var-requires */ import { AppClientManager } from '@rocket.chat/apps-engine/client/AppClientManager'; import { AppStatus } from '@rocket.chat/apps-engine/definition/AppStatus'; import type { IApiEndpointMetadata } from '@rocket.chat/apps-engine/definition/api'; @@ -13,15 +12,7 @@ import { CachedCollectionManager } from '../../../app/ui-cached-collection/clien import { sdk } from '../../../app/utils/client/lib/SDKClient'; import { dispatchToastMessage } from '../../../client/lib/toast'; import type { App } from '../../../client/views/marketplace/types'; -import type { - // IAppFromMarketplace, - IAppLanguage, - IAppExternalURL, - ICategory, - // IAppSynced, - // IAppScreenshots, - // IScreenshot, -} from './@types/IOrchestrator'; +import type { IAppLanguage, IAppExternalURL, ICategory } from './@types/IOrchestrator'; import { RealAppsEngineUIHost } from './RealAppsEngineUIHost'; import { AppWebsocketReceiver } from './communication'; import { handleI18nResources } from './i18n'; @@ -31,27 +22,27 @@ class AppClientOrchestrator { private _manager: AppClientManager; - private isLoaded: boolean; + private _isLoaded: boolean; - private ws: AppWebsocketReceiver; + private _ws: AppWebsocketReceiver; constructor() { this._appClientUIHost = new RealAppsEngineUIHost(); this._manager = new AppClientManager(this._appClientUIHost); - this.isLoaded = false; + this._isLoaded = false; } public async load(): Promise { - if (!this.isLoaded) { - this.ws = new AppWebsocketReceiver(); - this.isLoaded = true; + if (!this._isLoaded) { + this._ws = new AppWebsocketReceiver(); + this._isLoaded = true; } await handleI18nResources(); } public getWsListener(): AppWebsocketReceiver { - return this.ws; + return this._ws; } public getAppClientManager(): AppClientManager { @@ -82,8 +73,8 @@ class AppClientOrchestrator { throw new Error('Invalid response from API'); } - public async getAppsFromMarketplace(isAdminUser?: string): Promise { - const result = await sdk.rest.get('/apps/marketplace', { isAdminUser }); + public async getAppsFromMarketplace(isAdminUser?: boolean): Promise { + const result = await sdk.rest.get('/apps/marketplace', { isAdminUser: isAdminUser ? isAdminUser.toString() : 'false' }); if (!Array.isArray(result)) { // TODO: chapter day: multiple results are returned, but we only need one @@ -271,11 +262,11 @@ class AppClientOrchestrator { } } -export const Apps = new AppClientOrchestrator(); +export const AppClientOrchestratorInstance = new AppClientOrchestrator(); Meteor.startup(() => { CachedCollectionManager.onLogin(() => { - Apps.getAppClientManager().initialize(); - Apps.load(); + AppClientOrchestratorInstance.getAppClientManager().initialize(); + AppClientOrchestratorInstance.load(); }); }); diff --git a/apps/meteor/tests/e2e/federation/page-objects/fragments/home-flextab-members.ts b/apps/meteor/tests/e2e/federation/page-objects/fragments/home-flextab-members.ts index a690c35591677..929fd0299aef7 100644 --- a/apps/meteor/tests/e2e/federation/page-objects/fragments/home-flextab-members.ts +++ b/apps/meteor/tests/e2e/federation/page-objects/fragments/home-flextab-members.ts @@ -50,6 +50,6 @@ export class FederationHomeFlextabMembers { async showAllUsers() { await this.page.locator('.rcx-select >> text=Online').first().click(); - await this.page.locator('.rcx-option__content:has-text("All")').first().click(); + await this.page.locator('.rcx-option:has-text("All")').first().click(); } } diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-members.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-members.ts index dc8b04455a6eb..bbcd911a451fe 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-members.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-flextab-members.ts @@ -39,6 +39,6 @@ export class HomeFlextabMembers { async showAllUsers() { await this.page.locator('.rcx-select >> text=Online').first().click(); - await this.page.locator('.rcx-option__content:has-text("All")').first().click(); + await this.page.locator('.rcx-option:has-text("All")').first().click(); } } From 3126b51a81cc9f4a1bbd23f184db435562324772 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Mon, 26 Jun 2023 15:55:58 -0300 Subject: [PATCH 03/79] fix: Canned responses filter not working (#29648) --- .../omnichannel/cannedResponses/CannedResponsesTable.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx index fa5b4eb67ec39..d179a14458b9f 100644 --- a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx +++ b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx @@ -67,7 +67,10 @@ const CannedResponsesTable = () => { ); const getCannedResponses = useEndpoint('GET', '/v1/canned-responses'); - const { data, isLoading, isSuccess, refetch } = useQuery(['canned-responses', debouncedText], () => getCannedResponses(query)); + const { data, isLoading, isSuccess, refetch } = useQuery( + ['/v1/canned-responses', { debouncedText, sortDirection, itemsPerPage, current, sharing, createdBy }], + () => getCannedResponses(query), + ); const getTime = useFormatDateAndTime(); From 610f3e92e05b2d26950ccde04e484a115cdb71fc Mon Sep 17 00:00:00 2001 From: "lingohub[bot]" <69908207+lingohub[bot]@users.noreply.github.com> Date: Mon, 26 Jun 2023 19:52:30 +0000 Subject: [PATCH 04/79] =?UTF-8?q?i18n:=20Language=20update=20from=20LingoH?= =?UTF-8?q?ub=20=F0=9F=A4=96=20on=202023-06-26Z=20(#29646)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Douglas Fabris <27704687+dougfabris@users.noreply.github.com> --- .../rocketchat-i18n/i18n/af.i18n.json | 4 +- .../rocketchat-i18n/i18n/ar.i18n.json | 7 +- .../rocketchat-i18n/i18n/az.i18n.json | 4 +- .../rocketchat-i18n/i18n/be-BY.i18n.json | 4 +- .../rocketchat-i18n/i18n/bg.i18n.json | 4 +- .../rocketchat-i18n/i18n/bs.i18n.json | 4 +- .../rocketchat-i18n/i18n/ca.i18n.json | 7 +- .../rocketchat-i18n/i18n/cs.i18n.json | 5 +- .../rocketchat-i18n/i18n/cy.i18n.json | 4 +- .../rocketchat-i18n/i18n/da.i18n.json | 5 +- .../rocketchat-i18n/i18n/de-AT.i18n.json | 4 +- .../rocketchat-i18n/i18n/de.i18n.json | 30 +++++++-- .../rocketchat-i18n/i18n/el.i18n.json | 4 +- .../rocketchat-i18n/i18n/en.i18n.json | 2 +- .../rocketchat-i18n/i18n/eo.i18n.json | 4 +- .../rocketchat-i18n/i18n/es.i18n.json | 6 +- .../rocketchat-i18n/i18n/fa.i18n.json | 4 +- .../rocketchat-i18n/i18n/fi.i18n.json | 7 +- .../rocketchat-i18n/i18n/fr.i18n.json | 7 +- .../rocketchat-i18n/i18n/he.i18n.json | 4 +- .../rocketchat-i18n/i18n/hi-IN.i18n.json | 2 +- .../rocketchat-i18n/i18n/hi.i18n.json | 2 +- .../rocketchat-i18n/i18n/hr.i18n.json | 4 +- .../rocketchat-i18n/i18n/hu.i18n.json | 7 +- .../rocketchat-i18n/i18n/id.i18n.json | 4 +- .../rocketchat-i18n/i18n/it.i18n.json | 4 +- .../rocketchat-i18n/i18n/ja.i18n.json | 7 +- .../rocketchat-i18n/i18n/ka-GE.i18n.json | 4 +- .../rocketchat-i18n/i18n/km.i18n.json | 4 +- .../rocketchat-i18n/i18n/ko.i18n.json | 5 +- .../rocketchat-i18n/i18n/ku.i18n.json | 4 +- .../rocketchat-i18n/i18n/lo.i18n.json | 4 +- .../rocketchat-i18n/i18n/lt.i18n.json | 4 +- .../rocketchat-i18n/i18n/lv.i18n.json | 4 +- .../rocketchat-i18n/i18n/mn.i18n.json | 4 +- .../rocketchat-i18n/i18n/ms-MY.i18n.json | 4 +- .../rocketchat-i18n/i18n/nl.i18n.json | 7 +- .../rocketchat-i18n/i18n/no.i18n.json | 4 +- .../rocketchat-i18n/i18n/pl.i18n.json | 7 +- .../rocketchat-i18n/i18n/pt-BR.i18n.json | 7 +- .../rocketchat-i18n/i18n/pt.i18n.json | 4 +- .../rocketchat-i18n/i18n/ro.i18n.json | 4 +- .../rocketchat-i18n/i18n/ru.i18n.json | 66 +++++++++++++++---- .../rocketchat-i18n/i18n/sk-SK.i18n.json | 4 +- .../rocketchat-i18n/i18n/sl-SI.i18n.json | 4 +- .../rocketchat-i18n/i18n/sq.i18n.json | 4 +- .../rocketchat-i18n/i18n/sr.i18n.json | 4 +- .../rocketchat-i18n/i18n/sv.i18n.json | 7 +- .../rocketchat-i18n/i18n/ta-IN.i18n.json | 4 +- .../rocketchat-i18n/i18n/th-TH.i18n.json | 4 +- .../rocketchat-i18n/i18n/tr.i18n.json | 4 +- .../rocketchat-i18n/i18n/ug.i18n.json | 4 +- .../rocketchat-i18n/i18n/uk.i18n.json | 4 +- .../rocketchat-i18n/i18n/vi-VN.i18n.json | 4 +- .../rocketchat-i18n/i18n/zh-HK.i18n.json | 4 +- .../rocketchat-i18n/i18n/zh-TW.i18n.json | 7 +- .../rocketchat-i18n/i18n/zh.i18n.json | 5 +- 57 files changed, 199 insertions(+), 150 deletions(-) diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/af.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/af.i18n.json index 2efae96e645f0..cc915ea211865 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/af.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/af.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "Bevestig nuwe wagwoord", "Confirm_New_Password_Placeholder": "Voer asseblief nuwe wagwoord weer in ...", "Confirm_password": "Bevestig jou wagwoord", + "Confirm_your_password": "Bevestig jou wagwoord", "Connection_Closed": "Verbinding gesluit", "Connection_Reset": "Verbinding herstel", "Consulting": "Consulting", @@ -2474,7 +2475,6 @@ "Type_your_job_title": "Tik jou werk titel", "Type_your_message": "Tik jou boodskap", "Type_your_name": "Tik jou naam", - "Type_your_new_password": "Tik jou nuwe wagwoord", "Type_your_password": "Tik jou wagwoord", "Type_your_username": "Tik jou gebruikersnaam", "UI_Allow_room_names_with_special_chars": "Laat spesiale karakters in kamer name toe", @@ -2759,4 +2759,4 @@ "registration.component.form.invalidConfirmPass": "Die wagwoord bevestiging pas nie by die wagwoord nie", "registration.component.form.confirmPassword": "Bevestig jou wagwoord", "registration.component.form.sendConfirmationEmail": "Stuur bevestiging e-pos" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json index 99df7b5a6035d..f2691bb674b5c 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ar.i18n.json @@ -922,6 +922,7 @@ "Confirm_new_password": "تأكيد كلمة المرور الجديدة", "Confirm_New_Password_Placeholder": "jEv[n إعادة إدخال كلمة المرور الجديدة...", "Confirm_password": "تأكيد كلمة المرور", + "Confirm_your_password": "تأكيد كلمة السر", "Confirmation": "التأكيد", "Connect": "اتصال", "Connected": "تم الاتصال", @@ -2675,7 +2676,7 @@ "Livechat_OfflineMessageToChannel_enabled": "إرسال رسائل Livechat من دون اتصال إلى قناة", "Omnichannel_on_hold_chat_resumed": "تم استئناف الدردشة قيد الانتظار: {{comment}}", "Omnichannel_on_hold_chat_automatically": "تم استئناف الدردشة تلقائيًا من \"قيد الانتظار\" عند تلقي رسالة جديدة من {{guest}}", - "Omnichannel_on_hold_chat_manually": "تم استئناف الدردشة يدويًا من \"قيد الانتظار\" بواسطة {{user}}", + "Omnichannel_on_hold_chat_resumed_manually": "تم استئناف الدردشة يدويًا من \"قيد الانتظار\" بواسطة {{user}}", "Omnichannel_On_Hold_due_to_inactivity": "تم وضع الدردشة تلقائيًا قيد الانتظار لأننا لم نتلق أي رد من {{guest}} في {{timeout}} من الثواني", "Omnichannel_On_Hold_manually": "تم وضع الدردشة يدويًا قيد الانتظار بواسطة {{user}}", "Omnichannel_onHold_Chat": "وضع الدردشة قيد الانتظار", @@ -3921,7 +3922,6 @@ "Show_Avatars": "إظهار الصور الرمزية", "Show_counter": "عرض العداد", "Show_email_field": "عرض حقل البريد الإلكتروني", - "Show_Message_In_Main_Thread": "عرض رسائل الموضوع في الموضوع الرئيسي", "Show_more": "عرض المزيد", "Show_name_field": "عرض حقل الاسم", "show_offline_users": "عرض المستخدمين غير المتصلين", @@ -4373,7 +4373,6 @@ "Type_your_job_title": "اكتب المسمى الوظيفي الخاص بك", "Type_your_message": "اكتب رسالتك", "Type_your_name": "اكتب اسمك", - "Type_your_new_password": "اكتب كلمة المرور الجديدة", "Type_your_password": "اكتب كلمة المرور الخاصة بك", "Type_your_username": "اكتب اسم المستخدم الخاص بك", "UI_Allow_room_names_with_special_chars": "السماح بالأحرف الخاصة في أسماء Room", @@ -4945,4 +4944,4 @@ "RegisterWorkspace_Features_Omnichannel_Title": "قناة متعددة الاتجاهات", "RegisterWorkspace_Setup_Label": "البريد الإلكتروني لحساب السحابة", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "أوافق على <1>البنود والشروط و<3>سياسة الخصوصية" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/az.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/az.i18n.json index 0c39addf74982..a44c25a1b95b3 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/az.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/az.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "Yeni Şifrəni təsdiq", "Confirm_New_Password_Placeholder": "Yeni parol yenidən daxil edin ...", "Confirm_password": "Şifrənizi təsdiqləyin", + "Confirm_your_password": "Şifrənizi təsdiqləyin", "Connection_Closed": "Bağlantı bağlanıb", "Connection_Reset": "Bağlantı sıfırlandı", "Consulting": "Məsləhətçilik", @@ -2474,7 +2475,6 @@ "Type_your_job_title": "İşinizin adını yazın", "Type_your_message": "Mesajınızı yazın", "Type_your_name": "Adınızı yazın", - "Type_your_new_password": "Yeni parolunuzu yazın", "Type_your_password": "Şifrənizi yazın", "Type_your_username": "İstifadəçi adınızı yazın", "UI_Allow_room_names_with_special_chars": "Otaq adlarında xüsusi simvollara icazə verin", @@ -2759,4 +2759,4 @@ "registration.component.form.invalidConfirmPass": "Şifrənin təsdiqlənməsi şifrə uyğun gəlmir", "registration.component.form.confirmPassword": "Şifrənizi təsdiqləyin", "registration.component.form.sendConfirmationEmail": "Təsdiq e-poçt göndər" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/be-BY.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/be-BY.i18n.json index d6de507a89d38..e448574f6b48d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/be-BY.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/be-BY.i18n.json @@ -508,6 +508,7 @@ "Confirm_new_password": "Пацвердзіце новы пароль", "Confirm_New_Password_Placeholder": "Калі ласка, паўторна ўвесці новы пароль ...", "Confirm_password": "Пацвердзіць пароль", + "Confirm_your_password": "Пацвердзіць пароль", "Connection_Closed": "Сувязь спынена", "Connection_Reset": "скід падлучэння", "Consulting": "кансалтынг", @@ -2492,7 +2493,6 @@ "Type_your_job_title": "Калі ласка, увядзіце назву пасады", "Type_your_message": "Увядзіце ваша паведамленне", "Type_your_name": "Увядзіце сваё імя", - "Type_your_new_password": "Калі ласка, увядзіце новы пароль", "Type_your_password": "Калі ласка, увядзіце пароль", "Type_your_username": "Калі ласка, увядзіце імя карыстальніка", "UI_Allow_room_names_with_special_chars": "Дазволіць спецыяльныя сімвалы ў нумары імёнаў", @@ -2777,4 +2777,4 @@ "registration.component.form.invalidConfirmPass": "Пацвярджэнне пароля не супадае пароль", "registration.component.form.confirmPassword": "Пацвердзіць пароль", "registration.component.form.sendConfirmationEmail": "Адправіць па электроннай пошце пацвярджэнне" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/bg.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/bg.i18n.json index 6e6c2ab85bd4d..35a907b728d3e 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/bg.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/bg.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "Потвърждение на новата парола", "Confirm_New_Password_Placeholder": "Моля, въведете отново нова парола ...", "Confirm_password": "Потвърдите паролата", + "Confirm_your_password": "Потвърдите паролата", "Connection_Closed": "Връзката е затворена", "Connection_Reset": "Рестартиране на връзката", "Consulting": "консултативен", @@ -2470,7 +2471,6 @@ "Type_your_job_title": "Въведете длъжността си", "Type_your_message": "Въведете съобщението си", "Type_your_name": "Въведете името си", - "Type_your_new_password": "Въведете новата си парола", "Type_your_password": "Въведете паролата си", "Type_your_username": "Въведете потребителското си име", "UI_Allow_room_names_with_special_chars": "Позволете специални символи в имената на стаите", @@ -2751,4 +2751,4 @@ "registration.component.form.invalidConfirmPass": "Потвърждението на паролата не съвпада с паролата", "registration.component.form.confirmPassword": "Потвърдите паролата", "registration.component.form.sendConfirmationEmail": "Изпратете имейл за потвърждение" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/bs.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/bs.i18n.json index 81e32ac96d21e..4776295db4491 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/bs.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/bs.i18n.json @@ -488,6 +488,7 @@ "Confirm_new_password": "Potvrdi novu lozinku", "Confirm_New_Password_Placeholder": "Ponovno unesite novu zaporku ...", "Confirm_password": "Potvrdi svoju lozinku", + "Confirm_your_password": "Potvrdi svoju lozinku", "Connection_Closed": "Veza je zatvorena", "Connection_Reset": "Ponovno postavljanje veze", "Consulting": "savjetodavni", @@ -2466,7 +2467,6 @@ "Type_your_job_title": "Upišite svoj posao", "Type_your_message": "Upišite svoju poruku", "Type_your_name": "Upišite svoje ime", - "Type_your_new_password": "Upišite novu lozinku", "Type_your_password": "Upišite svoju lozinku", "Type_your_username": "Upišite svoje korisničko ime", "UI_Allow_room_names_with_special_chars": "Omogući posebne znakove u imenima soba", @@ -2747,4 +2747,4 @@ "registration.component.form.invalidConfirmPass": "Potvrda lozinke se ne slaže sa lozinkom", "registration.component.form.confirmPassword": "Potvrdi svoju lozinku", "registration.component.form.sendConfirmationEmail": "Pošalji potvrdni email" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ca.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ca.i18n.json index 175c45a59ed0c..ddb2d9d3e259d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ca.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ca.i18n.json @@ -917,6 +917,7 @@ "Confirm_new_password": "Confirmar nova contrasenya", "Confirm_New_Password_Placeholder": "Si us plau, torni a ingressar una nova contrasenya ...", "Confirm_password": "Confirma la contrasenya", + "Confirm_your_password": "Confirma la contrasenya", "Confirmation": "Confirmació", "Connect": "Connectar", "Connected": "Connectat", @@ -2648,7 +2649,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Enviar missatges sense connexió d'LiveChat a un canal", "Omnichannel_on_hold_chat_resumed": "Represa de xat en espera: {{comment}}", "Omnichannel_on_hold_chat_automatically": "El xat es va reprendre automàticament des de En espera a l'rebre un nou missatge de {{guest}}", - "Omnichannel_on_hold_chat_manually": "El xat va ser reprès manualment des de En espera per {{user}}", + "Omnichannel_on_hold_chat_resumed_manually": "El xat va ser reprès manualment des de En espera per {{user}}", "Omnichannel_On_Hold_due_to_inactivity": "El xat es va posar automàticament en espera perquè no hem rebut cap resposta de {{guest}} a {{timeout}} segons", "Omnichannel_On_Hold_manually": "El xat va ser posat manualment en espera per {{user}}", "Omnichannel_onHold_Chat": "Posar xat en espera", @@ -3856,7 +3857,6 @@ "Show_Avatars": "Mostra Avatars", "Show_counter": "Mostra comptador", "Show_email_field": "Mostra el camp de correu electrònic", - "Show_Message_In_Main_Thread": "Mostra els missatges del fil al fil principal", "Show_more": "Mostrar més", "Show_name_field": "Mostra el camp del nom", "show_offline_users": "Mostra els usuaris desconnectats", @@ -4300,7 +4300,6 @@ "Type_your_job_title": "Escriviu el vostre títol de treball", "Type_your_message": "Introduïu el missatge", "Type_your_name": "Escriu el teu nom", - "Type_your_new_password": "Escriviu la nova contrasenya", "Type_your_password": "Escriviu la vostra contrasenya", "Type_your_username": "Escriviu el vostre nom d'usuari", "UI_Allow_room_names_with_special_chars": "Permetre caràcters especials en noms de sales", @@ -4746,4 +4745,4 @@ "registration.component.form.sendConfirmationEmail": "Envia correu-e de confirmació", "RegisterWorkspace_Features_Marketplace_Title": "Mercat", "RegisterWorkspace_Features_Omnichannel_Title": "LiveChat" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/cs.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/cs.i18n.json index 8bd4079ea77e1..4d5b1232d9d29 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/cs.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/cs.i18n.json @@ -751,6 +751,7 @@ "Confirm_new_password": "Potvrďte nové heslo", "Confirm_New_Password_Placeholder": "Zadejte znovu nové heslo ...", "Confirm_password": "Potvrďte heslo", + "Confirm_your_password": "Potvrďte heslo", "Connect": "Připojit", "Connection_Closed": "Připojení bylo uzavřeno", "Connection_Reset": "Obnovit připojení", @@ -3241,7 +3242,6 @@ "Show_Avatars": "Zobrazit avatary", "Show_counter": "Zobrazit počítadlo", "Show_email_field": "Zobrazit pole email", - "Show_Message_In_Main_Thread": "Zobrazit zprávy vlákna i v hlavním vlákně", "Show_more": "Zobrazit více", "Show_name_field": "Zobrazit pole jméno", "show_offline_users": "zobrazit offline uživatele", @@ -3596,7 +3596,6 @@ "Type_your_job_title": "Zadejte svou pozici", "Type_your_message": "Napište zprávu", "Type_your_name": "Zadejte své jméno", - "Type_your_new_password": "Zadejte nové heslo", "Type_your_password": "Zadejte své heslo", "Type_your_username": "Zadejte své uživatelské jméno", "UI_Allow_room_names_with_special_chars": "Povolit speciální znaky v názvech místností", @@ -3980,4 +3979,4 @@ "registration.component.form.invalidConfirmPass": "Hesla nesouhlasí", "registration.component.form.confirmPassword": "Potvrďte heslo", "registration.component.form.sendConfirmationEmail": "Zaslat potvrzovací e-mail" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/cy.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/cy.i18n.json index 0082ea09da145..0c3bb7349d5d4 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/cy.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/cy.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "Cadarnhau cyfrinair Newydd", "Confirm_New_Password_Placeholder": "Ail-gofnodwch gyfrinair newydd ...", "Confirm_password": "Cadarnhau eich cyfrinair", + "Confirm_your_password": "Cadarnhau eich cyfrinair", "Connection_Closed": "Cysylltiad ar gau", "Connection_Reset": "Ailosodiad Cysylltiad", "Consulting": "Ymgynghori", @@ -2468,7 +2469,6 @@ "Type_your_job_title": "Teipiwch deitl eich swydd", "Type_your_message": "Teipiwch eich neges", "Type_your_name": "Teipiwch eich enw", - "Type_your_new_password": "Teipiwch eich cyfrinair newydd", "Type_your_password": "Teipiwch eich cyfrinair", "Type_your_username": "Teipiwch eich enw defnyddiwr", "UI_Allow_room_names_with_special_chars": "Caniatáu Cymeriadau Arbennig mewn Enwau Ystafelloedd", @@ -2750,4 +2750,4 @@ "registration.component.form.invalidConfirmPass": "Nid yw'r cadarnhad cyfrinair yn cyfateb i'r cyfrinair", "registration.component.form.confirmPassword": "Cadarnhau eich cyfrinair", "registration.component.form.sendConfirmationEmail": "Anfon ebost cadarnhad" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/da.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/da.i18n.json index abf8e8119467b..e45395e5ab8ce 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/da.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/da.i18n.json @@ -755,6 +755,7 @@ "Confirm_new_password": "Bekræft ny adgangskode", "Confirm_New_Password_Placeholder": "Indtast venligst nyt kodeord igen...", "Confirm_password": "Bekræft dit kodeord", + "Confirm_your_password": "Bekræft dit kodeord", "Connect": "Forbind", "Connection_Closed": "Forbindelse lukket", "Connection_Reset": "Nulstilning af forbindelse", @@ -3260,7 +3261,6 @@ "Show_Avatars": "Vis avatars", "Show_counter": "Vis tæller", "Show_email_field": "Vis e-mail-feltet", - "Show_Message_In_Main_Thread": "Vis trådmeddelelser i hovedtråden", "Show_more": "Vis mere", "Show_name_field": "Vis navn felt", "show_offline_users": "Vis offline brugere", @@ -3617,7 +3617,6 @@ "Type_your_job_title": "Indtast din jobtitel", "Type_your_message": "Skriv din besked", "Type_your_name": "Indtast dit navn", - "Type_your_new_password": "Indtast din nye adgangskode", "Type_your_password": "Indtast dit kodeord", "Type_your_username": "Indtast dit brugernavn", "UI_Allow_room_names_with_special_chars": "Tillad særlige tegn i rumnavne", @@ -4002,4 +4001,4 @@ "registration.component.form.invalidConfirmPass": "Adgangskodebekræftelsen stemmer ikke overens med adgangskoden", "registration.component.form.confirmPassword": "Bekræft dit kodeord", "registration.component.form.sendConfirmationEmail": "Send bekræftelses-email" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/de-AT.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/de-AT.i18n.json index 58c040ca09a1e..465367b67ae8c 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/de-AT.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/de-AT.i18n.json @@ -490,6 +490,7 @@ "Confirm_new_password": "Bestätige neues Passwort", "Confirm_New_Password_Placeholder": "Bitte gib ein neues Passwort ein ...", "Confirm_password": "Bestätigen Sie Ihr Passwort.", + "Confirm_your_password": "Bestätigen Sie Ihr Passwort.", "Connection_Closed": "Verbindung geschlossen", "Connection_Reset": "Verbindung zurücksetzen", "Consulting": "Beratung", @@ -2476,7 +2477,6 @@ "Type_your_job_title": "Geben Sie Ihre Berufsbezeichnung ein", "Type_your_message": "Geben Sie Ihre Nachricht ein", "Type_your_name": "Geben Sie Ihren Namen ein", - "Type_your_new_password": "Geben Sie Ihr neues Passwort ein", "Type_your_password": "Geben Sie Ihr Passwort ein", "Type_your_username": "Gib deinen Benutzernamen ein", "UI_Allow_room_names_with_special_chars": "Erlaube Sonderzeichen in Raumnamen", @@ -2759,4 +2759,4 @@ "registration.component.form.invalidConfirmPass": "Die Passwörter stimmen nicht überein.", "registration.component.form.confirmPassword": "Bestätigen Sie Ihr Passwort.", "registration.component.form.sendConfirmationEmail": "Bestätigungsmail versenden" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/de.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/de.i18n.json index a4ecf3c390a61..9645db821afc5 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/de.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/de.i18n.json @@ -9,6 +9,8 @@ "__usersCount__people_will_be_invited": "{{usersCount}} Mitglieder werden eingeladen", "__username__is_no_longer__role__defined_by__user_by_": "{{username}} ist nicht länger {{role}}, geändert durch {{user_by}}", "__username__was_set__role__by__user_by_": "{{username}} ist jetzt {{role}}, geändert durch {{user_by}}", + "removed__username__as__role_": "{{username}} als {{role}} entfernt", + "set__username__as__role_": "{{username}} als {{role}} gesetzt", "This_room_encryption_has_been_enabled_by__username_": "Die Verschlüsselung dieses Raums wurde aktiviert von {{username}}", "This_room_encryption_has_been_disabled_by__username_": "Die Verschlüsselung dieses Raums wurde deaktiviert von {{username}}", "Enabled_E2E_Encryption_for_this_room": "E2E-Verschlüsselung für diesen Raum aktiviert", @@ -1046,6 +1048,7 @@ "Confirm_new_password": "Bestätigen Sie ihr neues Passwort", "Confirm_New_Password_Placeholder": "Bitte geben Sie ein neues Passwort ein ...", "Confirm_password": "Bestätigen Sie Ihr Passwort", + "Confirm_your_password": "Bestätigen Sie Ihr Passwort", "Confirmation": "Bestätigung", "Configure_video_conference": "Telefonkonferenz konfigurieren", "Connect": "Verbinden", @@ -2008,16 +2011,22 @@ "You_do_not_have_permission_to_do_this": "Sie haben keine Berechtigung, dies zu tun", "Errors_and_Warnings": "Fehler und Warnungen", "Esc_to": "Esc: ", + "Estimated_wait_time": "Geschätzte Wartezeit", + "Estimated_wait_time_in_minutes": "Geschätzte Wartezeit (Zeit in Minuten)", "Event_Trigger": "Ereignisauslöser", "Event_Trigger_Description": "Bitte wählen Sie aus, welche Ereignistypen diesen ausgehenden Webhook auslösen", "every_5_minutes": "Einmal alle 5 Minuten", "every_10_seconds": "Einmal alle 10 Sekunden", + "every_30_seconds": "Einmal alle 30 Sekunden", + "every_10_minutes": "Einmal alle 10 Minuten", "every_30_minutes": "alle 30 Minuten", "every_day": "Einmal jeden Tag", "every_hour": "Stündlich", "every_minute": "Einmal pro Minute", "every_second": "Einmal jede Sekunde", "every_six_hours": "Alle 6 Stunden", + "every_12_hours": "Einmal alle 12 Stunden", + "every_24_hours": "Einmal alle 24 Stunden", "Everyone_can_access_this_channel": "Jeder kann auf diesen Kanal zugreifen", "Exact": "Genau", "Example_payload": "Beispiel-Payload", @@ -2151,6 +2160,8 @@ "Federation_Matrix_bridge_localpart": "AppService User Localpart", "Federation_Matrix_registration_file": "Registrierungsdatei", "Federation_Matrix_enable_typing_status": "\"Benutzer schreibt\"-Status anzeigen", + "Federation_Matrix_not_allowed_to_change_moderator": "Sie dürfen den Moderator nicht wechseln", + "Federation_Matrix_not_allowed_to_change_owner": "Sie dürfen den Besitzer nicht wechseln", "Field": "Feld", "Field_removed": "Feld entfernt", "Field_required": "Feld erforderlich", @@ -2497,6 +2508,7 @@ "initials_avatar": "Avatar aus Initialen", "Inline_code": "Inline-Code", "Install": "Installieren", + "Install_anyway": "Trotzdem installieren", "Install_Extension": "Erweiterung installieren", "Install_FxOs": "Installieren Sie Rocket.Chat auf Ihrem Firefox", "Install_FxOs_done": "Super! Nun lässt sich Rocket.Chat über das Icon auf dem Startbildschirm nutzen. Viel Spaß mit Rocket.Chat!", @@ -2744,7 +2756,9 @@ "Layout_Login_Terms": "Anmeldebedingungen", "Layout_Privacy_Policy": "Datenschutzbestimmungen", "Layout_Show_Home_Button": "\"Home-Button\" anzeigen", + "Layout_Home_Custom_Block_Visible": "Benutzerdefinierte Inhalte auf der Startseite anzeigen", "Layout_Custom_Body_Only": "Nur benutzerdefinierte Inhalte anzeigen", + "Layout_Custom_Body_Only_Description": "Dadurch werden alle anderen Inhaltsblöcke auf der Homepage ausgeblendet.", "Layout_Sidenav_Footer": "Seitenfußzeile", "Layout_Sidenav_Footer_description": "Die Größe der Fußzeile beträgt 260 x 70 Pixel.", "Layout_Sidenav_Footer_Dark_description": "Die Größe der Fußzeile beträgt 260 x 70 Pixel.", @@ -2967,7 +2981,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Offline-Nachrichten des Livechats an einen Kanal senden", "Omnichannel_on_hold_chat_resumed": "Chat aus Warteschleife wieder aufgenommen: {{comment}}", "Omnichannel_on_hold_chat_automatically": "Der Chat wurde automatisch aus der Warteschleife wieder aufgenommen, nachdem eine neue Nachricht von {{guest}} eingegangen ist", - "Omnichannel_on_hold_chat_manually": "Der Chat wurde von {{user}} manuell aus der Warteschleife wieder aufgenommen ", + "Omnichannel_on_hold_chat_resumed_manually": "Der Chat wurde von {{user}} manuell aus der Warteschleife wieder aufgenommen ", "Omnichannel_On_Hold_due_to_inactivity": "Der Chat wurde automatisch in die Warteschleife gestellt, weil wir innerhalb von {{timeout}} Sekunden keine Antwort von {{guest}} erhalten haben", "Omnichannel_On_Hold_manually": "Der Chat wurde von {{user}} manuell in die Warteschleife gestellt", "Omnichannel_onHold_Chat": "Chat in Warteschleife stellen", @@ -2985,6 +2999,7 @@ "Livechat_title_color": "Hintergrundfarbe des Livechat-Titels", "Livechat_transcript_already_requested_warning": "Das Protokoll dieses Chats wurde bereits angefordert und wird gesendet, sobald das Gespräch beendet ist.", "Livechat_transcript_has_been_requested": "Das Chatprotokoll wurde angefordert.", + "Livechat_email_transcript_has_been_requested": "Das Transkript wurde angefordert. Es kann ein paar Sekunden dauern.", "Livechat_transcript_request_has_been_canceled": "Die Anforderung des Chatprotokolls wurde storniert.", "Livechat_transcript_sent": "Omnichannel-Mitschrift versendet", "Livechat_transfer_return_to_the_queue": "{{from}} hat den Chat in die Warteschlange gestellt", @@ -3626,6 +3641,7 @@ "Only_invited_users_can_acess_this_channel": "Nur eingeladene Benutzer können diesem Channel beitreten", "Oops_page_not_found": "Hoppla! Seite nicht gefunden", "Oops!": "Hoppla!", + "Person_Or_Channel": "Person oder Channel", "Open": "Öffnen", "Open_call": "Anruf öffnen", "Open_call_in_new_tab": "Anruf in neuem Registerkarte öffnen", @@ -4001,6 +4017,7 @@ "Request_comment_when_closing_conversation": "Kommentar beim Schließen der Konversation anfordern", "Request_comment_when_closing_conversation_description": "Wenn dies aktiviert ist, muss der Agent einen Kommentar eingeben, bevor das Gespräch geschlossen wird.", "Request_tag_before_closing_chat": "Fordern Sie Tags an, bevor Sie die Unterhaltung beenden", + "request-pdf-transcript": "PDF-Transkript anfordern", "Requested_At": "Angefordert am", "Requested_By": "Angefordert von", "Require": "Anfordern", @@ -4011,11 +4028,13 @@ "Require_password_change": "Passwortänderung verlangen", "Resend_verification_email": "Bestätigungsmail erneut versenden", "Reset": "Zurücksetzen", + "Reset_priorities": "Prioritäten zurücksetzen", "Reset_Connection": "Verbindung zurücksetzen", "Reset_E2E_Key": "Ende-zu-Ende-Verlüsselungsschlüssel zurücksetzen", "Reset_password": "Passwort zurücksetzen", "Reset_section_settings": "Abschnittseinstellungen zurücksetzen", "Reset_TOTP": "TOTP zurücksetzen", + "Moderation_Reset_user_avatar": "Benutzer-Avatar zurücksetzen", "reset-other-user-e2e-key": "Ende-zu-Ende-Verschlüsselungsschlüssel eines anderen Nutzers zurücksetzen", "Responding": "Antwortet", "Response_description_post": "Leere Textteile oder Textteile mit einem leeren Textmerkmal werden einfach ignoriert. Nicht-200-Antworten werden mehrmals wiederholt. Eine Antwort wird unter dem oben angegebenen Aliasnamen und Avatar veröffentlicht. Sie können diese Informationen wie im obigen Beispiel überschreiben.", @@ -4024,6 +4043,7 @@ "Restart_the_server": "Server neu starten", "restart-server": "Server neu starten", "restart-server_description": "Berechtigung zum Neustart des Servers", + "Results": "Ergebnisse", "Resume": "Fortfahren", "Retail": "Handel", "Retention_setting_changed_successfully": "Die Einstellung für die Aufbewahrungsrichtlinie wurde erfolgreich geändert", @@ -4331,6 +4351,7 @@ "Send_Test_Email": "Test-E-Mail senden", "Send_via_email": "Per E-Mail senden", "Send_via_Email_as_attachment": "Per E-Mail als Anhang senden", + "Export_as_PDF": "Als PDF exportieren", "Send_Visitor_navigation_history_as_a_message": "Besucher-Navigationsprotokoll als Nachricht senden", "Send_visitor_navigation_history_on_request": "Besucher-Navigationsprotokoll auf Anfrage senden", "Send_welcome_email": "Willkommens-E-Mail senden", @@ -4398,7 +4419,6 @@ "Show_counter": "Zähler anzeigen", "Show_email_field": "E-Mail-Feld anzeigen", "Show_mentions": "Zeige Abzeichen für Erwähnungen", - "Show_Message_In_Main_Thread": "Thread-Meldungen im Hauptthread anzeigen", "Show_more": "Weitere Nutzer zeigen", "Show_name_field": "Namensfeld anzeigen", "show_offline_users": "Benutzer anzeigen, die offline sind", @@ -4887,7 +4907,6 @@ "Type_your_job_title": "Geben Sie Ihre Berufsbezeichnung ein", "Type_your_message": "Geben Sie Ihre Nachricht ein", "Type_your_name": "Geben Sie Ihren Namen ein", - "Type_your_new_password": "Geben Sie ein neues Passwort ein", "Type_your_password": "Geben Sie Ihr Passwort ein", "Type_your_username": "Geben Sie Ihren Benutzernamen ein", "UI_Allow_room_names_with_special_chars": "Sonderzeichen im Room-Namen erlauben", @@ -5561,6 +5580,9 @@ "RegisterWorkspace_Features_Omnichannel_Title": "Omnichannel", "RegisterWorkspace_Setup_Label": "Cloud-Account-E-Mail", "RegisterWorkspace_Setup_Email_Verification": "Bitte überprüfen Sie, ob der unten stehende Sicherheitscode mit dem in der E-Mail übereinstimmt.", + "RegisterWorkspace_Syncing_Error": "Beim Synchronisieren Ihres Workspace ist ein Fehler aufgetreten", + "RegisterWorkspace_Syncing_Complete": "Synchronisierung abgeschlossen", + "RegisterWorkspace_Connection_Error": "Beim Verbinden ist ein Fehler aufgetreten", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Ich bin mit den Nutzungsvereinbarung und den Datenschutzbestimmungen einverstanden", "Uninstall_grandfathered_app": "{{appName}} deinstallieren?" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/el.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/el.i18n.json index 5bbda73f99b8f..74c40707b40de 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/el.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/el.i18n.json @@ -495,6 +495,7 @@ "Confirm_new_password": "Επιβεβαιώστε τον καινούριο σας κωδικό", "Confirm_New_Password_Placeholder": "Πληκτρολογήστε ξανά νέο κωδικό πρόσβασης ...", "Confirm_password": "Επιβεβαιώστε τον κωδικό σας", + "Confirm_your_password": "Επιβεβαιώστε τον κωδικό σας", "Connection_Closed": "Η σύνδεση έκλεισε", "Connection_Reset": "Επαναφορά σύνδεσης", "Consulting": "Συμβουλευτικές υπηρεσίες", @@ -2481,7 +2482,6 @@ "Type_your_job_title": "Πληκτρολογήστε τον τίτλο εργασίας σας", "Type_your_message": "Πληκτρολογήστε το μήνυμά σας", "Type_your_name": "Πληκτρολογήστε το όνομά σας", - "Type_your_new_password": "Πληκτρολογήστε τον νέο κωδικό πρόσβασής σας", "Type_your_password": "Πληκτρολογήστε τον κωδικό πρόσβασης", "Type_your_username": "Πληκτρολογήστε το όνομα χρήστη σας", "UI_Allow_room_names_with_special_chars": "Αφήστε τους ειδικούς χαρακτήρες στα ονόματα των δωματίων", @@ -2768,4 +2768,4 @@ "registration.component.form.invalidConfirmPass": "Η επιβεβαίωση κωδικού δεν ταιριάζει με τον αρχικό κωδικό", "registration.component.form.confirmPassword": "Επιβεβαιώστε τον κωδικό σας", "registration.component.form.sendConfirmationEmail": "Αποστολή email επιβεβαίωσης" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index b2c18812828e7..3690cbb353d76 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -5911,4 +5911,4 @@ "Uninstall_grandfathered_app": "Uninstall {{appName}}?", "App_will_lose_grandfathered_status": "**This {{context}} app will lose its grandfathered status.** \n \nWorkspaces on Community Edition can have up to {{limit}} {{context}} apps enabled. Grandfathered apps count towards the limit but the limit is not applied to them.", "Theme_Appearence": "Theme Appearence" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/eo.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/eo.i18n.json index 8f362b607b7cf..8006cd075d7af 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/eo.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/eo.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "Konfirmu novan pasvorton", "Confirm_New_Password_Placeholder": "Bonvolu re-eniri novan pasvorton ...", "Confirm_password": "Konfirmu vian pasvorton", + "Confirm_your_password": "Konfirmu vian pasvorton", "Connection_Closed": "Ligo fermita", "Connection_Reset": "Rilato de konekto", "Consulting": "Konsultado", @@ -2474,7 +2475,6 @@ "Type_your_job_title": "Tajpu vian laborpostitolon", "Type_your_message": "Tajpu vian mesaĝon", "Type_your_name": "Tajpu vian nomon", - "Type_your_new_password": "Tajpu vian novan pasvorton", "Type_your_password": "Tajpu vian pasvorton", "Type_your_username": "Tajpu vian uzantnomon", "UI_Allow_room_names_with_special_chars": "Permesu Specialajn Karakterojn en Ĉambraj Nomoj", @@ -2760,4 +2760,4 @@ "registration.component.form.invalidConfirmPass": "La konfirmilo de pasvorto ne kongruas kun pasvorto", "registration.component.form.confirmPassword": "Konfirmu vian pasvorton", "registration.component.form.sendConfirmationEmail": "Sendu konfirman retpoŝton" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json index ace375b94c17b..7e2219b43fb2c 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/es.i18n.json @@ -2652,7 +2652,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Enviar mensajes fuera de línea de Livechat a un canal", "Omnichannel_on_hold_chat_resumed": "Reanudación del chat en espera: {{comment}}", "Omnichannel_on_hold_chat_automatically": "El chat se ha reanudado automáticamente desde En espera al recibir un nuevo mensaje de {{guest}}", - "Omnichannel_on_hold_chat_manually": "{{user}} ha reanudado el chat manualmente desde En espera ", + "Omnichannel_on_hold_chat_resumed_manually": "{{user}} ha reanudado el chat manualmente desde En espera ", "Omnichannel_On_Hold_due_to_inactivity": "El chat se ha puesto automáticamente en espera porque no hemos recibido ninguna respuesta de {{guest}} en {{timeout}} segundos", "Omnichannel_On_Hold_manually": "{{user}} ha puesto el chat en espera manualmente ", "Omnichannel_onHold_Chat": "Poner chat en espera", @@ -3891,7 +3891,6 @@ "Show_Avatars": "Mostrar avatares", "Show_counter": "Mostrar contador", "Show_email_field": "Mostrar campo de correo electrónico", - "Show_Message_In_Main_Thread": "Mostrar mensajes del hilo en el hilo principal", "Show_more": "Mostrar más", "Show_name_field": "Mostrar campo de nombre", "show_offline_users": "mostrar usuarios fuera de línea", @@ -4343,7 +4342,6 @@ "Type_your_job_title": "Escribe tu cargo", "Type_your_message": "Escribe tu mensaje", "Type_your_name": "Escribe tu nombre", - "Type_your_new_password": "Escribe la nueva contraseña", "Type_your_password": "Escribe tu contraseña", "Type_your_username": "Escribe tu nombre de usuario", "UI_Allow_room_names_with_special_chars": "Permitir caracteres especiales en nombres de Room", @@ -4897,4 +4895,4 @@ "RegisterWorkspace_Features_Omnichannel_Title": "Omnichannel", "RegisterWorkspace_Setup_Label": "Cuenta de correo electrónico en la nube", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Acepto los <1>términos y condiciones y la <3>política de privacidad" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/fa.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/fa.i18n.json index 1f1356e23a32a..c23985c99cbce 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/fa.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/fa.i18n.json @@ -663,6 +663,7 @@ "Confirm_new_password": "تأیید رمز جدید", "Confirm_New_Password_Placeholder": "لطفا رمز عبور جدید را دوباره وارد کنید ...", "Confirm_password": "رمز عبور خود را تأیید کنید", + "Confirm_your_password": "رمز عبور خود را تأیید کنید", "Connect": "اتصال", "Connection_Closed": "اتصال بسته شد", "Connection_Reset": "تنظیم مجدد اتصال", @@ -2805,7 +2806,6 @@ "Type_your_job_title": "عنوان شغلی خود را تایپ کنید", "Type_your_message": "پیام خود را بنویسید", "Type_your_name": "نام خود را وارد نمایید", - "Type_your_new_password": "کلمه عبور جدید را وارد کنید", "Type_your_password": "رمز عبور خود را تایپ کنید", "Type_your_username": "نام کاربری خود را وارد کنید", "UI_Allow_room_names_with_special_chars": "اجازه کاراکترهای ویژه در نام اتاق", @@ -3105,4 +3105,4 @@ "registration.component.form.confirmPassword": "رمز عبور خود را تأیید کنید", "registration.component.form.sendConfirmationEmail": "ارسال ایمیل تایید", "RegisterWorkspace_Features_Omnichannel_Title": "کانال همه‌کاره" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/fi.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/fi.i18n.json index e65cb15ca5610..f3deb4a3063ea 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/fi.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/fi.i18n.json @@ -1059,6 +1059,7 @@ "Confirm_new_password": "Vahvista uusi salasana", "Confirm_New_Password_Placeholder": "Anna uusi salasana uudelleen...", "Confirm_password": "Vahvista salasanasi", + "Confirm_your_password": "Vahvista salasanasi", "Confirmation": "Vahvistus", "Configure_video_conference": "Määritä neuvottelupuhelu", "Connect": "Yhdistä", @@ -3013,7 +3014,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Lähetä Livechatin offline-viestit kanavalle", "Omnichannel_on_hold_chat_resumed": "Pidossa ollut keskustelu jatkuu: {{comment}}", "Omnichannel_on_hold_chat_automatically": "Keskustelu jatkui automaattisesti Pidossa-tilasta, kun uusi viesti saapui vieraalta {{guest}}", - "Omnichannel_on_hold_chat_manually": "{{user}} jatkoi keskustelua manuaalisesti Pidossa-tilasta", + "Omnichannel_on_hold_chat_resumed_manually": "{{user}} jatkoi keskustelua manuaalisesti Pidossa-tilasta", "Omnichannel_On_Hold_due_to_inactivity": "Keskustelu asetettiin pitoon automaattisesti, koska {{guest}} ei vastannut {{timeout}} sekuntiin", "Omnichannel_On_Hold_manually": "{{user}} asetti keskustelun pitoon manuaalisesti", "Omnichannel_onHold_Chat": "Aseta keskustelu pitoon", @@ -4495,7 +4496,6 @@ "Show_default_content": "Näytä oletussisältö", "Show_email_field": "Näytä sähköposti-kenttä", "Show_mentions": "Näytä mainintojen merkki", - "Show_Message_In_Main_Thread": "Näytä ketjun viestit pääketjussa", "Show_more": "Näytä lisää", "Show_name_field": "Näytä nimi-kenttä", "show_offline_users": "näytä offline-käyttäjät", @@ -5005,7 +5005,6 @@ "Type_your_job_title": "Kirjoita työtehtäväsi", "Type_your_message": "Kirjoita viestisi", "Type_your_name": "Kirjoita nimesi", - "Type_your_new_password": "Kirjoita uusi salasana", "Type_your_password": "Kirjoita salasanasi", "Type_your_username": "Kirjoita käyttäjätunnuksesi", "UI_Allow_room_names_with_special_chars": "Salli erikoismerkit huoneiden nimessä", @@ -5811,4 +5810,4 @@ "Uninstall_grandfathered_app": "Poistetaanko {{appName}}?", "App_will_lose_grandfathered_status": "**Tämä {{context}}sovellus menettää aikaisemmin käytetössä olleen sovelluksen tilansa.** \n \nYhteisöversion työtiloissa voi olla käytössä enintään {{limit}} {{context}} sovellusta. aikaisemmin Aikaisemmin käytössä olleet sovellukset lasketaan mukaan rajoitukseen, mutta rajoitusta ei sovelleta niihin.", "Theme_Appearence": "Teeman ulkoasu" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/fr.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/fr.i18n.json index 86b3b1f093fa6..5d956f706e85b 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/fr.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/fr.i18n.json @@ -925,6 +925,7 @@ "Confirm_new_password": "Confirmer le nouveau mot de passe", "Confirm_New_Password_Placeholder": "Entrez à nouveau le nouveau mot de passe...", "Confirm_password": "Confirmez votre mot de passe", + "Confirm_your_password": "Confirmez votre mot de passe", "Confirmation": "Confirmation", "Connect": "Connexion", "Connected": "Connecté", @@ -2668,7 +2669,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Envoyer des messages hors ligne Livechat à un canal", "Omnichannel_on_hold_chat_resumed": "Le chat en attente a repris : {{comment}}", "Omnichannel_on_hold_chat_automatically": "Le chat a été automatiquement repris lorsqu'un nouveau message a été reçu de {{guest}}", - "Omnichannel_on_hold_chat_manually": "Le chat a été repris manuellement par {{user}}", + "Omnichannel_on_hold_chat_resumed_manually": "Le chat a été repris manuellement par {{user}}", "Omnichannel_On_Hold_due_to_inactivity": "Le chat a été automatiquement mis en attente car nous n'avons reçu aucune réponse de {{guest}} depuis {{timeout}} secondes", "Omnichannel_On_Hold_manually": "Le chat a été mis manuellement en attente par {{user}}", "Omnichannel_onHold_Chat": "Mettre le chat en attente", @@ -3917,7 +3918,6 @@ "Show_Avatars": "Afficher les avatars", "Show_counter": "Afficher le compteur", "Show_email_field": "Afficher le champ d'adresse e-mail", - "Show_Message_In_Main_Thread": "Afficher les messages du fil dans le fil principal", "Show_more": "Afficher plus", "Show_name_field": "Afficher le champ de nom", "show_offline_users": "afficher les utilisateur hors ligne", @@ -4374,7 +4374,6 @@ "Type_your_job_title": "Entrez le titre de votre poste", "Type_your_message": "Entrez votre message", "Type_your_name": "Entrez votre nom", - "Type_your_new_password": "Entrez votre nouveau mot de passe", "Type_your_password": "Entrez votre mot de passe", "Type_your_username": "Entrez votre nom d'utilisateur", "UI_Allow_room_names_with_special_chars": "Autoriser les caractères spéciaux dans les noms de salon", @@ -4945,4 +4944,4 @@ "RegisterWorkspace_Features_Omnichannel_Title": "Omnicanal", "RegisterWorkspace_Setup_Label": "E-mail du compte cloud", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "J'accepte les <1>Conditions d'utilisation et la <3>Politique de confidentialité" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/he.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/he.i18n.json index 5c8eb5e7ab3bb..ee0c52ca4a393 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/he.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/he.i18n.json @@ -309,6 +309,7 @@ "Condensed": "מקובץ", "Computer": "מחשב", "Confirm_password": "אמת את הסיסמה שלך", + "Confirm_your_password": "אמת את הסיסמה שלך", "Connection_Closed": "החיבור נסגר", "Connection_Reset": "החיבור אופס", "Contact": "יצירת קשר", @@ -1348,7 +1349,6 @@ "Type_your_email": "הכנס את כתובת הדוא״ל שלך", "Type_your_message": "הקלד את ההודעה שלך", "Type_your_name": "הכנס את השם שלך", - "Type_your_new_password": "הכנס את הסיסמה החדשה שלך", "Type_your_password": "הכנס את הסיסמה שלך", "Type_your_username": "הכנס את שם המשתמש שלך", "UI_Click_Direct_Message": "לחץ לשליחת הודעה פרטית", @@ -1535,4 +1535,4 @@ "registration.component.form.invalidConfirmPass": "אימות הססמה אינו זהה לססמה", "registration.component.form.confirmPassword": "אמת את הסיסמה שלך", "registration.component.form.sendConfirmationEmail": "שליחת דוא״ל אימות" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/hi-IN.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/hi-IN.i18n.json index a06cbfcb732d6..0e74af00275e0 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/hi-IN.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/hi-IN.i18n.json @@ -213,4 +213,4 @@ "We_are_offline_Sorry_for_the_inconvenience": "हम ऑफ़लाइन हैं। असुविधा के लिए खेद है।", "Yes": "हाँ", "You": "आप" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/hi.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/hi.i18n.json index f97b713276b4f..c8d21e6638fc5 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/hi.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/hi.i18n.json @@ -213,4 +213,4 @@ "We_are_offline_Sorry_for_the_inconvenience": "हम ऑफ़लाइन हैं। असुविधा के लिए खेद है।", "Yes": "हाँ", "You": "आप" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/hr.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/hr.i18n.json index ded38dc7cc552..0db25d33568a6 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/hr.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/hr.i18n.json @@ -570,6 +570,7 @@ "Confirm_new_password": "Potvrdi novu lozinku", "Confirm_New_Password_Placeholder": "Ponovno unesite novu zaporku ...", "Confirm_password": "Potvrdi svoju lozinku", + "Confirm_your_password": "Potvrdi svoju lozinku", "Connect": "Povezati", "Connection_Closed": "Veza je zatvorena", "Connection_Reset": "Ponovno postavljanje veze", @@ -2609,7 +2610,6 @@ "Type_your_job_title": "Upišite svoj posao", "Type_your_message": "Upišite svoju poruku", "Type_your_name": "Upišite svoje ime", - "Type_your_new_password": "Upišite novu lozinku", "Type_your_password": "Upišite svoju lozinku", "Type_your_username": "Upišite svoje korisničko ime", "UI_Allow_room_names_with_special_chars": "Omogući posebne znakove u imenima soba", @@ -2896,4 +2896,4 @@ "registration.component.form.invalidConfirmPass": "Potvrda lozinke se ne slaže sa lozinkom", "registration.component.form.confirmPassword": "Potvrdi svoju lozinku", "registration.component.form.sendConfirmationEmail": "Pošalji potvrdni email" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/hu.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/hu.i18n.json index c89cca11c021c..6cbd626f75261 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/hu.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/hu.i18n.json @@ -1022,6 +1022,7 @@ "Confirm_new_password": "Új jelszó megerősítése", "Confirm_New_Password_Placeholder": "Adja meg újra az új jelszót…", "Confirm_password": "Erősítse meg a jelszavát", + "Confirm_your_password": "Erősítse meg a jelszavát", "Confirmation": "Megerősítés", "Configure_video_conference": "Konferenciahívás beállítása", "Connect": "Kapcsolódás", @@ -2905,7 +2906,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Élő csevegés kapcsolat nélküli üzeneteinek küldése egy csatornára", "Omnichannel_on_hold_chat_resumed": "Várakoztatott csevegés folytatva: {{comment}}", "Omnichannel_on_hold_chat_automatically": "A csevegés automatikusan folytatódott a várakoztatásból, amikor új üzenetet kapott {{guest}} vendégtől", - "Omnichannel_on_hold_chat_manually": "A csevegést {{user}} kézileg folytatta a várakoztatásból", + "Omnichannel_on_hold_chat_resumed_manually": "A csevegést {{user}} kézileg folytatta a várakoztatásból", "Omnichannel_On_Hold_due_to_inactivity": "A csevegés automatikusan várakoztatásba lett helyezve, mivel nem kaptunk semmilyen választ {{guest}} vendégtől {{timeout}} másodperc alatt", "Omnichannel_On_Hold_manually": "A csevegést {{user}} kézileg várakoztatásba helyezte", "Omnichannel_onHold_Chat": "Csevegés várakoztatásba helyezése", @@ -4326,7 +4327,6 @@ "Show_counter": "Megjelölés olvasatlanként", "Show_email_field": "E-mail-cím mező megjelenítése", "Show_mentions": "Említések jelvényének megjelenítése", - "Show_Message_In_Main_Thread": "Szálüzenetek megjelenítése a főszálban", "Show_more": "Több megjelenítése", "Show_name_field": "Név mező megjelenítése", "show_offline_users": "kilépett felhasználók megjelenítése", @@ -4820,7 +4820,6 @@ "Type_your_job_title": "Írja be a munkakörét", "Type_your_message": "Írja be az üzenetét", "Type_your_name": "Írja be a nevét", - "Type_your_new_password": "Írja be az új jelszavát", "Type_your_password": "Írja be a jelszavát", "Type_your_username": "Írja be felhasználónevét", "UI_Allow_room_names_with_special_chars": "Különleges karakterek engedélyezése a szobanevekben", @@ -5488,4 +5487,4 @@ "Join_your_team": "Csatlakozás csapathoz", "Create_an_account": "Fiók létrehozása", "RegisterWorkspace_Features_Marketplace_Title": "Piactér" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/id.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/id.i18n.json index 069f4183cd015..8a42c8f073181 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/id.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/id.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "Konfirmasi password baru", "Confirm_New_Password_Placeholder": "Silakan masukkan kembali kata sandi baru ...", "Confirm_password": "Konfirmasikan kata sandi anda", + "Confirm_your_password": "Konfirmasikan kata sandi anda", "Connection_Closed": "Koneksi ditutup", "Connection_Reset": "Koneksi diatur ulang", "Consulting": "Konsultasi", @@ -2482,7 +2483,6 @@ "Type_your_job_title": "Ketik jabatan Anda", "Type_your_message": "Ketik pesan Anda", "Type_your_name": "Ketik nama Anda", - "Type_your_new_password": "Ketik password baru Anda", "Type_your_password": "Ketikkan kata sandi Anda", "Type_your_username": "Ketikkan nama pengguna Anda", "UI_Allow_room_names_with_special_chars": "Biarkan Karakter Khusus dalam Nama Kamar", @@ -2769,4 +2769,4 @@ "registration.component.form.invalidConfirmPass": "Kata sandi konfirmasi tidak cocok dengan kata sandi utama", "registration.component.form.confirmPassword": "Konfirmasikan kata sandi anda", "registration.component.form.sendConfirmationEmail": "Kirim email konfirmasi" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/it.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/it.i18n.json index 6c421f3896117..decd72db4baa4 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/it.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/it.i18n.json @@ -518,6 +518,7 @@ "Confirm_new_password": "Conferma la nuova password", "Confirm_New_Password_Placeholder": "Inserisci nuovamente la nuova password ...", "Confirm_password": "Conferma la tua password", + "Confirm_your_password": "Conferma la tua password", "Connect": "Connetti", "Connection_Closed": "Connessione chiusa", "Connection_Reset": "Reset della connessione", @@ -2567,7 +2568,6 @@ "Type_your_job_title": "Digita il titolo del tuo lavoro", "Type_your_message": "Inserisci la tua messaggio", "Type_your_name": "Inserire il proprio nome", - "Type_your_new_password": "Inserisci la nuova password", "Type_your_password": "Digita la tua password", "Type_your_username": "Inserisci il tuo nome utente", "UI_Allow_room_names_with_special_chars": "Consenti caratteri speciali nei nomi delle stanze", @@ -2863,4 +2863,4 @@ "registration.component.form.invalidConfirmPass": "La password di conferma non corrisponde con la password", "registration.component.form.confirmPassword": "Conferma la tua password", "registration.component.form.sendConfirmationEmail": "Invia email di conferma" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ja.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ja.i18n.json index dd932e0b38862..18bd26b34c07f 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ja.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ja.i18n.json @@ -912,6 +912,7 @@ "Confirm_new_password": "新しいパスワードの確認", "Confirm_New_Password_Placeholder": "新しいパスワードを再入力してください...", "Confirm_password": "パスワードを確認", + "Confirm_your_password": "パスワードの確認", "Confirmation": "確認", "Connect": "接続", "Connected": "接続済み", @@ -2645,7 +2646,7 @@ "Livechat_OfflineMessageToChannel_enabled": "ライブチャットオフラインメッセージをチャネルに送信", "Omnichannel_on_hold_chat_resumed": "保留中のチャットが再開されました:{{comment}}", "Omnichannel_on_hold_chat_automatically": "{{guest}}から新しいメッセージを受信し、チャットが保留中から自動的に再開されました", - "Omnichannel_on_hold_chat_manually": " {{user}}がチャットを手動で保留中から再開しました", + "Omnichannel_on_hold_chat_resumed_manually": " {{user}}がチャットを手動で保留中から再開しました", "Omnichannel_On_Hold_due_to_inactivity": "{{guest}}から{{timeout}}秒間返信を受信しなかったため、チャットが自動的に保留中になりました", "Omnichannel_On_Hold_manually": "{{user}}がチャットを手動で保留中にしました", "Omnichannel_onHold_Chat": "チャットを保留中にする", @@ -3883,7 +3884,6 @@ "Show_Avatars": "アバターの表示", "Show_counter": "カウンターを表示", "Show_email_field": "メールフィールドを表示", - "Show_Message_In_Main_Thread": "メインスレッドにスレッドメッセージを表示", "Show_more": "さらに表示", "Show_name_field": "名前フィールドを表示", "show_offline_users": "オフラインユーザーを表示", @@ -4333,7 +4333,6 @@ "Type_your_job_title": "役職を入力", "Type_your_message": "メッセージを入力", "Type_your_name": "名前を入力してください", - "Type_your_new_password": "新しいパスワードを入力", "Type_your_password": "パスワードを入力", "Type_your_username": "ユーザー名を入力", "UI_Allow_room_names_with_special_chars": "Room名に特殊文字を許可", @@ -4895,4 +4894,4 @@ "RegisterWorkspace_Features_Omnichannel_Title": "オムニチャネル", "RegisterWorkspace_Setup_Label": "クラウドアカウントメール", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "<1>使用と<3>プライバシーポリシーに同意します" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ka-GE.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ka-GE.i18n.json index c4c1a5b55b0d0..e953602513103 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ka-GE.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ka-GE.i18n.json @@ -692,6 +692,7 @@ "Confirm_new_password": "დაადასტურეთ ახალი პაროლი", "Confirm_New_Password_Placeholder": "გთხოვთ, ხელახლა შეიყვანოთ ახალი პაროლი ...", "Confirm_password": "დაადასტურეთ თქვენი პაროლი", + "Confirm_your_password": "დაადასტურეთ თქვენი პაროლი", "Connect": "დაკავშირება", "Connection_Closed": "კავშირი დაიხურა", "Connection_Reset": "კავშირის გადატვირთვა", @@ -3337,7 +3338,6 @@ "Type_your_job_title": "აკრიფეთ თქვენისამუშაოს დასახელება", "Type_your_message": "აკრიფეთ თქვენი შეტყობინება", "Type_your_name": "აკრიფეთ თქვენი სახელი", - "Type_your_new_password": "აკრიფეთ თქვენი ახალი პაროლი", "Type_your_password": "აკრიფეთ თქვენი პაროლი", "Type_your_username": "აკრიფეთ თქვენი მომხმარებლის სახელი", "UI_Allow_room_names_with_special_chars": "ოთახების სახელებში სპეციალური ნიშნების დაშვება", @@ -3683,4 +3683,4 @@ "registration.component.form.invalidConfirmPass": "პაროლის დასტური არ შეესაბამება პაროლს", "registration.component.form.confirmPassword": "დაადასტურეთ თქვენი პაროლი", "registration.component.form.sendConfirmationEmail": "დადასტურების ელ.ფოსტის გაგზავნა" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/km.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/km.i18n.json index 607dbb646244d..43f5ee0e1cd4e 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/km.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/km.i18n.json @@ -647,6 +647,7 @@ "Confirm_new_password": "បញ្ជាក់​លេខសំងាត់​ថ្មី", "Confirm_New_Password_Placeholder": "សូមបញ្ចូលពាក្យសម្ងាត់ថ្មីម្តងទៀត ...", "Confirm_password": "បញ្ជាក់​ពាក្យ​សម្ងាត់", + "Confirm_your_password": "បញ្ជាក់​ពាក្យ​សម្ងាត់", "Connect": "ភ្ជាប់", "Connection_Closed": "ការភ្ជាប់បានបិទ", "Connection_Reset": "កំណត់ការភ្ជាប់ឡើងវិញ", @@ -2815,7 +2816,6 @@ "Type_your_job_title": "វាយចំណងជើងការងាររបស់អ្នក", "Type_your_message": "វាយសាររបស់អ្នក", "Type_your_name": "វាយបញ្ចូលឈ្មោះរបស់អ្នក", - "Type_your_new_password": "បញ្ចូលពាក្យសម្ងាត់ថ្មីរបស់អ្នក", "Type_your_password": "បញ្ចូលពាក្យសម្ងាត់របស់អ្នក", "Type_your_username": "បញ្ចូលឈ្មោះអ្នកប្រើរបស់អ្នក", "UI_Allow_room_names_with_special_chars": "អនុញ្ញាតតួអក្សរពិសេសក្នុងឈ្មោះបន្ទប់", @@ -3115,4 +3115,4 @@ "registration.component.form.invalidConfirmPass": "ពាក្យ​សម្ងាត់​បញ្ជាក់​មិន​ដូច​ពាក្យ​សម្ងាត់​បាន​បញ្ចូល​", "registration.component.form.confirmPassword": "បញ្ជាក់​ពាក្យ​សម្ងាត់", "registration.component.form.sendConfirmationEmail": "ផ្ញើរអ៊ីម៉ែល​បញ្ជាក់" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ko.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ko.i18n.json index df08ed5b1be9b..f660099b04c73 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ko.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ko.i18n.json @@ -793,6 +793,7 @@ "Confirm_new_password": "새 비밀번호 확인", "Confirm_New_Password_Placeholder": "새 비밀번호를 다시 입력하세요...", "Confirm_password": "비밀번호를 확인하세요", + "Confirm_your_password": "비밀번호를 확인하세요", "Connect": "연결", "Connection_Closed": "연결이 닫혔습니다.", "Connection_Reset": "연결 재설정", @@ -3300,7 +3301,6 @@ "Show_Avatars": "아바타 표시", "Show_counter": "미확인 메시지수 표시", "Show_email_field": "이메일 필드 표시", - "Show_Message_In_Main_Thread": "기본 스레드에 스레드 메시지 표시", "Show_more": "더보기", "Show_name_field": "이름 필드 표시", "show_offline_users": "오프라인 사용자 표시", @@ -3659,7 +3659,6 @@ "Type_your_job_title": "직책을 입력하세요.", "Type_your_message": "메시지를 입력하세요.", "Type_your_name": "이름을 입력하세요.", - "Type_your_new_password": "새 암호를 입력하세요.", "Type_your_password": "암호를 입력하세요.", "Type_your_username": "사용자명을 입력하세요.", "UI_Allow_room_names_with_special_chars": "대화방명에 특수문자 허용", @@ -4044,4 +4043,4 @@ "registration.component.form.invalidConfirmPass": "비밀번호가 일치하지 않습니다.", "registration.component.form.confirmPassword": "비밀번호를 확인하세요", "registration.component.form.sendConfirmationEmail": "확인 메일 보내기" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ku.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ku.i18n.json index 562ca42944e5d..da57c23dec1c9 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ku.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ku.i18n.json @@ -488,6 +488,7 @@ "Confirm_new_password": "Şîfreya Nû ya piştrast bikin", "Confirm_New_Password_Placeholder": "Ji kerema xwe şîfreya nû ve nû bike ...", "Confirm_password": "تێپەڕەوشەکەت پشتڕاستکەوە", + "Confirm_your_password": "تێپەڕەوشەکەت پشتڕاستکەوە", "Connection_Closed": "Girêdana girêdayî ye", "Connection_Reset": "Girêdana veguhastinê", "Consulting": "Şêwirmendî", @@ -2466,7 +2467,6 @@ "Type_your_job_title": "Navekî karê xwe binivîse", "Type_your_message": "Mesaja we", "Type_your_name": "navê te Type", - "Type_your_new_password": "Şîfreya nû xwe binivîsin", "Type_your_password": "Şîfreya te binivîse", "Type_your_username": "Navê te binivîse", "UI_Allow_room_names_with_special_chars": "Li Niştimanî Nasnameyên Taybet Taybetî bide", @@ -2749,4 +2749,4 @@ "registration.component.form.invalidConfirmPass": "دووبارەکراوەی تێپەڕەوشە یەکناگرێتەوە لەگەڵ تێپەڕەوشە", "registration.component.form.confirmPassword": "تێپەڕەوشەکەت پشتڕاستکەوە", "registration.component.form.sendConfirmationEmail": "ئیمەیڵی پشتڕاستکردنەوە بنێرە" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/lo.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/lo.i18n.json index 55fd1ececb188..25ea4a1dd26f9 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/lo.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/lo.i18n.json @@ -506,6 +506,7 @@ "Confirm_new_password": "ຢືນ​ຢັນ​ລະ​ຫັດ​ຜ່ານ​ໃຫມ່", "Confirm_New_Password_Placeholder": "ກະລຸນາໃສ່ລະຫັດຜ່ານໃຫມ່ອີກເທື່ອຫນຶ່ງ ...", "Confirm_password": "ຢືນຢັນລະຫັດຜ່ານຂອງທ່ານ", + "Confirm_your_password": "ຢືນຢັນລະຫັດຜ່ານຂອງທ່ານ", "Connection_Closed": "ການເຊື່ອມຕໍ່ປິດ", "Connection_Reset": "ການຕັ້ງຄ່າການເຊື່ອມຕໍ່", "Consulting": "ການປຶກສາຫາລື", @@ -2510,7 +2511,6 @@ "Type_your_job_title": "ພິມຊື່ຕໍາແຫນ່ງວຽກຂອງທ່ານ", "Type_your_message": "ພິມຂໍ້ຄວາມຂອງທ່ານ", "Type_your_name": "ພິມຊື່ຂອງທ່ານ", - "Type_your_new_password": "ພິມລະຫັດຜ່ານໃຫມ່ຂອງທ່ານ", "Type_your_password": "ພິມລະຫັດຜ່ານຂອງທ່ານ", "Type_your_username": "ພິມຊື່ຜູ້ໃຊ້ຂອງທ່ານ", "UI_Allow_room_names_with_special_chars": "ອະນຸຍາດໃຫ້ຕົວອັກສອນພິເສດໃນຊື່ຫ້ອງ", @@ -2797,4 +2797,4 @@ "registration.component.form.invalidConfirmPass": "ການຢືນຢັນລະຫັດຜ່ານບໍ່ກົງກັບລະຫັດຜ່ານ", "registration.component.form.confirmPassword": "ຢືນຢັນລະຫັດຜ່ານຂອງທ່ານ", "registration.component.form.sendConfirmationEmail": "ສົ່ງອີເມວການຢືນຢັນ" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/lt.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/lt.i18n.json index e2185988156d9..43d780cd153d6 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/lt.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/lt.i18n.json @@ -544,6 +544,7 @@ "Confirm_new_password": "Patvirtinti naują slaptažodį", "Confirm_New_Password_Placeholder": "Prašome dar kartą įvesti naują slaptažodį ...", "Confirm_password": "Patvirtinkite savo slaptažodį", + "Confirm_your_password": "Patvirtinkite savo slaptažodį", "Connection_Closed": "Ryšys uždarytas", "Connection_Reset": "Ryšio atstatymas", "Consulting": "Konsultavimas", @@ -2529,7 +2530,6 @@ "Type_your_job_title": "Įveskite savo pareigų pavadinimą", "Type_your_message": "Įveskite savo pranešimą", "Type_your_name": "Įveskite savo vardą", - "Type_your_new_password": "Įveskite naują slaptažodį", "Type_your_password": "Įveskite savo slaptažodį", "Type_your_username": "Įveskite savo vartotojo vardą", "UI_Allow_room_names_with_special_chars": "Leisti specialius simbolius kambario pavadinimuose", @@ -2814,4 +2814,4 @@ "registration.component.form.invalidConfirmPass": "Slaptažodžio patvirtinimas nesutampa su slaptažodžiu", "registration.component.form.confirmPassword": "Patvirtinkite savo slaptažodį", "registration.component.form.sendConfirmationEmail": "Siųsti patvirtinimo el. Laišką" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/lv.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/lv.i18n.json index 727e937963689..b8e0f5d84a66d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/lv.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/lv.i18n.json @@ -499,6 +499,7 @@ "Confirm_new_password": "Apstipriniet jauno paroli", "Confirm_New_Password_Placeholder": "Lūdzu, vēlreiz ievadiet jauno paroli ...", "Confirm_password": "Apstipriniet savu paroli", + "Confirm_your_password": "Apstipriniet savu paroli", "Connection_Closed": "Savienojums ir aizvērts", "Connection_Reset": "Savienojuma atiestatīšana", "Consulting": "Konsultēšana", @@ -2481,7 +2482,6 @@ "Type_your_job_title": "Ieraksti savu darba nosaukumu", "Type_your_message": "Ierakstiet savu ziņojumu", "Type_your_name": "Ierakstiet savu vārdu", - "Type_your_new_password": "Ierakstiet savu jauno paroli", "Type_your_password": "Ievadiet savu paroli", "Type_your_username": "Ievadiet savu lietotājvārdu", "UI_Allow_room_names_with_special_chars": "Atļaut īpašus simbolus istabu nosaukumos", @@ -2755,4 +2755,4 @@ "registration.component.form.invalidConfirmPass": "Paroles apstiprinājums neatbilst parolei", "registration.component.form.confirmPassword": "Apstipriniet savu paroli", "registration.component.form.sendConfirmationEmail": "Nosūtīt apstiprinājuma e-pastu" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/mn.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/mn.i18n.json index 6f8e66867f3dd..a78f078579733 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/mn.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/mn.i18n.json @@ -488,6 +488,7 @@ "Confirm_new_password": "Шинэ нууц үг баталгаажуулах", "Confirm_New_Password_Placeholder": "Шинэ нууц үгээ дахин оруулна уу ...", "Confirm_password": "Нууц үгээ батлах", + "Confirm_your_password": "Нууц үгээ батлах", "Connection_Closed": "Холболт хаалттай байна", "Connection_Reset": "Холболтыг дахин тохируулах", "Consulting": "Зөвлөгөө өгөх", @@ -2467,7 +2468,6 @@ "Type_your_job_title": "Ажлын нэрээ оруулна уу", "Type_your_message": "Мессеж бичнэ үү", "Type_your_name": "Нэрээ оруулна уу", - "Type_your_new_password": "Шинэ нууц үгээ бичнэ үү", "Type_your_password": "Нууц үгээ бичнэ үү", "Type_your_username": "Хэрэглэгчийн нэрээ оруулна уу", "UI_Allow_room_names_with_special_chars": "Өрөөний нэрээр тусгай тэмдэгтүүдийг зөвшөөрөх", @@ -2748,4 +2748,4 @@ "registration.component.form.invalidConfirmPass": "Нууц үг баталгаажуулалт нь нууц үгтэй таарахгүй байна", "registration.component.form.confirmPassword": "Нууц үгээ батлах", "registration.component.form.sendConfirmationEmail": "Баталгаажуулах имэйл илгээх" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ms-MY.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ms-MY.i18n.json index 6deca6ccab509..0749c30b656f8 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ms-MY.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ms-MY.i18n.json @@ -488,6 +488,7 @@ "Confirm_new_password": "Sahkan Kata Laluan Baru", "Confirm_New_Password_Placeholder": "Sila masukkan semula kata laluan baru ...", "Confirm_password": "Sahkan kata laluan anda", + "Confirm_your_password": "Sahkan kata laluan anda", "Connection_Closed": "Sambungan ditutup", "Connection_Reset": "Tetap semula sambungan", "Consulting": "Perundingan", @@ -2481,7 +2482,6 @@ "Type_your_job_title": "Taipkan jawatan anda", "Type_your_message": "Taipkan mesej anda", "Type_your_name": "Taipkan nama anda", - "Type_your_new_password": "Taipkan kata laluan baru anda", "Type_your_password": "Taip kata laluan anda", "Type_your_username": "Taip nama pengguna anda", "UI_Allow_room_names_with_special_chars": "Benarkan Huruf Khas dalam Nama Bilik", @@ -2765,4 +2765,4 @@ "registration.component.form.invalidConfirmPass": "Pengesahan kata laluan tidak sepadan dengan kata laluan", "registration.component.form.confirmPassword": "Sahkan kata laluan anda", "registration.component.form.sendConfirmationEmail": "Hantar e-mel pengesahan" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/nl.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/nl.i18n.json index 74fa0d3441b35..fdc52656a9c88 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/nl.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/nl.i18n.json @@ -920,6 +920,7 @@ "Confirm_new_password": "Bevestig nieuw wachtwoord", "Confirm_New_Password_Placeholder": "Voer het nieuwe wachtwoord opnieuw in...", "Confirm_password": "Bevestig uw wachtwoord", + "Confirm_your_password": "Bevestig uw wachtwoord", "Confirmation": "Bevestiging", "Connect": "Verbinden", "Connected": "Verbonden", @@ -2662,7 +2663,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Stuur de Livechat offline berichten naar een kanaal", "Omnichannel_on_hold_chat_resumed": "Chat in wachtstand hervat: {{comment}}", "Omnichannel_on_hold_chat_automatically": "De chat werd automatisch hervat vanuit de wachtrij bij het ontvangen van een nieuw bericht van {{guest}}", - "Omnichannel_on_hold_chat_manually": "De chat is handmatig hervat vanuit de wachstand door {{user}}", + "Omnichannel_on_hold_chat_resumed_manually": "De chat is handmatig hervat vanuit de wachstand door {{user}}", "Omnichannel_On_Hold_due_to_inactivity": "De chat is automatisch in de wacht gezet omdat we in {{timeout}} seconden geen antwoord hebben ontvangen van {{guest}}", "Omnichannel_On_Hold_manually": "De chat is handmatig in de wacht gezet door {{user}}", "Omnichannel_onHold_Chat": "Chat on-hold plaatsen", @@ -3910,7 +3911,6 @@ "Show_Avatars": "Toon Avatars", "Show_counter": "Toon teller", "Show_email_field": "Toon e-mailveld", - "Show_Message_In_Main_Thread": "Toon threadberichten in de hoofdthread", "Show_more": "Laat meer zien", "Show_name_field": "Naamveld tonen", "show_offline_users": "offline gebruikers tonen", @@ -4364,7 +4364,6 @@ "Type_your_job_title": "Typ uw functietitel", "Type_your_message": "Schrijf je bericht", "Type_your_name": "Typ je naam", - "Type_your_new_password": "Typ je nieuwe wachtwoord", "Type_your_password": "Typ je wachtwoord", "Type_your_username": "Typ je gebruikersnaam", "UI_Allow_room_names_with_special_chars": "Sta speciale tekens toe in kamernamen", @@ -4933,4 +4932,4 @@ "RegisterWorkspace_Features_Omnichannel_Title": "Omnichannel", "RegisterWorkspace_Setup_Label": "E-mailadres van cloudaccount", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Ik ga akkoord met de <1>Algemene voorwaarden en <3>Privacybeleid" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/no.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/no.i18n.json index 0899857363cbc..e3ce45d342d8c 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/no.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/no.i18n.json @@ -552,6 +552,7 @@ "Confirm_new_password": "Bekrefte nytt passord", "Confirm_New_Password_Placeholder": "Vennligst skriv nytt passord igjen ...", "Confirm_password": "Bekreft passordet ditt", + "Confirm_your_password": "Bekreft passordet ditt", "Connection_Closed": "Tilkoblingen er stengt", "Connection_Reset": "Tilbakestilling av tilkobling", "Consulting": "Consulting", @@ -2571,7 +2572,6 @@ "Type_your_job_title": "Skriv inn jobbtittel", "Type_your_message": "Skriv inn meldingen din", "Type_your_name": "Skriv inn navnet ditt", - "Type_your_new_password": "Skriv inn ditt nye passord", "Type_your_password": "Skriv inn passordet ditt", "Type_your_username": "Skriv inn brukernavnet ditt", "UI_Allow_room_names_with_special_chars": "Tillat spesialtegn i romnavn", @@ -2861,4 +2861,4 @@ "registration.component.form.invalidConfirmPass": "Passordbekreftelsen stemmer ikke overens med passordet", "registration.component.form.confirmPassword": "Bekreft passordet ditt", "registration.component.form.sendConfirmationEmail": "Send bekreftelses-e-post" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/pl.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/pl.i18n.json index 7e38962f16a42..ee9d3c9a95f3d 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/pl.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/pl.i18n.json @@ -1003,6 +1003,7 @@ "Confirm_new_password": "Potwierdź nowe hasło", "Confirm_New_Password_Placeholder": "Wprowadź ponownie nowe hasło…", "Confirm_password": "Potwierdź hasło", + "Confirm_your_password": "Potwierdź hasło", "Confirmation": "Potwierdzenie", "Configure_video_conference": "Konfiguracja połączenia konferencyjnego", "Connect": "Połącz", @@ -2866,7 +2867,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Wyślij wiadomości offline Livechat-u do kanału", "Omnichannel_on_hold_chat_resumed": "Czat wznowiony: {{comment}}", "Omnichannel_on_hold_chat_automatically": "Czat został automatycznie wznowiony z trybu wstrzymania po otrzymaniu nowej wiadomości od {{guest}}", - "Omnichannel_on_hold_chat_manually": "Czat został ręcznie wznowiony z trybu wstrzymania przez {{user}}", + "Omnichannel_on_hold_chat_resumed_manually": "Czat został ręcznie wznowiony z trybu wstrzymania przez {{user}}", "Omnichannel_On_Hold_due_to_inactivity": "Czat został automatycznie zawieszony, ponieważ nie otrzymaliśmy żadnej odpowiedzi od {{guest}} w ciągu {{timeout}} seconds", "Omnichannel_On_Hold_manually": "Czat został ręcznie zawieszony przez {{user}}", "Omnichannel_onHold_Chat": "Umieść w zawieszeniu", @@ -4258,7 +4259,6 @@ "Show_counter": "Pokaż licznik", "Show_email_field": "Pokaż pole e-mail", "Show_mentions": "Pokaż odznakę za wzmianki", - "Show_Message_In_Main_Thread": "Pokaż wiadomości wątku w głównym wątku", "Show_more": "Pokaż więcej", "Show_name_field": "Pokaż pole nazwy", "show_offline_users": "Pokaż użytkowników offline", @@ -4744,7 +4744,6 @@ "Type_your_job_title": "Wpisz swój tytuł pracy", "Type_your_message": "Wpisz wiadomość", "Type_your_name": "Wpisz swoje imię i nazwisko", - "Type_your_new_password": "Wprowadź nowe hasło", "Type_your_password": "Wpisz swoje hasło", "Type_your_username": "Wpisz swoją nazwę użytkownika", "UI_Allow_room_names_with_special_chars": "Zezwalaj na specjalne znaki w nazwach pokoi", @@ -5407,4 +5406,4 @@ "RegisterWorkspace_Setup_Label": "E-mail konta w chmurze", "RegisterWorkspace_Syncing_Complete": "Synchronizacja zakończona", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Zgadzam się z <1>zasadami i warunkami i <3>Polityką prywatności." -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json index 705ee21c6c546..efbe4b426654a 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/pt-BR.i18n.json @@ -947,6 +947,7 @@ "Confirm_new_password": "Confirme a nova senha", "Confirm_New_Password_Placeholder": "Insira novamente a nova senha ...", "Confirm_password": "Confirmar a senha", + "Confirm_your_password": "Confirmar a senha", "Confirmation": "Confirmação", "Connect": "Conectar", "Connected": "Conectado", @@ -2697,7 +2698,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Envie mensagens offline do livechat para um canal", "Omnichannel_on_hold_chat_resumed": "Conversa em espera retomada: {{comment}}", "Omnichannel_on_hold_chat_automatically": "A conversa foi automaticamente retomada de em espera após receber uma nova mensagem de {{guest}}", - "Omnichannel_on_hold_chat_manually": "A conversa foi manualmente retomada de em espera por {{user}}", + "Omnichannel_on_hold_chat_resumed_manually": "A conversa foi manualmente retomada de em espera por {{user}}", "Omnichannel_On_Hold_due_to_inactivity": "A conversa foi colocada em espera automaticamente porque não recebemos nenhuma resposta de {{guest}} em {{timeout}} segundos", "Omnichannel_On_Hold_manually": "A conversa foi colocada em espera manualmente por {{user}}", "Omnichannel_onHold_Chat": "Colocar conversa em espera", @@ -3950,7 +3951,6 @@ "Show_Avatars": "Mostrar avatares", "Show_counter": "Marcar como não lido", "Show_email_field": "Mostrar campo de e-mail", - "Show_Message_In_Main_Thread": "Exibir mensagens no tópico principal", "Show_more": "Mostrar mais", "Show_name_field": "Mostrar campo de nome", "show_offline_users": "mostrar usuários offline", @@ -4412,7 +4412,6 @@ "Type_your_job_title": "Digite o seu cargo", "Type_your_message": "Digite sua mensagem", "Type_your_name": "Digite seu nome", - "Type_your_new_password": "Digite sua nova senha", "Type_your_password": "Digite sua senha", "Type_your_username": "Digite seu nome", "UI_Allow_room_names_with_special_chars": "Permitir caracteres especiais em nomes de salas", @@ -4986,4 +4985,4 @@ "RegisterWorkspace_Features_Omnichannel_Title": "Omnichannel", "RegisterWorkspace_Setup_Label": "E-mail da conta da nuvem", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Eu concordo com os <1>Termos e condições e a <3>Política de privacidade" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json index 9a19691295c35..3e2ea1db9c0fb 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/pt.i18n.json @@ -623,6 +623,7 @@ "Confirm_new_password": "Confirme a nova palavra-passe", "Confirm_New_Password_Placeholder": "Por favor, escreva novamente a nova palavra-passe...", "Confirm_password": "Confirmar a senha", + "Confirm_your_password": "Confirmar a senha", "Connect": "Conectar", "Connection_Closed": "Conexão encerrada", "Connection_Reset": "Redefinir Conexão", @@ -2868,7 +2869,6 @@ "Type_your_job_title": "Digite o seu cargo", "Type_your_message": "Escreva a sua mensagem", "Type_your_name": "Escreva o seu nome", - "Type_your_new_password": "Escreva a sua nova palavra-passe", "Type_your_password": "Digite a sua senha", "Type_your_username": "Digite o seu nome de utilizador", "UI_Allow_room_names_with_special_chars": "Permitir caracteres especiais em nomes de salas", @@ -3183,4 +3183,4 @@ "registration.component.form.invalidConfirmPass": "A confirmação da senha não é igual à senha", "registration.component.form.confirmPassword": "Confirmar a senha", "registration.component.form.sendConfirmationEmail": "Enviar email de confirmação" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ro.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ro.i18n.json index 4229282c38cb4..4a9bf52c94563 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ro.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ro.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "Confirma noua parola", "Confirm_New_Password_Placeholder": "Reintroduceți noua parolă ...", "Confirm_password": "Confirmați parola", + "Confirm_your_password": "Confirmați parola", "Connection_Closed": "Conexiunea este închisă", "Connection_Reset": "Resetarea conexiunii", "Consulting": "consultant", @@ -2472,7 +2473,6 @@ "Type_your_job_title": "Introduceți titlul postului", "Type_your_message": "Scrie mesajul", "Type_your_name": "Introduceți numele dvs.", - "Type_your_new_password": "Introduceți noua parolă", "Type_your_password": "Introduceți parola", "Type_your_username": "Introduceți numele de utilizator", "UI_Allow_room_names_with_special_chars": "Permiteți caracterele speciale în numele camerelor", @@ -2754,4 +2754,4 @@ "registration.component.form.invalidConfirmPass": "Confirmarea parolei nu se potrivește cu parola introdusă", "registration.component.form.confirmPassword": "Confirmați parola", "registration.component.form.sendConfirmationEmail": "Trimite email de confirmare" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ru.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ru.i18n.json index a3fb8ba56937b..42f750b956dbf 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ru.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ru.i18n.json @@ -1024,6 +1024,7 @@ "Confirm_new_password": "Подтвердите новый пароль", "Confirm_New_Password_Placeholder": "Повторно введите новый пароль ...", "Confirm_password": "Подтвердить пароль", + "Confirm_your_password": "Подтвердить пароль", "Confirmation": "Подтверждение", "Configure_video_conference": "Настройка звонков", "Connect": "Подключение", @@ -1404,6 +1405,7 @@ "Custom_User_Status_Updated_Successfully": "Пользовательский статус успешно обновлен", "Customer_without_registered_email": "У клиента нет зарегистрированного адреса электронной почты", "Customize": "Настроить", + "Customize_Content": "Настроить содержимое", "CustomSoundsFilesystem": "Хранилище пользовательских звуков", "Daily_Active_Users": "Активные пользователи за день", "Dashboard": "Панель", @@ -1474,6 +1476,7 @@ "Deployment": "Развертывание", "Description": "Описание", "Desktop": "Компьютер", + "Desktop_apps": "Приложения для настольных ПК", "Desktop_Notification_Test": "Проверка десктопных уведомлений", "Desktop_Notifications": "Уведомления на рабочем столе", "Desktop_Notifications_Default_Alert": "Стандартные оповещения на рабочем столе", @@ -1482,6 +1485,7 @@ "Desktop_Notifications_Duration_Description": "Количество секунд в течение которых отображаются уведомления на компьютере. Это может повлиять на центр уведомлений OS X. Введите 0, чтобы использовать настройки браузера по умолчанию и не влиять на центр уведомлений OS X.", "Desktop_Notifications_Enabled": "Уведомления на компьютере включены", "Desktop_Notifications_Not_Enabled": "Уведомления на рабочем столе не включены", + "Unselected_by_default": "Выбрано не по умолчанию", "Details": "Подробности", "Device_Management": "Управление устройствами", "Device_Management_Client": "Клиент", @@ -2131,6 +2135,7 @@ "Filter": "Фильтр", "Filter_by_category": "Фильтрация по категориям", "Filter_By_Price": "Фильтрация по цене", + "Filter_By_Status": "Фильтрация по статусу", "Filters": "Фильтры", "Filters_applied": "Примененные фильтры", "Financial_Services": "Финансовые услуги", @@ -2242,6 +2247,7 @@ "Hide_flextab": "Скрывать правую боковую панель по клику", "Hide_Group_Warning": "Вы уверены, что хотите спрятать группу \"%s\"?", "Hide_Livechat_Warning": "Вы уверены, что хотите спрятать Livechat с \"%s\"?", + "Hide_On_Workspace": "Скрыть в рабочем пространстве", "Hide_Private_Warning": "Вы уверены, что хотите спрятать беседу с \"%s\"?", "Hide_roles": "Скрывать роли пользователей", "Hide_room": "Скрыть комнату", @@ -2290,6 +2296,7 @@ "Iframe_X_Frame_Options_Description": "Параметры X-Frame-Options. [Вы можете посмотреть все опции здесь.](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options#Syntax)", "Ignore": "Игнорировать", "Ignored": "Игнорируется", + "Ignore_Two_Factor_Authentication": "Игнорировать двухфакторную авторизацию", "Images": "Изображения", "IMAP_intercepter_already_running": "Перехватчик IMAP уже запущен", "IMAP_intercepter_Not_running": "Перехватчик IMAP не запущен", @@ -2382,6 +2389,7 @@ "Instructions_to_your_visitor_fill_the_form_to_send_a_message": "Инструкции для вашего посетителя заполнить форму, чтобы отправить сообщение", "Insert_Contact_Name": "Введите имя контакта", "Insert_Placeholder": "Вставить заполнитель", + "Install_rocket_chat_on_your_preferred_desktop_platform": "Установите Rocket.Chat на свой компьютер.", "Insurance": "Страхование", "Integration_added": "Интеграция была добавлена", "Integration_Advanced_Settings": "Дополнительные настройки", @@ -2459,6 +2467,7 @@ "Invitation_Subject_Default": "Вы были приглашены на [Site_Name]", "Invite": "Приглашение", "Invites": "Приглашения", + "Invite_and_add_members_to_this_workspace_to_start_communicating": "Приглашайте и добавляйте участников в это рабочее пространство, чтобы начать общение.", "Invite_Link": "Пригласительная ссылка", "link": "ссылка", "Invite_removed": "Приглашение удалено", @@ -2737,6 +2746,7 @@ "Lead_capture_email_regex": "Регулярное перемещение по электронной почте", "Lead_capture_phone_regex": "Повторное использование", "Least_recent_updated": "Наименее недавнее обновление", + "Learn_how_to_unlock_the_myriad_possibilities_of_rocket_chat": "Узнайте о всех возможностях Rocket.Chat.", "Leave": "Покинуть", "Leave_a_comment": "Оставить комментарий", "Leave_Group_Warning": "Вы уверены, что хотите покинуть группу \"%s\"?", @@ -2751,6 +2761,7 @@ "leave-p": "Оставить личные группы", "leave-p_description": "Разрешение покидать приватные группы", "Lets_get_you_new_one": "Давайте получим новый!", + "License": "Лицензия", "List_of_Channels": "Список чатов", "List_of_departments_for_forward": "Список департаментов, разрешенных к перенаправлению (необязательно)", "List_of_departments_for_forward_description": "Разрешить установить ограниченный список департаментов, которые могут принимать перенаправляемые чаты от данного департамента", @@ -2800,7 +2811,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Отправка оффлайн-сообщений Livechat в чат", "Omnichannel_on_hold_chat_resumed": "Чат в режиме удержания возобновлен: {{comment}}", "Omnichannel_on_hold_chat_automatically": "Чат был автоматически возобновлен из режима удержания при получении нового сообщения от {{guest}}", - "Omnichannel_on_hold_chat_manually": "Чат был вручную возобновлен из режима удержания пользователем {{user}}", + "Omnichannel_on_hold_chat_resumed_manually": "Чат был вручную возобновлен из режима удержания пользователем {{user}}", "Omnichannel_On_Hold_due_to_inactivity": "Чат был автоматически поставлен на удержание, так как не было получено ответа от {{guest}} в течение {{timeout}} секунд", "Omnichannel_On_Hold_manually": "Чат был вручную поставлен на удержание пользователем {{user}}", "Omnichannel_onHold_Chat": "Поставить чат на удержание", @@ -2908,6 +2919,7 @@ "manage-assets_description": "Разрешение на управление ресурсами сервера", "manage-cloud": "Управление облаком", "manage-cloud_description": "Управление облаком", + "Manage_Devices": "Управление устройствами", "manage-email-inbox": "Управление входящей электронной почтой", "manage-email-inbox_description": "Разрешение на управление входящей электронной почтой", "manage-emoji": "Управление смайлами", @@ -3139,6 +3151,7 @@ "Mobex_sms_gateway_restful_address_desc": "IP или хост вашего Mobex REST API. Например. `http: //192.168.1.1:8080` или` https: //www.example.com:8080`", "Mobex_sms_gateway_username": "Имя пользователя", "Mobile": "Мобильные устройства", + "Mobile_apps": "Мобильные приложения", "mobile-upload-file": "Разрешить загрузку файлов на мобильные устройства", "Mobile_Push_Notifications_Default_Alert": "Уведомления на мобильных устройствах", "Moderation_Delete_message": "Удалить сообщение", @@ -3260,6 +3273,7 @@ "No_pages_yet_Try_hitting_Reload_Pages_button": "Пока нет страниц. Попробуйте нажать кнопку «Обновить страницы».", "No_pinned_messages": "Нет прикрепленных сообщений", "No_previous_chat_found": "Предыдущий чат не найден", + "No_requested_apps": "Нет запрошенных приложений", "No_results_found": "Ничего не найдено", "No_results_found_for": "Ничего не найдено:", "No_snippet_messages": "Нет сниппетов", @@ -3283,6 +3297,7 @@ "Not_likely": "Вряд ли", "Not_started": "Не начато", "Not_verified": "Не подтверждён", + "Not_Visible_To_Workspace": "Скрыто в рабочем пространстве", "Nothing": "Ничего", "Nothing_found": "Ничего не найдено", "Notice_that_public_channels_will_be_public_and_visible_to_everyone": "Обратите внимание, что публичные чаты будут публичными и видимыми для всех.", @@ -3526,6 +3541,7 @@ "Privacy": "Приватность", "Privacy_Policy": "Политика конфиденциальности", "Private": "Закрытый канал", + "Private_Apps": "Приватные приложения", "Private_Channel": "Закрытый канал", "Private_Channels": "Закрытый канал", "Private_Chats": "Приватные чаты", @@ -3591,8 +3607,8 @@ "Queue_delay_timeout": "Время ожидания задержки обработки очереди истекло", "Queue_Time": "Время очереди", "Queue_management": "Управление очередью", - "quote": "цитата", - "Quote": "цитата", + "quote": "цитировать", + "Quote": "Цитировать", "Random": "Случайный", "Rate_Limiter_Limit_RegisterUser": "Звонки с номера по умолчанию на ограничитель скорости для регистрации пользователя", "Rate_Limiter_Limit_RegisterUser_Description": "Количество вызовов по умолчанию для регистрации конечных точек пользователем (API REST и API реального времени), разрешенных в пределах временного диапазона, указанного в разделе \"Ограничение частоты запросов к API\".", @@ -3697,6 +3713,8 @@ "Request_comment_when_closing_conversation": "Запросить комментарий при закрытии разговора", "Request_comment_when_closing_conversation_description": "Если эта функция включена, то перед закрытием разговора агенту необходимо будет задать комментарий.", "Request_tag_before_closing_chat": "Запросить метки до закрытия разговора", + "Requested": "Запросы", + "Requested_apps_will_appear_here": "Запрошенные приложения появятся здесь", "Requested_At": "Запрошено в", "Requested_By": "Запрошено", "Require": "Требуется", @@ -3921,6 +3939,7 @@ "Script_Enabled": "Использовать скрипт", "Search": "Поиск", "Search_Apps": "Поиск приложений", + "Search_Requested_Apps": "Поиск запрошенных приложений", "Search_by_file_name": "Поиск по имени файла", "Search_by_username": "Поиск по логину", "Search_by_category": "Поиск по категории", @@ -3944,6 +3963,7 @@ "seconds": "секунды", "Secret_token": "Секретный токен", "Security": "Безопасность", + "See_documentation": "Открыть документацию", "See_full_profile": "Смотреть полный профиль", "See_on_Engagement_Dashboard": "Смотри на панели взаимодействия", "Select_a_department": "Выберите отдел", @@ -3963,7 +3983,9 @@ "Select_user": "Выберите пользователя", "Select_users": "Выберите пользователей", "Selected_agents": "Выбранные представители", + "Selected_by_default": "Выбрано по умолчанию", "Selected_departments": "Выбранные отделы", + "Selected_first_reply_unselected_following_replies": "Выбрано для первого ответа, не выбрано для последующих ответов", "Selected_monitors": "Выбранные мониторы", "Selecting_users": "Выбор пользователей", "Send": "Отправить", @@ -4053,18 +4075,19 @@ "Show_Avatars": "Показывать аватары", "Show_counter": "Показывать счетчик", "Show_email_field": "Показать поле электронной почты", - "Show_Message_In_Main_Thread": "Показывать сообщения треда в основном чате", "Show_more": "Показать больше", "Show_name_field": "Показать поле имени", "show_offline_users": "показывать офлайн пользователей", "Show_on_offline_page": "Показать на офлайн странице", "Show_on_registration_page": "Показывать на странице регистрации", "Show_only_online": "Показать только подключенных", + "Show_Only_This_Content": "Показать только это содержимое", "Show_preregistration_form": "Показать предварительную регистрационную форму", "Show_queue_list_to_all_agents": "Показывать список очередей всем представителям", "Show_room_counter_on_sidebar": "Показывать число комнат на боковой панели", "Show_Setup_Wizard": "Показывать мастер установки", "Show_the_keyboard_shortcut_list": "Показывать список горячих клавиш", + "Show_To_Workspace": "Показать в рабочем пространстве", "Show_video": "Показать видео", "Showing_archived_results": "

Показано %s архивных результатов

", "Showing_online_users": "Показано: {{total_showing}}. Подключенных: {{online}}. Всего: {{total}} пользователей", @@ -4096,8 +4119,8 @@ "Slash_Gimme_Description": "Показывает (つ ◕_◕) つ перед сообщением", "Slash_LennyFace_Description": "Показывает (͡ ° ͜ʖ ͡ °) после сообщения", "Slash_Shrug_Description": "Показывает ¯ \\ _ (ツ) _ / ¯ после сообщения", - "Slash_Status_Description": "Установить Ваше статусное сообщение", - "Slash_Status_Params": "Статусное сообщение", + "Slash_Status_Description": "Установить Ваш статус", + "Slash_Status_Params": "Ваш статус", "Slash_Tableflip_Description": "Показывает (╯ ° □ °) ╯( ┻━┻", "Slash_TableUnflip_Description": "Показывает ┬─┬ ノ (゜ - ゜ ノ)", "Slash_Topic_Description": "Установить тему", @@ -4126,6 +4149,7 @@ "Snippet_name": "Название сниппета", "Snippeted_a_message": "Создан сниппет {{snippetLink}}", "Social_Network": "Социальная сеть", + "Some_ideas_to_get_you_started": "Советы по началу работы", "Sorry_page_you_requested_does_not_exist_or_was_deleted": "Извините, запрошенная вами страница не существует или была удалена!", "Sort": "Сортировка", "Sort_By": "Сортировать по", @@ -4194,7 +4218,7 @@ "Stats_Total_Uploads_Size": "Общий размер загрузок", "Stats_Total_Users": "Всего пользователей", "Status": "Статус", - "StatusMessage": "Статусное Сообщение", + "StatusMessage": "Ваш Статус", "StatusMessage_Change_Disabled": "Администратор Вашего Rocket.Chat запретил изменение статусов", "StatusMessage_Changed_Successfully": "Статусное сообщения успешно изменено.", "StatusMessage_Placeholder": "Что Вы сейчас делаете?", @@ -4232,6 +4256,7 @@ "Tag_removed": "Метка удалена", "Tag_already_exists": "Метка уже существует", "Take_it": "Возьми это!", + "Take_rocket_chat_with_you_with_mobile_applications": "Установите Rocket.Chat в свой мобильный телефон.", "Taken_at": "Взят в", "Talk_Time": "Время разговора", "Target user not allowed to receive messages": "Целевому пользователю не разрешено получать сообщения", @@ -4514,7 +4539,6 @@ "Type_your_job_title": "Введите название своей должности", "Type_your_message": "Текст вашего сообщения", "Type_your_name": "Введите ваше имя", - "Type_your_new_password": "Введите ваш новый пароль", "Type_your_password": "Введите пароль", "Type_your_username": "Введите имя пользователя", "UI_Allow_room_names_with_special_chars": "Разрешить специальные символы в названии комнаты", @@ -4559,6 +4583,8 @@ "Unread_on_top": "Непрочитанное наверх", "Unread_Rooms": "Непрочитанные комнаты", "Unread_Rooms_Mode": "Режим непрочитанные комнаты", + "Unread_Requested_First": "Непрочитанные первые запросы", + "Unread_Requested_Last": "Непрочитанные последние запросы", "Unread_Tray_Icon_Alert": "Иконка уведомлений о непрочитанных сообщениях в трее", "Unstar_Message": "Убрать отметку", "Unmute_microphone": "Включить микрофон", @@ -4652,7 +4678,7 @@ "User_not_found": "Пользователь не найден", "User_not_found_or_incorrect_password": "Пользователь не найден или введен неверный пароль", "User_or_channel_name": "Имя пользователя или канала", - "User_Presence": "Присутствие пользователя", + "User_Presence": "Статус пользователя", "User_removed": "Пользователь удален", "User_removed_by": "Пользователь {{user_removed}} удален {{user_by}}.", "User_sent_a_message_on_channel": "{{username}} отправил сообщение в{{channel}}", @@ -4826,6 +4852,7 @@ "Viewing_room_administration": "Просмотр комнаты администрирования", "Visibility": "Видимость", "Visible": "Видимый", + "Visible_To_Workspace": "Видимый в рабочем пространстве", "Visit_Site_Url_and_try_the_best_open_source_chat_solution_available_today": "Посетите {{Site_URL}} и попробуйте лучшее решение для чата с открытым исходным кодом, доступное сегодня!", "Visitor": "Посетитель", "Visitor_Email": "Электронная почта посетителя", @@ -4925,6 +4952,7 @@ "Would_you_like_to_place_chat_on_hold": "Вы хотите поставить этот чат на удержание?", "Wrap_up_the_call": "Завершить звонок", "Wrap_Up_Notes": "Заключительные заметки", + "Workspace": "Рабочее пространство", "Yes": "Да", "Yes_archive_it": "Да, архивировать!", "Yes_clear_all": "Да, удалить все!", @@ -5096,10 +5124,26 @@ "onboarding.form.standaloneServerForm.servicesUnavailable": "Некоторые сервисы будут недоступны или потребуется ручная настройка", "onboarding.form.standaloneServerForm.publishOwnApp": "Чтобы отправлять push-уведомления, необходимо создать и опубликовать собственное приложение в Google Play и App Store", "onboarding.form.standaloneServerForm.manuallyIntegrate": "Необходимо вручную выполнить интеграцию с внешними сервисами", + "Theme_light": "Светлая", + "Theme_dark": "Темная", + "Theme_match_system": "Системная", + "Conversational_transcript": "Экспорт содержимого диалога", + "Always_send_the_transcript_to_contacts_at_the_end_of_the_conversations": "Всегда отправлять содержимое чата контактам по окончанию диалога.", + "Omnichannel_transcript_email": "Отправить содержимое диалога по электронной почте.", + "Accounts_Default_User_Preferences_omnichannelTranscriptEmail_Description": "Всегда отправлять содержимое чата контактам по окончанию диалога.", + "Omnichannel_transcript_pdf": "Экспорт содержимого чата в PDF.", + "Accounts_Default_User_Preferences_omnichannelTranscriptPDF_Description": "Всегда экспортировать содержимое чата по окончанию диалога.", + "User_Status": "Пользовательский статус", + "Presence_service": "Служба присутствия", + "New_custom_status": "Новый пользовательский статус", "Awaiting_confirmation": "Ожидает подтверждения", + "RegisterWorkspace_Registered_Description": "Доступные службы", "RegisterWorkspace_Features_MobileNotifications_Title": "Push-уведомления на мобильных устройствах", + "RegisterWorkspace_Features_MobileNotifications_Description": "Позволяет участникам рабочего пространства получать уведомления на своих мобильных устройствах.", "RegisterWorkspace_Features_Marketplace_Title": "Магазин", + "RegisterWorkspace_Features_Marketplace_Description": "Установите приложения в \"Магазине приложений\" Rocket.Chat для этого рабочего пространства.", "RegisterWorkspace_Features_Omnichannel_Title": "Настройки Omnichannel", "RegisterWorkspace_Setup_Label": "Адрес электронной почты учетной записи в облаке", - "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Я принимаю <1>Положения и условия и <3>Политику конфиденциальности" -} + "cloud.RegisterWorkspace_Setup_Terms_Privacy": "Я принимаю <1>Положения и условия и <3>Политику конфиденциальности", + "Theme_Appearence": "Внешний вид" +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sk-SK.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sk-SK.i18n.json index e28c9cdb98e02..eb54defefe86c 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sk-SK.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sk-SK.i18n.json @@ -495,6 +495,7 @@ "Confirm_new_password": "Potvrďte nové heslo", "Confirm_New_Password_Placeholder": "Zadajte nové heslo znovu ...", "Confirm_password": "Potvrďte svoje heslo", + "Confirm_your_password": "Potvrďte svoje heslo", "Connection_Closed": "Pripojenie je ukončené", "Connection_Reset": "Reset pripojenia", "Consulting": "Konzultovanie", @@ -2482,7 +2483,6 @@ "Type_your_job_title": "Zadajte názov úlohy", "Type_your_message": "Zadajte svoju správu", "Type_your_name": "Zadajte svoje meno", - "Type_your_new_password": "Zadajte nové heslo", "Type_your_password": "Zadajte svoje heslo", "Type_your_username": "Zadajte svoje používateľské meno", "UI_Allow_room_names_with_special_chars": "Povoliť špeciálne znaky v názvoch miestností", @@ -2764,4 +2764,4 @@ "registration.component.form.invalidConfirmPass": "Potvrdenie hesla sa nezhoduje s heslom", "registration.component.form.confirmPassword": "Potvrďte svoje heslo", "registration.component.form.sendConfirmationEmail": "Pošlite potvrdzovací e-mail" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sl-SI.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sl-SI.i18n.json index 89ae3ded22ecf..954c548307178 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sl-SI.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sl-SI.i18n.json @@ -487,6 +487,7 @@ "Confirm_new_password": "Potrdite novo geslo", "Confirm_New_Password_Placeholder": "Ponovno vnesite novo geslo ...", "Confirm_password": "Potrdi geslo", + "Confirm_your_password": "Potrdi geslo", "Connection_Closed": "Povezava je zaprta", "Connection_Reset": "Ponastavitev povezave", "Consulting": "Svetovanje", @@ -2462,7 +2463,6 @@ "Type_your_job_title": "Vnesite naslov dela", "Type_your_message": "Vnesite vaše sporočilo", "Type_your_name": "Vnesite vaše ime", - "Type_your_new_password": "Vnesite vaše novo gleslo", "Type_your_password": "Vnesite geslo", "Type_your_username": "Vnesite svoje uporabniško ime", "UI_Allow_room_names_with_special_chars": "V imenih sob dovoli posebne znake", @@ -2744,4 +2744,4 @@ "registration.component.form.invalidConfirmPass": "Potrditev gesla se ne ujema z geslom", "registration.component.form.confirmPassword": "Potrdi geslo", "registration.component.form.sendConfirmationEmail": "Pošlji potrditveno e-poštno sporočilo" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sq.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sq.i18n.json index f8e1f75abd7d2..fdd705dd663b3 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sq.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sq.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "Konfirmo fjalëkalimin e ri", "Confirm_New_Password_Placeholder": "Ri-futni fjalëkalimin e ri ...", "Confirm_password": "Konfirmoni fjalëkalimin tuaj", + "Confirm_your_password": "Konfirmoni fjalëkalimin tuaj", "Connection_Closed": "Lidhja u mbyll", "Connection_Reset": "Rivendosja e lidhjes", "Consulting": "këshillues", @@ -2472,7 +2473,6 @@ "Type_your_job_title": "Shkruani titullin e punës", "Type_your_message": "Shkruani mesazhin tuaj", "Type_your_name": "Shkruani emrin tuaj", - "Type_your_new_password": "Shkruaj fjalëkalimin tuaj të ri", "Type_your_password": "Shkruaj fjalëkalimin tënd", "Type_your_username": "Shkruaj emrin e përdoruesit", "UI_Allow_room_names_with_special_chars": "Lejo karaktere speciale në Emrat e dhomave", @@ -2755,4 +2755,4 @@ "registration.component.form.invalidConfirmPass": "Konfirmimi Fjalëkalimi nuk përputhet me fjalëkalimin", "registration.component.form.confirmPassword": "Konfirmoni fjalëkalimin tuaj", "registration.component.form.sendConfirmationEmail": "Dërgo email konfirmimi" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sr.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sr.i18n.json index fc82e5e3190b1..725a094d4e2c9 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sr.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sr.i18n.json @@ -407,6 +407,7 @@ "Confirm_new_password": "Потврдите нову лозинку", "Confirm_New_Password_Placeholder": "Поново унесите нову лозинку ...", "Confirm_password": "Потврдите лозинку", + "Confirm_your_password": "Потврдите лозинку", "Connection_Closed": "Веза је затворена", "Connection_Reset": "Ресет везе", "Consulting": "Консалтинг", @@ -2295,7 +2296,6 @@ "Type_your_job_title": "Унесите свој назив посла", "Type_your_message": "Унесите поруку", "Type_your_name": "Типе иоур наме", - "Type_your_new_password": "Унесите нову лозинку", "Type_your_password": "Унесите своју лозинку", "Type_your_username": "Унесите своје корисничко име", "UI_Allow_room_names_with_special_chars": "Дозволи посебне знакове у именима соба", @@ -2565,4 +2565,4 @@ "registration.component.form.invalidConfirmPass": "Потврдна лозинка се не поклапа са лозинком", "registration.component.form.confirmPassword": "Потврдите лозинку", "registration.component.form.sendConfirmationEmail": "Пошаљи потврдну поруку" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/sv.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/sv.i18n.json index 4c49597530eae..873952ba420c3 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/sv.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/sv.i18n.json @@ -1059,6 +1059,7 @@ "Confirm_new_password": "Bekräfta nytt lösenord", "Confirm_New_Password_Placeholder": "Vänligen skriv in nytt lösenord...", "Confirm_password": "Bekräfta ditt lösenord", + "Confirm_your_password": "Bekräfta ditt lösenord", "Confirmation": "Bekräftelse", "Configure_video_conference": "Konfigurera konferenssamtal", "Connect": "Anslut", @@ -3016,7 +3017,7 @@ "Livechat_OfflineMessageToChannel_enabled": "Skicka offlinemeddelanden för Livechat till en kanal ", "Omnichannel_on_hold_chat_resumed": "Parkerad chatt återupptogs: {{comment}}", "Omnichannel_on_hold_chat_automatically": "Chatten återupptogs automatiskt från parkerat läge när ett nytt meddelande från {{guest}} mottogs", - "Omnichannel_on_hold_chat_manually": "Chatten återupptogs manuellt från parkerat läge av {{user}}", + "Omnichannel_on_hold_chat_resumed_manually": "Chatten återupptogs manuellt från parkerat läge av {{user}}", "Omnichannel_On_Hold_due_to_inactivity": "Chatten parkerades manuellt eftersom {{guest}} inte svarade på {{timeout}} sekunder", "Omnichannel_On_Hold_manually": "Chatten parkerades manuellt av {{user}}", "Omnichannel_onHold_Chat": "Parkera chatten", @@ -4500,7 +4501,6 @@ "Show_default_content": "Visa standardinnehåll", "Show_email_field": "Visa e-postfältet", "Show_mentions": "Visa märke för omnämnanden", - "Show_Message_In_Main_Thread": "Visa trådmeddelanden i huvudtråden", "Show_more": "Visa mer", "Show_name_field": "Visa namnfältet", "show_offline_users": "visa offline-användare", @@ -5010,7 +5010,6 @@ "Type_your_job_title": "Skriv din jobbtitel", "Type_your_message": "Skriv in ditt meddelande", "Type_your_name": "Skriv in ditt namn", - "Type_your_new_password": "Skriv in ditt nya lösenord", "Type_your_password": "Skriv ditt lösenord", "Type_your_username": "Skriv ditt användarnamn", "UI_Allow_room_names_with_special_chars": "Tillåt särskilda tecken i rumsnamn", @@ -5816,4 +5815,4 @@ "Uninstall_grandfathered_app": "Avinstallera {{appName}}?", "App_will_lose_grandfathered_status": "**Denna {{context}}-app kommer att förlora sin status som gammal app.** \n \nArbetsytorna i Community Edition kan ha upp till {{limit}} __kontext__-appar aktiverade. Gamla appar inkluderas i gränsen, men gränsen tillämpas inte på dem.", "Theme_Appearence": "Utseende för tema" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ta-IN.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ta-IN.i18n.json index 15a19ce665870..fb9d1cffe1af6 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ta-IN.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ta-IN.i18n.json @@ -489,6 +489,7 @@ "Confirm_new_password": "புதிய கடவு சொல்லை உறுதி செய்", "Confirm_New_Password_Placeholder": "புதிய கடவுச்சொல்லை மீண்டும் உள்ளிடுக ...", "Confirm_password": "உங்கள் கடவுச்சொல்லை உறுதிப்படுத்துக", + "Confirm_your_password": "உங்கள் கடவுச்சொல்லை உறுதிப்படுத்துக", "Connection_Closed": "இணைப்பு மூடப்பட்டது", "Connection_Reset": "இணைப்பு மீட்டமை", "Consulting": "ஆலோசனை", @@ -2473,7 +2474,6 @@ "Type_your_job_title": "உங்கள் பணிப் பெயரை தட்டச்சு செய்யவும்", "Type_your_message": "உங்கள் செய்தியைத் தட்டச்சு", "Type_your_name": "உங்கள் பெயரை தட்டச்சு", - "Type_your_new_password": "உங்கள் புதிய கடவுச்சொல்லை உள்ளிடவும்", "Type_your_password": "உங்கள் கடவுச்சொல்லை உள்ளிடவும்", "Type_your_username": "உங்கள் பயனர்பெயரைத் தட்டச்சு செய்க", "UI_Allow_room_names_with_special_chars": "அறை பெயர்களில் சிறப்பு எழுத்துகள் அனுமதி", @@ -2759,4 +2759,4 @@ "registration.component.form.invalidConfirmPass": "கடவுச்சொல்லை உறுதிப்படுத்தும் கடவுச்சொல் பொருந்தவில்லை", "registration.component.form.confirmPassword": "உங்கள் கடவுச்சொல்லை உறுதிப்படுத்துக", "registration.component.form.sendConfirmationEmail": "உறுதிப்படுத்தும் மின்னஞ்சல் அனுப்பவும்" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/th-TH.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/th-TH.i18n.json index 00916ba372692..7574fb516b5fc 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/th-TH.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/th-TH.i18n.json @@ -488,6 +488,7 @@ "Confirm_new_password": "ยืนยันรหัสผ่านใหม่", "Confirm_New_Password_Placeholder": "โปรดป้อนรหัสผ่านใหม่ ...", "Confirm_password": "ยืนยันรหัสผ่านของคุณ", + "Confirm_your_password": "ยืนยันรหัสผ่านของคุณ", "Connection_Closed": "ปิดการเชื่อมต่อ", "Connection_Reset": "รีเซ็ตการเชื่อมต่อ", "Consulting": "การให้คำปรึกษา", @@ -2464,7 +2465,6 @@ "Type_your_job_title": "พิมพ์ชื่องานของคุณ", "Type_your_message": "พิมพ์ข้อความของคุณ", "Type_your_name": "พิมพ์ชื่อของคุณ", - "Type_your_new_password": "พิมพ์รหัสผ่านใหม่", "Type_your_password": "พิมพ์รหัสผ่านของคุณ", "Type_your_username": "พิมพ์ชื่อผู้ใช้ของคุณ", "UI_Allow_room_names_with_special_chars": "อนุญาตให้ใช้อักขระพิเศษในชื่อห้อง", @@ -2742,4 +2742,4 @@ "registration.component.form.invalidConfirmPass": "การยืนยันรหัสผ่านไม่ตรงกับรหัสผ่าน", "registration.component.form.confirmPassword": "ยืนยันรหัสผ่านของคุณ", "registration.component.form.sendConfirmationEmail": "ส่งอีเมลยืนยัน" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/tr.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/tr.i18n.json index d37f2c20769a2..4b5a57995436f 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/tr.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/tr.i18n.json @@ -615,6 +615,7 @@ "Confirm_new_password": "Yeni Şifreyi Onayla", "Confirm_New_Password_Placeholder": "Lütfen yeni şifreyi tekrar girin...", "Confirm_password": "Parolanızı onaylayın", + "Confirm_your_password": "Parolanızı onaylayın", "Connect": "Bağlan", "Connection_Closed": "Bağlantı kapandı", "Connection_Reset": "Bağlantı sıfırlama", @@ -2948,7 +2949,6 @@ "Type_your_job_title": "İş başlığınızı yazın", "Type_your_message": "İletinizi yazın", "Type_your_name": "Adınızı yazın", - "Type_your_new_password": "Yeni şifrenizi yazın", "Type_your_password": "Şifrenizi giriniz", "Type_your_username": "Kullanıcı adınızı yazın", "UI_Allow_room_names_with_special_chars": "Oda Adlarında Özel Karakterlere İzin Ver", @@ -3271,4 +3271,4 @@ "registration.component.form.confirmPassword": "Parolanızı onaylayın", "registration.component.form.sendConfirmationEmail": "Doğrulama e-postası gönder", "RegisterWorkspace_Features_Omnichannel_Title": "Çoklu Kanal" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/ug.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/ug.i18n.json index 66cfa374dd531..e72486a80a6f3 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/ug.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/ug.i18n.json @@ -246,6 +246,7 @@ "Color": "رەڭ", "Commands": "كوماندا", "Confirm_password": "مەخپىي نومۇرنى جەزملەشتۈرۈش", + "Confirm_your_password": "مەخپىي نومۇرنى جەزملەشتۈرۈش", "Contact": "ئالاقىلىشىش", "Conversation": "پاراڭلىشىش", "Conversation_closed": "{{comment}} پاراڭلىشىش ئاخىرلاشتى", @@ -1092,7 +1093,6 @@ "Type_your_email": "سىزنىڭ ئىلخەت نومۇرىڭىزنى كىرگۈزۈڭ", "Type_your_message": "قالدۇرۇلغان سۆزنى كىرگۈزۈڭ", "Type_your_name": "ئىسمىڭىزنى كىرگۈزۈڭ", - "Type_your_new_password": "يېڭى پارولنى كىرگۈزۈڭ", "UI_DisplayRoles": "رولنى كۆرسىتىش", "UI_Merge_Channels_Groups": "خۇسۇسىي تەشكىلات ئېرىقلارنى بىرىكلەشتۈرۈش", "Unarchive": "تۈرگە ئايرىپ ئارخىپقا ساقلاشنى بىكار قىلىش", @@ -1243,4 +1243,4 @@ "registration.component.form.invalidConfirmPass": "ئىككىنچى قېتىم كىرگۈزۈلگەن مەخپىي نومۇر بىلەن بىرىنچى قېتىم كىرگۈزۈلگىنى بىلەن ماس كەلمىدى.", "registration.component.form.confirmPassword": "مەخپىي نومۇرنى جەزملەشتۈرۈش", "registration.component.form.sendConfirmationEmail": "جەزملەشتۈرگەن ئىلخەت يوللاندى" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/uk.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/uk.i18n.json index 69cb5b30cc903..9919c95fc36e9 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/uk.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/uk.i18n.json @@ -692,6 +692,7 @@ "Confirm_new_password": "Підтвердити новий пароль", "Confirm_New_Password_Placeholder": "Будь ласка, введіть новий пароль ще раз ...", "Confirm_password": "Підтвердити новий пароль", + "Confirm_your_password": "Підтвердити новий пароль", "Connect": "Підключення", "Connection_Closed": "З'єднання закрито", "Connection_Reset": "З'єднання скинуто", @@ -3043,7 +3044,6 @@ "Type_your_job_title": "Введіть назву вашої роботи", "Type_your_message": "текст повідомлення", "Type_your_name": "Введіть ваше ім'я", - "Type_your_new_password": "Введіть новий пароль", "Type_your_password": "Введіть свій пароль", "Type_your_username": "Введіть своє ім'я користувача", "UI_Allow_room_names_with_special_chars": "Дозволити спеціальні символи в іменах номерів", @@ -3360,4 +3360,4 @@ "registration.component.form.invalidConfirmPass": "Підтвердження пароля не збігаються пароль", "registration.component.form.confirmPassword": "Підтвердити новий пароль", "registration.component.form.sendConfirmationEmail": "Надіслати електронною поштою підтвердження" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/vi-VN.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/vi-VN.i18n.json index 0038e5a708a18..d531bd034f625 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/vi-VN.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/vi-VN.i18n.json @@ -584,6 +584,7 @@ "Confirm_new_password": "Xác nhận mật khẩu mới", "Confirm_New_Password_Placeholder": "Vui lòng nhập lại mật khẩu mới ...", "Confirm_password": "Xác nhận mật khẩu của bạn", + "Confirm_your_password": "Xác nhận mật khẩu của bạn", "Connection_Closed": "Kêt nôi bị đong", "Connection_Reset": "Đặt lại kết nối", "Consulting": "Tư vấn", @@ -2573,7 +2574,6 @@ "Type_your_job_title": "Loại chức vụ nghề nghiệp", "Type_your_message": "Nhập tin nhắn của bạn", "Type_your_name": "Gõ tên của bạn", - "Type_your_new_password": "Nhập mật khẩu mới của bạn", "Type_your_password": "Loại mật khẩu", "Type_your_username": "Nhập tên người dùng", "UI_Allow_room_names_with_special_chars": "Cho phép các ký tự đặc biệt trong tên phòng", @@ -2853,4 +2853,4 @@ "registration.component.form.invalidConfirmPass": "Xác nhận mật khẩu không khớp với mật khẩu", "registration.component.form.confirmPassword": "Xác nhận mật khẩu của bạn", "registration.component.form.sendConfirmationEmail": "Gửi email xác nhận" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/zh-HK.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/zh-HK.i18n.json index f7f7ccffc3227..ed085a4b9630b 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/zh-HK.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/zh-HK.i18n.json @@ -510,6 +510,7 @@ "Confirm_new_password": "确认新密码", "Confirm_New_Password_Placeholder": "请重新输入新密码...", "Confirm_password": "确认密码", + "Confirm_your_password": "确认密码", "Connection_Closed": "连接关闭", "Connection_Reset": "连接重置", "Consulting": "咨询", @@ -2497,7 +2498,6 @@ "Type_your_job_title": "输入你的职位", "Type_your_message": "输入你的讯息", "Type_your_name": "输入你的名字", - "Type_your_new_password": "输入您的新密码", "Type_your_password": "输入您的密码", "Type_your_username": "输入您的用户名", "UI_Allow_room_names_with_special_chars": "允许房间名称中的特殊字符", @@ -2774,4 +2774,4 @@ "registration.component.form.invalidConfirmPass": "第二次密码输入与第一次不匹配", "registration.component.form.confirmPassword": "确认密码", "registration.component.form.sendConfirmationEmail": "已发送确认电子邮件" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/zh-TW.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/zh-TW.i18n.json index 97025f68b44ab..a23e2152c5cf2 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/zh-TW.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/zh-TW.i18n.json @@ -911,6 +911,7 @@ "Confirm_new_password": "確認新密碼", "Confirm_New_Password_Placeholder": "請重新輸入新密碼...", "Confirm_password": "確認密碼", + "Confirm_your_password": "確認密碼", "Confirmation": "確認", "Connect": "連線", "Connected": "已連線", @@ -2576,7 +2577,7 @@ "Livechat_OfflineMessageToChannel_enabled": "將即時聊天離線消息傳送到頻道", "Omnichannel_on_hold_chat_resumed": "暫停聊天恢復:{{comment}}", "Omnichannel_on_hold_chat_automatically": "收到來自 {{guest}} 的新訊息後,聊天自動從 On Hold 恢復", - "Omnichannel_on_hold_chat_manually": "聊天是由 {{user}} 從 On Hold 手動恢復的", + "Omnichannel_on_hold_chat_resumed_manually": "聊天是由 {{user}} 從 On Hold 手動恢復的", "Omnichannel_On_Hold_due_to_inactivity": "因為我們在 {{timeout}} 秒內沒有收到來自 {{guest}} 的任何回覆,所以聊天被自動置於暫停狀態", "Omnichannel_On_Hold_manually": "{{user}} 手動將聊天置於暫停狀態", "Livechat_online": "即時聊天線上", @@ -3723,7 +3724,6 @@ "Show_Avatars": "顯示頭像", "Show_counter": "顯示櫃檯", "Show_email_field": "顯示電子郵件欄位", - "Show_Message_In_Main_Thread": "在主討論中顯示討論訊息", "Show_more": "顯示更多", "Show_name_field": "顯示名稱欄位", "show_offline_users": "顯示離線使用者", @@ -4107,7 +4107,6 @@ "Type_your_job_title": "輸入你的職位", "Type_your_message": "輸入您的訊息", "Type_your_name": "輸入您的姓名", - "Type_your_new_password": "輸入新密碼", "Type_your_password": "輸入您的密碼", "Type_your_username": "輸入您的使用者名稱", "UI_Allow_room_names_with_special_chars": "允許 Room 名稱中的特殊字元", @@ -4613,4 +4612,4 @@ "RegisterWorkspace_Features_Omnichannel_Title": "Omnichannel", "RegisterWorkspace_Setup_Label": "雲端帳戶電子郵件", "cloud.RegisterWorkspace_Setup_Terms_Privacy": "我同意<1>條款及條件和<3>隱私權政策" -} +} \ No newline at end of file diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/zh.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/zh.i18n.json index b2150de8f1575..17b890b822451 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/zh.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/zh.i18n.json @@ -806,6 +806,7 @@ "Confirm_new_password": "确认新密码", "Confirm_New_Password_Placeholder": "请重新输入新密码...", "Confirm_password": "确认密码", + "Confirm_your_password": "确认密码", "Connect": "连接", "Connect_SSL_TLS": "使用 SSL/TLS 连接", "Connection_Closed": "连接关闭", @@ -3389,7 +3390,6 @@ "Show_Avatars": "显示头像", "Show_counter": "显示柜台", "Show_email_field": "显示电子邮件字段", - "Show_Message_In_Main_Thread": "在主讨论串中展示讨论串消息", "Show_more": "显示更多", "Show_name_field": "显示名称字段", "show_offline_users": "显示离线用户", @@ -3754,7 +3754,6 @@ "Type_your_job_title": "输入你的职位", "Type_your_message": "输入您的留言", "Type_your_name": "输入您的姓名", - "Type_your_new_password": "输入新密码", "Type_your_password": "输入您的密码", "Type_your_username": "输入您的用户名", "UI_Allow_room_names_with_special_chars": "允许Room名称中的特殊字符", @@ -4153,4 +4152,4 @@ "registration.component.form.invalidConfirmPass": "两次输入的密码不一致", "registration.component.form.confirmPassword": "确认密码", "registration.component.form.sendConfirmationEmail": "已发送确认电子邮件" -} +} \ No newline at end of file From e527f51f1a4ad681fbdf9cdf9ea2c66443a75cdb Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva Date: Mon, 26 Jun 2023 17:43:05 -0300 Subject: [PATCH 05/79] refactor: simplified canned response filter query (#29650) --- .../omnichannel/cannedResponses/CannedResponsesTable.tsx | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx index d179a14458b9f..8a563e94bcc57 100644 --- a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx +++ b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponsesTable.tsx @@ -67,10 +67,7 @@ const CannedResponsesTable = () => { ); const getCannedResponses = useEndpoint('GET', '/v1/canned-responses'); - const { data, isLoading, isSuccess, refetch } = useQuery( - ['/v1/canned-responses', { debouncedText, sortDirection, itemsPerPage, current, sharing, createdBy }], - () => getCannedResponses(query), - ); + const { data, isLoading, isSuccess, refetch } = useQuery(['/v1/canned-responses', query], () => getCannedResponses(query)); const getTime = useFormatDateAndTime(); From 33be8f16b9332ca6818a366cdf4c884ed4879e9e Mon Sep 17 00:00:00 2001 From: Tasso Evangelista Date: Tue, 27 Jun 2023 10:54:02 -0300 Subject: [PATCH 06/79] fix(livechat): Storybook adjustments and TypeScript migration (#29631) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .changeset/strong-trains-enjoy.md | 5 + .../preact-npm-10.15.1-bd458de913.patch | 13 + ...nichannel-auto-onhold-chat-closing.spec.ts | 6 +- ...nnel-auto-transfer-unanswered-chat.spec.ts | 6 +- ...nichannel-canned-responses-sidebar.spec.ts | 6 +- ...nel-changing-room-priority-and-sla.spec.ts | 6 +- .../omnichannel-chat-history.spec.ts | 8 +- .../omnichannel-close-chat.spec.ts | 6 +- .../omnichannel-close-inquiry.spec.ts | 14 +- .../omnichannel-contact-info.spec.ts | 6 +- .../omnichannel/omnichannel-livechat.spec.ts | 6 +- .../omnichannel-send-transcript.spec.ts | 6 +- .../omnichannel/omnichannel-takeChat.spec.ts | 6 +- ...channel-transfer-to-another-agents.spec.ts | 6 +- .../omnichannel/omnichannel-triggers.spec.ts | 6 +- .../e2e/page-objects/omnichannel-livechat.ts | 9 +- package.json | 3 +- packages/livechat/.storybook/main.js | 3 +- packages/livechat/babel.config.js | 31 +- packages/livechat/package.json | 15 +- .../livechat/src/components/Alert/index.tsx | 3 +- .../livechat/src/components/Alert/stories.js | 82 ---- .../livechat/src/components/Alert/stories.tsx | 66 +++ .../livechat/src/components/Avatar/stories.js | 92 ---- .../src/components/Avatar/stories.tsx | 66 +++ .../livechat/src/components/Button/index.tsx | 9 +- .../livechat/src/components/Button/stories.js | 211 --------- .../src/components/Button/stories.tsx | 112 +++++ .../src/components/ButtonGroup/stories.js | 50 --- .../src/components/ButtonGroup/stories.tsx | 62 +++ .../src/components/Composer/stories.js | 89 ---- .../src/components/Composer/stories.tsx | 77 ++++ .../src/components/FilesDropTarget/stories.js | 103 ----- .../components/FilesDropTarget/stories.tsx | 91 ++++ .../livechat/src/components/Footer/index.js | 2 +- .../livechat/src/components/Footer/stories.js | 53 --- .../src/components/Footer/stories.tsx | 65 +++ .../src/components/Form/DateInput/stories.js | 87 ---- .../src/components/Form/DateInput/stories.tsx | 59 +++ .../src/components/Form/FormField/index.js | 2 +- .../src/components/Form/FormField/stories.js | 46 -- .../src/components/Form/FormField/stories.tsx | 39 ++ .../components/Form/PasswordInput/stories.js | 86 ---- .../components/Form/PasswordInput/stories.tsx | 59 +++ .../components/Form/SelectInput/stories.js | 111 ----- .../components/Form/SelectInput/stories.tsx | 64 +++ .../src/components/Form/TextInput/stories.js | 107 ----- .../src/components/Form/TextInput/stories.tsx | 67 +++ .../livechat/src/components/Form/stories.js | 43 -- .../livechat/src/components/Form/stories.tsx | 51 +++ .../livechat/src/components/Header/index.js | 16 +- .../livechat/src/components/Header/stories.js | 254 ----------- .../src/components/Header/stories.tsx | 222 ++++++++++ .../src/components/Menu/Group.stories.tsx | 48 ++ .../src/components/Menu/Item.stories.tsx | 92 ++++ .../src/components/Menu/Menu.stories.tsx | 62 +++ .../components/Menu/PopoverMenu.stories.tsx | 45 ++ .../livechat/src/components/Menu/index.js | 17 +- .../livechat/src/components/Menu/stories.js | 164 ------- .../Messages/AudioAttachment/stories.js | 11 - .../Messages/AudioAttachment/stories.tsx | 19 + .../Messages/FileAttachment/stories.js | 20 - .../Messages/FileAttachment/stories.tsx | 62 +++ .../Messages/ImageAttachment/stories.js | 11 - .../Messages/ImageAttachment/stories.tsx | 19 + .../Message/{stories.js => stories.tsx} | 63 +-- .../Messages/MessageAvatars/stories.js | 23 - .../Messages/MessageAvatars/stories.tsx | 46 ++ .../Messages/MessageBlocks/index.js | 2 +- .../MessageBlocks/{stories.js => stories.tsx} | 10 +- .../Messages/MessageBubble/stories.js | 31 -- .../Messages/MessageBubble/stories.tsx | 44 ++ .../Messages/MessageList/stories.js | 107 ----- .../Messages/MessageList/stories.tsx | 99 +++++ .../Messages/MessageSeparator/stories.js | 14 - .../Messages/MessageSeparator/stories.tsx | 33 ++ .../Messages/MessageTime/stories.js | 14 - .../Messages/MessageTime/stories.tsx | 29 ++ .../components/Messages/TypingDots/stories.js | 10 - .../Messages/TypingDots/stories.tsx | 20 + .../Messages/TypingIndicator/stories.js | 19 - .../Messages/TypingIndicator/stories.tsx | 29 ++ .../Messages/VideoAttachment/stories.js | 11 - .../Messages/VideoAttachment/stories.tsx | 21 + .../Messages/{constants.js => constants.ts} | 0 .../livechat/src/components/Modal/stories.js | 75 ---- .../livechat/src/components/Modal/stories.tsx | 83 ++++ .../livechat/src/components/Popover/index.js | 1 + .../src/components/Popover/stories.js | 36 -- .../src/components/Popover/stories.tsx | 44 ++ .../src/components/Screen/Footer.stories.tsx | 67 +++ .../livechat/src/components/Screen/index.js | 1 + .../livechat/src/components/Screen/stories.js | 321 -------------- .../src/components/Screen/stories.tsx | 121 +++++ .../livechat/src/components/Sound/stories.js | 18 - .../livechat/src/components/Sound/stories.tsx | 33 ++ .../src/components/Tooltip/stories.js | 44 -- .../src/components/Tooltip/stories.tsx | 59 +++ ...ns.stories.js => ActionsBlock.stories.tsx} | 5 +- .../ButtonElement/{stories.js => stories.tsx} | 44 +- ...xt.stories.js => ContextBlock.stories.tsx} | 6 +- ...er.stories.js => DividerBlock.stories.tsx} | 4 +- ...mage.stories.js => ImageBlock.stories.tsx} | 6 +- ...on.stories.js => SectionBlock.stories.tsx} | 7 +- .../uiKit/message/{index.js => index.tsx} | 26 +- packages/livechat/src/global.d.ts | 33 +- packages/livechat/src/helpers.stories.js | 29 +- packages/livechat/src/icons/stories.js | 52 --- packages/livechat/src/icons/stories.tsx | 57 +++ packages/livechat/src/routes/Chat/stories.js | 131 ------ packages/livechat/src/routes/Chat/stories.tsx | 91 ++++ .../src/routes/ChatFinished/stories.js | 31 -- .../src/routes/ChatFinished/stories.tsx | 34 ++ .../src/routes/GDPRAgreement/stories.js | 19 - .../src/routes/GDPRAgreement/stories.tsx | 27 ++ .../src/routes/LeaveMessage/stories.js | 43 -- .../src/routes/LeaveMessage/stories.tsx | 42 ++ .../livechat/src/routes/Register/stories.js | 106 ----- .../livechat/src/routes/Register/stories.tsx | 71 +++ .../src/routes/SwitchDepartment/stories.js | 63 --- .../src/routes/SwitchDepartment/stories.tsx | 48 ++ .../src/routes/TriggerMessage/stories.js | 56 --- .../src/routes/TriggerMessage/stories.tsx | 48 ++ yarn.lock | 417 ++++++------------ 124 files changed, 2972 insertions(+), 3335 deletions(-) create mode 100644 .changeset/strong-trains-enjoy.md create mode 100644 .yarn/patches/preact-npm-10.15.1-bd458de913.patch delete mode 100644 packages/livechat/src/components/Alert/stories.js create mode 100644 packages/livechat/src/components/Alert/stories.tsx delete mode 100644 packages/livechat/src/components/Avatar/stories.js create mode 100644 packages/livechat/src/components/Avatar/stories.tsx delete mode 100644 packages/livechat/src/components/Button/stories.js create mode 100644 packages/livechat/src/components/Button/stories.tsx delete mode 100644 packages/livechat/src/components/ButtonGroup/stories.js create mode 100644 packages/livechat/src/components/ButtonGroup/stories.tsx delete mode 100644 packages/livechat/src/components/Composer/stories.js create mode 100644 packages/livechat/src/components/Composer/stories.tsx delete mode 100644 packages/livechat/src/components/FilesDropTarget/stories.js create mode 100644 packages/livechat/src/components/FilesDropTarget/stories.tsx delete mode 100644 packages/livechat/src/components/Footer/stories.js create mode 100644 packages/livechat/src/components/Footer/stories.tsx delete mode 100644 packages/livechat/src/components/Form/DateInput/stories.js create mode 100644 packages/livechat/src/components/Form/DateInput/stories.tsx delete mode 100644 packages/livechat/src/components/Form/FormField/stories.js create mode 100644 packages/livechat/src/components/Form/FormField/stories.tsx delete mode 100644 packages/livechat/src/components/Form/PasswordInput/stories.js create mode 100644 packages/livechat/src/components/Form/PasswordInput/stories.tsx delete mode 100644 packages/livechat/src/components/Form/SelectInput/stories.js create mode 100644 packages/livechat/src/components/Form/SelectInput/stories.tsx delete mode 100644 packages/livechat/src/components/Form/TextInput/stories.js create mode 100644 packages/livechat/src/components/Form/TextInput/stories.tsx delete mode 100644 packages/livechat/src/components/Form/stories.js create mode 100644 packages/livechat/src/components/Form/stories.tsx delete mode 100644 packages/livechat/src/components/Header/stories.js create mode 100644 packages/livechat/src/components/Header/stories.tsx create mode 100644 packages/livechat/src/components/Menu/Group.stories.tsx create mode 100644 packages/livechat/src/components/Menu/Item.stories.tsx create mode 100644 packages/livechat/src/components/Menu/Menu.stories.tsx create mode 100644 packages/livechat/src/components/Menu/PopoverMenu.stories.tsx delete mode 100644 packages/livechat/src/components/Menu/stories.js delete mode 100644 packages/livechat/src/components/Messages/AudioAttachment/stories.js create mode 100644 packages/livechat/src/components/Messages/AudioAttachment/stories.tsx delete mode 100644 packages/livechat/src/components/Messages/FileAttachment/stories.js create mode 100644 packages/livechat/src/components/Messages/FileAttachment/stories.tsx delete mode 100644 packages/livechat/src/components/Messages/ImageAttachment/stories.js create mode 100644 packages/livechat/src/components/Messages/ImageAttachment/stories.tsx rename packages/livechat/src/components/Messages/Message/{stories.js => stories.tsx} (79%) delete mode 100644 packages/livechat/src/components/Messages/MessageAvatars/stories.js create mode 100644 packages/livechat/src/components/Messages/MessageAvatars/stories.tsx rename packages/livechat/src/components/Messages/MessageBlocks/{stories.js => stories.tsx} (96%) delete mode 100644 packages/livechat/src/components/Messages/MessageBubble/stories.js create mode 100644 packages/livechat/src/components/Messages/MessageBubble/stories.tsx delete mode 100644 packages/livechat/src/components/Messages/MessageList/stories.js create mode 100644 packages/livechat/src/components/Messages/MessageList/stories.tsx delete mode 100644 packages/livechat/src/components/Messages/MessageSeparator/stories.js create mode 100644 packages/livechat/src/components/Messages/MessageSeparator/stories.tsx delete mode 100644 packages/livechat/src/components/Messages/MessageTime/stories.js create mode 100644 packages/livechat/src/components/Messages/MessageTime/stories.tsx delete mode 100644 packages/livechat/src/components/Messages/TypingDots/stories.js create mode 100644 packages/livechat/src/components/Messages/TypingDots/stories.tsx delete mode 100644 packages/livechat/src/components/Messages/TypingIndicator/stories.js create mode 100644 packages/livechat/src/components/Messages/TypingIndicator/stories.tsx delete mode 100644 packages/livechat/src/components/Messages/VideoAttachment/stories.js create mode 100644 packages/livechat/src/components/Messages/VideoAttachment/stories.tsx rename packages/livechat/src/components/Messages/{constants.js => constants.ts} (100%) delete mode 100644 packages/livechat/src/components/Modal/stories.js create mode 100644 packages/livechat/src/components/Modal/stories.tsx delete mode 100644 packages/livechat/src/components/Popover/stories.js create mode 100644 packages/livechat/src/components/Popover/stories.tsx create mode 100644 packages/livechat/src/components/Screen/Footer.stories.tsx delete mode 100644 packages/livechat/src/components/Screen/stories.js create mode 100644 packages/livechat/src/components/Screen/stories.tsx delete mode 100644 packages/livechat/src/components/Sound/stories.js create mode 100644 packages/livechat/src/components/Sound/stories.tsx delete mode 100644 packages/livechat/src/components/Tooltip/stories.js create mode 100644 packages/livechat/src/components/Tooltip/stories.tsx rename packages/livechat/src/components/uiKit/message/{actions.stories.js => ActionsBlock.stories.tsx} (97%) rename packages/livechat/src/components/uiKit/message/ButtonElement/{stories.js => stories.tsx} (75%) rename packages/livechat/src/components/uiKit/message/{context.stories.js => ContextBlock.stories.tsx} (92%) rename packages/livechat/src/components/uiKit/message/{divider.stories.js => DividerBlock.stories.tsx} (85%) rename packages/livechat/src/components/uiKit/message/{image.stories.js => ImageBlock.stories.tsx} (85%) rename packages/livechat/src/components/uiKit/message/{section.stories.js => SectionBlock.stories.tsx} (96%) rename packages/livechat/src/components/uiKit/message/{index.js => index.tsx} (75%) delete mode 100644 packages/livechat/src/icons/stories.js create mode 100644 packages/livechat/src/icons/stories.tsx delete mode 100644 packages/livechat/src/routes/Chat/stories.js create mode 100644 packages/livechat/src/routes/Chat/stories.tsx delete mode 100644 packages/livechat/src/routes/ChatFinished/stories.js create mode 100644 packages/livechat/src/routes/ChatFinished/stories.tsx delete mode 100644 packages/livechat/src/routes/GDPRAgreement/stories.js create mode 100644 packages/livechat/src/routes/GDPRAgreement/stories.tsx delete mode 100644 packages/livechat/src/routes/LeaveMessage/stories.js create mode 100644 packages/livechat/src/routes/LeaveMessage/stories.tsx delete mode 100644 packages/livechat/src/routes/Register/stories.js create mode 100644 packages/livechat/src/routes/Register/stories.tsx delete mode 100644 packages/livechat/src/routes/SwitchDepartment/stories.js create mode 100644 packages/livechat/src/routes/SwitchDepartment/stories.tsx delete mode 100644 packages/livechat/src/routes/TriggerMessage/stories.js create mode 100644 packages/livechat/src/routes/TriggerMessage/stories.tsx diff --git a/.changeset/strong-trains-enjoy.md b/.changeset/strong-trains-enjoy.md new file mode 100644 index 0000000000000..91aa7f417cb10 --- /dev/null +++ b/.changeset/strong-trains-enjoy.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/livechat': patch +--- + +Storybook adjustments, TypeScript migration, and minor fixes diff --git a/.yarn/patches/preact-npm-10.15.1-bd458de913.patch b/.yarn/patches/preact-npm-10.15.1-bd458de913.patch new file mode 100644 index 0000000000000..b7e38e6e3b827 --- /dev/null +++ b/.yarn/patches/preact-npm-10.15.1-bd458de913.patch @@ -0,0 +1,13 @@ +diff --git a/src/index.d.ts b/src/index.d.ts +index c57fcb6f6f3734d182313c324de9b28e6dcee68d..b7602c71e9f4b4254f4056fb598ef72ab157c921 100644 +--- a/src/index.d.ts ++++ b/src/index.d.ts +@@ -110,7 +110,7 @@ export interface ComponentConstructor

+ // Type alias for a component instance considered generally, whether stateless or stateful. + export type AnyComponent

= + | FunctionComponent

+- | Component; ++ | ComponentConstructor; + + export interface Component

{ + componentWillMount?(): void; diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts index a20ccf05006a7..e3ed090689c5f 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts @@ -36,7 +36,7 @@ test.describe('omnichannel-auto-onhold-chat-closing', () => { await agent.page.close(); }); - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ page, api }) => { // make "user-1" online await agent.poHomeChannel.sidenav.switchStatus('online'); @@ -45,9 +45,9 @@ test.describe('omnichannel-auto-onhold-chat-closing', () => { name: faker.person.firstName(), email: faker.internet.email(), }; - poLiveChat = new OmnichannelLiveChat(page); + poLiveChat = new OmnichannelLiveChat(page, api); await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newVisitor, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_user'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-transfer-unanswered-chat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-transfer-unanswered-chat.spec.ts index 67bef3dbac527..a1620f3d70c7e 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-transfer-unanswered-chat.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-transfer-unanswered-chat.spec.ts @@ -42,7 +42,7 @@ test.describe('omnichannel-auto-transfer-unanswered-chat', () => { await agent2.page.close(); }); - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ page, api }) => { // make "user-1" online await agent1.poHomeChannel.sidenav.switchOmnichannelStatus('online'); await agent2.poHomeChannel.sidenav.switchOmnichannelStatus('offline'); @@ -52,9 +52,9 @@ test.describe('omnichannel-auto-transfer-unanswered-chat', () => { name: faker.person.firstName(), email: faker.internet.email(), }; - poLiveChat = new OmnichannelLiveChat(page); + poLiveChat = new OmnichannelLiveChat(page, api); await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newVisitor, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_user'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-canned-responses-sidebar.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-canned-responses-sidebar.spec.ts index 4df5ee1dc8d13..b0c7c22491b6e 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-canned-responses-sidebar.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-canned-responses-sidebar.spec.ts @@ -28,8 +28,8 @@ test.describe('Omnichannel Canned Responses Sidebar', () => { const { page } = await createAuxContext(browser, Users.user1); agent = { page, poHomeChannel: new HomeChannel(page) }; }); - test.beforeEach(async ({ page }) => { - poLiveChat = new OmnichannelLiveChat(page); + test.beforeEach(async ({ page, api }) => { + poLiveChat = new OmnichannelLiveChat(page, api); }); test.afterAll(async ({ api }) => { @@ -41,7 +41,7 @@ test.describe('Omnichannel Canned Responses Sidebar', () => { test('Receiving a message from visitor', async ({ page }) => { await test.step('Expect send a message as a visitor', async () => { await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newUser, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-changing-room-priority-and-sla.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-changing-room-priority-and-sla.spec.ts index 042e43c5cc5ea..d4cc6484975e7 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-changing-room-priority-and-sla.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-changing-room-priority-and-sla.spec.ts @@ -57,14 +57,14 @@ test.describe('omnichannel-changing-room-priority-and-sla', () => { await agent.page.close(); }); - test('expect to initiate a new livechat conversation', async ({ page }) => { + test('expect to initiate a new livechat conversation', async ({ page, api }) => { newVisitor = { name: faker.person.firstName(), email: faker.internet.email(), }; - poLiveChat = new OmnichannelLiveChat(page); + poLiveChat = new OmnichannelLiveChat(page, api); await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newVisitor, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_user'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-chat-history.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-chat-history.spec.ts index 303285db35237..044d3df516f0a 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-chat-history.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-chat-history.spec.ts @@ -24,8 +24,8 @@ test.describe('Omnichannel chat histr', () => { const { page } = await createAuxContext(browser, Users.user1); agent = { page, poHomeOmnichannel: new HomeOmnichannel(page) }; }); - test.beforeEach(async ({ page }) => { - poLiveChat = new OmnichannelLiveChat(page); + test.beforeEach(async ({ page, api }) => { + poLiveChat = new OmnichannelLiveChat(page, api); }); test.afterAll(async ({ api }) => { @@ -37,7 +37,7 @@ test.describe('Omnichannel chat histr', () => { test('Receiving a message from visitor', async ({ page }) => { await test.step('Expect send a message as a visitor', async () => { await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newUser, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLiveChat.btnSendMessageToOnlineAgent.click(); @@ -56,7 +56,7 @@ test.describe('Omnichannel chat histr', () => { await test.step('Expect send a message as a visitor again to reopen chat', async () => { await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLiveChat.btnSendMessageToOnlineAgent.click(); }); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-close-chat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-close-chat.spec.ts index a69ccb1751f7f..75fb398ee1dec 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-close-chat.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-close-chat.spec.ts @@ -25,8 +25,8 @@ test.describe('Omnichannel close chat', () => { const { page } = await createAuxContext(browser, Users.user1); agent = { page, poHomeOmnichannel: new HomeOmnichannel(page) }; }); - test.beforeEach(async ({ page }) => { - poLiveChat = new OmnichannelLiveChat(page); + test.beforeEach(async ({ page, api }) => { + poLiveChat = new OmnichannelLiveChat(page, api); }); test.afterAll(async ({ api }) => { @@ -38,7 +38,7 @@ test.describe('Omnichannel close chat', () => { test('Receiving a message from visitor', async ({ page }) => { await test.step('Expect send a message as a visitor', async () => { await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newUser, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-close-inquiry.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-close-inquiry.spec.ts index f61da31329981..85c523c206636 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-close-inquiry.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-close-inquiry.spec.ts @@ -18,17 +18,15 @@ test.describe('Omnichannel close inquiry', () => { email: faker.internet.email(), }; - await Promise.all([ - await api.post('/livechat/users/manager', { username: 'user1' }), - await api.post('/livechat/users/agent', { username: 'user1' }), - await api.post('/settings/Livechat_Routing_Method', { value: 'Manual_Selection' }).then((res) => expect(res.status()).toBe(200)), - ]); + await api.post('/livechat/users/manager', { username: 'user1' }); + await api.post('/livechat/users/agent', { username: 'user1' }); + await api.post('/settings/Livechat_Routing_Method', { value: 'Manual_Selection' }).then((res) => expect(res.status()).toBe(200)); const { page } = await createAuxContext(browser, Users.user1); agent = { page, poHomeOmnichannel: new HomeOmnichannel(page) }; }); - test.beforeEach(async ({ page }) => { - poLiveChat = new OmnichannelLiveChat(page); + test.beforeEach(async ({ page, api }) => { + poLiveChat = new OmnichannelLiveChat(page, api); }); test.afterAll(async ({ api }) => { @@ -43,7 +41,7 @@ test.describe('Omnichannel close inquiry', () => { test('Receiving a message from visitor', async ({ page }) => { await test.step('Expect send a message as a visitor', async () => { await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newUser, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-info.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-info.spec.ts index 6f3daaffe964e..266df1849292c 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-info.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-info.spec.ts @@ -25,8 +25,8 @@ test.describe('Omnichannel contact info', () => { const { page } = await createAuxContext(browser, Users.user1); agent = { page, poHomeChannel: new HomeChannel(page) }; }); - test.beforeEach(async ({ page }) => { - poLiveChat = new OmnichannelLiveChat(page); + test.beforeEach(async ({ page, api }) => { + poLiveChat = new OmnichannelLiveChat(page, api); }); test.afterAll(async ({ api }) => { @@ -38,7 +38,7 @@ test.describe('Omnichannel contact info', () => { test('Receiving a message from visitor, and seeing its information', async ({ page }) => { await test.step('Expect send a message as a visitor', async () => { await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newUser, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts index 77ab9cf8d635e..c5250b1d1150e 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts @@ -21,7 +21,7 @@ test.describe('Livechat', () => { await expect(statusCode).toBe(200); page = await browser.newPage(); - poLiveChat = new OmnichannelLiveChat(page); + poLiveChat = new OmnichannelLiveChat(page, api); const { page: pageCtx } = await createAuxContext(browser, Users.user1); poAuxContext = { page: pageCtx, poHomeOmnichannel: new HomeOmnichannel(pageCtx) }; @@ -36,7 +36,7 @@ test.describe('Livechat', () => { test('Send message to online agent', async () => { await test.step('Expect message to be sent by livechat', async () => { - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newUser, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_user'); @@ -59,7 +59,7 @@ test.describe('Livechat', () => { }); await test.step('Expect when user minimizes the livechat screen, the composer should be hidden', async () => { - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await expect(page.locator('[contenteditable="true"]')).not.toBeVisible(); }); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-send-transcript.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-send-transcript.spec.ts index a1c15e44df947..032be52d14727 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-send-transcript.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-send-transcript.spec.ts @@ -25,8 +25,8 @@ test.describe('omnichannel-transcript', () => { const { page } = await createAuxContext(browser, Users.user1); agent = { page, poHomeChannel: new HomeChannel(page) }; }); - test.beforeEach(async ({ page }) => { - poLiveChat = new OmnichannelLiveChat(page); + test.beforeEach(async ({ page, api }) => { + poLiveChat = new OmnichannelLiveChat(page, api); }); test.afterAll(async ({ api }) => { @@ -38,7 +38,7 @@ test.describe('omnichannel-transcript', () => { test('Receiving a message from visitor', async ({ page }) => { await test.step('Expect send a message as a visitor', async () => { await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newUser, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts index 6314ee395f456..4738158ca23c2 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts @@ -33,7 +33,7 @@ test.describe('omnichannel-takeChat', () => { await agent.page.close(); }); - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ page, api }) => { // make "user-1" online await agent.poHomeChannel.sidenav.switchStatus('online'); @@ -42,9 +42,9 @@ test.describe('omnichannel-takeChat', () => { name: `${faker.person.firstName()} ${faker.string.uuid()}`, email: faker.internet.email(), }; - poLiveChat = new OmnichannelLiveChat(page); + poLiveChat = new OmnichannelLiveChat(page, api); await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newVisitor, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_user'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-transfer-to-another-agents.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-transfer-to-another-agents.spec.ts index 1a968253deffa..3c74065a9a84e 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-transfer-to-another-agents.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-transfer-to-another-agents.spec.ts @@ -38,7 +38,7 @@ test.describe('omnichannel-transfer-to-another-agent', () => { await agent1.page.close(); await agent2.page.close(); }); - test.beforeEach(async ({ page }) => { + test.beforeEach(async ({ page, api }) => { // make "user-1" online & "user-2" offline so that chat can be automatically routed to "user-1" await agent1.poHomeOmnichannel.sidenav.switchStatus('online'); await agent2.poHomeOmnichannel.sidenav.switchStatus('offline'); @@ -48,9 +48,9 @@ test.describe('omnichannel-transfer-to-another-agent', () => { name: faker.person.firstName(), email: faker.internet.email(), }; - poLiveChat = new OmnichannelLiveChat(page); + poLiveChat = new OmnichannelLiveChat(page, api); await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); await poLiveChat.sendMessage(newVisitor, false); await poLiveChat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLiveChat.btnSendMessageToOnlineAgent.click(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-triggers.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-triggers.spec.ts index f1a863d8fc9d3..d882296fae440 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-triggers.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-triggers.spec.ts @@ -32,8 +32,8 @@ test.describe.serial('omnichannel-triggers', () => { await page.locator('.main-content').waitFor(); }); - test.beforeEach(async ({ page }) => { - poLiveChat = new OmnichannelLiveChat(page); + test.beforeEach(async ({ page, api }) => { + poLiveChat = new OmnichannelLiveChat(page, api); }); test.afterAll(async ({ api }) => { @@ -55,7 +55,7 @@ test.describe.serial('omnichannel-triggers', () => { await test.step('expect triggers to be displaye on Livechat', async () => { await test.step('Expect send a message as a visitor', async () => { await page.goto('/livechat'); - await poLiveChat.btnOpenLiveChat('R').click(); + await poLiveChat.openLiveChat(); if (await page.locator('[type="button"] >> text="Chat now"').isVisible()) { await page.locator('[type="button"] >> text="Chat now"').click(); } diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts index 510028168013a..b8143f74f129e 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts @@ -1,9 +1,9 @@ -import type { Page, Locator } from '@playwright/test'; +import type { Page, Locator, APIResponse } from '@playwright/test'; export class OmnichannelLiveChat { private readonly page: Page; - constructor(page: Page) { + constructor(page: Page, private readonly api: { get(url: string): Promise }) { this.page = page; } @@ -11,6 +11,11 @@ export class OmnichannelLiveChat { return this.page.locator(`role=button[name="${label}"]`); } + async openLiveChat(): Promise { + const { value: siteName } = await (await this.api.get('/settings/Site_Name')).json(); + await this.btnOpenLiveChat(siteName).click(); + } + unreadMessagesBadge(count: number): Locator { const name = count === 1 ? `${count} unread message` : `${count} unread messages`; diff --git a/package.json b/package.json index d97fc268fee26..97f4e17804188 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ }, "resolutions": { "minimist": "1.2.6", - "adm-zip": "0.5.9" + "adm-zip": "0.5.9", + "preact@10.15.1": "patch:preact@npm:10.15.1#.yarn/patches/preact-npm-10.15.1-bd458de913.patch" } } diff --git a/packages/livechat/.storybook/main.js b/packages/livechat/.storybook/main.js index 7aa6ba647b1da..bae78807f8b4a 100644 --- a/packages/livechat/.storybook/main.js +++ b/packages/livechat/.storybook/main.js @@ -6,8 +6,7 @@ module.exports = { backgrounds: false, }, }, - '@storybook/addon-knobs', '@storybook/addon-postcss', ], - stories: ['../src/**/stories.js', '../src/**/story.js', '../src/**/*.stories.js', '../src/**/*.story.js'], + stories: ['../src/**/stories.{js,tsx}', '../src/**/story.{js,tsx}', '../src/**/*.stories.{js,tsx}', '../src/**/*.story.{js,tsx}'], }; diff --git a/packages/livechat/babel.config.js b/packages/livechat/babel.config.js index 37654a3a204c6..ce989051a52fa 100644 --- a/packages/livechat/babel.config.js +++ b/packages/livechat/babel.config.js @@ -1,32 +1,15 @@ module.exports = { - presets: [ - [ - '@babel/preset-env', - { - useBuiltIns: 'entry', - corejs: 3, - include: [ - '@babel/plugin-proposal-class-properties', - '@babel/plugin-proposal-optional-chaining', - '@babel/plugin-proposal-nullish-coalescing-operator', - ], - }, - ], - '@babel/preset-typescript', - ], + presets: [['@babel/preset-env', { loose: true }], '@babel/preset-typescript'], plugins: [ - ['@babel/plugin-transform-react-jsx', { pragma: 'h', pragmaFrag: 'Fragment' }], + ['@babel/plugin-transform-class-properties', { loose: true }], + ['@babel/plugin-transform-private-methods', { loose: true }], + ['@babel/plugin-transform-private-property-in-object', { loose: true }], [ - 'babel-plugin-jsx-pragmatic', + '@babel/plugin-transform-react-jsx', { - module: 'preact', - import: 'h', - export: 'h', + runtime: 'automatic', + importSource: 'preact', }, ], ], - assumptions: { - setPublicClassFields: true, - privateFieldsAsProperties: true, - }, }; diff --git a/packages/livechat/package.json b/packages/livechat/package.json index 26940f22c846d..775a44b6dad87 100644 --- a/packages/livechat/package.json +++ b/packages/livechat/package.json @@ -19,30 +19,26 @@ "lint": "run-s eslint stylelint", "eslint": "eslint --ext .js,.jsx,.ts,.tsx .", "stylelint": "stylelint 'src/**/*.scss'", - "storybook": "start-storybook -p 9001 -c .storybook", + "storybook": "start-storybook -p 9001 -c .storybook --no-version-updates", "build-storybook": "build-storybook", "typecheck": "tsc -p tsconfig.typecheck.json" }, "devDependencies": { "@babel/eslint-parser": "~7.22.5", "@babel/preset-env": "~7.22.5", + "@babel/preset-typescript": "~7.22.5", "@rocket.chat/ddp-client": "workspace:^", "@rocket.chat/eslint-config": "workspace:^", "@rocket.chat/fuselage-tokens": "next", "@rocket.chat/logo": "next", - "@storybook/addon-actions": "~6.5.16", - "@storybook/addon-backgrounds": "~6.5.16", "@storybook/addon-essentials": "~6.5.16", - "@storybook/addon-knobs": "~6.4.0", "@storybook/addon-postcss": "~2.0.0", - "@storybook/addon-viewport": "~6.5.16", - "@storybook/react": "~6.5.16", + "@storybook/preact": "~6.5.16", "@storybook/theming": "~6.5.16", "@typescript-eslint/eslint-plugin": "~5.60.0", "@typescript-eslint/parser": "~5.60.0", "autoprefixer": "^9.8.8", "babel-loader": "^8.3.0", - "babel-plugin-jsx-pragmatic": "^1.0.2", "cross-env": "^7.0.3", "css-loader": "^4.3.0", "cssnano": "^4.1.11", @@ -99,16 +95,15 @@ "markdown-it": "^11.0.1", "mem": "^6.1.1", "mitt": "^2.1.0", - "preact": "^10.8.2", + "preact": "10.15.1", "preact-router": "^3.2.1", "query-string": "^7.1.3", "react-i18next": "^11.16.9", "whatwg-fetch": "^3.6.2" }, "browserslist": [ - "> 1%", "last 2 versions", - "not ie < 11" + "Firefox ESR" ], "volta": { "extends": "../../package.json" diff --git a/packages/livechat/src/components/Alert/index.tsx b/packages/livechat/src/components/Alert/index.tsx index c68c88e5a58d9..fc568ae8022b1 100644 --- a/packages/livechat/src/components/Alert/index.tsx +++ b/packages/livechat/src/components/Alert/index.tsx @@ -1,3 +1,4 @@ +import type { ComponentChildren } from 'preact'; import { useCallback, useEffect } from 'preact/hooks'; import type { JSXInternal } from 'preact/src/jsx'; import { useTranslation } from 'react-i18next'; @@ -16,7 +17,7 @@ type AlertProps = { hideCloseButton?: boolean; className?: string; style?: JSXInternal.CSSProperties; - children?: JSXInternal.Element[]; + children?: ComponentChildren; timeout?: number; }; diff --git a/packages/livechat/src/components/Alert/stories.js b/packages/livechat/src/components/Alert/stories.js deleted file mode 100644 index 10dd0044696cb..0000000000000 --- a/packages/livechat/src/components/Alert/stories.js +++ /dev/null @@ -1,82 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, color, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import Alert from '.'; -import { screenCentered, loremIpsum } from '../../helpers.stories'; - -storiesOf('Components/Alert', module) - .addDecorator(withKnobs) - .addDecorator(screenCentered) - .add('default', () => ( - - {text('text', loremIpsum({ count: 3, units: 'words' }))} - - )) - .add('success', () => ( - - {text('text', loremIpsum({ count: 3, units: 'words' }))} - - )) - .add('warning', () => ( - - {text('text', loremIpsum({ count: 3, units: 'words' }))} - - )) - .add('error', () => ( - - {text('text', loremIpsum({ count: 3, units: 'words' }))} - - )) - .add('custom color', () => ( - - {text('text', loremIpsum({ count: 3, units: 'words' }))} - - )) - .add('with long text content', () => ( - - {text('text', loremIpsum({ count: 30, units: 'words' }))} - - )) - .add('without timeout', () => ( - - {text('text', loremIpsum({ count: 3, units: 'words' }))} - - )); diff --git a/packages/livechat/src/components/Alert/stories.tsx b/packages/livechat/src/components/Alert/stories.tsx new file mode 100644 index 0000000000000..6e63e98400eda --- /dev/null +++ b/packages/livechat/src/components/Alert/stories.tsx @@ -0,0 +1,66 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import Alert from '.'; +import { loremIpsum, screenDecorator } from '../../helpers.stories'; + +export default { + title: 'Components/Alert', + component: Alert, + args: { + success: false, + warning: false, + error: false, + color: '', + timeout: 5000, + onDismiss: action('dismiss'), + children: loremIpsum({ count: 3, units: 'words' }), + }, + parameters: { + layout: 'centered', + }, + decorators: [screenDecorator], +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; +Default.args = {}; + +export const Success = Template.bind({}); +Success.storyName = 'success'; +Success.args = { + success: true, +}; + +export const Warning = Template.bind({}); +Warning.storyName = 'warning'; +Warning.args = { + warning: true, +}; + +export const Error = Template.bind({}); +Error.storyName = 'error'; +Error.args = { + error: true, +}; + +export const CustomColor = Template.bind({}); +CustomColor.storyName = 'custom color'; +CustomColor.args = { + color: '#175CC4', +}; + +export const WithLongTextContent = Template.bind({}); +WithLongTextContent.storyName = 'with long text content'; +WithLongTextContent.args = { + children: loremIpsum({ count: 30, units: 'words' }), +}; + +export const WithoutTimeout = Template.bind({}); +WithoutTimeout.storyName = 'without timeout'; +WithoutTimeout.args = { + timeout: 0, +}; diff --git a/packages/livechat/src/components/Avatar/stories.js b/packages/livechat/src/components/Avatar/stories.js deleted file mode 100644 index 0cdb8730a026f..0000000000000 --- a/packages/livechat/src/components/Avatar/stories.js +++ /dev/null @@ -1,92 +0,0 @@ -import { withKnobs, boolean, text, select } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { Avatar } from '.'; -import { avatarResolver, centered } from '../../helpers.stories'; - -const defaultSrc = avatarResolver('guilherme.gazzo'); -const defaultDescription = 'user description'; -const statuses = [null, 'offline', 'away', 'busy', 'online']; - -storiesOf('Components/Avatar', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('default', () => ( - - )) - .add('large', () => ( - - )) - .add('small', () => ( - - )) - .add('as placeholder', () => ( -

- - - -
- )) - .add('with status indicator', () => ( -
- - - - -
- )); diff --git a/packages/livechat/src/components/Avatar/stories.tsx b/packages/livechat/src/components/Avatar/stories.tsx new file mode 100644 index 0000000000000..ac2d6bc6b9c98 --- /dev/null +++ b/packages/livechat/src/components/Avatar/stories.tsx @@ -0,0 +1,66 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { Avatar } from '.'; +import { gazzoAvatar } from '../../helpers.stories'; + +export default { + title: 'Components/Avatar', + component: Avatar, + args: { + src: gazzoAvatar, + description: 'user description', + status: null, + large: false, + }, + argTypes: { + status: { + control: { + type: 'select', + options: [null, 'offline', 'away', 'busy', 'online'], + }, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; + +export const Large = Template.bind({}); +Large.storyName = 'large'; +Large.args = { + large: true, +}; + +export const Small = Template.bind({}); +Small.storyName = 'small'; +Small.args = { + small: true, +}; + +export const AsPlaceholder: Story> = (args) => ( +
+ + + +
+); +AsPlaceholder.storyName = 'as placeholder'; +AsPlaceholder.args = { + src: '', +}; + +export const WithStatusIndicator: Story> = (args) => ( +
+ + + + +
+); +WithStatusIndicator.storyName = 'with status indicator'; diff --git a/packages/livechat/src/components/Button/index.tsx b/packages/livechat/src/components/Button/index.tsx index d7d957d23acf1..d774bc78b6ec8 100644 --- a/packages/livechat/src/components/Button/index.tsx +++ b/packages/livechat/src/components/Button/index.tsx @@ -1,3 +1,4 @@ +import type { ComponentChildren } from 'preact'; import type { CSSProperties } from 'preact/compat'; import type { JSXInternal } from 'preact/src/jsx'; import { useTranslation } from 'react-i18next'; @@ -9,6 +10,7 @@ const handleMouseUp: JSXInternal.EventHandler; + icon?: ComponentChildren; className?: string; style?: CSSProperties; - children?: JSXInternal.Element[]; img?: string; + onClick?: JSXInternal.MouseEventHandler; }; export const Button = ({ @@ -52,7 +53,7 @@ export const Button = ({ disabled={disabled} onClick={onClick} onMouseUp={handleMouseUp} - aria-label={icon && children ? children[0] : null} + aria-label={icon && Array.isArray(children) ? children[0] : children} className={createClassName( styles, 'button', diff --git a/packages/livechat/src/components/Button/stories.js b/packages/livechat/src/components/Button/stories.js deleted file mode 100644 index cfe5e09c9a450..0000000000000 --- a/packages/livechat/src/components/Button/stories.js +++ /dev/null @@ -1,211 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { Button } from '.'; -import { avatarResolver, centered } from '../../helpers.stories'; -import ChatIcon from '../../icons/chat.svg'; - -const defaultSrc = avatarResolver('guilherme.gazzo'); - -const defaultText = 'Powered by Rocket.Chat'; -const defaultBadge = 'badged'; - -storiesOf('Components/Button', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('normal', () => ( - - )) - .add('disabled', () => ( - - )) - .add('outline', () => ( - - )) - .add('nude', () => ( - - )) - .add('danger', () => ( - - )) - .add('secondary', () => ( - - )) - .add('stack', () => ( - - )) - .add('small', () => ( - - )) - .add('loading', () => ( - - )) - .add('with badge', () => ( - - )) - .add('with icon', () => ( - - )) - .add('transparent with background image', () => ( - - )); diff --git a/packages/livechat/src/components/Button/stories.tsx b/packages/livechat/src/components/Button/stories.tsx new file mode 100644 index 0000000000000..42bdee65166bf --- /dev/null +++ b/packages/livechat/src/components/Button/stories.tsx @@ -0,0 +1,112 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { Button } from '.'; +import { gazzoAvatar } from '../../helpers.stories'; +import ChatIcon from '../../icons/chat.svg'; + +const iconElement = ; + +export default { + title: 'components/Button', + component: Button, + args: { + disabled: false, + outline: false, + nude: false, + danger: false, + secondary: false, + stack: false, + small: false, + loading: false, + badge: undefined, + icon: null, + img: undefined, + children: 'Powered by Rocket.Chat', + onClick: action('clicked'), + }, + argTypes: { + icon: { + control: { + type: 'select', + options: [null, iconElement], + }, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => - - - - )) - .add('with buttons of different sizes', () => ( - - - - - - )) - .add('with only small buttons', () => ( - - - - - - )) - .add('with stacked buttons', () => ( - - - - - - )); diff --git a/packages/livechat/src/components/ButtonGroup/stories.tsx b/packages/livechat/src/components/ButtonGroup/stories.tsx new file mode 100644 index 0000000000000..91b516ed7cda7 --- /dev/null +++ b/packages/livechat/src/components/ButtonGroup/stories.tsx @@ -0,0 +1,62 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { ButtonGroup } from '.'; +import { Button } from '../Button'; + +/** @type {import('@storybook/preact').Meta>} */ +export default { + title: 'Components/ButtonGroup', + component: ButtonGroup, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +export const WithButtonsOfSameSize: Story> = (args) => ( + + + + + +); +WithButtonsOfSameSize.storyName = 'with buttons of same size'; + +export const WithButtonsOfDifferentSizes: Story> = (args) => ( + + + + + +); +WithButtonsOfDifferentSizes.storyName = 'with buttons of different sizes'; + +export const WithOnlySmallButtons: Story> = (args) => ( + + + + + +); +WithOnlySmallButtons.storyName = 'with only small buttons'; + +export const WithStackedButtons: Story> = (args) => ( + + + + + +); +WithStackedButtons.storyName = 'with stacked buttons'; diff --git a/packages/livechat/src/components/Composer/stories.js b/packages/livechat/src/components/Composer/stories.js deleted file mode 100644 index 39f5460fd334a..0000000000000 --- a/packages/livechat/src/components/Composer/stories.js +++ /dev/null @@ -1,89 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { Composer, ComposerActions, ComposerAction } from '.'; -import { centered } from '../../helpers.stories'; -import PlusIcon from '../../icons/plus.svg'; -import SendIcon from '../../icons/send.svg'; -import SmileIcon from '../../icons/smile.svg'; - -const centeredWithWidth = (storyFn, ...args) => centered(() =>
{storyFn()}
, ...args); - -const defaultPlaceholder = 'Insert your text here'; - -storiesOf('Components/Composer', module) - .addDecorator(centeredWithWidth) - .addDecorator(withKnobs({ escapeHTML: false })) - .add('default', () => ( - - )) - .add('connecting', () => ) - .add('with large placeholder', () => ( - - )) - .add('with plain text', () => ( - - )) - .add('with emojis', () => ( - - )) - .add('with mentions', () => ( - - )) - .add('with actions', () => ( - - - - - - - - - } - post={ - - - - - - } - onChange={action('change')} - onSubmit={action('submit')} - onUpload={action('upload')} - /> - )); diff --git a/packages/livechat/src/components/Composer/stories.tsx b/packages/livechat/src/components/Composer/stories.tsx new file mode 100644 index 0000000000000..0386602b50144 --- /dev/null +++ b/packages/livechat/src/components/Composer/stories.tsx @@ -0,0 +1,77 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { Composer, ComposerActions, ComposerAction } from '.'; +import PlusIcon from '../../icons/plus.svg'; +import SendIcon from '../../icons/send.svg'; +import SmileIcon from '../../icons/smile.svg'; + +const defaultPlaceholder = 'Insert your text here'; + +export default { + title: 'Components/Composer', + component: Composer, + args: { + value: '', + placeholder: 'Insert your text here', + onChange: action('change'), + onSubmit: action('submit'), + onUpload: action('upload'), + }, + decorators: [(storyFn) =>
{storyFn()}
], + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; + +export const WithLargePlaceholder = Template.bind({}); +WithLargePlaceholder.storyName = 'with large placeholder'; +WithLargePlaceholder.args = { + placeholder: new Array(5).fill(defaultPlaceholder).join(' '), +}; + +export const WithPlainText = Template.bind({}); +WithPlainText.storyName = 'with plain text'; +WithPlainText.args = { + value: 'Should I use & or &?', +}; + +export const WithEmojis = Template.bind({}); +WithEmojis.storyName = 'with emojis'; +WithEmojis.args = { + value: ":heart: :smile: :'(", +}; + +export const WithMentions = Template.bind({}); +WithMentions.storyName = 'with mentions'; +WithMentions.args = { + value: "@all, I'm @here with @user.", +}; + +export const WithActions = Template.bind({}); +WithActions.storyName = 'with actions'; +WithActions.args = { + pre: ( + + + + + + + + + ), + post: ( + + + + + + ), +}; diff --git a/packages/livechat/src/components/FilesDropTarget/stories.js b/packages/livechat/src/components/FilesDropTarget/stories.js deleted file mode 100644 index acd2a32a331b9..0000000000000 --- a/packages/livechat/src/components/FilesDropTarget/stories.js +++ /dev/null @@ -1,103 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, button, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { FilesDropTarget } from '.'; -import { centered } from '../../helpers.stories'; - -const DummyContent = () => ( -
- Drop files here - Or into this span -
-); - -storiesOf('Components/FilesDropTarget', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('default', () => ( - - - - )) - .add('overlayed', () => ( - - - - )) - .add('overlayed with text', () => ( - - - - )) - .add('accepting only images', () => ( - - - - )) - .add('accepting multiple', () => ( - - - - )) - .add('triggering browse action', () => { - let filesDropTarget; - - function handleRef(ref) { - filesDropTarget = ref; - } - - button('Browse for files', () => { - filesDropTarget.browse(); - }); - - return ( - - ); - }); diff --git a/packages/livechat/src/components/FilesDropTarget/stories.tsx b/packages/livechat/src/components/FilesDropTarget/stories.tsx new file mode 100644 index 0000000000000..e1a01cbaea09e --- /dev/null +++ b/packages/livechat/src/components/FilesDropTarget/stories.tsx @@ -0,0 +1,91 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; +import { createRef } from 'preact'; + +import { FilesDropTarget } from '.'; +import { Button } from '../Button'; + +const DummyContent = () => ( +
+ Drop files here + Or into this span +
+); + +export default { + title: 'Components/FilesDropTarget', + component: FilesDropTarget, + args: { + children: , + overlayed: false, + overlayText: '', + accept: '', + multiple: false, + onUpload: action('upload'), + }, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; +Default.args = {}; + +export const Overlayed = Template.bind({}); +Overlayed.storyName = 'overlayed'; +Overlayed.args = { + overlayed: true, +}; + +export const OverlayedWithText = Template.bind({}); +OverlayedWithText.storyName = 'overlayed with text'; +OverlayedWithText.args = { + overlayed: true, + overlayText: 'You can release your files now', +}; + +export const AcceptingOnlyImages = Template.bind({}); +AcceptingOnlyImages.storyName = 'accepting only images'; +AcceptingOnlyImages.args = { + accept: 'image/*', +}; + +export const AcceptingMultipleFiles = Template.bind({}); +AcceptingMultipleFiles.storyName = 'accepting multiple files'; +AcceptingMultipleFiles.args = { + multiple: true, +}; + +export const TriggeringBrowseAction = Template.bind({}); +TriggeringBrowseAction.storyName = 'triggering browse action'; +const ref = createRef(); +TriggeringBrowseAction.args = { + children: ( +
+ +
+ ), + ref, +}; diff --git a/packages/livechat/src/components/Footer/index.js b/packages/livechat/src/components/Footer/index.js index dd7270cd7ca40..78b023d5d8720 100644 --- a/packages/livechat/src/components/Footer/index.js +++ b/packages/livechat/src/components/Footer/index.js @@ -11,7 +11,7 @@ export const Footer = ({ children, className, ...props }) => ( ); -export const FooterContent = ({ children, className, ...props }) => ( +export const FooterContent = ({ children, className = undefined, ...props }) => (
{children}
diff --git a/packages/livechat/src/components/Footer/stories.js b/packages/livechat/src/components/Footer/stories.js deleted file mode 100644 index d9c813e4a26f7..0000000000000 --- a/packages/livechat/src/components/Footer/stories.js +++ /dev/null @@ -1,53 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { storiesOf } from '@storybook/react'; -import '../../i18next'; - -import { Footer, FooterContent, FooterOptions, PoweredBy } from '.'; -import ChangeIcon from '../../icons/change.svg'; -import FinishIcon from '../../icons/finish.svg'; -import RemoveIcon from '../../icons/remove.svg'; -import { Composer } from '../Composer'; -import Menu from '../Menu'; -import { PopoverContainer } from '../Popover'; - -const bottomWithPopoverContainer = (storyFn) => ( -
- -
- {storyFn()} - -
-); - -storiesOf('Components/Footer', module) - .addDecorator(bottomWithPopoverContainer) - .add('simple', () => ( -
- - - -
- )) - .add('with Composer and options', () => ( -
- - - - - - - - Change department - - - Forget/Remove my personal data - - - Finish this chat - - - - - -
- )); diff --git a/packages/livechat/src/components/Footer/stories.tsx b/packages/livechat/src/components/Footer/stories.tsx new file mode 100644 index 0000000000000..0f3a5e2f79619 --- /dev/null +++ b/packages/livechat/src/components/Footer/stories.tsx @@ -0,0 +1,65 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { Footer, FooterContent, FooterOptions, PoweredBy } from '.'; +import ChangeIcon from '../../icons/change.svg'; +import FinishIcon from '../../icons/finish.svg'; +import RemoveIcon from '../../icons/remove.svg'; +import { Composer } from '../Composer'; +import Menu from '../Menu'; +import { PopoverContainer } from '../Popover'; + +import '../../i18next'; + +export default { + title: 'Components/Footer', + component: Footer, + decorators: [ + (storyFn) => ( +
+ +
+ {storyFn()} + +
+ ), + ], + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta>; + +export const Simple: Story> = (args) => ( +
+ + + +
+); +Simple.storyName = 'simple'; + +export const WithComposerAndOptions: Story> = (args) => ( +
+ + + + + + + + Change department + + + Forget/Remove my personal data + + + Finish this chat + + + + + +
+); +WithComposerAndOptions.storyName = 'with Composer and options'; diff --git a/packages/livechat/src/components/Form/DateInput/stories.js b/packages/livechat/src/components/Form/DateInput/stories.js deleted file mode 100644 index 135cf6e3a3cf9..0000000000000 --- a/packages/livechat/src/components/Form/DateInput/stories.js +++ /dev/null @@ -1,87 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import DateInput from '.'; -import { Form, FormField } from '..'; - -storiesOf('Forms/DateInput', module) - .addDecorator(withKnobs) - .addParameters({ - layout: 'centered', - }) - .add('default', () => ( -
- - - -
- )) - .add('filled', () => ( -
- - - -
- )) - .add('disabled', () => ( -
- - - -
- )) - .add('small', () => ( -
- - - -
- )) - .add('with error', () => ( -
- - - -
- )); diff --git a/packages/livechat/src/components/Form/DateInput/stories.tsx b/packages/livechat/src/components/Form/DateInput/stories.tsx new file mode 100644 index 0000000000000..deeca2c4fad80 --- /dev/null +++ b/packages/livechat/src/components/Form/DateInput/stories.tsx @@ -0,0 +1,59 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import DateInput from '.'; +import { Form, FormField } from '..'; + +export default { + title: 'Forms/DateInput', + component: DateInput, + args: { + value: '', + placeholder: 'Placeholder', + disabled: false, + small: false, + error: false, + onChange: action('change'), + onInput: action('input'), + }, + decorators: [ + (storyFn) => ( +
+ {storyFn()} +
+ ), + ], + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; + +export const Filled = Template.bind({}); +Filled.storyName = 'filled'; +Filled.args = { + value: 'Value', +}; + +export const Disabled = Template.bind({}); +Disabled.storyName = 'disabled'; +Disabled.args = { + disabled: true, +}; + +export const Small = Template.bind({}); +Small.storyName = 'small'; +Small.args = { + small: true, +}; + +export const WithError = Template.bind({}); +WithError.storyName = 'with error'; +WithError.args = { + error: true, +}; diff --git a/packages/livechat/src/components/Form/FormField/index.js b/packages/livechat/src/components/Form/FormField/index.js index 52f6f0f83814a..ba85500462e6a 100644 --- a/packages/livechat/src/components/Form/FormField/index.js +++ b/packages/livechat/src/components/Form/FormField/index.js @@ -3,7 +3,7 @@ import { cloneElement } from 'preact'; import { createClassName } from '../../helpers'; import styles from './styles.scss'; -export const FormField = ({ required, label, description, error, className, style = {}, children }) => ( +export const FormField = ({ required = false, label = '', description = '', error = '', className = undefined, style = {}, children }) => (
- )); - -const centeredWithPopoverContainer = (storyFn, ...args) => ( -
- {centered(storyFn, ...args)} -
-); - -storiesOf('Components/Menu/PopoverMenu', module) - .addDecorator(withKnobs) - .addDecorator(centeredWithPopoverContainer) - .add('default', () => ( - } - > - - Reload - Delete... - - - )) - .add('with overlay', () => ( - } - > - - Reload - Delete... - - - )); -storiesOf('Components/Menu/Group', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('single', () => ( - - )) - .add('multiple', () => ( - - )) - .add('with title', () => ( - - )); -storiesOf('Components/Menu/Item', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('simple', () => ( - - )) - .add('primary', () => ( - - )) - .add('danger', () => ( - - )) - .add('disabled', () => ( - - )) - .add('with icon', () => ( - - )); diff --git a/packages/livechat/src/components/Messages/AudioAttachment/stories.js b/packages/livechat/src/components/Messages/AudioAttachment/stories.js deleted file mode 100644 index 1c1923bfcc1b2..0000000000000 --- a/packages/livechat/src/components/Messages/AudioAttachment/stories.js +++ /dev/null @@ -1,11 +0,0 @@ -import { withKnobs, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import AudioAttachment from '.'; -import sampleAudio from '../../../../.storybook/assets/sample-audio.mp3'; -import { centered } from '../../../helpers.stories'; - -storiesOf('Messages/AudioAttachment', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('default', () => ); diff --git a/packages/livechat/src/components/Messages/AudioAttachment/stories.tsx b/packages/livechat/src/components/Messages/AudioAttachment/stories.tsx new file mode 100644 index 0000000000000..a07b6671cc15d --- /dev/null +++ b/packages/livechat/src/components/Messages/AudioAttachment/stories.tsx @@ -0,0 +1,19 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import AudioAttachment from '.'; +import { sampleAudio } from '../../../helpers.stories'; + +export default { + title: 'Messages/AudioAttachment', + component: AudioAttachment, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +export const Default: Story> = (args) => ; +Default.storyName = 'default'; +Default.args = { + url: sampleAudio, +}; diff --git a/packages/livechat/src/components/Messages/FileAttachment/stories.js b/packages/livechat/src/components/Messages/FileAttachment/stories.js deleted file mode 100644 index c7e130b96df41..0000000000000 --- a/packages/livechat/src/components/Messages/FileAttachment/stories.js +++ /dev/null @@ -1,20 +0,0 @@ -import { withKnobs, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { FileAttachment } from '.'; -import { loremIpsum, centered } from '../../../helpers.stories'; - -const centeredWithWidth = (storyFn, ...args) => centered(() =>
{storyFn()}
, ...args); - -storiesOf('Messages/FileAttachment', module) - .addDecorator(centeredWithWidth) - .addDecorator(withKnobs) - .add('for pdf', () => ) - .add('for doc', () => ) - .add('for ppt', () => ) - .add('for xls', () => ) - .add('for zip', () => ) - .add('for unknown extension', () => ) - .add('with long title', () => ( - - )); diff --git a/packages/livechat/src/components/Messages/FileAttachment/stories.tsx b/packages/livechat/src/components/Messages/FileAttachment/stories.tsx new file mode 100644 index 0000000000000..d342d523b4e06 --- /dev/null +++ b/packages/livechat/src/components/Messages/FileAttachment/stories.tsx @@ -0,0 +1,62 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { FileAttachment } from '.'; +import { loremIpsum } from '../../../helpers.stories'; + +export default { + title: 'Messages/FileAttachment', + component: FileAttachment, + args: { + title: 'Untitled', + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const PDF = Template.bind({}); +PDF.storyName = 'for pdf'; +PDF.args = { + url: 'http://example.com/demo.pdf', +}; + +export const DOC = Template.bind({}); +DOC.storyName = 'for doc'; +DOC.args = { + url: 'http://example.com/demo.doc', +}; + +export const PPT = Template.bind({}); +PPT.storyName = 'for ppt'; +PPT.args = { + url: 'http://example.com/demo.ppt', +}; + +export const XLS = Template.bind({}); +XLS.storyName = 'for xls'; +XLS.args = { + url: 'http://example.com/demo.xls', +}; + +export const ZIP = Template.bind({}); +ZIP.storyName = 'for zip'; +ZIP.args = { + url: 'http://example.com/demo.zip', +}; + +export const UnknownExtension = Template.bind({}); +UnknownExtension.storyName = 'for unknown extension'; +UnknownExtension.args = { + url: 'http://example.com/demo.abc', +}; + +export const WithLongTitle = Template.bind({}); +WithLongTitle.storyName = 'with long title'; +WithLongTitle.args = { + title: loremIpsum({ count: 50, units: 'words' }), + url: 'http://example.com/demo.abc', +}; +WithLongTitle.decorators = [(storyFn) =>
{storyFn()}
]; diff --git a/packages/livechat/src/components/Messages/ImageAttachment/stories.js b/packages/livechat/src/components/Messages/ImageAttachment/stories.js deleted file mode 100644 index 538867d972ec3..0000000000000 --- a/packages/livechat/src/components/Messages/ImageAttachment/stories.js +++ /dev/null @@ -1,11 +0,0 @@ -import { withKnobs, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { ImageAttachment } from '.'; -import sampleImage from '../../../../.storybook/assets/sample-image.jpg'; -import { centered } from '../../../helpers.stories'; - -storiesOf('Messages/ImageAttachment', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('default', () => ); diff --git a/packages/livechat/src/components/Messages/ImageAttachment/stories.tsx b/packages/livechat/src/components/Messages/ImageAttachment/stories.tsx new file mode 100644 index 0000000000000..fc31727290765 --- /dev/null +++ b/packages/livechat/src/components/Messages/ImageAttachment/stories.tsx @@ -0,0 +1,19 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { ImageAttachment } from '.'; +import { sampleImage } from '../../../helpers.stories'; + +export default { + title: 'Messages/ImageAttachment', + component: ImageAttachment, + args: { + url: sampleImage, + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +export const Default: Story> = (args) => ; +Default.storyName = 'default'; diff --git a/packages/livechat/src/components/Messages/Message/stories.js b/packages/livechat/src/components/Messages/Message/stories.tsx similarity index 79% rename from packages/livechat/src/components/Messages/Message/stories.js rename to packages/livechat/src/components/Messages/Message/stories.tsx index 5237af9e03201..1b07dbf96f92c 100644 --- a/packages/livechat/src/components/Messages/Message/stories.js +++ b/packages/livechat/src/components/Messages/Message/stories.tsx @@ -1,8 +1,8 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + import Message from '.'; -import sampleAudio from '../../../../.storybook/assets/sample-audio.mp3'; -import sampleImage from '../../../../.storybook/assets/sample-image.jpg'; -import sampleVideo from '../../../../.storybook/assets/sample-video.mp4'; -import { attachmentResolver, avatarResolver, loremIpsum } from '../../../helpers.stories'; +import { attachmentResolver, avatarResolver, loremIpsum, sampleAudio, sampleImage, sampleVideo } from '../../../helpers.stories'; import { MESSAGE_TYPE_ROOM_NAME_CHANGED, MESSAGE_TYPE_USER_ADDED, @@ -14,18 +14,6 @@ import { MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY, } from '../constants'; -const messageTypes = { - NULL: null, - ROOM_NAME_CHANGED: MESSAGE_TYPE_ROOM_NAME_CHANGED, - USER_ADDED: MESSAGE_TYPE_USER_ADDED, - USER_REMOVED: MESSAGE_TYPE_USER_REMOVED, - USER_JOINED: MESSAGE_TYPE_USER_JOINED, - USER_LEFT: MESSAGE_TYPE_USER_LEFT, - WELCOME: MESSAGE_TYPE_WELCOME, - LIVECHAT_CLOSED: MESSAGE_TYPE_LIVECHAT_CLOSED, - TRANSFER_HISTORY: MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY, -}; - const defaultMessage = loremIpsum({ count: 1, units: 'sentences' }); const defaultMessageExtra = loremIpsum({ count: 1, units: 'sentences' }); @@ -75,7 +63,22 @@ export default { me: { control: 'boolean' }, compact: { control: 'boolean' }, msg: { control: 'text' }, - t: { control: { type: 'select', options: messageTypes } }, + t: { + control: { + type: 'select', + options: { + NULL: null, + ROOM_NAME_CHANGED: MESSAGE_TYPE_ROOM_NAME_CHANGED, + USER_ADDED: MESSAGE_TYPE_USER_ADDED, + USER_REMOVED: MESSAGE_TYPE_USER_REMOVED, + USER_JOINED: MESSAGE_TYPE_USER_JOINED, + USER_LEFT: MESSAGE_TYPE_USER_LEFT, + WELCOME: MESSAGE_TYPE_WELCOME, + LIVECHAT_CLOSED: MESSAGE_TYPE_LIVECHAT_CLOSED, + TRANSFER_HISTORY: MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY, + }, + }, + }, u: { control: 'object' }, ts: { control: 'date' }, attachments: { control: 'object' }, @@ -91,9 +94,9 @@ export default { attachments: [], blocks: [], }, -}; +} satisfies Meta>; -export const Default = (args) => ( +export const Default: Story> = (args) => ( ( ); Default.storyName = 'default'; -export const System = (args) => ( +export const System: Story> = (args) => ( ( +export const Me: Story> = (args) => ( ( +export const Markdown: Story> = (args) => ( ( +export const Grouping: Story> = (args) => (
( +export const WithQuotation: Story> = (args) => ( ( +export const WithAudioAttachment: Story> = (args) => ( ( +export const WithVideoAttachment: Story> = (args) => ( ( +export const WithImageAttachment: Story> = (args) => ( ( +export const WithFilesAttachments: Story> = (args) => ( ( +export const WithMultipleAttachments: Story> = (args) => ( ( +export const WithUiKitBlocks: Story> = (args) => ( ) - .add('with one avatar', () => ) - .add('with two avatars', () => ( - - )) - .add('with three avatars', () => ( - - )) - .add('with name as avatar instead of username for guests', () => ( - - )); diff --git a/packages/livechat/src/components/Messages/MessageAvatars/stories.tsx b/packages/livechat/src/components/Messages/MessageAvatars/stories.tsx new file mode 100644 index 0000000000000..957df00a7f670 --- /dev/null +++ b/packages/livechat/src/components/Messages/MessageAvatars/stories.tsx @@ -0,0 +1,46 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { MessageAvatars } from '.'; +import { avatarResolver } from '../../../helpers.stories'; + +export default { + title: 'Messages/MessageAvatars', + component: MessageAvatars, + args: { + avatarResolver, + usernames: [], + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Empty = Template.bind({}); +Empty.storyName = 'empty'; + +export const WithOneAvatar = Template.bind({}); +WithOneAvatar.storyName = 'with one avatar'; +WithOneAvatar.args = { + usernames: ['guilherme.gazzo'], +}; + +export const WithTwoAvatars = Template.bind({}); +WithTwoAvatars.storyName = 'with two avatars'; +WithTwoAvatars.args = { + usernames: ['guilherme.gazzo', 'tasso.evangelista'], +}; + +export const WithThreeAvatars = Template.bind({}); +WithThreeAvatars.storyName = 'with three avatars'; +WithThreeAvatars.args = { + usernames: ['guilherme.gazzo', 'tasso.evangelista', 'martin.schoeler'], +}; + +export const WithNameAsAvatarInsteadOfUsernameForGuests = Template.bind({}); +WithNameAsAvatarInsteadOfUsernameForGuests.storyName = 'with name as avatar instead of username for guests'; +WithNameAsAvatarInsteadOfUsernameForGuests.args = { + usernames: ['livechat guest'], +}; diff --git a/packages/livechat/src/components/Messages/MessageBlocks/index.js b/packages/livechat/src/components/Messages/MessageBlocks/index.js index 5078155c2d335..5faa4c50d1ee3 100644 --- a/packages/livechat/src/components/Messages/MessageBlocks/index.js +++ b/packages/livechat/src/components/Messages/MessageBlocks/index.js @@ -6,7 +6,7 @@ import { renderMessageBlocks } from '../../uiKit'; import Surface from '../../uiKit/message/Surface'; import styles from './styles.scss'; -const MessageBlocks = ({ blocks = [], mid, rid }) => { +const MessageBlocks = ({ blocks = [], mid = undefined, rid = undefined }) => { const dispatchAction = useCallback( ({ appId, actionId, payload }) => triggerAction({ diff --git a/packages/livechat/src/components/Messages/MessageBlocks/stories.js b/packages/livechat/src/components/Messages/MessageBlocks/stories.tsx similarity index 96% rename from packages/livechat/src/components/Messages/MessageBlocks/stories.js rename to packages/livechat/src/components/Messages/MessageBlocks/stories.tsx index df201dc4c6b9b..ee534155bb4e5 100644 --- a/packages/livechat/src/components/Messages/MessageBlocks/stories.js +++ b/packages/livechat/src/components/Messages/MessageBlocks/stories.tsx @@ -1,6 +1,8 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + import MessageBlocks from '.'; -import accessoryImage from '../../../../.storybook/assets/accessoryImage.png'; -import imageBlock from '../../../../.storybook/assets/imageBlock.png'; +import { accessoryImage, imageBlock } from '../../../helpers.stories'; import { PopoverContainer } from '../../Popover'; export default { @@ -9,9 +11,9 @@ export default { layout: 'fullscreen', }, decorators: [(storyFn) => ], -}; +} satisfies Meta>; -export const WithBlocks = () => ( +export const WithBlocks: Story> = () => ( ( - - {text} - - )) - .add('inverse', () => ( - - {text} - - )) - .add('nude', () => ( - - {text} - - )) - .add('quoted', () => ( - - {text} - - )); diff --git a/packages/livechat/src/components/Messages/MessageBubble/stories.tsx b/packages/livechat/src/components/Messages/MessageBubble/stories.tsx new file mode 100644 index 0000000000000..34ccf2523962e --- /dev/null +++ b/packages/livechat/src/components/Messages/MessageBubble/stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { MessageBubble } from '.'; +import { loremIpsum } from '../../../helpers.stories'; + +const text = loremIpsum({ count: 1, units: 'sentences' }); + +export default { + title: 'Messages/MessageBubble', + component: MessageBubble, + args: { + children: text, + inverse: false, + nude: false, + quoted: false, + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; + +export const Inverse = Template.bind({}); +Inverse.storyName = 'inverse'; +Inverse.args = { + inverse: true, +}; + +export const Nude = Template.bind({}); +Nude.storyName = 'nude'; +Nude.args = { + nude: true, +}; + +export const Quoted = Template.bind({}); +Quoted.storyName = 'quoted'; +Quoted.args = { + quoted: true, +}; diff --git a/packages/livechat/src/components/Messages/MessageList/stories.js b/packages/livechat/src/components/Messages/MessageList/stories.js deleted file mode 100644 index 8a8ffee38b3b3..0000000000000 --- a/packages/livechat/src/components/Messages/MessageList/stories.js +++ /dev/null @@ -1,107 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, number, object } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { MessageList } from '.'; -import { avatarResolver, loremIpsum, centered } from '../../../helpers.stories'; -import { MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY } from '../constants'; - -const fittingScreen = (storyFn, ...args) => - centered(() =>
{storyFn()}
, ...args); - -const now = new Date(Date.parse('2021-01-01T00:00:00.000Z')); - -const users = [ - { - _id: 1, - username: 'tasso.evangelista', - }, - { - _id: 2, - username: 'guilherme.gazzo', - }, - { - _id: 3, - username: 'martin.schoeler', - }, -]; - -const messages = new Array(10); -for (let i = 0; i < messages.length; ++i) { - messages[i] = { - _id: i + 1, - u: users[i % users.length], - msg: loremIpsum({ count: 1, units: 'sentences' }), - ts: new Date(now.getTime() - (15 - i) * 60000).toISOString(), - }; -} - -storiesOf('Messages/MessageList', module) - .addDecorator(fittingScreen) - .addDecorator(withKnobs) - .add('normal', () => ( - - )) - .add('with system message', () => ( - - )) - .add('with hidden agent info system message', () => ( - - )) - .add('with typing users', () => ( - - )); diff --git a/packages/livechat/src/components/Messages/MessageList/stories.tsx b/packages/livechat/src/components/Messages/MessageList/stories.tsx new file mode 100644 index 0000000000000..f1b46a89e84e2 --- /dev/null +++ b/packages/livechat/src/components/Messages/MessageList/stories.tsx @@ -0,0 +1,99 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { MessageList } from '.'; +import { avatarResolver, loremIpsum } from '../../../helpers.stories'; +import { MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY } from '../constants'; + +const now = new Date(Date.parse('2021-01-01T00:00:00.000Z')); + +const users = [ + { + _id: 1, + username: 'tasso.evangelista', + }, + { + _id: 2, + username: 'guilherme.gazzo', + }, + { + _id: 3, + username: 'martin.schoeler', + }, +]; + +const messages = new Array(10); +for (let i = 0; i < messages.length; ++i) { + messages[i] = { + _id: i + 1, + u: users[i % users.length], + msg: loremIpsum({ count: 1, units: 'sentences' }), + ts: new Date(now.getTime() - (15 - i) * 60000).toISOString(), + }; +} + +export default { + title: 'Messages/MessageList', + component: MessageList, + args: { + messages, + uid: 1, + avatarResolver, + lastReadMessageId: 7, + typingUsernames: [], + onScrollTo: action('scrollTo'), + }, + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Normal = Template.bind({}); +Normal.storyName = 'normal'; + +export const WithSystemMessage = Template.bind({}); +WithSystemMessage.storyName = 'with system message'; +WithSystemMessage.args = { + messages: [ + ...messages, + { + msg: '', + ts: now.toISOString(), + t: MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY, + transferData: { + transferredBy: users[0], + scope: 'queue', + }, + u: users[0], + _id: 'AGiTzCjYyaypDxpDm', + }, + ], +}; + +export const WithHiddenAgentInfoSystemMessage = Template.bind({}); +WithHiddenAgentInfoSystemMessage.storyName = 'with hidden agent info system message'; +WithHiddenAgentInfoSystemMessage.args = { + messages: [ + ...messages, + { + msg: '', + t: MESSAGE_TYPE_LIVECHAT_TRANSFER_HISTORY, + transferData: { + transferredBy: { ...users[0], username: undefined }, + scope: 'queue', + }, + ts: now.toISOString(), + u: { ...users[0], username: undefined }, + _id: 'AGiTzCjYyaypDxpDm', + }, + ], +}; + +export const WithTypingUsers = Template.bind({}); +WithTypingUsers.storyName = 'with typing users'; +WithTypingUsers.args = { + typingUsernames: [users[1].username, users[2].username], +}; diff --git a/packages/livechat/src/components/Messages/MessageSeparator/stories.js b/packages/livechat/src/components/Messages/MessageSeparator/stories.js deleted file mode 100644 index 7a3dac28d07ca..0000000000000 --- a/packages/livechat/src/components/Messages/MessageSeparator/stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import { withKnobs, boolean, date } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import MessageSeparator from '.'; -import { centered } from '../../../helpers.stories'; - -const defaultDate = new Date(Date.parse('2021-01-01T00:00:00.000Z')); - -storiesOf('Messages/MessageSeparator', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('default', () => ) - .add('date', () => ) - .add('unread', () => ); diff --git a/packages/livechat/src/components/Messages/MessageSeparator/stories.tsx b/packages/livechat/src/components/Messages/MessageSeparator/stories.tsx new file mode 100644 index 0000000000000..d55d85f745d4a --- /dev/null +++ b/packages/livechat/src/components/Messages/MessageSeparator/stories.tsx @@ -0,0 +1,33 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import MessageSeparator from '.'; + +export default { + title: 'Messages/MessageSeparator', + component: MessageSeparator, + args: { + date: null, + unread: false, + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; + +export const _Date = Template.bind({}); +_Date.storyName = 'date'; +_Date.args = { + date: new Date(Date.parse('2021-01-01T00:00:00.000Z')), +}; + +export const Unread = Template.bind({}); +Unread.storyName = 'unread'; +Unread.args = { + unread: true, +}; diff --git a/packages/livechat/src/components/Messages/MessageTime/stories.js b/packages/livechat/src/components/Messages/MessageTime/stories.js deleted file mode 100644 index 84eb51a263fc5..0000000000000 --- a/packages/livechat/src/components/Messages/MessageTime/stories.js +++ /dev/null @@ -1,14 +0,0 @@ -import { withKnobs, date } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import MessageTime from '.'; -import { centered } from '../../../helpers.stories'; - -const today = new Date(Date.parse('2021-01-01T00:00:00.000Z')); -const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000); - -storiesOf('Messages/MessageTime', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('today', () => ) - .add('yesterday', () => ); diff --git a/packages/livechat/src/components/Messages/MessageTime/stories.tsx b/packages/livechat/src/components/Messages/MessageTime/stories.tsx new file mode 100644 index 0000000000000..7a3cbf571d8a1 --- /dev/null +++ b/packages/livechat/src/components/Messages/MessageTime/stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import MessageTime from '.'; + +const today = new Date(Date.parse('2021-01-01T00:00:00.000Z')); +const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000); + +export default { + title: 'Messages/MessageTime', + component: MessageTime, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Today = Template.bind({}); +Today.storyName = 'today'; +Today.args = { + ts: today, +}; + +export const Yesterday = Template.bind({}); +Yesterday.storyName = 'yesterday'; +Yesterday.args = { + ts: yesterday, +}; diff --git a/packages/livechat/src/components/Messages/TypingDots/stories.js b/packages/livechat/src/components/Messages/TypingDots/stories.js deleted file mode 100644 index c006b6ec66207..0000000000000 --- a/packages/livechat/src/components/Messages/TypingDots/stories.js +++ /dev/null @@ -1,10 +0,0 @@ -import { withKnobs, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { TypingDots } from '.'; -import { centered } from '../../../helpers.stories'; - -storiesOf('Messages/TypingDots', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('default', () => ); diff --git a/packages/livechat/src/components/Messages/TypingDots/stories.tsx b/packages/livechat/src/components/Messages/TypingDots/stories.tsx new file mode 100644 index 0000000000000..f86e8a8904e45 --- /dev/null +++ b/packages/livechat/src/components/Messages/TypingDots/stories.tsx @@ -0,0 +1,20 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { TypingDots } from '.'; + +export default { + title: 'Messages/TypingDots', + component: TypingDots, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; +Default.args = { + text: 'The attendant is typing', +}; diff --git a/packages/livechat/src/components/Messages/TypingIndicator/stories.js b/packages/livechat/src/components/Messages/TypingIndicator/stories.js deleted file mode 100644 index 7ee4da241d376..0000000000000 --- a/packages/livechat/src/components/Messages/TypingIndicator/stories.js +++ /dev/null @@ -1,19 +0,0 @@ -import { withKnobs, object, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { TypingIndicator } from '.'; -import { avatarResolver, centered } from '../../../helpers.stories'; - -storiesOf('Messages/TypingIndicator', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('default', () => ( - - )) - .add('with avatars', () => ( - - )); diff --git a/packages/livechat/src/components/Messages/TypingIndicator/stories.tsx b/packages/livechat/src/components/Messages/TypingIndicator/stories.tsx new file mode 100644 index 0000000000000..68a0c377565ea --- /dev/null +++ b/packages/livechat/src/components/Messages/TypingIndicator/stories.tsx @@ -0,0 +1,29 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { TypingIndicator } from '.'; +import { avatarResolver } from '../../../helpers.stories'; + +export default { + title: 'Messages/TypingIndicator', + component: TypingIndicator, + args: { + avatarResolver, + usernames: [], + text: 'The attendant is typing', + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; + +export const WithAvatars = Template.bind({}); +WithAvatars.storyName = 'with avatars'; +WithAvatars.args = { + usernames: ['guilherme.gazzo', 'tasso.evangelista', 'martin.schoeler'], +}; diff --git a/packages/livechat/src/components/Messages/VideoAttachment/stories.js b/packages/livechat/src/components/Messages/VideoAttachment/stories.js deleted file mode 100644 index c13ae1ef7c41b..0000000000000 --- a/packages/livechat/src/components/Messages/VideoAttachment/stories.js +++ /dev/null @@ -1,11 +0,0 @@ -import { withKnobs, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import VideoAttachment from '.'; -import sampleVideo from '../../../../.storybook/assets/sample-video.mp4'; -import { centered } from '../../../helpers.stories'; - -storiesOf('Messages/VideoAttachment', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('default', () => ); diff --git a/packages/livechat/src/components/Messages/VideoAttachment/stories.tsx b/packages/livechat/src/components/Messages/VideoAttachment/stories.tsx new file mode 100644 index 0000000000000..d6c54fe6ef746 --- /dev/null +++ b/packages/livechat/src/components/Messages/VideoAttachment/stories.tsx @@ -0,0 +1,21 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import VideoAttachment from '.'; +import { sampleVideo } from '../../../helpers.stories'; + +export default { + title: 'Messages/VideoAttachment', + component: VideoAttachment, + args: { + url: sampleVideo, + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Default = Template.bind({}); +Default.storyName = 'default'; diff --git a/packages/livechat/src/components/Messages/constants.js b/packages/livechat/src/components/Messages/constants.ts similarity index 100% rename from packages/livechat/src/components/Messages/constants.js rename to packages/livechat/src/components/Messages/constants.ts diff --git a/packages/livechat/src/components/Modal/stories.js b/packages/livechat/src/components/Modal/stories.js deleted file mode 100644 index 4bb52a71de966..0000000000000 --- a/packages/livechat/src/components/Modal/stories.js +++ /dev/null @@ -1,75 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, number, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { loremIpsum, centered } from '../../helpers.stories'; -import Modal from './component'; - -const LoremIpsum = ({ padding = '5rem', count = 5, units = 'paragraphs', ...options }) => ( -
- {loremIpsum({ count, units, ...options }) - .split('\n') - .map((paragraph) => ( -

{paragraph}

- ))} -
-); - -storiesOf('Components/Modal', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('normal', () => ( -
- - - {text('content', loremIpsum({ count: 1, units: 'paragraphs' }))} - -
- )) - .add('animated', () => ( -
- - - {text('content', loremIpsum({ count: 1, units: 'paragraphs' }))} - -
- )) - .add('timeout', () => ( -
- - - {text('content', loremIpsum({ count: 1, units: 'paragraphs' }))} - -
- )) - .add('disallow dismiss by overlay', () => ( -
- - - {text('content', loremIpsum({ count: 1, units: 'paragraphs' }))} - -
- )) - .add('confirm', () => ( -
- - -
- )) - .add('alert', () => ( -
- - -
- )); diff --git a/packages/livechat/src/components/Modal/stories.tsx b/packages/livechat/src/components/Modal/stories.tsx new file mode 100644 index 0000000000000..7af9aeb6605ea --- /dev/null +++ b/packages/livechat/src/components/Modal/stories.tsx @@ -0,0 +1,83 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { loremIpsum } from '../../helpers.stories'; +import Modal from './component'; + +export default { + title: 'Components/Modal', + decorators: [ + (storyFn) => ( +
+
+ {loremIpsum({ count: 5, units: 'paragraphs' }) + .split('\n') + .map((paragraph, i) => ( +

{paragraph}

+ ))} +
+ {storyFn()} +
+ ), + ], + parameters: { + layout: 'centered', + }, +} satisfies Meta; + +export const Normal: Story> = (args) => ; +Normal.storyName = 'normal'; +Normal.args = { + children: loremIpsum({ count: 1, units: 'paragraphs' }), + open: true, + animated: false, + onDismiss: action('dismiss'), +}; + +export const Animated: Story> = (args) => ; +Animated.storyName = 'animated'; +Animated.args = { + children: loremIpsum({ count: 1, units: 'paragraphs' }), + open: true, + animated: true, + onDismiss: action('dismiss'), +}; + +export const Timeout: Story> = (args) => ; +Timeout.storyName = 'timeout'; +Timeout.args = { + children: loremIpsum({ count: 1, units: 'paragraphs' }), + open: true, + animated: false, + timeout: 3000, + onDismiss: action('dismiss'), +}; + +export const DisallowDismissByOverlay: Story> = (args) => ; +DisallowDismissByOverlay.storyName = 'disallow dismiss by overlay'; +DisallowDismissByOverlay.args = { + children: loremIpsum({ count: 1, units: 'paragraphs' }), + open: true, + animated: false, + dismissByOverlay: false, + onDismiss: action('dismiss'), +}; + +export const Confirm: Story> = (args) => ; +Confirm.storyName = 'confirm'; +Confirm.args = { + text: 'Are you ok?', + confirmButtonText: 'Yes', + cancelButtonText: 'No', + onConfirm: action('confirm'), + onCancel: action('cancel'), +}; + +export const Alert: Story> = (args) => ; +Alert.storyName = 'alert'; +Alert.args = { + text: 'You look great today.', + buttonText: 'OK', + onConfirm: action('confirm'), +}; diff --git a/packages/livechat/src/components/Popover/index.js b/packages/livechat/src/components/Popover/index.js index 55ed2147acc20..306afc87348ad 100644 --- a/packages/livechat/src/components/Popover/index.js +++ b/packages/livechat/src/components/Popover/index.js @@ -83,6 +83,7 @@ export class PopoverContainer extends Component { ); } +/** @type {function({ children: [function({ pop: function() }), function({ dismiss: any, triggerBounds?: any })], overlayProps?: any }): any} */ export const PopoverTrigger = ({ children, ...props }) => ( {({ open }) => children[0]({ pop: open.bind(null, children[1], props) })} ); diff --git a/packages/livechat/src/components/Popover/stories.js b/packages/livechat/src/components/Popover/stories.js deleted file mode 100644 index f7c9ae875a0ca..0000000000000 --- a/packages/livechat/src/components/Popover/stories.js +++ /dev/null @@ -1,36 +0,0 @@ -import { withKnobs, object } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { PopoverContainer, PopoverTrigger } from '.'; -import { centered } from '../../helpers.stories'; -import { Button } from '../Button'; - -const centeredWithPopoverContainer = (storyFn, ...args) => ( -
- {centered(storyFn, ...args)} -
-); - -storiesOf('Components/Popover', module) - .addDecorator(withKnobs) - .addDecorator(centeredWithPopoverContainer) - .add('arbitrary renderer', () => ( - - {({ pop }) => } - {({ dismiss, triggerBounds = {} }) => ( - - )} - - )) - .add('with overlay props', () => ( - - {({ pop }) => } - {({ dismiss, triggerBounds = {} }) => ( - - )} - - )); diff --git a/packages/livechat/src/components/Popover/stories.tsx b/packages/livechat/src/components/Popover/stories.tsx new file mode 100644 index 0000000000000..ecf87d9ddd978 --- /dev/null +++ b/packages/livechat/src/components/Popover/stories.tsx @@ -0,0 +1,44 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { PopoverContainer, PopoverTrigger } from '.'; +import { Button } from '../Button'; + +export default { + title: 'Components/Popover', + component: PopoverTrigger, + decorators: [ + (storyFn) => ( + +
{storyFn()}
+
+ ), + ], + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta>; + +export const ArbitraryRenderer: Story> = () => ( + + {({ pop }) => } + {({ dismiss, triggerBounds = {} }) => ( + + )} + +); +ArbitraryRenderer.storyName = 'arbitrary renderer'; + +export const WithOverlayProps: Story> = () => ( + + {({ pop }) => } + {({ dismiss, triggerBounds = {} }) => ( + + )} + +); +WithOverlayProps.storyName = 'with overlay props'; diff --git a/packages/livechat/src/components/Screen/Footer.stories.tsx b/packages/livechat/src/components/Screen/Footer.stories.tsx new file mode 100644 index 0000000000000..5038b1faf5840 --- /dev/null +++ b/packages/livechat/src/components/Screen/Footer.stories.tsx @@ -0,0 +1,67 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import i18next from 'i18next'; +import type { ComponentProps } from 'preact'; + +import { Screen } from '.'; +import { screenDecorator } from '../../helpers.stories'; +import { FooterOptions } from '../Footer'; +import Menu from '../Menu'; + +export default { + title: 'Components/Screen/Footer', + component: Screen.Footer, + decorators: [ + (storyFn) => ( + + + {storyFn()} + + ), + screenDecorator, + ], + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +export const Empty: Story> = () => ; +Empty.storyName = 'empty'; + +export const WithChildren: Story> = () => ( + Lorem ipsum dolor sit amet, his id atqui repudiare. +); +WithChildren.storyName = 'with children'; + +export const WithOptions: Story> = () => ( + + + {i18next.t('change_department')} + {i18next.t('forget_remove_my_data')} + + {i18next.t('finish_this_chat')} + + + + } + /> +); +WithOptions.storyName = 'with options'; diff --git a/packages/livechat/src/components/Screen/index.js b/packages/livechat/src/components/Screen/index.js index c7116b27349fa..3a11000c43cba 100644 --- a/packages/livechat/src/components/Screen/index.js +++ b/packages/livechat/src/components/Screen/index.js @@ -72,6 +72,7 @@ const CssVar = ({ theme }) => { ); }; +/** @type {{ (props: any) => JSX.Element; Content: (props: any) => JSX.Element; Footer: (props: any) => JSX.Element }} */ export const Screen = ({ theme = {}, agent, diff --git a/packages/livechat/src/components/Screen/stories.js b/packages/livechat/src/components/Screen/stories.js deleted file mode 100644 index 3590b00c68709..0000000000000 --- a/packages/livechat/src/components/Screen/stories.js +++ /dev/null @@ -1,321 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, color, object, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; -import i18next from 'i18next'; - -import { Screen } from '.'; -import { screenCentered, gazzoAvatar } from '../../helpers.stories'; -import { FooterOptions } from '../Footer'; -import Menu from '../Menu'; - -const alerts = [ - { id: 1, children: 'Success alert', success: true }, - { id: 2, children: 'Warning alert', warning: true, timeout: 0 }, - { id: 3, children: 'Error alert', error: true, timeout: 1000 }, - { id: 4, children: 'Custom colored alert', color: '#000', timeout: 5000 }, -]; - -storiesOf('Components/Screen', module) - .addDecorator(withKnobs) - .addDecorator(screenCentered) - .add('normal', () => ( - - {text('content', 'Content')} - - )) - .add('minimized', () => ( - - {text('content', 'Content')} - - )) - .add('expanded', () => ( - - {text('content', 'Content')} - - )) - .add('windowed', () => ( - - {text('content', 'Content')} - - )) - .add('with agent (email)', () => ( - - {text('content', 'Content')} - - )) - .add('with agent (phone)', () => ( - - {text('content', 'Content')} - - )) - .add('with agent', () => ( - - {text('content', 'Content')} - - )) - .add('with hidden agent', () => ( - - {text('content', 'Content')} - - )) - .add('with multiple alerts', () => ( - - {text('content', 'Content')} - - )); -storiesOf('Components/Screen/Footer', module) - .addDecorator(withKnobs) - .addDecorator(screenCentered) - .add('empty', () => ( - - - - - )) - .add('with children', () => ( - - - {text('content', 'Lorem ipsum dolor sit amet, his id atqui repudiare.')} - - )) - .add('with options', () => ( - - - - - {i18next.t('change_department')} - {i18next.t('forget_remove_my_data')} - - {i18next.t('finish_this_chat')} - - - - } - /> - - )); diff --git a/packages/livechat/src/components/Screen/stories.tsx b/packages/livechat/src/components/Screen/stories.tsx new file mode 100644 index 0000000000000..5113046cc97a0 --- /dev/null +++ b/packages/livechat/src/components/Screen/stories.tsx @@ -0,0 +1,121 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { Screen } from '.'; +import { gazzoAvatar, screenDecorator } from '../../helpers.stories'; + +export default { + title: 'Components/Screen', + component: Screen, + args: { + theme: { + color: '', + fontColor: '', + iconColor: '', + }, + title: 'Title', + notificationsEnabled: true, + minimized: false, + expanded: false, + windowed: false, + onEnableNotifications: action('enableNotifications'), + onDisableNotifications: action('disableNotifications'), + onMinimize: action('minimize'), + onRestore: action('restore'), + onOpenWindow: action('openWindow'), + }, + decorators: [screenDecorator], + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ( + + Content + +); + +export const Normal = Template.bind({}); +Normal.storyName = 'normal'; + +export const Minimized = Template.bind({}); +Minimized.storyName = 'minimized'; +Minimized.args = { + minimized: true, +}; + +export const Expanded = Template.bind({}); +Expanded.storyName = 'expanded'; +Expanded.args = { + expanded: true, +}; + +export const Windowed = Template.bind({}); +Windowed.storyName = 'windowed'; +Windowed.args = { + windowed: true, +}; + +export const WithAgentEmail = Template.bind({}); +WithAgentEmail.storyName = 'with agent (email)'; +WithAgentEmail.args = { + agent: { + name: 'Guilherme Gazzo', + status: 'away', + email: 'guilherme.gazzo@rocket.chat', + avatar: { + description: 'guilherme.gazzo', + src: gazzoAvatar, + }, + }, +}; + +export const WithAgentPhone = Template.bind({}); +WithAgentPhone.storyName = 'with agent (phone)'; +WithAgentPhone.args = { + agent: { + name: 'Guilherme Gazzo', + status: 'away', + phone: '+ 55 42423 24242', + avatar: { + description: 'guilherme.gazzo', + src: gazzoAvatar, + }, + }, +}; + +export const WithAgentPhoneAndEmail = Template.bind({}); +WithAgentPhoneAndEmail.storyName = 'with agent (phone and email)'; +WithAgentPhoneAndEmail.args = { + agent: { + name: 'Guilherme Gazzo', + status: 'away', + email: 'guilherme.gazzo@rocket.chat', + phone: '+ 55 42423 24242', + avatar: { + description: 'guilherme.gazzo', + src: gazzoAvatar, + }, + }, +}; + +export const WithHiddenAgent = Template.bind({}); +WithHiddenAgent.storyName = 'with hidden agent'; +WithHiddenAgent.args = { + agent: { + hiddenInfo: true, + }, +}; + +export const WithMultipleAlerts = Template.bind({}); +WithMultipleAlerts.storyName = 'with multiple alerts'; +WithMultipleAlerts.args = { + alerts: [ + { id: 1, children: 'Success alert', success: true }, + { id: 2, children: 'Warning alert', warning: true, timeout: 0 }, + { id: 3, children: 'Error alert', error: true, timeout: 1000 }, + { id: 4, children: 'Custom colored alert', color: '#000', timeout: 5000 }, + ], +}; diff --git a/packages/livechat/src/components/Sound/stories.js b/packages/livechat/src/components/Sound/stories.js deleted file mode 100644 index 91c58950afbe5..0000000000000 --- a/packages/livechat/src/components/Sound/stories.js +++ /dev/null @@ -1,18 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { Sound } from '.'; -import beepAudio from '../../../.storybook/assets/beep.mp3'; -import sampleAudio from '../../../.storybook/assets/sample-audio.mp3'; -import { centered } from '../../helpers.stories'; - -storiesOf('Components/Sound', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('short', () => ( - - )) - .add('long', () => ( - - )); diff --git a/packages/livechat/src/components/Sound/stories.tsx b/packages/livechat/src/components/Sound/stories.tsx new file mode 100644 index 0000000000000..4a5a89b16ab51 --- /dev/null +++ b/packages/livechat/src/components/Sound/stories.tsx @@ -0,0 +1,33 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { Sound } from '.'; +import { beepAudio, sampleAudio } from '../../helpers.stories'; + +export default { + title: 'Components/Sound', + component: Sound, + args: { + play: false, + onStart: action('start'), + onStop: action('stop'), + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +const Template: Story> = (args) => ; + +export const Short = Template.bind({}); +Short.storyName = 'short'; +Short.args = { + src: beepAudio, +}; + +export const Long = Template.bind({}); +Long.storyName = 'long'; +Long.args = { + src: sampleAudio, +}; diff --git a/packages/livechat/src/components/Tooltip/stories.js b/packages/livechat/src/components/Tooltip/stories.js deleted file mode 100644 index d2acf87b50fd2..0000000000000 --- a/packages/livechat/src/components/Tooltip/stories.js +++ /dev/null @@ -1,44 +0,0 @@ -import { withKnobs, boolean, select, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import Tooltip, { withTooltip } from '.'; -import { centered } from '../../helpers.stories'; -import { Button } from '../Button'; - -const tooltipText = 'A simple tool tip'; -const tooltipHidden = false; -const placements = [null, 'left', 'top', 'right', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right']; - -storiesOf('Components/Tooltip', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('inline', () => ( - - )) - .add('placements', () => ( -
- {placements.map((placement) => ( - - ))} -
- )) - .add('connected to another component', () => ( - - - - - - )) - .add('withTooltip()', () => { - const MyButton = withTooltip(Button); - - return ( - - A simple button - - ); - }); diff --git a/packages/livechat/src/components/Tooltip/stories.tsx b/packages/livechat/src/components/Tooltip/stories.tsx new file mode 100644 index 0000000000000..8c1f4f9790c35 --- /dev/null +++ b/packages/livechat/src/components/Tooltip/stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import Tooltip, { withTooltip } from '.'; +import { Button } from '../Button'; + +const placements = [null, 'left', 'top', 'right', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right'] as const; + +export default { + title: 'Components/Tooltip', + component: Tooltip, + args: { + children: 'A simple tool tip', + hidden: false, + }, + argTypes: { + placement: { + control: { + type: 'select', + options: placements, + }, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies Meta>; + +export const Inline: Story> = (args) => ; +Inline.storyName = 'inline'; + +export const Placements: Story> = (args) => ( +
+ {placements.map((placement, i) => ( + + ))} +
+); +Placements.storyName = 'placements'; + +export const ConnectedToAnotherComponent: Story> = (args) => ( + + + +); +ConnectedToAnotherComponent.storyName = 'connected to another component'; +ConnectedToAnotherComponent.args = { + content: 'A simple tool tip', +}; +ConnectedToAnotherComponent.decorators = [(storyFn) => {storyFn()}]; + +const MyButton = withTooltip(Button); + +export const WithTooltip: Story<{ tooltip?: string }> = ({ tooltip }) => A simple button; +WithTooltip.storyName = 'withTooltip()'; +WithTooltip.args = { + tooltip: 'A simple tool tip', +}; +WithTooltip.decorators = [(storyFn) => {storyFn()}]; diff --git a/packages/livechat/src/components/uiKit/message/actions.stories.js b/packages/livechat/src/components/uiKit/message/ActionsBlock.stories.tsx similarity index 97% rename from packages/livechat/src/components/uiKit/message/actions.stories.js rename to packages/livechat/src/components/uiKit/message/ActionsBlock.stories.tsx index 482f807e1cd57..4892e9c4c926c 100644 --- a/packages/livechat/src/components/uiKit/message/actions.stories.js +++ b/packages/livechat/src/components/uiKit/message/ActionsBlock.stories.tsx @@ -1,4 +1,5 @@ import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/preact'; import { renderMessageBlocks } from '.'; import Surface from './Surface'; @@ -13,14 +14,14 @@ export default { (storyFn) => ( { + dispatchAction={async (payload: unknown) => { await new Promise((resolve) => setTimeout(resolve, 1000)); action('dispatchAction')(payload); }} /> ), ], -}; +} as Meta; export const AllSelects = () => renderMessageBlocks([ diff --git a/packages/livechat/src/components/uiKit/message/ButtonElement/stories.js b/packages/livechat/src/components/uiKit/message/ButtonElement/stories.tsx similarity index 75% rename from packages/livechat/src/components/uiKit/message/ButtonElement/stories.js rename to packages/livechat/src/components/uiKit/message/ButtonElement/stories.tsx index 1f75aaae0cc6a..adf60b56dc958 100644 --- a/packages/livechat/src/components/uiKit/message/ButtonElement/stories.js +++ b/packages/livechat/src/components/uiKit/message/ButtonElement/stories.tsx @@ -1,5 +1,7 @@ import { BlockContext } from '@rocket.chat/ui-kit'; import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; import ButtonElement from '.'; import { parser } from '..'; @@ -7,38 +9,38 @@ import Surface from '../Surface'; export default { title: 'UiKit/Message/Button element', - parameters: { - layout: 'centered', + args: { + text: { type: 'plain_text', text: 'Click Me' }, + actionId: undefined, + url: undefined, + value: undefined, + style: null, + context: undefined, + confirm: undefined, + }, + argTypes: { + text: { control: 'object' }, + actionId: { control: 'text' }, + url: { control: 'text' }, + value: { control: 'text' }, + style: { control: { type: 'inline-radio', options: [null, 'primary', 'danger'] } }, + context: { control: { type: 'select', options: BlockContext } }, }, decorators: [ (storyFn) => ( { + dispatchAction={async (payload: unknown) => { await new Promise((resolve) => setTimeout(resolve, 1000)); action('dispatchAction')(payload); }} /> ), ], - argTypes: { - text: { control: 'object' }, - actionId: { control: 'text' }, - url: { control: 'text' }, - value: { control: 'text' }, - style: { control: { type: 'inline-radio', options: [null, 'primary', 'danger'] } }, - context: { control: { type: 'select', options: BlockContext } }, - }, - args: { - text: { type: 'plain_text', text: 'Click Me' }, - actionId: undefined, - url: undefined, - value: undefined, - style: null, - context: undefined, - confirm: undefined, + parameters: { + layout: 'centered', }, -}; +} as Meta>; -export const Default = (args) => ; +export const Default: Story> = (args) => ; Default.storyName = 'default'; diff --git a/packages/livechat/src/components/uiKit/message/context.stories.js b/packages/livechat/src/components/uiKit/message/ContextBlock.stories.tsx similarity index 92% rename from packages/livechat/src/components/uiKit/message/context.stories.js rename to packages/livechat/src/components/uiKit/message/ContextBlock.stories.tsx index 2446351ac5408..1760ca18135d5 100644 --- a/packages/livechat/src/components/uiKit/message/context.stories.js +++ b/packages/livechat/src/components/uiKit/message/ContextBlock.stories.tsx @@ -1,5 +1,7 @@ +import type { Meta } from '@storybook/preact'; + import { renderMessageBlocks } from '.'; -import accessoryImage from '../../../../.storybook/assets/accessoryImage.png'; +import { accessoryImage } from '../../../helpers.stories'; export default { title: 'UiKit/Message/Context block', @@ -7,7 +9,7 @@ export default { layout: 'centered', }, decorators: [(storyFn) =>
], -}; +} as Meta; export const PlainText = () => renderMessageBlocks([ diff --git a/packages/livechat/src/components/uiKit/message/divider.stories.js b/packages/livechat/src/components/uiKit/message/DividerBlock.stories.tsx similarity index 85% rename from packages/livechat/src/components/uiKit/message/divider.stories.js rename to packages/livechat/src/components/uiKit/message/DividerBlock.stories.tsx index 0e01c4be1c2cc..c5b9c39d70fc7 100644 --- a/packages/livechat/src/components/uiKit/message/divider.stories.js +++ b/packages/livechat/src/components/uiKit/message/DividerBlock.stories.tsx @@ -1,3 +1,5 @@ +import type { Meta } from '@storybook/preact'; + import { renderMessageBlocks } from '.'; export default { @@ -6,7 +8,7 @@ export default { layout: 'centered', }, decorators: [(storyFn) =>
], -}; +} as Meta; export const Default = () => renderMessageBlocks([ diff --git a/packages/livechat/src/components/uiKit/message/image.stories.js b/packages/livechat/src/components/uiKit/message/ImageBlock.stories.tsx similarity index 85% rename from packages/livechat/src/components/uiKit/message/image.stories.js rename to packages/livechat/src/components/uiKit/message/ImageBlock.stories.tsx index 50681475bbb5d..2623e606dbc9b 100644 --- a/packages/livechat/src/components/uiKit/message/image.stories.js +++ b/packages/livechat/src/components/uiKit/message/ImageBlock.stories.tsx @@ -1,5 +1,7 @@ +import type { Meta } from '@storybook/preact'; + import { renderMessageBlocks } from '.'; -import imageBlock from '../../../../.storybook/assets/imageBlock.png'; +import { imageBlock } from '../../../helpers.stories'; export default { title: 'UiKit/Message/Image block', @@ -7,7 +9,7 @@ export default { layout: 'centered', }, decorators: [(storyFn) =>
], -}; +} satisfies Meta; export const WithTitle = () => renderMessageBlocks([ diff --git a/packages/livechat/src/components/uiKit/message/section.stories.js b/packages/livechat/src/components/uiKit/message/SectionBlock.stories.tsx similarity index 96% rename from packages/livechat/src/components/uiKit/message/section.stories.js rename to packages/livechat/src/components/uiKit/message/SectionBlock.stories.tsx index 5b6026286003c..f24c308da0902 100644 --- a/packages/livechat/src/components/uiKit/message/section.stories.js +++ b/packages/livechat/src/components/uiKit/message/SectionBlock.stories.tsx @@ -1,7 +1,8 @@ import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/preact'; import { renderMessageBlocks } from '.'; -import accessoryImage from '../../../../.storybook/assets/accessoryImage.png'; +import { accessoryImage } from '../../../helpers.stories'; import { PopoverContainer } from '../../Popover'; import Surface from './Surface'; @@ -16,14 +17,14 @@ export default { (storyFn) => ( { + dispatchAction={async (payload: unknown) => { await new Promise((resolve) => setTimeout(resolve, 1000)); action('dispatchAction')(payload); }} /> ), ], -}; +} as Meta; export const TextAsPlainText = () => renderMessageBlocks([ diff --git a/packages/livechat/src/components/uiKit/message/index.js b/packages/livechat/src/components/uiKit/message/index.tsx similarity index 75% rename from packages/livechat/src/components/uiKit/message/index.js rename to packages/livechat/src/components/uiKit/message/index.tsx index e7caf59d9ded0..d5b417b1fb0d6 100644 --- a/packages/livechat/src/components/uiKit/message/index.js +++ b/packages/livechat/src/components/uiKit/message/index.tsx @@ -13,8 +13,8 @@ import PlainText from './PlainText'; import SectionBlock from './SectionBlock'; import StaticSelectElement from './StaticSelectElement'; -class MessageParser extends UiKitParserMessage { - divider = (element, context, index) => { +class MessageParser extends UiKitParserMessage { + divider = (element: any, context: any, index: any) => { if (context !== BlockContext.BLOCK) { return null; } @@ -22,7 +22,7 @@ class MessageParser extends UiKitParserMessage { return ; }; - section = (element, context, index) => { + section = (element: any, context: any, index: any) => { if (context !== BlockContext.BLOCK) { return null; } @@ -30,7 +30,7 @@ class MessageParser extends UiKitParserMessage { return ; }; - image = (element, context, index) => { + image = (element: any, context: any, index: any) => { if (context === BlockContext.BLOCK) { return ; } @@ -38,7 +38,7 @@ class MessageParser extends UiKitParserMessage { return ; }; - actions = (element, context, index) => { + actions = (element: any, context: any, index: any) => { if (context !== BlockContext.BLOCK) { return null; } @@ -46,7 +46,7 @@ class MessageParser extends UiKitParserMessage { return ; }; - context = (element, context, index) => { + context = (element: any, context: any, index: any) => { if (context !== BlockContext.BLOCK) { return null; } @@ -54,7 +54,7 @@ class MessageParser extends UiKitParserMessage { return ; }; - plain_text = (element, context, index) => { + plain_text = (element: any, context: any, index: any) => { if (context === BlockContext.BLOCK) { return null; } @@ -62,7 +62,7 @@ class MessageParser extends UiKitParserMessage { return ; }; - mrkdwn = (element, context, index) => { + mrkdwn = (element: any, context: any, index: any) => { if (context === BlockContext.BLOCK) { return null; } @@ -70,7 +70,7 @@ class MessageParser extends UiKitParserMessage { return <Mrkdwn key={index} {...element} />; }; - button = (element, context, index) => { + button = (element: any, context: any, index: any) => { if (context === BlockContext.BLOCK) { return null; } @@ -78,7 +78,7 @@ class MessageParser extends UiKitParserMessage { return <ButtonElement key={index} {...element} parser={this} context={context} />; }; - overflow = (element, context, index) => { + overflow = (element: any, context: any, index: any) => { if (context === BlockContext.BLOCK) { return null; } @@ -86,7 +86,7 @@ class MessageParser extends UiKitParserMessage { return <OverflowElement key={index} {...element} parser={this} context={context} />; }; - datePicker = (element, context, index) => { + datePicker = (element: any, context: any, index: any) => { if (context === BlockContext.BLOCK) { return null; } @@ -94,7 +94,7 @@ class MessageParser extends UiKitParserMessage { return <DatePickerElement key={index} {...element} parser={this} context={context} />; }; - staticSelect = (element, context, index) => { + staticSelect = (element: any, context: any, index: any) => { if (context === BlockContext.BLOCK) { return null; } @@ -109,4 +109,4 @@ export const parser = new MessageParser(); export const renderMessageBlocks = uiKitMessage(parser, { engine: 'livechat', -}); +}) as { (blocks: any): JSX.Element[] }; diff --git a/packages/livechat/src/global.d.ts b/packages/livechat/src/global.d.ts index 79f8eda9cdb65..c19e42b90d1b8 100644 --- a/packages/livechat/src/global.d.ts +++ b/packages/livechat/src/global.d.ts @@ -1,3 +1,32 @@ -interface Window { - SERVER_URL: string; +import type { ComponentType } from 'preact'; + +export {}; + +declare global { + interface Window { + SERVER_URL: string; + } + + namespace preact { + interface Component { + // This is a workaround for https://github.com/preactjs/preact/issues/1206 + refs: Record<string, any>; + } + + interface Provider<P> { + $$typeof: symbol; + props: P & { children?: ComponentChildren }; + } + + interface Consumer { + $$typeof: symbol; + } + } +} + +declare module '@storybook/preact/dist/ts3.9/client/preview/types-6-0.d.ts' { + export type PreactFramework = { + component: ComponentType<any, any>; + storyResult: StoryFnPreactReturnType; + }; } diff --git a/packages/livechat/src/helpers.stories.js b/packages/livechat/src/helpers.stories.js index 54a6c141e2e67..1a597d8a2e82e 100644 --- a/packages/livechat/src/helpers.stories.js +++ b/packages/livechat/src/helpers.stories.js @@ -1,29 +1,21 @@ import { action } from '@storybook/addon-actions'; -import { boolean, color } from '@storybook/addon-knobs'; import { loremIpsum as originalLoremIpsum } from 'lorem-ipsum'; import gazzoAvatar from '../.storybook/assets/gazzo.jpg'; import martinAvatar from '../.storybook/assets/martin.jpg'; import tassoAvatar from '../.storybook/assets/tasso.jpg'; -export const centered = (storyFn) => ( - <div style={{ position: 'fixed', top: 0, left: 0, bottom: 0, right: 0, display: 'flex', alignItems: 'center', overflow: 'auto' }}> - <div style={{ margin: 'auto', maxHeight: '100%' }}>{storyFn()}</div> - </div> -); - -export const screenCentered = (storyFn, ...args) => - centered(() => <div style={{ display: 'flex', width: '365px', height: '500px' }}>{storyFn()}</div>, ...args); +export const screenDecorator = (storyFn) => <div style={{ display: 'flex', width: 365, height: 500 }}>{storyFn()}</div>; export const screenProps = () => ({ theme: { - color: color('theme.color', ''), - fontColor: color('theme.fontColor', ''), - iconColor: color('theme.iconColor', ''), + color: '', + fontColor: '', + iconColor: '', }, - notificationsEnabled: boolean('notificationsEnabled', true), - minimized: boolean('minimized', false), - windowed: boolean('windowed', false), + notificationsEnabled: true, + minimized: false, + windowed: false, onEnableNotifications: action('enableNotifications'), onDisableNotifications: action('disableNotifications'), onMinimize: action('minimize'), @@ -48,3 +40,10 @@ const loremIpsumRandom = createRandom(42); export const loremIpsum = (options) => originalLoremIpsum({ random: loremIpsumRandom, ...options }); export { gazzoAvatar, martinAvatar, tassoAvatar }; + +export { default as sampleAudio } from '../.storybook/assets/sample-audio.mp3'; +export { default as sampleImage } from '../.storybook/assets/sample-image.jpg'; +export { default as sampleVideo } from '../.storybook/assets/sample-video.mp4'; +export { default as accessoryImage } from '../.storybook/assets/accessoryImage.png'; +export { default as imageBlock } from '../.storybook/assets/imageBlock.png'; +export { default as beepAudio } from '../.storybook/assets/beep.mp3'; diff --git a/packages/livechat/src/icons/stories.js b/packages/livechat/src/icons/stories.js deleted file mode 100644 index 970ecb23fd4a2..0000000000000 --- a/packages/livechat/src/icons/stories.js +++ /dev/null @@ -1,52 +0,0 @@ -import path from 'path'; - -import { withKnobs, color } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { centered } from '../helpers.stories'; - -const req = require.context('./', true, /\.svg$/); -const iconset = req.keys().map((filename) => ({ - component: req(filename), - name: path.basename(filename, '.svg'), -})); - -const IconDisplay = ({ component: Icon, name, color }) => ( - <div - style={{ - width: '130px', - height: '130px', - margin: '10px', - display: 'flex', - flexDirection: 'column', - alignItems: 'center', - justifyContent: 'stretch', - }} - > - <div style={{ flex: '1', display: 'flex', alignItems: 'center', color }}> - <Icon width={48} height={48} /> - </div> - <div style={{ flex: '0' }}>{name}</div> - </div> -); - -storiesOf('Components/Icons', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add('all', () => ( - <div style={{ width: '100%', display: 'flex', flexWrap: 'wrap' }}> - {iconset.map((props) => ( - <IconDisplay color={color('color', '#E0364D')} {...props} /> - ))} - </div> - )); -iconset.forEach(({ component: Icon, name }) => - storiesOf('Components/Icons', module) - .addDecorator(centered) - .addDecorator(withKnobs) - .add(name, () => ( - <div style={{ color: color('color', '#E0364D') }}> - <Icon width={256} height={256} /> - </div> - )), -); diff --git a/packages/livechat/src/icons/stories.tsx b/packages/livechat/src/icons/stories.tsx new file mode 100644 index 0000000000000..2351e50f39af5 --- /dev/null +++ b/packages/livechat/src/icons/stories.tsx @@ -0,0 +1,57 @@ +import path from 'path'; + +import type { Meta, Story } from '@storybook/preact'; + +export default { + title: 'Components/Icons', + argTypes: { + color: { + control: 'color', + defaultValue: '#E0364D', + }, + }, + decorators: [(storyFn) => <div style={{ width: '100%', display: 'flex', flexWrap: 'wrap' }}>{storyFn()}</div>], + parameters: { + layout: 'centered', + }, +} satisfies Meta<{ color: string }>; + +const req = require.context('./', true, /\.svg$/); +const iconset = req.keys().map((filename) => ({ + component: req(filename), + name: path.basename(filename, '.svg'), +})); + +export const All: Story<{ color: string }> = ({ color }) => ( + <> + {iconset.map( + ( + { + // eslint-disable-next-line @typescript-eslint/naming-convention + component: Icon, + name, + }, + i, + ) => ( + <div + key={i} + style={{ + width: 130, + height: 130, + margin: 10, + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'stretch', + }} + > + <div style={{ flex: 1, display: 'flex', alignItems: 'center', color }}> + <Icon width={48} height={48} /> + </div> + <div style={{ flex: 0 }}>{name}</div> + </div> + ), + )} + </> +); +All.storyName = 'all'; diff --git a/packages/livechat/src/routes/Chat/stories.js b/packages/livechat/src/routes/Chat/stories.js deleted file mode 100644 index 2ed307d65c367..0000000000000 --- a/packages/livechat/src/routes/Chat/stories.js +++ /dev/null @@ -1,131 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, number, object, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import soundSrc from '../../../.storybook/assets/beep.mp3'; -import { screenCentered, screenProps, avatarResolver } from '../../helpers.stories'; -import Chat from './component'; - -const agent = { - name: 'Guilherme Gazzo', - status: 'online', - email: 'guilherme.gazzo@rocket.chat', - phone: '+55 99 99999 9999', - username: 'guilherme.gazzo', -}; - -const now = new Date(Date.parse('2021-01-01T00:00:00.000Z')); - -const messages = [ - { - _id: 1, - u: { _id: 1, username: 'tasso.evangelista' }, - msg: 'Lorem ipsum dolor sit amet, ea usu quod eirmod lucilius, mea veri viris concludaturque id, vel eripuit fabulas ea', - }, - { _id: 2, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Putent appareat te sea, dico recusabo pri te' }, - { _id: 3, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Iudico utinam volutpat eos eu, sadipscing repudiandae pro te' }, - { _id: 4, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Movet doming ad ius, mel id adversarium disputationi' }, - { _id: 5, u: { _id: 1, username: 'tasso.evangelista' }, msg: 'Adhuc latine et nec' }, - { _id: 6, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Vis at verterem adversarium concludaturque' }, - { _id: 7, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Sea no congue scripta persecuti, sed amet fabulas voluptaria ex' }, - { _id: 8, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Invidunt repudiandae has eu' }, - { _id: 9, u: { _id: 1, username: 'tasso.evangelista' }, msg: 'Veri soluta suscipit mel no' }, -]; - -const triggers = [ - { _id: 1, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Putent appareat te sea, dico recusabo pri te' }, - { _id: 2, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Iudico utinam volutpat eos eu, sadipscing repudiandae pro te' }, -]; - -const normalizeMessages = (messages = []) => - messages.map((message, i) => ({ - ...message, - ts: new Date(now.getTime() - (15 - i) * 60000 - (i < 5 ? 24 * 60 * 60 * 1000 : 0)).toISOString(), - })); - -storiesOf('Routes/Chat', module) - .addDecorator(screenCentered) - .addDecorator(withKnobs) - .add('loading', () => ( - <Chat - title={text('title', '')} - sound={{ src: soundSrc, play: false }} - avatarResolver={avatarResolver} - uid={number('uid', 1)} - agent={object('agent', agent)} - messages={object('messages', [])} - typingUsernames={object('typingUsernames', [])} - emoji={boolean('emoji', false)} - uploads={boolean('uploads', false)} - loading={boolean('loading', true)} - onTop={action('top')} - onBottom={action('bottom')} - onUpload={action('upload')} - onSubmit={action('submit')} - limitTextLength={number('limitTextLength', 0)} - {...screenProps()} - /> - )) - .add('normal', () => ( - <Chat - title={text('title', '')} - sound={{ src: soundSrc, play: false }} - avatarResolver={avatarResolver} - uid={number('uid', 1)} - agent={object('agent', agent)} - messages={object('messages', normalizeMessages(messages))} - typingUsernames={object('typingUsernames', [])} - emoji={boolean('emoji', false)} - uploads={boolean('uploads', false)} - loading={boolean('loading', false)} - lastReadMessageId={number('lastReadMessageId', 8)} - onTop={action('top')} - onBottom={action('bottom')} - onUpload={action('upload')} - onSubmit={action('submit')} - limitTextLength={number('limitTextLength', 0)} - {...screenProps()} - /> - )) - .add('with typing user', () => ( - <Chat - title={text('title', '')} - sound={{ src: soundSrc, play: false }} - avatarResolver={avatarResolver} - uid={number('uid', 1)} - agent={object('agent', agent)} - messages={object('messages', normalizeMessages(messages))} - typingUsernames={object('typingUsernames', ['guilherme.gazzo'])} - emoji={boolean('emoji', false)} - uploads={boolean('uploads', false)} - loading={boolean('loading', false)} - lastReadMessageId={number('lastReadMessageId', 8)} - onTop={action('top')} - onBottom={action('bottom')} - onUpload={action('upload')} - onSubmit={action('submit')} - limitTextLength={number('limitTextLength', 0)} - {...screenProps()} - /> - )) - .add('with trigger messages', () => ( - <Chat - title={text('title', '')} - sound={{ src: soundSrc, play: false }} - avatarResolver={avatarResolver} - uid={number('uid', 1)} - agent={object('agent', agent)} - messages={object('messages', normalizeMessages(triggers))} - typingUsernames={object('typingUsernames', [])} - emoji={boolean('emoji', false)} - uploads={boolean('uploads', false)} - loading={boolean('loading', false)} - lastReadMessageId={number('lastReadMessageId', 8)} - onTop={action('top')} - onBottom={action('bottom')} - onUpload={action('upload')} - onSubmit={action('submit')} - registrationRequired={boolean('registrationRequired', true)} - {...screenProps()} - /> - )); diff --git a/packages/livechat/src/routes/Chat/stories.tsx b/packages/livechat/src/routes/Chat/stories.tsx new file mode 100644 index 0000000000000..f2aa1a4f6c802 --- /dev/null +++ b/packages/livechat/src/routes/Chat/stories.tsx @@ -0,0 +1,91 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { screenProps, avatarResolver, beepAudio, screenDecorator } from '../../helpers.stories'; +import Chat from './component'; + +const now = new Date(Date.parse('2021-01-01T00:00:00.000Z')); + +const normalizeMessages = (messages: any[] = []) => + messages.map((message, i) => ({ + ...message, + ts: new Date(now.getTime() - (15 - i) * 60000 - (i < 5 ? 24 * 60 * 60 * 1000 : 0)).toISOString(), + })); + +export default { + title: 'Routes/Chat', + component: Chat, + args: { + title: '', + sound: { src: beepAudio, play: false }, + avatarResolver, + uid: 1, + agent: { + name: 'Guilherme Gazzo', + status: 'online', + email: 'guilherme.gazzo@rocket.chat', + phone: '+55 99 99999 9999', + username: 'guilherme.gazzo', + }, + messages: normalizeMessages([ + { + _id: 1, + u: { _id: 1, username: 'tasso.evangelista' }, + msg: 'Lorem ipsum dolor sit amet, ea usu quod eirmod lucilius, mea veri viris concludaturque id, vel eripuit fabulas ea', + }, + { _id: 2, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Putent appareat te sea, dico recusabo pri te' }, + { _id: 3, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Iudico utinam volutpat eos eu, sadipscing repudiandae pro te' }, + { _id: 4, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Movet doming ad ius, mel id adversarium disputationi' }, + { _id: 5, u: { _id: 1, username: 'tasso.evangelista' }, msg: 'Adhuc latine et nec' }, + { _id: 6, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Vis at verterem adversarium concludaturque' }, + { _id: 7, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Sea no congue scripta persecuti, sed amet fabulas voluptaria ex' }, + { _id: 8, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Invidunt repudiandae has eu' }, + { _id: 9, u: { _id: 1, username: 'tasso.evangelista' }, msg: 'Veri soluta suscipit mel no' }, + ]), + typingUsernames: [], + emoji: false, + uploads: false, + loading: false, + limitTextLength: 0, + registrationRequired: false, + onTop: action('top'), + onBottom: action('bottom'), + onUpload: action('upload'), + onSubmit: action('submit'), + ...screenProps(), + }, + decorators: [screenDecorator], + parameters: { + layout: 'fullscreen', + }, +} satisfies Meta<ComponentProps<typeof Chat>>; + +const Template: Story<ComponentProps<typeof Chat>> = (args) => <Chat {...args} />; + +export const Loading = Template.bind({}); +Loading.storyName = 'loading'; +Loading.args = { + messages: [], + loading: true, +}; + +export const Normal = Template.bind({}); +Normal.storyName = 'normal'; + +export const WithTypingUser = Template.bind({}); +WithTypingUser.storyName = 'with typing user'; +WithTypingUser.args = { + typingUsernames: ['guilherme.gazzo'], +}; + +export const WithTriggerMessages = Template.bind({}); +WithTriggerMessages.storyName = 'with trigger messages'; +WithTriggerMessages.args = { + messages: normalizeMessages([ + { _id: 1, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Putent appareat te sea, dico recusabo pri te' }, + { _id: 2, u: { _id: 2, username: 'guilherme.gazzo' }, msg: 'Iudico utinam volutpat eos eu, sadipscing repudiandae pro te' }, + ]), + lastReadMessageId: 8, + registrationRequired: true, +}; diff --git a/packages/livechat/src/routes/ChatFinished/stories.js b/packages/livechat/src/routes/ChatFinished/stories.js deleted file mode 100644 index 6764f490c0d86..0000000000000 --- a/packages/livechat/src/routes/ChatFinished/stories.js +++ /dev/null @@ -1,31 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { screenCentered, screenProps, loremIpsum } from '../../helpers.stories'; -import ChatFinished from './component'; - -const customGreeting = loremIpsum({ count: 3, units: 'words' }); -const customText = loremIpsum({ count: 2, units: 'sentences' }); - -storiesOf('Routes/ChatFinished', module) - .addDecorator(screenCentered) - .addDecorator(withKnobs) - .add('normal', () => ( - <ChatFinished - title={text('title', 'Chat Finished')} - greeting={text('greeting', '')} - message={text('message', '')} - onRedirectChat={action('redirectChat')} - {...screenProps()} - /> - )) - .add('with custom messages', () => ( - <ChatFinished - title={text('title', 'Chat Finished')} - greeting={text('greeting', customGreeting)} - message={text('message', customText)} - onRedirectChat={action('redirectChat')} - {...screenProps()} - /> - )); diff --git a/packages/livechat/src/routes/ChatFinished/stories.tsx b/packages/livechat/src/routes/ChatFinished/stories.tsx new file mode 100644 index 0000000000000..4479ee57d99df --- /dev/null +++ b/packages/livechat/src/routes/ChatFinished/stories.tsx @@ -0,0 +1,34 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { screenProps, loremIpsum, screenDecorator } from '../../helpers.stories'; +import ChatFinished from './component'; + +export default { + title: 'Routes/ChatFinished', + component: ChatFinished, + args: { + title: 'Chat Finished', + greeting: '', + message: '', + onRedirectChat: action('redirectChat'), + ...screenProps(), + }, + decorators: [screenDecorator], + parameters: { + layout: 'centered', + }, +} satisfies Meta<ComponentProps<typeof ChatFinished>>; + +const Template: Story<ComponentProps<typeof ChatFinished>> = (args) => <ChatFinished {...args} />; + +export const Normal = Template.bind({}); +Normal.storyName = 'normal'; + +export const WithCustomMessages = Template.bind({}); +WithCustomMessages.storyName = 'with custom messages'; +WithCustomMessages.args = { + greeting: loremIpsum({ count: 3, units: 'words' }), + message: loremIpsum({ count: 2, units: 'sentences' }), +}; diff --git a/packages/livechat/src/routes/GDPRAgreement/stories.js b/packages/livechat/src/routes/GDPRAgreement/stories.js deleted file mode 100644 index 7b5fa28347386..0000000000000 --- a/packages/livechat/src/routes/GDPRAgreement/stories.js +++ /dev/null @@ -1,19 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { screenCentered, screenProps } from '../../helpers.stories'; -import GDPRAgreement from './component'; - -storiesOf('Routes/GDPRAgreement', module) - .addDecorator(screenCentered) - .addDecorator(withKnobs) - .add('normal', () => ( - <GDPRAgreement - title={text('title', 'GDPR')} - consentText={text('consentText', '')} - instructions={text('instructions', '')} - onAgree={action('agree')} - {...screenProps()} - /> - )); diff --git a/packages/livechat/src/routes/GDPRAgreement/stories.tsx b/packages/livechat/src/routes/GDPRAgreement/stories.tsx new file mode 100644 index 0000000000000..cc764b888441d --- /dev/null +++ b/packages/livechat/src/routes/GDPRAgreement/stories.tsx @@ -0,0 +1,27 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { screenDecorator, screenProps } from '../../helpers.stories'; +import GDPRAgreement from './component'; + +export default { + title: 'Routes/GDPRAgreement', + component: GDPRAgreement, + args: { + title: 'GDPR', + consentText: '', + instructions: '', + onAgree: action('agree'), + ...screenProps(), + }, + decorators: [screenDecorator], + parameters: { + layout: 'centered', + }, +} satisfies Meta<ComponentProps<typeof GDPRAgreement>>; + +const Template: Story<ComponentProps<typeof GDPRAgreement>> = (args) => <GDPRAgreement {...args} />; + +export const Normal = Template.bind({}); +Normal.storyName = 'normal'; diff --git a/packages/livechat/src/routes/LeaveMessage/stories.js b/packages/livechat/src/routes/LeaveMessage/stories.js deleted file mode 100644 index 14fcd93200493..0000000000000 --- a/packages/livechat/src/routes/LeaveMessage/stories.js +++ /dev/null @@ -1,43 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { screenCentered, screenProps } from '../../helpers.stories'; -import LeaveMessage from './component'; - -storiesOf('Routes/Leave a message', module) - .addDecorator(screenCentered) - .addDecorator(withKnobs) - .add('normal', () => ( - <LeaveMessage - title={text('title', '')} - message={text('message', '')} - unavailableMessage={text('unavailableMessage', '')} - hasForm={boolean('hasForm', true)} - loading={boolean('loading', false)} - onSubmit={action('submit')} - {...screenProps()} - /> - )) - .add('loading', () => ( - <LeaveMessage - title={text('title', '')} - message={text('message', '')} - unavailableMessage={text('unavailableMessage', '')} - hasForm={boolean('hasForm', true)} - loading={boolean('loading', true)} - onSubmit={action('submit')} - {...screenProps()} - /> - )) - .add('unavailable', () => ( - <LeaveMessage - title={text('title', '')} - message={text('message', '')} - unavailableMessage={text('unavailableMessage', '')} - hasForm={boolean('hasForm', false)} - loading={boolean('loading', false)} - onSubmit={action('submit')} - {...screenProps()} - /> - )); diff --git a/packages/livechat/src/routes/LeaveMessage/stories.tsx b/packages/livechat/src/routes/LeaveMessage/stories.tsx new file mode 100644 index 0000000000000..58f683459ca21 --- /dev/null +++ b/packages/livechat/src/routes/LeaveMessage/stories.tsx @@ -0,0 +1,42 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { screenDecorator, screenProps } from '../../helpers.stories'; +import LeaveMessage from './component'; + +export default { + title: 'Routes/Leave a message', + component: LeaveMessage, + args: { + title: '', + message: '', + unavailableMessage: '', + hasForm: true, + loading: false, + onSubmit: action('submit'), + ...screenProps(), + }, + decorators: [screenDecorator], + parameters: { + layout: 'centered', + }, +} satisfies Meta<ComponentProps<typeof LeaveMessage>>; + +const Template: Story<ComponentProps<typeof LeaveMessage>> = (args) => <LeaveMessage {...args} />; + +export const Normal = Template.bind({}); +Normal.storyName = 'normal'; + +export const Loading = Template.bind({}); +Loading.storyName = 'loading'; +Loading.args = { + loading: true, +}; + +export const Unavailable = Template.bind({}); +Unavailable.storyName = 'unavailable'; +Unavailable.args = { + hasForm: false, + unavailableMessage: 'Sorry, we are not available at the moment. Please leave a message.', +}; diff --git a/packages/livechat/src/routes/Register/stories.js b/packages/livechat/src/routes/Register/stories.js deleted file mode 100644 index 014d0cfeb4dae..0000000000000 --- a/packages/livechat/src/routes/Register/stories.js +++ /dev/null @@ -1,106 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, object, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { screenCentered, screenProps } from '../../helpers.stories'; -import Register from './component'; - -const customFields = [ - { - _id: 'website', - label: 'Website', - type: 'input', - required: true, - }, - { - _id: 'area', - label: 'Area', - type: 'select', - defaultValue: 'Marketing', - options: ['Human Resources', 'Sales', 'Marketing', 'Supply', 'Development'], - required: true, - }, -]; - -storiesOf('Routes/Register', module) - .addDecorator(screenCentered) - .addDecorator(withKnobs) - .add('normal', () => ( - <Register - title={text('title', '')} - message={text('message', '')} - hasNameField={boolean('hasNameField', true)} - hasEmailField={boolean('hasEmailField', true)} - hasDepartmentField={boolean('hasDepartmentField', true)} - departments={object('departments', [ - { - _id: 1, - name: 'Department #1', - }, - { - _id: 2, - name: 'Department #2', - }, - { - _id: 3, - name: 'Department #3', - }, - ])} - loading={boolean('loading', false)} - onSubmit={action('submit')} - {...screenProps()} - /> - )) - .add('loading', () => ( - <Register - title={text('title', '')} - message={text('message', '')} - hasNameField={boolean('hasNameField', true)} - hasEmailField={boolean('hasEmailField', true)} - hasDepartmentField={boolean('hasDepartmentField', true)} - departments={object('departments', [ - { - _id: 1, - name: 'Department #1', - }, - { - _id: 2, - name: 'Department #2', - }, - { - _id: 3, - name: 'Department #3', - }, - ])} - loading={boolean('loading', true)} - onSubmit={action('submit')} - {...screenProps()} - /> - )) - .add('with custom fields', () => ( - <Register - title={text('title', '')} - message={text('message', '')} - hasNameField={boolean('hasNameField', true)} - hasEmailField={boolean('hasEmailField', true)} - hasDepartmentField={boolean('hasDepartmentField', true)} - departments={object('departments', [ - { - _id: 1, - name: 'Department #1', - }, - { - _id: 2, - name: 'Department #2', - }, - { - _id: 3, - name: 'Department #3', - }, - ])} - loading={boolean('loading', false)} - onSubmit={action('submit')} - customFields={customFields} - {...screenProps()} - /> - )); diff --git a/packages/livechat/src/routes/Register/stories.tsx b/packages/livechat/src/routes/Register/stories.tsx new file mode 100644 index 0000000000000..26a77d090e63c --- /dev/null +++ b/packages/livechat/src/routes/Register/stories.tsx @@ -0,0 +1,71 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { screenDecorator, screenProps } from '../../helpers.stories'; +import Register from './component'; + +export default { + title: 'Routes/Register', + component: Register, + args: { + title: '', + message: '', + hasNameField: true, + hasEmailField: true, + hasDepartmentField: true, + departments: [ + { + _id: 1, + name: 'Department #1', + }, + { + _id: 2, + name: 'Department #2', + }, + { + _id: 3, + name: 'Department #3', + }, + ], + loading: false, + onSubmit: action('submit'), + ...screenProps(), + }, + decorators: [screenDecorator], + parameters: { + layout: 'centered', + }, +} satisfies Meta<ComponentProps<typeof Register>>; + +const Template: Story<ComponentProps<typeof Register>> = (args) => <Register {...args} />; + +export const Normal = Template.bind({}); +Normal.storyName = 'normal'; + +export const Loading = Template.bind({}); +Loading.storyName = 'loading'; +Loading.args = { + loading: true, +}; + +export const WithCustomFields = Template.bind({}); +WithCustomFields.storyName = 'with custom fields'; +WithCustomFields.args = { + customFields: [ + { + _id: 'website', + label: 'Website', + type: 'input', + required: true, + }, + { + _id: 'area', + label: 'Area', + type: 'select', + defaultValue: 'Marketing', + options: ['Human Resources', 'Sales', 'Marketing', 'Supply', 'Development'], + required: true, + }, + ], +}; diff --git a/packages/livechat/src/routes/SwitchDepartment/stories.js b/packages/livechat/src/routes/SwitchDepartment/stories.js deleted file mode 100644 index 03584ad0d8084..0000000000000 --- a/packages/livechat/src/routes/SwitchDepartment/stories.js +++ /dev/null @@ -1,63 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, boolean, color, object, text } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { screenCentered, screenProps } from '../../helpers.stories'; -import SwitchDepartment from './component'; - -storiesOf('Routes/SwitchDepartment', module) - .addDecorator(screenCentered) - .addDecorator(withKnobs) - .add('normal', () => ( - <SwitchDepartment - theme={{ - color: color('theme/color', ''), - fontColor: color('theme/fontColor', ''), - iconColor: color('theme/iconColor', ''), - }} - title={text('title', '')} - message={text('message', '')} - departments={object('departments', [ - { - _id: 1, - name: 'Department #1', - }, - { - _id: 2, - name: 'Department #2', - }, - { - _id: 3, - name: 'Department #3', - }, - ])} - loading={boolean('loading', false)} - onSubmit={action('submit')} - onCancel={action('cancel')} - {...screenProps()} - /> - )) - .add('loading', () => ( - <SwitchDepartment - title={text('title', '')} - message={text('message', '')} - departments={object('departments', [ - { - _id: 1, - name: 'Department #1', - }, - { - _id: 2, - name: 'Department #2', - }, - { - _id: 3, - name: 'Department #3', - }, - ])} - loading={boolean('loading', true)} - onSubmit={action('submit')} - onCancel={action('cancel')} - {...screenProps()} - /> - )); diff --git a/packages/livechat/src/routes/SwitchDepartment/stories.tsx b/packages/livechat/src/routes/SwitchDepartment/stories.tsx new file mode 100644 index 0000000000000..def96290b3157 --- /dev/null +++ b/packages/livechat/src/routes/SwitchDepartment/stories.tsx @@ -0,0 +1,48 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { screenDecorator, screenProps } from '../../helpers.stories'; +import SwitchDepartment from './component'; + +export default { + title: 'Routes/SwitchDepartment', + component: SwitchDepartment, + args: { + title: '', + message: '', + departments: [ + { + _id: 1, + name: 'Department #1', + }, + { + _id: 2, + name: 'Department #2', + }, + { + _id: 3, + name: 'Department #3', + }, + ], + loading: false, + onSubmit: action('submit'), + onCancel: action('cancel'), + ...screenProps(), + }, + decorators: [screenDecorator], + parameters: { + layout: 'centered', + }, +} as Meta<ComponentProps<typeof SwitchDepartment>>; + +const Template: Story<ComponentProps<typeof SwitchDepartment>> = (args) => <SwitchDepartment {...args} />; + +export const Normal = Template.bind({}); +Normal.storyName = 'normal'; + +export const Loading = Template.bind({}); +Loading.storyName = 'loading'; +Loading.args = { + loading: true, +}; diff --git a/packages/livechat/src/routes/TriggerMessage/stories.js b/packages/livechat/src/routes/TriggerMessage/stories.js deleted file mode 100644 index 0fe12f21096fc..0000000000000 --- a/packages/livechat/src/routes/TriggerMessage/stories.js +++ /dev/null @@ -1,56 +0,0 @@ -import { action } from '@storybook/addon-actions'; -import { withKnobs, color, text, object } from '@storybook/addon-knobs'; -import { storiesOf } from '@storybook/react'; - -import { screenCentered, screenProps } from '../../helpers.stories'; -import TriggerMessage from './component'; - -const now = new Date(Date.parse('2021-01-01T00:00:00.000Z')); - -const messages = [ - { _id: 1, u: { _id: 1, username: 'guilherme.gazzo' }, msg: 'Hi There!' }, - { - _id: 2, - u: { _id: 2, username: 'guilherme.gazzo' }, - msg: 'Rocket.Chat allows you to chat and create better relationship with your customers on their favorite channels. ', - }, - { _id: 3, u: { _id: 3, username: 'guilherme.gazzo' }, msg: 'Let us know if you have any question.' }, -].map((message, i) => ({ - ...message, - ts: new Date(now.getTime() - (15 - i) * 60000 - (i < 5 ? 24 * 60 * 60 * 1000 : 0)).toISOString(), -})); - -storiesOf('Routes/TriggerMessage', module) - .addDecorator(screenCentered) - .addDecorator(withKnobs) - .add('single', () => ( - <TriggerMessage - theme={{ - color: color('theme/color', ''), - fontColor: color('theme/fontColor', ''), - iconColor: color('theme/iconColor', ''), - }} - messages={object( - 'messages', - messages.filter(({ _id }) => _id === 3), - )} - title={text('title', '')} - onSubmit={action('submit')} - onCancel={action('cancel')} - {...screenProps()} - /> - )) - .add('multiple', () => ( - <TriggerMessage - theme={{ - color: color('theme/color', ''), - fontColor: color('theme/fontColor', ''), - iconColor: color('theme/iconColor', ''), - }} - messages={object('messages', messages)} - title={text('title', '')} - onSubmit={action('submit')} - onCancel={action('cancel')} - {...screenProps()} - /> - )); diff --git a/packages/livechat/src/routes/TriggerMessage/stories.tsx b/packages/livechat/src/routes/TriggerMessage/stories.tsx new file mode 100644 index 0000000000000..b530461a9d51b --- /dev/null +++ b/packages/livechat/src/routes/TriggerMessage/stories.tsx @@ -0,0 +1,48 @@ +import { action } from '@storybook/addon-actions'; +import type { Meta, Story } from '@storybook/preact'; +import type { ComponentProps } from 'preact'; + +import { screenDecorator, screenProps } from '../../helpers.stories'; +import TriggerMessage from './component'; + +const now = new Date(Date.parse('2021-01-01T00:00:00.000Z')); + +const messages = [ + { _id: 1, u: { _id: 1, username: 'guilherme.gazzo' }, msg: 'Hi There!' }, + { + _id: 2, + u: { _id: 2, username: 'guilherme.gazzo' }, + msg: 'Rocket.Chat allows you to chat and create better relationship with your customers on their favorite channels. ', + }, + { _id: 3, u: { _id: 3, username: 'guilherme.gazzo' }, msg: 'Let us know if you have any question.' }, +].map((message, i) => ({ + ...message, + ts: new Date(now.getTime() - (15 - i) * 60000 - (i < 5 ? 24 * 60 * 60 * 1000 : 0)).toISOString(), +})); + +export default { + title: 'Routes/TriggerMessage', + component: TriggerMessage, + args: { + messages, + title: '', + onSubmit: action('submit'), + onCancel: action('cancel'), + ...screenProps(), + }, + decorators: [screenDecorator], + parameters: { + layout: 'centered', + }, +} satisfies Meta<ComponentProps<typeof TriggerMessage>>; + +const Template: Story<ComponentProps<typeof TriggerMessage>> = (args) => <TriggerMessage {...args} />; + +export const Single = Template.bind({}); +Single.storyName = 'single'; +Single.args = { + messages: messages.slice(-1), +}; + +export const Multiple = Template.bind({}); +Multiple.storyName = 'multiple'; diff --git a/yarn.lock b/yarn.lock index 0b573475ee3ce..210c2672084bd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1453,7 +1453,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.0.0, @babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.21.4": +"@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.21.4": version: 7.21.4 resolution: "@babel/helper-module-imports@npm:7.21.4" dependencies: @@ -3864,7 +3864,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.4.4, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.8.7, @babel/runtime@npm:^7.9.2": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": version: 7.21.0 resolution: "@babel/runtime@npm:7.21.0" dependencies: @@ -4656,52 +4656,6 @@ __metadata: languageName: node linkType: hard -"@emotion/cache@npm:^10.0.27, @emotion/cache@npm:^10.0.9": - version: 10.0.29 - resolution: "@emotion/cache@npm:10.0.29" - dependencies: - "@emotion/sheet": 0.9.4 - "@emotion/stylis": 0.8.5 - "@emotion/utils": 0.11.3 - "@emotion/weak-memoize": 0.2.5 - checksum: 78b37fb0c2e513c90143a927abef229e995b6738ef8a92ce17abe2ed409b38859ddda7c14d7f4854d6f4e450b6db50231532f53a7fec4903d7ae775b2ae3fd64 - languageName: node - linkType: hard - -"@emotion/core@npm:^10.0.9": - version: 10.3.1 - resolution: "@emotion/core@npm:10.3.1" - dependencies: - "@babel/runtime": ^7.5.5 - "@emotion/cache": ^10.0.27 - "@emotion/css": ^10.0.27 - "@emotion/serialize": ^0.11.15 - "@emotion/sheet": 0.9.4 - "@emotion/utils": 0.11.3 - peerDependencies: - react: ">=16.3.0" - checksum: d2dad428e1b2cf0777badfb55e262d369273be9b2e6e9e7d61c953066c00811d544a6234db36b17ee07872ed092f4dd102bf6ffe2c76fc38d53eef3a60fddfd0 - languageName: node - linkType: hard - -"@emotion/css@npm:^10.0.27, @emotion/css@npm:^10.0.9": - version: 10.0.27 - resolution: "@emotion/css@npm:10.0.27" - dependencies: - "@emotion/serialize": ^0.11.15 - "@emotion/utils": 0.11.3 - babel-plugin-emotion: ^10.0.27 - checksum: 1420f5b514fc3a8500bcf90384b309b0d9acc9f687ec3a655166b55dc81d1661d6b6132ea6fe6730d0071c10da93bf9427937c22a90a18088af4ba5e11d59141 - languageName: node - linkType: hard - -"@emotion/hash@npm:0.8.0": - version: 0.8.0 - resolution: "@emotion/hash@npm:0.8.0" - checksum: 4b35d88a97e67275c1d990c96d3b0450451d089d1508619488fc0acb882cb1ac91e93246d471346ebd1b5402215941ef4162efe5b51534859b39d8b3a0e3ffaa - languageName: node - linkType: hard - "@emotion/hash@npm:^0.9.0": version: 0.9.0 resolution: "@emotion/hash@npm:0.9.0" @@ -4709,61 +4663,6 @@ __metadata: languageName: node linkType: hard -"@emotion/memoize@npm:0.7.4": - version: 0.7.4 - resolution: "@emotion/memoize@npm:0.7.4" - checksum: 4e3920d4ec95995657a37beb43d3f4b7d89fed6caa2b173a4c04d10482d089d5c3ea50bbc96618d918b020f26ed6e9c4026bbd45433566576c1f7b056c3271dc - languageName: node - linkType: hard - -"@emotion/serialize@npm:^0.11.15, @emotion/serialize@npm:^0.11.16": - version: 0.11.16 - resolution: "@emotion/serialize@npm:0.11.16" - dependencies: - "@emotion/hash": 0.8.0 - "@emotion/memoize": 0.7.4 - "@emotion/unitless": 0.7.5 - "@emotion/utils": 0.11.3 - csstype: ^2.5.7 - checksum: 2949832fab9d803e6236f2af6aad021c09c6b6722ae910b06b4ec3bfb84d77cbecfe3eab9a7dcc269ac73e672ef4b696c7836825931670cb110731712e331438 - languageName: node - linkType: hard - -"@emotion/sheet@npm:0.9.4": - version: 0.9.4 - resolution: "@emotion/sheet@npm:0.9.4" - checksum: 53bb833b4bb69ea2af04e1ecad164f78fb2614834d2820f584c909686a8e047c44e96a6e824798c5c558e6d95e10772454a9e5c473c5dbe0d198e50deb2815bc - languageName: node - linkType: hard - -"@emotion/stylis@npm:0.8.5": - version: 0.8.5 - resolution: "@emotion/stylis@npm:0.8.5" - checksum: 67ff5958449b2374b329fb96e83cb9025775ffe1e79153b499537c6c8b2eb64b77f32d7b5d004d646973662356ceb646afd9269001b97c54439fceea3203ce65 - languageName: node - linkType: hard - -"@emotion/unitless@npm:0.7.5": - version: 0.7.5 - resolution: "@emotion/unitless@npm:0.7.5" - checksum: f976e5345b53fae9414a7b2e7a949aa6b52f8bdbcc84458b1ddc0729e77ba1d1dfdff9960e0da60183877873d3a631fa24d9695dd714ed94bcd3ba5196586a6b - languageName: node - linkType: hard - -"@emotion/utils@npm:0.11.3": - version: 0.11.3 - resolution: "@emotion/utils@npm:0.11.3" - checksum: 9c4204bda84f9acd153a9be9478a83f9baa74d5d7a4c21882681c4d1b86cd113b84540cb1f92e1c30313b5075f024da2658dbc553f5b00776ef9b6ec7991c0c9 - languageName: node - linkType: hard - -"@emotion/weak-memoize@npm:0.2.5": - version: 0.2.5 - resolution: "@emotion/weak-memoize@npm:0.2.5" - checksum: 27d402b0c683b94658220b6d47840346ee582329ca2a15ec9c233492e0f1a27687ccb233b76eedc922f2e185e444cc89f7b97a81a1d3e5ae9f075bab08e965ea - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.17.18": version: 0.17.18 resolution: "@esbuild/android-arm64@npm:0.17.18" @@ -9955,25 +9854,21 @@ __metadata: dependencies: "@babel/eslint-parser": ~7.22.5 "@babel/preset-env": ~7.22.5 + "@babel/preset-typescript": ~7.22.5 "@rocket.chat/ddp-client": "workspace:^" "@rocket.chat/eslint-config": "workspace:^" "@rocket.chat/fuselage-tokens": next "@rocket.chat/logo": next "@rocket.chat/sdk": ^1.0.0-alpha.42 "@rocket.chat/ui-kit": next - "@storybook/addon-actions": ~6.5.16 - "@storybook/addon-backgrounds": ~6.5.16 "@storybook/addon-essentials": ~6.5.16 - "@storybook/addon-knobs": ~6.4.0 "@storybook/addon-postcss": ~2.0.0 - "@storybook/addon-viewport": ~6.5.16 - "@storybook/react": ~6.5.16 + "@storybook/preact": ~6.5.16 "@storybook/theming": ~6.5.16 "@typescript-eslint/eslint-plugin": ~5.60.0 "@typescript-eslint/parser": ~5.60.0 autoprefixer: ^9.8.8 babel-loader: ^8.3.0 - babel-plugin-jsx-pragmatic: ^1.0.2 cross-env: ^7.0.3 crypto-js: ^4.1.1 css-loader: ^4.3.0 @@ -10008,7 +9903,7 @@ __metadata: postcss-logical: ^4.0.2 postcss-scss: ^4.0.6 postcss-selector-not: ^4.0.1 - preact: ^10.8.2 + preact: 10.15.1 preact-router: ^3.2.1 query-string: ^7.1.3 react-dom: ~17.0.2 @@ -11350,7 +11245,7 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-backgrounds@npm:6.5.16, @storybook/addon-backgrounds@npm:~6.5.16": +"@storybook/addon-backgrounds@npm:6.5.16": version: 6.5.16 resolution: "@storybook/addon-backgrounds@npm:6.5.16" dependencies: @@ -11545,38 +11440,6 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-knobs@npm:~6.4.0": - version: 6.4.0 - resolution: "@storybook/addon-knobs@npm:6.4.0" - dependencies: - copy-to-clipboard: ^3.3.1 - core-js: ^3.8.2 - escape-html: ^1.0.3 - fast-deep-equal: ^3.1.3 - global: ^4.4.0 - lodash: ^4.17.20 - prop-types: ^15.7.2 - qs: ^6.10.0 - react-colorful: ^5.1.2 - react-lifecycles-compat: ^3.0.4 - react-select: ^3.2.0 - peerDependencies: - "@storybook/addons": ^6.4.0 - "@storybook/api": ^6.4.0 - "@storybook/components": ^6.4.0 - "@storybook/core-events": ^6.4.0 - "@storybook/theming": ^6.4.0 - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - peerDependenciesMeta: - react: - optional: true - react-dom: - optional: true - checksum: d9ea65af55e3983e7150aa0492f99027db06ca267eae5be19ace88f9733d1e858a35ba6cd0ff324f42fc32c03665ebf39e0f04777ec3b715300a2504de81a493 - languageName: node - linkType: hard - "@storybook/addon-links@npm:~6.5.16": version: 6.5.16 resolution: "@storybook/addon-links@npm:6.5.16" @@ -11691,7 +11554,7 @@ __metadata: languageName: node linkType: hard -"@storybook/addon-viewport@npm:6.5.16, @storybook/addon-viewport@npm:~6.5.16": +"@storybook/addon-viewport@npm:6.5.16": version: 6.5.16 resolution: "@storybook/addon-viewport@npm:6.5.16" dependencies: @@ -12381,6 +12244,37 @@ __metadata: languageName: node linkType: hard +"@storybook/preact@npm:~6.5.16": + version: 6.5.16 + resolution: "@storybook/preact@npm:6.5.16" + dependencies: + "@babel/plugin-transform-react-jsx": ^7.12.12 + "@storybook/addons": 6.5.16 + "@storybook/core": 6.5.16 + "@storybook/core-common": 6.5.16 + "@storybook/csf": 0.0.2--canary.4566f4d.1 + "@storybook/store": 6.5.16 + "@types/node": ^14.14.20 || ^16.0.0 + "@types/webpack-env": ^1.16.0 + core-js: ^3.8.2 + global: ^4.4.0 + react: 16.14.0 + react-dom: 16.14.0 + read-pkg-up: ^7.0.1 + regenerator-runtime: ^0.13.7 + ts-dedent: ^2.0.0 + webpack: ">=4.0.0 <6.0.0" + peerDependencies: + "@babel/core": "*" + preact: ^8.0.0||^10.0.0 + bin: + build-storybook: bin/build.js + start-storybook: bin/index.js + storybook-server: bin/index.js + checksum: ddad4337b2a05ff84522f16d51402f0d10956b2084aca69169cf9dedf0c19643a449da642bbb29fcf83c9571ada2a56da6da8d90208bd17a86e5123284bd51f5 + languageName: node + linkType: hard + "@storybook/preview-web@npm:6.5.16": version: 6.5.16 resolution: "@storybook/preview-web@npm:6.5.16" @@ -16725,24 +16619,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-emotion@npm:^10.0.27": - version: 10.2.2 - resolution: "babel-plugin-emotion@npm:10.2.2" - dependencies: - "@babel/helper-module-imports": ^7.0.0 - "@emotion/hash": 0.8.0 - "@emotion/memoize": 0.7.4 - "@emotion/serialize": ^0.11.16 - babel-plugin-macros: ^2.0.0 - babel-plugin-syntax-jsx: ^6.18.0 - convert-source-map: ^1.5.0 - escape-string-regexp: ^1.0.5 - find-root: ^1.1.0 - source-map: ^0.5.7 - checksum: 763f38c67ffbe7d091691d68c74686ba478296cc24716699fb5b0feddce1b1b47878a20b0bbe2aa4dea17f41074ead4deae7935d2cf6823638766709812c5b40 - languageName: node - linkType: hard - "babel-plugin-extract-import-names@npm:1.6.22": version: 1.6.22 resolution: "babel-plugin-extract-import-names@npm:1.6.22" @@ -16777,26 +16653,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-jsx-pragmatic@npm:^1.0.2": - version: 1.0.2 - resolution: "babel-plugin-jsx-pragmatic@npm:1.0.2" - dependencies: - babel-plugin-syntax-jsx: ^6.0.0 - checksum: dbb5e5c07d8ca8525c3023b32fd93cabaa9a452dcd3446e2612074c93210a9e1163657c97436719e80705c1bf3e5ac4d453d8fd25051ce03bfb2b027e1a397cf - languageName: node - linkType: hard - -"babel-plugin-macros@npm:^2.0.0": - version: 2.8.0 - resolution: "babel-plugin-macros@npm:2.8.0" - dependencies: - "@babel/runtime": ^7.7.2 - cosmiconfig: ^6.0.0 - resolve: ^1.12.0 - checksum: 59b09a21cf3ae1e14186c1b021917d004b49b953824b24953a54c6502da79e8051d4ac31cfd4a0ae7f6ea5ddf1f7edd93df4895dd3c3982a5b2431859c2889ac - languageName: node - linkType: hard - "babel-plugin-macros@npm:^3.0.1": version: 3.1.0 resolution: "babel-plugin-macros@npm:3.1.0" @@ -16910,13 +16766,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-syntax-jsx@npm:^6.0.0, babel-plugin-syntax-jsx@npm:^6.18.0": - version: 6.18.0 - resolution: "babel-plugin-syntax-jsx@npm:6.18.0" - checksum: 0c7ce5b81d6cfc01a7dd7a76a9a8f090ee02ba5c890310f51217ef1a7e6163fb7848994bbc14fd560117892e82240df9c7157ad0764da67ca5f2afafb73a7d27 - languageName: node - linkType: hard - "babel-polyfill@npm:^6.2.0": version: 6.26.0 resolution: "babel-polyfill@npm:6.26.0" @@ -19311,7 +19160,7 @@ __metadata: languageName: node linkType: hard -"convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.5.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": +"convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": version: 1.8.0 resolution: "convert-source-map@npm:1.8.0" dependencies: @@ -19402,15 +19251,6 @@ __metadata: languageName: node linkType: hard -"copy-to-clipboard@npm:^3.3.1": - version: 3.3.1 - resolution: "copy-to-clipboard@npm:3.3.1" - dependencies: - toggle-selection: ^1.0.6 - checksum: 3c7b1c333dc6a4b2e9905f52e4df6bbd34ff9f9c97ecd3ca55378a6bc1c191bb12a3252e6289c7b436e9188cff0360d393c0161626851d2301607860bbbdcfd5 - languageName: node - linkType: hard - "core-js-compat@npm:^3.25.1, core-js-compat@npm:^3.8.1": version: 3.25.1 resolution: "core-js-compat@npm:3.25.1" @@ -20068,13 +19908,6 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^2.5.7": - version: 2.6.20 - resolution: "csstype@npm:2.6.20" - checksum: cb5d5ded49c3390909e93b20b285d4a63d0ba5b10294bdfbc4cf911f80e91d6cf367ea671f99f09570762535c14ea7074a2c7fa73f02008203f01328dea8968b - languageName: node - linkType: hard - "csstype@npm:^3.0.2": version: 3.0.11 resolution: "csstype@npm:3.0.11" @@ -21016,16 +20849,6 @@ __metadata: languageName: node linkType: hard -"dom-helpers@npm:^5.0.1": - version: 5.2.1 - resolution: "dom-helpers@npm:5.2.1" - dependencies: - "@babel/runtime": ^7.8.7 - csstype: ^3.0.2 - checksum: 863ba9e086f7093df3376b43e74ce4422571d404fc9828bf2c56140963d5edf0e56160f9b2f3bb61b282c07f8fc8134f023c98fd684bddcb12daf7b0f14d951c - languageName: node - linkType: hard - "dom-serializer@npm:0": version: 0.2.2 resolution: "dom-serializer@npm:0.2.2" @@ -23383,13 +23206,6 @@ __metadata: languageName: node linkType: hard -"find-root@npm:^1.1.0": - version: 1.1.0 - resolution: "find-root@npm:1.1.0" - checksum: b2a59fe4b6c932eef36c45a048ae8f93c85640212ebe8363164814990ee20f154197505965f3f4f102efc33bfb1cbc26fd17c4a2fc739ebc51b886b137cbefaf - languageName: node - linkType: hard - "find-up@npm:5.0.0, find-up@npm:^5.0.0": version: 5.0.0 resolution: "find-up@npm:5.0.0" @@ -29611,7 +29427,7 @@ __metadata: languageName: node linkType: hard -"memoize-one@npm:^5.0.0, memoize-one@npm:^5.1.1": +"memoize-one@npm:^5.1.1": version: 5.2.1 resolution: "memoize-one@npm:5.2.1" checksum: a3cba7b824ebcf24cdfcd234aa7f86f3ad6394b8d9be4c96ff756dafb8b51c7f71320785fbc2304f1af48a0467cbbd2a409efc9333025700ed523f254cb52e3d @@ -33808,10 +33624,17 @@ __metadata: languageName: node linkType: hard -"preact@npm:^10.8.2": - version: 10.8.2 - resolution: "preact@npm:10.8.2" - checksum: 183358ba03b4c104c89b383ea926099b49acd0435f24e95dee9ff0e81350d1bfbd77908f6a2ed16b3654d75e84832fd5b4479216c85fe8af6ad8c2ebe795c4f9 +"preact@npm:10.15.1": + version: 10.15.1 + resolution: "preact@npm:10.15.1" + checksum: dabad11843b19b40b11846a25ff0b1fc4bc58268909e01a7faddb341a40983d24fbe7d44ad6e2c5d35e43091963af616800552fec9af44dd0a2f0f698d1bba1f + languageName: node + linkType: hard + +"preact@patch:preact@npm:10.15.1#.yarn/patches/preact-npm-10.15.1-bd458de913.patch::locator=rocket.chat%40workspace%3A.": + version: 10.15.1 + resolution: "preact@patch:preact@npm%3A10.15.1#.yarn/patches/preact-npm-10.15.1-bd458de913.patch::version=10.15.1&hash=6e6d0e&locator=rocket.chat%40workspace%3A." + checksum: 6258efa196625543b88cb02ca1fe4eb3e2867be82bc58209cd8f0dce5aefc3a7da0df4ca440c26731fed9085c780e8b6b265183dd2a16ad66216d8a6480868b7 languageName: node linkType: hard @@ -34150,7 +33973,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:^15.0.0, prop-types@npm:^15.5.4, prop-types@npm:^15.5.8, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": +"prop-types@npm:^15.0.0, prop-types@npm:^15.5.4, prop-types@npm:^15.6.0, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -34784,16 +34607,6 @@ __metadata: languageName: node linkType: hard -"react-colorful@npm:^5.1.2": - version: 5.5.1 - resolution: "react-colorful@npm:5.5.1" - peerDependencies: - react: ">=16.8.0" - react-dom: ">=16.8.0" - checksum: e60811781716e57f0990379eff20d6f22d4d35b9e858c47ecf857c1dc1c1a2274c924ded7248bad5f1e2fbf2aab06e59b12852910c8dee5e6850f8e4df293670 - languageName: node - linkType: hard - "react-docgen-typescript-plugin@npm:^1.0.5, react-docgen-typescript-plugin@npm:~1.0.5": version: 1.0.5 resolution: "react-docgen-typescript-plugin@npm:1.0.5" @@ -34841,6 +34654,20 @@ __metadata: languageName: node linkType: hard +"react-dom@npm:16.14.0": + version: 16.14.0 + resolution: "react-dom@npm:16.14.0" + dependencies: + loose-envify: ^1.1.0 + object-assign: ^4.1.1 + prop-types: ^15.6.2 + scheduler: ^0.19.1 + peerDependencies: + react: ^16.14.0 + checksum: 5a5c49da0f106b2655a69f96c622c347febcd10532db391c262b26aec225b235357d9da1834103457683482ab1b229af7a50f6927a6b70e53150275e31785544 + languageName: node + linkType: hard + "react-dom@npm:^17.0.2, react-dom@npm:~17.0.2": version: 17.0.2 resolution: "react-dom@npm:17.0.2" @@ -34927,17 +34754,6 @@ __metadata: languageName: node linkType: hard -"react-input-autosize@npm:^3.0.0": - version: 3.0.0 - resolution: "react-input-autosize@npm:3.0.0" - dependencies: - prop-types: ^15.5.8 - peerDependencies: - react: ^16.3.0 || ^17.0.0 - checksum: cc3309ddc87446ade742c7d0e88ef089dd8b6981f21506a2bb27daf01a8803ac697f64157c4ffc7e81dfcf3892b54a4072dbc3652fd9addcf6d22dd0b87ab723 - languageName: node - linkType: hard - "react-inspector@npm:^5.1.0": version: 5.1.1 resolution: "react-inspector@npm:5.1.1" @@ -35056,25 +34872,6 @@ __metadata: languageName: node linkType: hard -"react-select@npm:^3.2.0": - version: 3.2.0 - resolution: "react-select@npm:3.2.0" - dependencies: - "@babel/runtime": ^7.4.4 - "@emotion/cache": ^10.0.9 - "@emotion/core": ^10.0.9 - "@emotion/css": ^10.0.9 - memoize-one: ^5.0.0 - prop-types: ^15.6.0 - react-input-autosize: ^3.0.0 - react-transition-group: ^4.3.0 - peerDependencies: - react: ^16.8.0 || ^17.0.0 - react-dom: ^16.8.0 || ^17.0.0 - checksum: 082c818369fb8c7ce50bbd51260b21794f58dd41e9df5e0798c10e10478fb44b9fd88247f24720d5b443d77a6ec8afa733ecdd15a7722fde85c27bb87c379962 - languageName: node - linkType: hard - "react-split-pane@npm:^0.1.92": version: 0.1.92 resolution: "react-split-pane@npm:0.1.92" @@ -35129,21 +34926,6 @@ __metadata: languageName: node linkType: hard -"react-transition-group@npm:^4.3.0": - version: 4.4.2 - resolution: "react-transition-group@npm:4.4.2" - dependencies: - "@babel/runtime": ^7.5.5 - dom-helpers: ^5.0.1 - loose-envify: ^1.4.0 - prop-types: ^15.6.2 - peerDependencies: - react: ">=16.6.0" - react-dom: ">=16.6.0" - checksum: b67bf5b3e86dbab72d658b9a52a3589e5960583ab28c7c66272427d8fe30d4c7de422d5046ae96bd2683cdf80cc3264b2516f5ce80cae1dbe6cf3ca6dda392c5 - languageName: node - linkType: hard - "react-virtuoso@npm:^1.11.1": version: 1.11.1 resolution: "react-virtuoso@npm:1.11.1" @@ -35167,6 +34949,17 @@ __metadata: languageName: node linkType: hard +"react@npm:16.14.0": + version: 16.14.0 + resolution: "react@npm:16.14.0" + dependencies: + loose-envify: ^1.1.0 + object-assign: ^4.1.1 + prop-types: ^15.6.2 + checksum: 8484f3ecb13414526f2a7412190575fc134da785c02695eb92bb6028c930bfe1c238d7be2a125088fec663cc7cda0a3623373c46807cf2c281f49c34b79881ac + languageName: node + linkType: hard + "react@npm:^17.0.2, react@npm:~17.0.2": version: 17.0.2 resolution: "react@npm:17.0.2" @@ -36469,6 +36262,16 @@ __metadata: languageName: node linkType: hard +"scheduler@npm:^0.19.1": + version: 0.19.1 + resolution: "scheduler@npm:0.19.1" + dependencies: + loose-envify: ^1.1.0 + object-assign: ^4.1.1 + checksum: 73e185a59e2ff5aa3609f5b9cb97ddd376f89e1610579d29939d952411ca6eb7a24907a4ea4556569dacb931467a1a4a56d94fe809ef713aa76748642cd96a6c + languageName: node + linkType: hard + "scheduler@npm:^0.20.2": version: 0.20.2 resolution: "scheduler@npm:0.20.2" @@ -37366,7 +37169,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.5.0, source-map@npm:^0.5.6, source-map@npm:^0.5.7": +"source-map@npm:^0.5.0, source-map@npm:^0.5.6": version: 0.5.7 resolution: "source-map@npm:0.5.7" checksum: 5dc2043b93d2f194142c7f38f74a24670cd7a0063acdaf4bf01d2964b402257ae843c2a8fa822ad5b71013b5fcafa55af7421383da919752f22ff488bc553f4d @@ -39144,13 +38947,6 @@ __metadata: languageName: node linkType: hard -"toggle-selection@npm:^1.0.6": - version: 1.0.6 - resolution: "toggle-selection@npm:1.0.6" - checksum: a90dc80ed1e7b18db8f4e16e86a5574f87632dc729cfc07d9ea3ced50021ad42bb4e08f22c0913e0b98e3837b0b717e0a51613c65f30418e21eb99da6556a74c - languageName: node - linkType: hard - "toidentifier@npm:1.0.1": version: 1.0.1 resolution: "toidentifier@npm:1.0.1" @@ -41318,6 +41114,43 @@ __metadata: languageName: node linkType: hard +"webpack@npm:>=4.0.0 <6.0.0": + version: 5.88.0 + resolution: "webpack@npm:5.88.0" + dependencies: + "@types/eslint-scope": ^3.7.3 + "@types/estree": ^1.0.0 + "@webassemblyjs/ast": ^1.11.5 + "@webassemblyjs/wasm-edit": ^1.11.5 + "@webassemblyjs/wasm-parser": ^1.11.5 + acorn: ^8.7.1 + acorn-import-assertions: ^1.9.0 + browserslist: ^4.14.5 + chrome-trace-event: ^1.0.2 + enhanced-resolve: ^5.15.0 + es-module-lexer: ^1.2.1 + eslint-scope: 5.1.1 + events: ^3.2.0 + glob-to-regexp: ^0.4.1 + graceful-fs: ^4.2.9 + json-parse-even-better-errors: ^2.3.1 + loader-runner: ^4.2.0 + mime-types: ^2.1.27 + neo-async: ^2.6.2 + schema-utils: ^3.2.0 + tapable: ^2.1.1 + terser-webpack-plugin: ^5.3.7 + watchpack: ^2.4.0 + webpack-sources: ^3.2.3 + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: 9fd1568b34ec2e99ba97c8509a15ab2576ec80c396e7015551ec814b24cfc11de173acba3e114dafe95f1a6d460781b09d6201e6a1fb15110e1d01a09f61a283 + languageName: node + linkType: hard + "webpack@npm:>=4.43.0 <6.0.0": version: 5.74.0 resolution: "webpack@npm:5.74.0" From 9ed8d45a667a0c27557dda7e923e4abb9058d6a1 Mon Sep 17 00:00:00 2001 From: Kevin Aleman <kaleman960@gmail.com> Date: Tue, 27 Jun 2023 11:05:47 -0600 Subject: [PATCH 07/79] chore: Add tests to business hours and remove logs (#29626) --- .../business-hour/AbstractBusinessHour.ts | 1 - .../business-hour/BusinessHourManager.ts | 1 + .../server/functions/getDefaultUserFields.ts | 1 + .../app/livechat-enterprise/client/startup.ts | 6 +- .../tests/data/livechat/business-hours.ts | 5 +- .../tests/data/livechat/businessHours.ts | 18 +++ apps/meteor/tests/data/livechat/rooms.ts | 11 +- .../api/livechat/19-business-hours.ts | 120 +++++++++++++++++- 8 files changed, 149 insertions(+), 14 deletions(-) diff --git a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts index 1c43aaf97a444..7c0ddd007d157 100644 --- a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts +++ b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts @@ -73,7 +73,6 @@ export abstract class AbstractBusinessHourType { } private convertWorkHours(businessHourData: ILivechatBusinessHour): ILivechatBusinessHour { - console.log('convertWorkHours', businessHourData); businessHourData.workHours.forEach((hour: any) => { const startUtc = moment.tz(`${hour.day}:${hour.start}`, 'dddd:HH:mm', businessHourData.timezone.name).utc(); const finishUtc = moment.tz(`${hour.day}:${hour.finish}`, 'dddd:HH:mm', businessHourData.timezone.name).utc(); diff --git a/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts b/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts index ddb80126efece..b128fa64485e8 100644 --- a/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts +++ b/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts @@ -132,6 +132,7 @@ export class BusinessHourManager { } const { start, finish } = workHours; + await Promise.all(start.map(({ day, times }) => this.scheduleCronJob(times, day, 'open', this.openWorkHoursCallback))); await Promise.all(finish.map(({ day, times }) => this.scheduleCronJob(times, day, 'close', this.closeWorkHoursCallback))); } diff --git a/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts b/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts index c5c939cfcaa5a..03d0cae77ab94 100644 --- a/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts +++ b/apps/meteor/app/utils/server/functions/getDefaultUserFields.ts @@ -35,4 +35,5 @@ export const getDefaultUserFields = (): DefaultUserFields => ({ '_updatedAt': 1, 'avatarETag': 1, 'extension': 1, + 'openBusinessHours': 1, }); diff --git a/apps/meteor/ee/app/livechat-enterprise/client/startup.ts b/apps/meteor/ee/app/livechat-enterprise/client/startup.ts index 8b4f715498623..b97823adf25e6 100644 --- a/apps/meteor/ee/app/livechat-enterprise/client/startup.ts +++ b/apps/meteor/ee/app/livechat-enterprise/client/startup.ts @@ -8,14 +8,14 @@ import { EESingleBusinessHourBehaviour } from './SingleBusinessHour'; import { hasLicense } from '../../license/client'; const businessHours: Record<string, IBusinessHourBehavior> = { - Multiple: new MultipleBusinessHoursBehavior(), - Single: new EESingleBusinessHourBehaviour(), + multiple: new MultipleBusinessHoursBehavior(), + single: new EESingleBusinessHourBehaviour(), }; Meteor.startup(function () { settings.onload('Livechat_business_hour_type', async (_, value) => { if (await hasLicense('livechat-enterprise')) { - businessHourManager.registerBusinessHourBehavior(businessHours[value as string]); + businessHourManager.registerBusinessHourBehavior(businessHours[(value as string).toLowerCase()]); } }); }); diff --git a/apps/meteor/tests/data/livechat/business-hours.ts b/apps/meteor/tests/data/livechat/business-hours.ts index 28177c63056b8..f3335047cca3f 100644 --- a/apps/meteor/tests/data/livechat/business-hours.ts +++ b/apps/meteor/tests/data/livechat/business-hours.ts @@ -2,10 +2,11 @@ import type { ILivechatBusinessHour } from '@rocket.chat/core-typings'; import { credentials, methodCall, request } from '../api-data'; export const saveBusinessHour = async (businessHour: ILivechatBusinessHour) => { - await request + const { body } = await request .post(methodCall('livechat:saveBusinessHour')) .set(credentials) .send({ message: JSON.stringify({ params: [businessHour], msg: 'method', method: 'livechat:saveBusinessHour', id: '101' }) }) .expect(200); - return true; + + return JSON.parse(body.message); }; diff --git a/apps/meteor/tests/data/livechat/businessHours.ts b/apps/meteor/tests/data/livechat/businessHours.ts index ec254a88fc814..59b232a38d227 100644 --- a/apps/meteor/tests/data/livechat/businessHours.ts +++ b/apps/meteor/tests/data/livechat/businessHours.ts @@ -1,5 +1,8 @@ +import { ILivechatBusinessHour } from "@rocket.chat/core-typings"; import { api, credentials, methodCall, request } from "../api-data"; import { updateEESetting, updateSetting } from "../permissions.helper" +import moment from "moment"; +type ISaveBhApiWorkHour = Omit<ILivechatBusinessHour, '_id' | 'ts' | 'timezone'> & { workHours: { day: string, start: string, finish: string, open: boolean }[] } & { departmentsToApplyBusinessHour?: string } & { timezoneName: string }; export const makeDefaultBusinessHourActiveAndClosed = async () => { // enable settings @@ -72,3 +75,18 @@ export const disableDefaultBusinessHour = async () => { }), }); } + +export const getWorkHours = (open = true): ISaveBhApiWorkHour['workHours'] => { + const workHours: ISaveBhApiWorkHour['workHours'] = []; + + for (let i = 0; i < 7; i++) { + workHours.push({ + day: moment().day(i).format('dddd'), + start: '00:00', + finish: '23:59', + open, + }); + } + + return workHours; +} diff --git a/apps/meteor/tests/data/livechat/rooms.ts b/apps/meteor/tests/data/livechat/rooms.ts index 70c731b69ea80..bceedec83b1c1 100644 --- a/apps/meteor/tests/data/livechat/rooms.ts +++ b/apps/meteor/tests/data/livechat/rooms.ts @@ -12,6 +12,7 @@ import { getSettingValueById, updatePermission, updateSetting } from '../permiss import { IUserCredentialsHeader, adminUsername } from '../user'; import { getRandomVisitorToken } from './users'; import { DummyResponse, sleep } from './utils'; +import { Response } from 'supertest'; export const createLivechatRoom = async (visitorToken: string, extraRoomParams?: Record<string, string>): Promise<IOmnichannelRoom> => { const urlParams = new URLSearchParams(); @@ -153,7 +154,7 @@ export const createManager = (overrideUsername?: string): Promise<ILivechatAgent }); }); -export const makeAgentAvailable = async (overrideCredentials?: { 'X-Auth-Token': string; 'X-User-Id': string }): Promise<void> => { +export const makeAgentAvailable = async (overrideCredentials?: { 'X-Auth-Token': string | undefined; 'X-User-Id': string | undefined }): Promise<Response> => { await updatePermission('view-l-room', ['livechat-agent', 'livechat-manager', 'admin']); await request .post(api('users.setStatus')) @@ -161,16 +162,14 @@ export const makeAgentAvailable = async (overrideCredentials?: { 'X-Auth-Token': .send({ message: '', status: 'online', - }) - .expect(200); + }); - await request + return request .post(api('livechat/agent.status')) .set(overrideCredentials || credentials) .send({ status: 'available', - }) - .expect(200); + }); }; export const makeAgentUnavailable = async (overrideCredentials?: { 'X-Auth-Token': string; 'X-User-Id': string }): Promise<void> => { diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index 5342f47f5ea58..eb5393920c2d1 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -1,12 +1,14 @@ /* eslint-env mocha */ -import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; +import { LivechatBusinessHourTypes, LivechatBusinessHourBehaviors } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; import { saveBusinessHour } from '../../../data/livechat/business-hours'; import { updatePermission, updateSetting } from '../../../data/permissions.helper'; import { IS_EE } from '../../../e2e/config/constants'; +import { createAgent, makeAgentAvailable } from '../../../data/livechat/rooms'; +import { getWorkHours } from '../../../data/livechat/businessHours'; describe('[CE] LIVECHAT - business hours', function () { this.retries(0); @@ -15,8 +17,11 @@ describe('[CE] LIVECHAT - business hours', function () { before(async () => { await updateSetting('Livechat_enabled', true); + await updateSetting('Livechat_enable_business_hours', true); + await createAgent(); }); + let defaultBhId: any; describe('livechat/business-hour', () => { it('should fail when user doesnt have view-livechat-business-hours permission', async () => { await updatePermission('view-livechat-business-hours', []); @@ -43,6 +48,36 @@ describe('[CE] LIVECHAT - business hours', function () { expect(response.body.businessHour.workHours[0].finish).to.be.an('object'); expect(response.body.businessHour.workHours[0].open).to.be.a('boolean'); expect(response.body.businessHour.timezone).to.be.an('object').that.has.property('name').that.is.an('string'); + + defaultBhId = response.body.businessHour; + }); + it('should not allow a user to be available if BH are closed', async () => { + await saveBusinessHour({ + ...defaultBhId, + workHours: [ + { + day: 'Monday', + open: true, + start: '00:00', + finish: '00:01', + }, + ], + }); + + const { body } = await makeAgentAvailable(credentials); + + expect(body).to.have.property('success', false); + expect(body.error).to.be.equal('error-business-hours-are-closed'); + }); + it('should allow a user to be available if BH are open', async () => { + await saveBusinessHour({ + ...defaultBhId, + workHours: getWorkHours(true), + }); + + const { body } = await makeAgentAvailable(credentials); + + expect(body).to.have.property('success', true); }); }); @@ -72,7 +107,7 @@ describe('[CE] LIVECHAT - business hours', function () { }); it('should return a just created custom business hour', async () => { const name = `business-hour-${Date.now()}`; - await updateSetting('Livechat_business_hour_type', 'multiple'); + await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE); await saveBusinessHour({ name, active: true, @@ -109,5 +144,86 @@ describe('[CE] LIVECHAT - business hours', function () { expect(body.businessHours[0].workHours[0]).to.have.property('finish').that.is.an('object'); expect(body.businessHours[0]).to.have.property('timezone').that.is.an('object').with.property('name', 'America/Sao_Paulo'); }); + it('should fail if start and finish time are the same', async () => { + const name = `business-hour-${Date.now()}`; + await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE); + const result = await saveBusinessHour({ + name, + active: true, + type: LivechatBusinessHourTypes.CUSTOM, + workHours: [ + { + day: 'Monday', + open: true, + // @ts-expect-error - this is valid for endpoint, actual type converts this into an object + start: '08:00', + // @ts-expect-error - same as previous one + finish: '08:00', + }, + ], + timezone: { + name: 'America/Sao_Paulo', + utc: '-03:00', + }, + departmentsToApplyBusinessHour: '', + timezoneName: 'America/Sao_Paulo', + }); + + expect(result).to.have.property('error'); + }); + it('should fail if finish is before start time', async () => { + const name = `business-hour-${Date.now()}`; + await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE); + const result = await saveBusinessHour({ + name, + active: true, + type: LivechatBusinessHourTypes.CUSTOM, + workHours: [ + { + day: 'Monday', + open: true, + // @ts-expect-error - this is valid for endpoint, actual type converts this into an object + start: '10:00', + // @ts-expect-error - same as previous one + finish: '08:00', + }, + ], + timezone: { + name: 'America/Sao_Paulo', + utc: '-03:00', + }, + departmentsToApplyBusinessHour: '', + timezoneName: 'America/Sao_Paulo', + }); + + expect(result).to.have.property('error'); + }); + it('should fail if data is invalid', async () => { + const name = `business-hour-${Date.now()}`; + await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE); + const result = await saveBusinessHour({ + name, + active: true, + type: LivechatBusinessHourTypes.CUSTOM, + workHours: [ + { + day: 'Monday', + open: true, + // @ts-expect-error - this is valid for endpoint, actual type converts this into an object + start: '20000', + // @ts-expect-error - same as previous one + finish: 'xxxxx', + }, + ], + timezone: { + name: 'America/Sao_Paulo', + utc: '-03:00', + }, + departmentsToApplyBusinessHour: '', + timezoneName: 'America/Sao_Paulo', + }); + + expect(result).to.have.property('error'); + }); }); }); From ef107614e53b4ba48ca3a5737f402cec824fdabe Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Tue, 27 Jun 2023 15:25:36 -0300 Subject: [PATCH 08/79] fix: Canned responses text editor without contrast in dark mode (#29636) --- .changeset/smooth-hotels-hunt.md | 5 +++++ .../CannedResponse/TextEditor/Textarea.tsx | 19 ++++--------------- 2 files changed, 9 insertions(+), 15 deletions(-) create mode 100644 .changeset/smooth-hotels-hunt.md diff --git a/.changeset/smooth-hotels-hunt.md b/.changeset/smooth-hotels-hunt.md new file mode 100644 index 0000000000000..a8a0b5ef7dd79 --- /dev/null +++ b/.changeset/smooth-hotels-hunt.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed Canned Responses text editor having no contrast in dark mode. diff --git a/apps/meteor/ee/client/omnichannel/components/CannedResponse/TextEditor/Textarea.tsx b/apps/meteor/ee/client/omnichannel/components/CannedResponse/TextEditor/Textarea.tsx index 518628fa20294..69b7124bbb117 100644 --- a/apps/meteor/ee/client/omnichannel/components/CannedResponse/TextEditor/Textarea.tsx +++ b/apps/meteor/ee/client/omnichannel/components/CannedResponse/TextEditor/Textarea.tsx @@ -1,22 +1,11 @@ -import { Box } from '@rocket.chat/fuselage'; +import { TextAreaInput } from '@rocket.chat/fuselage'; import type { ComponentProps } from 'react'; import React, { forwardRef } from 'react'; -type TextareaProps = ComponentProps<typeof Box>; +type TextareaProps = ComponentProps<typeof TextAreaInput>; -const Textarea = forwardRef<Element, TextareaProps>(function Textarea(props, ref) { - return ( - <Box - is='textarea' - ref={ref} - w='full' - style={{ wordBreak: 'normal' }} - rcx-box--animated - rcx-input-box--type={'textarea'} - rcx-input-box--undecorated - {...props} - /> - ); +const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(function Textarea(props, ref) { + return <TextAreaInput ref={ref} w='full' style={{ wordBreak: 'normal' }} rcx-input-box--undecorated {...props} />; }); export default Textarea; From 6f3eeec009ef3d233e7b022d9c13bfd2cd3b534b Mon Sep 17 00:00:00 2001 From: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Date: Tue, 27 Jun 2023 19:40:00 -0300 Subject: [PATCH 09/79] fix(meteor): Video Record button disabled on iOS browsers (#29649) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .changeset/itchy-hotels-care.md | 5 +++++ .../ui/client/lib/recorderjs/videoRecorder.ts | 18 +++++++++++------- .../VideoMessageRecorder.tsx | 16 ++++++++++++---- .../actions/VideoMessageAction.tsx | 3 ++- 4 files changed, 30 insertions(+), 12 deletions(-) create mode 100644 .changeset/itchy-hotels-care.md diff --git a/.changeset/itchy-hotels-care.md b/.changeset/itchy-hotels-care.md new file mode 100644 index 0000000000000..93aed3ed6dce4 --- /dev/null +++ b/.changeset/itchy-hotels-care.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fixed video message button disabled on iOS browsers diff --git a/apps/meteor/app/ui/client/lib/recorderjs/videoRecorder.ts b/apps/meteor/app/ui/client/lib/recorderjs/videoRecorder.ts index 9d6a445218941..10424c5b3f860 100644 --- a/apps/meteor/app/ui/client/lib/recorderjs/videoRecorder.ts +++ b/apps/meteor/app/ui/client/lib/recorderjs/videoRecorder.ts @@ -17,6 +17,16 @@ class VideoRecorder { private mediaRecorder: MediaRecorder | undefined; + public getSupportedMimeTypes() { + if (window.MediaRecorder.isTypeSupported('video/webm')) { + return 'video/webm; codecs=vp8,opus'; + } + if (window.MediaRecorder.isTypeSupported('video/mp4')) { + return 'video/mp4'; + } + return ''; + } + public start(videoel?: HTMLVideoElement, cb?: (this: this, success: boolean) => void) { this.videoel = videoel; @@ -51,7 +61,7 @@ class VideoRecorder { return; } - this.mediaRecorder = new MediaRecorder(this.stream, { mimeType: 'video/webm; codecs=vp8,opus' }); + this.mediaRecorder = new MediaRecorder(this.stream, { mimeType: this.getSupportedMimeTypes() }); this.mediaRecorder.ondataavailable = (blobev) => { this.chunks.push(blobev.data); if (!this.recordingAvailable.get()) { @@ -66,7 +76,6 @@ class VideoRecorder { if (!this.videoel) { return; } - this.stream = stream; try { @@ -76,11 +85,6 @@ class VideoRecorder { this.videoel.src = URL.createObjectURL(stream as unknown as MediaSource | Blob); } - this.videoel.muted = true; - this.videoel.onloadedmetadata = () => { - void this.videoel?.play(); - }; - this.started = true; return this.cameraStarted.set(true); } diff --git a/apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx b/apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx index f9c1f2ed5a972..5d43a07d8b0b1 100644 --- a/apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx +++ b/apps/meteor/client/views/composer/VideoMessageRecorder/VideoMessageRecorder.tsx @@ -29,6 +29,14 @@ const videoContainerClass = css` } `; +const getVideoRecordingExtension = () => { + const supported = VideoRecorder.getSupportedMimeTypes(); + if (supported.match(/video\/webm/)) { + return 'webm'; + } + return 'mp4'; +}; + const VideoMessageRecorder = ({ rid, tmid, chatContext, reference }: VideoMessageRecorderProps) => { const t = useTranslation(); const videoRef = useRef<HTMLVideoElement>(null); @@ -75,8 +83,8 @@ const VideoMessageRecorder = ({ rid, tmid, chatContext, reference }: VideoMessag const handleSendRecord = async () => { const cb = async (blob: Blob) => { - const fileName = `${t('Video_record')}.webm`; - const file = new File([blob], fileName, { type: 'video/webm' }); + const fileName = `${t('Video_record')}.${getVideoRecordingExtension()}`; + const file = new File([blob], fileName, { type: VideoRecorder.getSupportedMimeTypes().split(';')[0] }); await chat?.flows.uploadFiles([file]); chat?.composer?.setRecordingVideo(false); }; @@ -94,7 +102,7 @@ const VideoMessageRecorder = ({ rid, tmid, chatContext, reference }: VideoMessag }; useEffect(() => { - if (!window.MediaRecorder.isTypeSupported('video/webm; codecs=vp8,opus')) { + if (!VideoRecorder.getSupportedMimeTypes()) { return dispatchToastMessage({ type: 'error', message: t('Browser_does_not_support_recording_video') }); } @@ -109,7 +117,7 @@ const VideoMessageRecorder = ({ rid, tmid, chatContext, reference }: VideoMessag <PositionAnimated visible='visible' anchor={reference} placement='top-end'> <Box bg='light' padding={4} borderRadius={4} elevation='2'> <Box className={videoContainerClass} overflow='hidden' height={240} borderRadius={4}> - <video ref={videoRef} width={320} height={240} /> + <video muted autoPlay playsInline ref={videoRef} width={320} height={240} /> </Box> <Box mbs={4} display='flex' justifyContent='space-between'> <Button small onClick={handleRecord}> diff --git a/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBoxActionsToolbar/actions/VideoMessageAction.tsx b/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBoxActionsToolbar/actions/VideoMessageAction.tsx index ec7796830a1da..d77be675243ff 100644 --- a/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBoxActionsToolbar/actions/VideoMessageAction.tsx +++ b/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBoxActionsToolbar/actions/VideoMessageAction.tsx @@ -5,6 +5,7 @@ import { useTranslation, useSetting } from '@rocket.chat/ui-contexts'; import type { AllHTMLAttributes } from 'react'; import React, { useEffect, useMemo } from 'react'; +import { VideoRecorder } from '../../../../../../../../../app/ui/client/lib/recorderjs/videoRecorder'; import type { ChatAPI } from '../../../../../../../../lib/chats/ChatAPI'; import { useChat } from '../../../../../../contexts/ChatContext'; import { useMediaActionTitle } from '../../hooks/useMediaActionTitle'; @@ -33,7 +34,7 @@ const VideoMessageAction = ({ collapsed, chatContext, disabled, ...props }: Vide isVideoRecorderEnabled && !fileUploadMediaTypeBlackList?.match(/video\/webm|video\/\*/i) && (!fileUploadMediaTypeWhiteList || fileUploadMediaTypeWhiteList.match(/video\/webm|video\/\*/i)) && - window.MediaRecorder.isTypeSupported('video/webm; codecs=vp8,opus'), + Boolean(VideoRecorder.getSupportedMimeTypes()), ), [fileUploadMediaTypeBlackList, fileUploadMediaTypeWhiteList, isFileUploadEnabled, isPermissionDenied, isVideoRecorderEnabled], ); From 361864a745ed6578d3097985c99792db9d910791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Wed, 28 Jun 2023 13:33:16 -0300 Subject: [PATCH 10/79] chore: Menu v2 (#29580) --- .../AdministrationList/AdministrationList.tsx | 22 -- .../AdministrationModelList.spec.tsx | 141 ------------- .../AdministrationModelList.tsx | 107 ---------- .../AdministrationList/AppsModelList.spec.tsx | 141 ------------- .../AdministrationList/AppsModelList.tsx | 101 --------- .../AuditModelList.spec.tsx | 55 ----- .../AdministrationList/AuditModelList.tsx | 51 ----- apps/meteor/client/components/GenericMenu.tsx | 64 ++++++ .../client/components/GenericMenuItem.tsx | 21 ++ .../components/SortList/GroupingList.tsx | 54 ----- .../client/components/SortList/SortList.tsx | 21 -- .../components/SortList/SortModeList.tsx | 46 ----- .../components/SortList/ViewModeList.tsx | 65 ------ .../client/components/SortList/index.ts | 1 - .../client/hooks/useHandleMenuAction.tsx | 10 + .../client/providers/TooltipProvider.tsx | 2 +- .../sidebar/header/actions/Administration.tsx | 105 ++-------- .../sidebar/header/actions/CreateRoom.tsx | 42 ++-- .../sidebar/header/actions/CreateRoomList.tsx | 109 ---------- .../client/sidebar/header/actions/Sort.tsx | 27 +-- .../actions/hooks/useAdministrationItems.tsx | 94 +++++++++ .../actions/hooks/useAdministrationMenu.tsx | 75 +++++++ .../header/actions/hooks/useAppsItems.tsx | 78 +++++++ .../header/actions/hooks/useAuditItems.tsx | 30 +++ .../actions/hooks/useCreateRoomItems.tsx | 83 ++++++++ .../actions/hooks/useCreateRoomMenu.tsx | 24 +++ .../actions/hooks/useGroupingListItems.tsx | 43 ++++ .../hooks/useMatrixFederationItems.tsx.tsx | 26 +++ .../header/actions/hooks/useSortMenu.tsx | 17 ++ .../header/actions/hooks/useSortModeItems.tsx | 46 +++++ .../header/actions/hooks/useViewModeItems.tsx | 53 +++++ .../header/hooks/useCreateRoomModal.tsx | 8 +- .../tests/e2e/administration-menu.spec.ts | 2 +- .../page-objects/fragments/home-sidenav.ts | 6 +- .../header/actions/Administration.spec.tsx | 192 ------------------ ee/packages/ui-theming/src/paletteDark.ts | 2 +- yarn.lock | 34 ++-- 37 files changed, 731 insertions(+), 1267 deletions(-) delete mode 100644 apps/meteor/client/components/AdministrationList/AdministrationList.tsx delete mode 100644 apps/meteor/client/components/AdministrationList/AdministrationModelList.spec.tsx delete mode 100644 apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx delete mode 100644 apps/meteor/client/components/AdministrationList/AppsModelList.spec.tsx delete mode 100644 apps/meteor/client/components/AdministrationList/AppsModelList.tsx delete mode 100644 apps/meteor/client/components/AdministrationList/AuditModelList.spec.tsx delete mode 100644 apps/meteor/client/components/AdministrationList/AuditModelList.tsx create mode 100644 apps/meteor/client/components/GenericMenu.tsx create mode 100644 apps/meteor/client/components/GenericMenuItem.tsx delete mode 100644 apps/meteor/client/components/SortList/GroupingList.tsx delete mode 100644 apps/meteor/client/components/SortList/SortList.tsx delete mode 100644 apps/meteor/client/components/SortList/SortModeList.tsx delete mode 100644 apps/meteor/client/components/SortList/ViewModeList.tsx delete mode 100644 apps/meteor/client/components/SortList/index.ts create mode 100644 apps/meteor/client/hooks/useHandleMenuAction.tsx delete mode 100644 apps/meteor/client/sidebar/header/actions/CreateRoomList.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useAdministrationMenu.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useAuditItems.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomMenu.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useGroupingListItems.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useMatrixFederationItems.tsx.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useSortMenu.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useSortModeItems.tsx create mode 100644 apps/meteor/client/sidebar/header/actions/hooks/useViewModeItems.tsx delete mode 100644 apps/meteor/tests/unit/client/sidebar/header/actions/Administration.spec.tsx diff --git a/apps/meteor/client/components/AdministrationList/AdministrationList.tsx b/apps/meteor/client/components/AdministrationList/AdministrationList.tsx deleted file mode 100644 index a57671a1534af..0000000000000 --- a/apps/meteor/client/components/AdministrationList/AdministrationList.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { OptionDivider } from '@rocket.chat/fuselage'; -import type { ReactElement, ReactNode } from 'react'; -import React, { Fragment } from 'react'; - -type AdministrationListProps = { - optionsList: ReactNode[]; -}; - -const AdministrationList = ({ optionsList }: AdministrationListProps): ReactElement => { - return ( - <> - {optionsList.map((item, index) => ( - <Fragment key={index}> - {index > 0 && <OptionDivider />} - {item} - </Fragment> - ))} - </> - ); -}; - -export default AdministrationList; diff --git a/apps/meteor/client/components/AdministrationList/AdministrationModelList.spec.tsx b/apps/meteor/client/components/AdministrationList/AdministrationModelList.spec.tsx deleted file mode 100644 index 4d98d15f3e3c9..0000000000000 --- a/apps/meteor/client/components/AdministrationList/AdministrationModelList.spec.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { expect, spy } from 'chai'; -import proxyquire from 'proxyquire'; -import type { ReactNode } from 'react'; -import React from 'react'; - -import ModalContextMock from '../../../tests/mocks/client/ModalContextMock'; -import RouterContextMock from '../../../tests/mocks/client/RouterContextMock'; -import type * as AdministrationModelListModule from './AdministrationModelList'; - -describe('AdministrationModelList', () => { - const loadMock = (stubs?: Record<string, unknown>) => { - return proxyquire.noCallThru().load<typeof AdministrationModelListModule>('./AdministrationModelList', { - '../../../app/ui-utils/client': {}, - 'meteor/kadira:flow-router': { - FlowRouter: {}, - }, - '../../views/hooks/useUpgradeTabParams': { - useUpgradeTabParams: () => ({ - isLoading: false, - tabType: 'Upgrade', - trialEndDate: '2020-01-01', - }), - }, - '../../../lib/upgradeTab': { - getUpgradeTabLabel: () => 'Upgrade', - isFullyFeature: () => true, - }, - '../../../app/authorization/client': { - userHasAllPermission: () => true, - }, - '@tanstack/react-query': { - useQuery: () => '', - }, - ...stubs, - }).default; - }; - - it('should render administration', async () => { - const AdministrationModelList = loadMock(); - render(<AdministrationModelList accountBoxItems={[]} showWorkspace={true} onDismiss={() => null} />, { wrapper: ModalContextMock }); - - expect(screen.getByText('Administration')).to.exist; - expect(screen.getByText('Workspace')).to.exist; - expect(screen.getByText('Upgrade')).to.exist; - }); - - it('should not render workspace', async () => { - const AdministrationModelList = loadMock(); - render(<AdministrationModelList accountBoxItems={[]} showWorkspace={false} onDismiss={() => null} />, { wrapper: ModalContextMock }); - - expect(screen.getByText('Administration')).to.exist; - expect(screen.queryByText('Workspace')).to.not.exist; - expect(screen.getByText('Upgrade')).to.exist; - }); - - context('when clicked', () => { - const pushRoute = spy(); - const handleDismiss = spy(); - - const ProvidersMock = ({ children }: { children: ReactNode }) => { - return ( - <ModalContextMock> - <RouterContextMock pushRoute={pushRoute}>{children}</RouterContextMock> - </ModalContextMock> - ); - }; - - it('should go to admin index', async () => { - const AdministrationModelList = loadMock(); - - render(<AdministrationModelList accountBoxItems={[]} showWorkspace={true} onDismiss={handleDismiss} />, { wrapper: ProvidersMock }); - - const button = screen.getByText('Workspace'); - - userEvent.click(button); - await waitFor(() => expect(pushRoute).to.have.been.called.with('admin-index')); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - - it('should call upgrade route', async () => { - const AdministrationModelList = loadMock(); - - render(<AdministrationModelList accountBoxItems={[]} showWorkspace={false} onDismiss={handleDismiss} />, { wrapper: ProvidersMock }); - - const button = screen.getByText('Upgrade'); - - userEvent.click(button); - - await waitFor(() => expect(pushRoute).to.have.been.called.with('upgrade', { type: 'Upgrade' }, { trialEndDate: '2020-01-01' })); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - - it('should render admin box and call router', async () => { - const router = spy(); - const handleDismiss = spy(); - const AdministrationModelList = loadMock({ - 'meteor/kadira:flow-router': { - FlowRouter: { - go: router, - }, - }, - }); - - render( - <AdministrationModelList - accountBoxItems={[{ name: 'Admin Item', href: 'admin-item' } as any]} - showWorkspace={false} - onDismiss={handleDismiss} - />, - { wrapper: ProvidersMock }, - ); - - const button = screen.getByText('Admin Item'); - - userEvent.click(button); - - await waitFor(() => expect(router).to.have.been.called.with('admin-item')); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - - it('should render admin box and call sidenav', async () => { - const AdministrationModelList = loadMock(); - - render( - <AdministrationModelList - accountBoxItems={[{ name: 'Admin Item', sideNav: 'admin' } as any]} - showWorkspace={false} - onDismiss={handleDismiss} - />, - { wrapper: ProvidersMock }, - ); - - const button = screen.getByText('Admin Item'); - - userEvent.click(button); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - }); -}); diff --git a/apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx b/apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx deleted file mode 100644 index 3add4ad541284..0000000000000 --- a/apps/meteor/client/components/AdministrationList/AdministrationModelList.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { OptionTitle } from '@rocket.chat/fuselage'; -import { useTranslation, useRoute, useMethod, useSetModal, useRole } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import type { FC } from 'react'; -import React from 'react'; - -import type { AccountBoxItem } from '../../../app/ui-utils/client/lib/AccountBox'; -import { getUpgradeTabLabel, isFullyFeature } from '../../../lib/upgradeTab'; -import RegisterWorkspaceModal from '../../views/admin/cloud/modals/RegisterWorkspaceModal'; -import { useUpgradeTabParams } from '../../views/hooks/useUpgradeTabParams'; -import Emoji from '../Emoji'; -import ListItem from '../Sidebar/ListItem'; - -type AdministrationModelListProps = { - accountBoxItems: AccountBoxItem[]; - showWorkspace: boolean; - onDismiss: () => void; -}; - -const AdministrationModelList: FC<AdministrationModelListProps> = ({ accountBoxItems, showWorkspace, onDismiss }) => { - const t = useTranslation(); - const { tabType, trialEndDate, isLoading } = useUpgradeTabParams(); - const shouldShowEmoji = isFullyFeature(tabType); - const label = getUpgradeTabLabel(tabType); - const isAdmin = useRole('admin'); - const setModal = useSetModal(); - - const checkCloudRegisterStatus = useMethod('cloud:checkRegisterStatus'); - const result = useQuery(['admin/cloud/register-status'], async () => checkCloudRegisterStatus()); - const { workspaceRegistered } = result.data || {}; - - const handleRegisterWorkspaceClick = (): void => { - const handleModalClose = (): void => setModal(null); - setModal(<RegisterWorkspaceModal onClose={handleModalClose} />); - }; - - const adminRoute = useRoute('admin-index'); - const upgradeRoute = useRoute('upgrade'); - const cloudRoute = useRoute('cloud'); - const showUpgradeItem = !isLoading && tabType; - - return ( - <> - <OptionTitle>{t('Administration')}</OptionTitle> - <ul> - {showUpgradeItem && ( - <ListItem - icon='arrow-stack-up' - role='listitem' - text={ - <> - {t(label)} {shouldShowEmoji && <Emoji emojiHandle=':zap:' />} - </> - } - onClick={() => { - upgradeRoute.push({ type: tabType }, trialEndDate ? { trialEndDate } : undefined); - onDismiss(); - }} - /> - )} - {isAdmin && ( - <ListItem - icon='cloud-plus' - role='listitem' - text={workspaceRegistered ? t('Registration') : t('Register')} - onClick={() => { - if (workspaceRegistered) { - cloudRoute.push({ context: '/' }); - onDismiss(); - return; - } - handleRegisterWorkspaceClick(); - }} - /> - )} - {showWorkspace && ( - <ListItem - icon='cog' - role='listitem' - text={t('Workspace')} - onClick={() => { - adminRoute.push({ context: '/' }); - onDismiss(); - }} - /> - )} - {accountBoxItems.length > 0 && ( - <> - {accountBoxItems.map((item, key) => { - const action = () => { - if (item.href) { - FlowRouter.go(item.href); - } - onDismiss(); - }; - - return <ListItem role='listitem' text={t(item.name)} icon={item.icon} onClick={action} key={item.name + key} />; - })} - </> - )} - </ul> - </> - ); -}; - -export default AdministrationModelList; diff --git a/apps/meteor/client/components/AdministrationList/AppsModelList.spec.tsx b/apps/meteor/client/components/AdministrationList/AppsModelList.spec.tsx deleted file mode 100644 index ffd2eabab93ff..0000000000000 --- a/apps/meteor/client/components/AdministrationList/AppsModelList.spec.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { expect, spy } from 'chai'; -import proxyquire from 'proxyquire'; -import type { ReactNode } from 'react'; -import React from 'react'; - -import RouterContextMock from '../../../tests/mocks/client/RouterContextMock'; -import type * as AppsModelListModel from './AppsModelList'; - -describe('AppsModelList', () => { - const loadMock = (stubs?: Record<string, unknown>) => { - return proxyquire.noCallThru().load<typeof AppsModelListModel>('./AppsModelList', { - '../../../app/ui-message/client/ActionManager': { - triggerActionButtonAction: {}, - }, - '../../views/marketplace/hooks/useAppRequestStats': { - useAppRequestStats: () => { - return { - isLoading: false, - isSuccess: true, - data: { - data: { - totalUnseen: 5, - }, - }, - }; - }, - }, - ...stubs, - }).default; - }; - - it('should render all apps options when a user has manage apps permission', async () => { - const AppsModelList = loadMock(); - - render(<AppsModelList onDismiss={() => null} appBoxItems={[]} appsManagementAllowed showMarketplace />); - - expect(screen.getByText('Apps')).to.exist; - expect(screen.getByText('Marketplace')).to.exist; - expect(screen.getByText('Installed')).to.exist; - expect(screen.getByText('Requested')).to.exist; - }); - - it('should render only marketplace and installed options when a user does not have manage apps permission', async () => { - const AppsModelList = loadMock({ - '@rocket.chat/ui-contexts': { - 'useAtLeastOnePermission': (): boolean => false, - '@noCallThru': false, - }, - }); - - render(<AppsModelList onDismiss={() => null} appBoxItems={[]} appsManagementAllowed={false} showMarketplace />); - - expect(screen.getByText('Apps')).to.exist; - expect(screen.getByText('Marketplace')).to.exist; - expect(screen.getByText('Installed')).to.exist; - expect(screen.queryByText('Requested')).to.not.exist; - }); - - it('should not render marketplace and installed options when user does not have access-marketplace permission', async () => { - const AppsModelList = loadMock({ - '@rocket.chat/ui-contexts': { - 'useAtLeastOnePermission': (): boolean => false, - '@noCallThru': false, - }, - }); - - render(<AppsModelList onDismiss={() => null} appBoxItems={[]} appsManagementAllowed={false} />); - - expect(screen.getByText('Apps')).to.exist; - expect(screen.queryByText('Marketplace')).to.not.exist; - expect(screen.queryByText('Installed')).to.not.exist; - expect(screen.queryByText('Requested')).to.not.exist; - }); - - context('when clicked', () => { - const pushRoute = spy(); - const handleDismiss = spy(); - - const ProvidersMock = ({ children }: { children: ReactNode }) => { - return <RouterContextMock pushRoute={pushRoute}>{children}</RouterContextMock>; - }; - - it('should go to marketplace', async () => { - const AppsModelList = loadMock(); - - render(<AppsModelList onDismiss={handleDismiss} appBoxItems={[]} showMarketplace />, { wrapper: ProvidersMock }); - - const button = screen.getByText('Marketplace'); - userEvent.click(button); - await waitFor(() => expect(pushRoute).to.have.been.called.with('marketplace', { context: 'explore', page: 'list' })); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - - it('should go to installed', async () => { - const AppsModelList = loadMock(); - - render(<AppsModelList onDismiss={handleDismiss} appBoxItems={[]} showMarketplace />, { wrapper: ProvidersMock }); - - const button = screen.getByText('Installed'); - - userEvent.click(button); - await waitFor(() => expect(pushRoute).to.have.been.called.with('marketplace', { context: 'installed', page: 'list' })); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - - it('should go to requested if user has manage apps permission', async () => { - const AppsModelList = loadMock(); - - render(<AppsModelList onDismiss={handleDismiss} appBoxItems={[]} appsManagementAllowed />, { wrapper: ProvidersMock }); - - const button = screen.getByText('Requested'); - - userEvent.click(button); - await waitFor(() => expect(pushRoute).to.have.been.called.with('marketplace', { context: 'requested', page: 'list' })); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - - it('should render apps and trigger action', async () => { - const triggerActionButtonAction = spy(); - - const AppsModelList = loadMock({ - '../../../app/ui-message/client/ActionManager': { - triggerActionButtonAction, - '@noCallThru': true, - }, - }); - - render(<AppsModelList onDismiss={handleDismiss} appBoxItems={[{ name: 'Custom App' } as any]} appsManagementAllowed />, { - wrapper: ProvidersMock, - }); - - const button = screen.getByText('Custom App'); - - userEvent.click(button); - await waitFor(() => expect(triggerActionButtonAction).to.have.been.called()); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - }); -}); diff --git a/apps/meteor/client/components/AdministrationList/AppsModelList.tsx b/apps/meteor/client/components/AdministrationList/AppsModelList.tsx deleted file mode 100644 index 35108f7ad0bc8..0000000000000 --- a/apps/meteor/client/components/AdministrationList/AppsModelList.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { Badge, OptionTitle, Skeleton } from '@rocket.chat/fuselage'; -import { useTranslation, useRoute } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React from 'react'; - -import { triggerActionButtonAction } from '../../../app/ui-message/client/ActionManager'; -import type { IAppAccountBoxItem } from '../../../app/ui-utils/client/lib/AccountBox'; -import { useAppRequestStats } from '../../views/marketplace/hooks/useAppRequestStats'; -import ListItem from '../Sidebar/ListItem'; - -type AppsModelListProps = { - appBoxItems: IAppAccountBoxItem[]; - appsManagementAllowed?: boolean; - onDismiss: () => void; - showMarketplace?: boolean; -}; - -const AppsModelList = ({ appBoxItems, appsManagementAllowed, showMarketplace, onDismiss }: AppsModelListProps): ReactElement => { - const t = useTranslation(); - const marketplaceRoute = useRoute('marketplace'); - const page = 'list'; - - const appRequestStats = useAppRequestStats(); - - return ( - <> - <OptionTitle>{t('Apps')}</OptionTitle> - <ul> - <> - {showMarketplace && ( - <> - <ListItem - role='listitem' - icon='store' - text={t('Marketplace')} - onClick={() => { - marketplaceRoute.push({ context: 'explore', page }); - onDismiss(); - }} - /> - <ListItem - role='listitem' - icon='circle-arrow-down' - text={t('Installed')} - onClick={() => { - marketplaceRoute.push({ context: 'installed', page }); - onDismiss(); - }} - /> - </> - )} - - {appsManagementAllowed && ( - <> - <ListItem - role='listitem' - icon='cube' - text={t('Requested')} - onClick={(): void => { - marketplaceRoute.push({ context: 'requested', page }); - onDismiss(); - }} - > - {appRequestStats.isLoading && <Skeleton variant='circle' height={16} width={16} />} - {appRequestStats.isSuccess && appRequestStats.data.data.totalUnseen > 0 && ( - <Badge variant='primary'>{appRequestStats.data.data.totalUnseen}</Badge> - )} - </ListItem> - </> - )} - </> - {appBoxItems.length > 0 && ( - <> - {appBoxItems.map((item, key) => { - const action = () => { - triggerActionButtonAction({ - rid: '', - mid: '', - actionId: item.actionId, - appId: item.appId, - payload: { context: item.context }, - }); - onDismiss(); - }; - return ( - <ListItem - role='listitem' - text={(t.has(item.name) && t(item.name)) || item.name} - onClick={action} - key={item.actionId + key} - /> - ); - })} - </> - )} - </ul> - </> - ); -}; - -export default AppsModelList; diff --git a/apps/meteor/client/components/AdministrationList/AuditModelList.spec.tsx b/apps/meteor/client/components/AdministrationList/AuditModelList.spec.tsx deleted file mode 100644 index 086440343df8f..0000000000000 --- a/apps/meteor/client/components/AdministrationList/AuditModelList.spec.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { expect, spy } from 'chai'; -import type { ReactNode } from 'react'; -import React from 'react'; - -import RouterContextMock from '../../../tests/mocks/client/RouterContextMock'; -import AuditModelList from './AuditModelList'; - -describe('AuditModelList', () => { - it('should render audit', async () => { - render(<AuditModelList showAudit={true} showAuditLog={true} onDismiss={() => null} />); - - expect(screen.getByText('Audit')).to.exist; - expect(screen.getByText('Messages')).to.exist; - expect(screen.getByText('Logs')).to.exist; - }); - - it('should not render messages and log when does not have permission', async () => { - render(<AuditModelList showAudit={false} showAuditLog={false} onDismiss={() => null} />); - - expect(screen.getByText('Audit')).to.exist; - expect(screen.queryByText('Messages')).to.not.exist; - expect(screen.queryByText('Logs')).to.not.exist; - }); - - context('when clicked', () => { - const pushRoute = spy(); - const handleDismiss = spy(); - - const ProvidersMock = ({ children }: { children: ReactNode }) => ( - <RouterContextMock pushRoute={pushRoute}>{children}</RouterContextMock> - ); - - it('should go to audit home', async () => { - render(<AuditModelList showAudit={true} showAuditLog={false} onDismiss={handleDismiss} />, { wrapper: ProvidersMock }); - - const button = screen.getByText('Messages'); - - userEvent.click(button); - await waitFor(() => expect(pushRoute).to.have.been.called.with('audit-home')); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - - it('should go to audit log', async () => { - render(<AuditModelList showAudit={false} showAuditLog={true} onDismiss={handleDismiss} />, { wrapper: ProvidersMock }); - - const button = screen.getByText('Logs'); - - userEvent.click(button); - await waitFor(() => expect(pushRoute).to.have.been.called.with('audit-log')); - await waitFor(() => expect(handleDismiss).to.have.been.called()); - }); - }); -}); diff --git a/apps/meteor/client/components/AdministrationList/AuditModelList.tsx b/apps/meteor/client/components/AdministrationList/AuditModelList.tsx deleted file mode 100644 index 61e5c3c2cabb8..0000000000000 --- a/apps/meteor/client/components/AdministrationList/AuditModelList.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { OptionTitle } from '@rocket.chat/fuselage'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; -import type { FC } from 'react'; -import React from 'react'; - -import ListItem from '../Sidebar/ListItem'; - -type AuditModelListProps = { - onDismiss: () => void; - showAudit: boolean; - showAuditLog: boolean; -}; - -const AuditModelList: FC<AuditModelListProps> = ({ showAudit, showAuditLog, onDismiss }) => { - const t = useTranslation(); - - const auditHomeRoute = useRoute('audit-home'); - const auditSettingsRoute = useRoute('audit-log'); - - return ( - <> - <OptionTitle>{t('Audit')}</OptionTitle> - <ul> - {showAudit && ( - <ListItem - role='listitem' - icon='document-eye' - text={t('Messages')} - onClick={() => { - auditHomeRoute.push(); - onDismiss(); - }} - /> - )} - {showAuditLog && ( - <ListItem - role='listitem' - icon='document-eye' - text={t('Logs')} - onClick={() => { - auditSettingsRoute.push(); - onDismiss(); - }} - /> - )} - </ul> - </> - ); -}; - -export default AuditModelList; diff --git a/apps/meteor/client/components/GenericMenu.tsx b/apps/meteor/client/components/GenericMenu.tsx new file mode 100644 index 0000000000000..21a413a866447 --- /dev/null +++ b/apps/meteor/client/components/GenericMenu.tsx @@ -0,0 +1,64 @@ +import type { Icon } from '@rocket.chat/fuselage'; +import { MenuItem, MenuSection, MenuV2 } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { ComponentProps } from 'react'; +import React from 'react'; + +import type { GenericMenuItemProps } from './GenericMenuItem'; +import GenericMenuItem from './GenericMenuItem'; + +type GenericMenuCommonProps = { + icon?: ComponentProps<typeof Icon>['name']; + title: string; +}; +type GenericMenuConditionalProps = + | { + sections?: { + title?: string; + items: GenericMenuItemProps[]; + permission?: boolean | '' | 0 | null | undefined; + }[]; + items?: never; + } + | { + items?: GenericMenuItemProps[]; + sections?: never; + }; + +type GenericMenuProps = GenericMenuCommonProps & GenericMenuConditionalProps & Omit<ComponentProps<typeof MenuV2>, 'children'>; + +const GenericMenu = ({ title, icon = 'menu', ...props }: GenericMenuProps) => { + const t = useTranslation(); + + const sections = 'sections' in props && props.sections; + const items = 'items' in props && props.items; + + return ( + <> + {sections && ( + <MenuV2 icon={icon} title={t.has(title) ? t(title) : title} {...props}> + {sections.map(({ title, items }, key) => ( + <MenuSection title={title && (t.has(title) ? t(title) : title)} items={items} key={`${title}-${key}`}> + {(item) => ( + <MenuItem key={item.id}> + <GenericMenuItem {...item} /> + </MenuItem> + )} + </MenuSection> + ))} + </MenuV2> + )} + {items && ( + <MenuV2 icon={icon} title={t.has(title) ? t(title) : title} {...props}> + {items.map((item) => ( + <MenuItem key={item.id}> + <GenericMenuItem {...item} /> + </MenuItem> + ))} + </MenuV2> + )} + </> + ); +}; + +export default GenericMenu; diff --git a/apps/meteor/client/components/GenericMenuItem.tsx b/apps/meteor/client/components/GenericMenuItem.tsx new file mode 100644 index 0000000000000..ad546e0473efc --- /dev/null +++ b/apps/meteor/client/components/GenericMenuItem.tsx @@ -0,0 +1,21 @@ +import { MenuItemContent, MenuItemIcon, MenuItemInput } from '@rocket.chat/fuselage'; +import type { ComponentProps, ReactNode } from 'react'; +import React from 'react'; + +export type GenericMenuItemProps = { + id?: string; + icon?: ComponentProps<typeof MenuItemIcon>['name']; + content?: ReactNode; + addon?: ReactNode; + onClick?: () => void; +}; + +const GenericMenuItem = ({ icon, content, addon }: GenericMenuItemProps) => ( + <> + {icon && <MenuItemIcon name={icon} />} + {content && <MenuItemContent>{content}</MenuItemContent>} + {addon && <MenuItemInput>{addon}</MenuItemInput>} + </> +); + +export default GenericMenuItem; diff --git a/apps/meteor/client/components/SortList/GroupingList.tsx b/apps/meteor/client/components/SortList/GroupingList.tsx deleted file mode 100644 index 4cb72b5f5bc82..0000000000000 --- a/apps/meteor/client/components/SortList/GroupingList.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { CheckBox, OptionTitle } from '@rocket.chat/fuselage'; -import { useUserPreference, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React, { useCallback } from 'react'; - -import ListItem from '../Sidebar/ListItem'; - -const GroupingList = function GroupingList(): ReactElement { - const t = useTranslation(); - - const sidebarGroupByType = useUserPreference<boolean>('sidebarGroupByType'); - const sidebarShowFavorites = useUserPreference<boolean>('sidebarShowFavorites'); - const sidebarShowUnread = useUserPreference<boolean>('sidebarShowUnread'); - - const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); - - const useHandleChange = (key: 'sidebarGroupByType' | 'sidebarShowFavorites' | 'sidebarShowUnread', value: boolean): (() => void) => - useCallback(() => saveUserPreferences({ data: { [key]: value } }), [key, value]); - - const handleChangeGroupByType = useHandleChange('sidebarGroupByType', !sidebarGroupByType); - const handleChangeShoFavorite = useHandleChange('sidebarShowFavorites', !sidebarShowFavorites); - const handleChangeShowUnread = useHandleChange('sidebarShowUnread', !sidebarShowUnread); - - return ( - <> - <OptionTitle>{t('Group_by')}</OptionTitle> - <ul> - <ListItem - is='label' - role='listitem' - icon='flag' - text={t('Unread')} - input={<CheckBox mi='x16' onChange={handleChangeShowUnread} checked={sidebarShowUnread} />} - /> - <ListItem - is='label' - role='listitem' - icon='star' - text={t('Favorites')} - input={<CheckBox mi='x16' onChange={handleChangeShoFavorite} checked={sidebarShowFavorites} />} - /> - <ListItem - is='label' - role='listitem' - icon='group-by-type' - text={t('Types')} - input={<CheckBox mi='x16' onChange={handleChangeGroupByType} checked={sidebarGroupByType} />} - /> - </ul> - </> - ); -}; - -export default GroupingList; diff --git a/apps/meteor/client/components/SortList/SortList.tsx b/apps/meteor/client/components/SortList/SortList.tsx deleted file mode 100644 index 64fcd97519520..0000000000000 --- a/apps/meteor/client/components/SortList/SortList.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { OptionDivider } from '@rocket.chat/fuselage'; -import type { ReactElement } from 'react'; -import React from 'react'; - -import GroupingList from './GroupingList'; -import SortModeList from './SortModeList'; -import ViewModeList from './ViewModeList'; - -function SortList(): ReactElement { - return ( - <> - <ViewModeList /> - <OptionDivider /> - <SortModeList /> - <OptionDivider /> - <GroupingList /> - </> - ); -} - -export default SortList; diff --git a/apps/meteor/client/components/SortList/SortModeList.tsx b/apps/meteor/client/components/SortList/SortModeList.tsx deleted file mode 100644 index 172b26bc7afd1..0000000000000 --- a/apps/meteor/client/components/SortList/SortModeList.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { RadioButton, OptionTitle } from '@rocket.chat/fuselage'; -import { useUserPreference, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React, { useCallback } from 'react'; - -import { useOmnichannelEnterpriseEnabled } from '../../hooks/omnichannel/useOmnichannelEnterpriseEnabled'; -import { OmnichannelSortingDisclaimer } from '../Omnichannel/OmnichannelSortingDisclaimer'; -import ListItem from '../Sidebar/ListItem'; - -function SortModeList(): ReactElement { - const t = useTranslation(); - const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); - const sidebarSortBy = useUserPreference<'activity' | 'alphabetical'>('sidebarSortby', 'activity'); - const isOmnichannelEnabled = useOmnichannelEnterpriseEnabled(); - - const useHandleChange = (value: 'alphabetical' | 'activity'): (() => void) => - useCallback(() => saveUserPreferences({ data: { sidebarSortby: value } }), [value]); - - const setToAlphabetical = useHandleChange('alphabetical'); - const setToActivity = useHandleChange('activity'); - - return ( - <> - <OptionTitle>{t('Sort_By')}</OptionTitle> - <ul> - <ListItem - is='label' - role='listitem' - icon='clock' - text={t('Activity')} - input={<RadioButton mi='x16' onChange={setToActivity} checked={sidebarSortBy === 'activity'} />} - /> - <ListItem - is='label' - role='listitem' - icon='sort-az' - text={t('Name')} - input={<RadioButton mi='x16' onChange={setToAlphabetical} checked={sidebarSortBy === 'alphabetical'} />} - /> - </ul> - {isOmnichannelEnabled && <OmnichannelSortingDisclaimer id='sortByList' />} - </> - ); -} - -export default SortModeList; diff --git a/apps/meteor/client/components/SortList/ViewModeList.tsx b/apps/meteor/client/components/SortList/ViewModeList.tsx deleted file mode 100644 index ea36f93097db1..0000000000000 --- a/apps/meteor/client/components/SortList/ViewModeList.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { ToggleSwitch, RadioButton, OptionTitle } from '@rocket.chat/fuselage'; -import { useUserPreference, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React, { useCallback } from 'react'; - -import ListItem from '../Sidebar/ListItem'; - -function ViewModeList(): ReactElement { - const t = useTranslation(); - - const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); - - const useHandleChange = (value: 'medium' | 'extended' | 'condensed'): (() => void) => - useCallback(() => saveUserPreferences({ data: { sidebarViewMode: value } }), [value]); - - const sidebarViewMode = useUserPreference<'medium' | 'extended' | 'condensed'>('sidebarViewMode', 'extended'); - const sidebarDisplayAvatar = useUserPreference('sidebarDisplayAvatar', false); - - const setToExtended = useHandleChange('extended'); - const setToMedium = useHandleChange('medium'); - const setToCondensed = useHandleChange('condensed'); - - const handleChangeSidebarDisplayAvatar = useCallback( - () => saveUserPreferences({ data: { sidebarDisplayAvatar: !sidebarDisplayAvatar } }), - [saveUserPreferences, sidebarDisplayAvatar], - ); - - return ( - <> - <OptionTitle>{t('Display')}</OptionTitle> - <ul> - <ListItem - is='label' - role='listitem' - icon='extended-view' - text={t('Extended')} - input={<RadioButton mi='x16' onChange={setToExtended} checked={sidebarViewMode === 'extended'} />} - /> - <ListItem - is='label' - role='listitem' - icon='medium-view' - text={t('Medium')} - input={<RadioButton mi='x16' onChange={setToMedium} checked={sidebarViewMode === 'medium'} />} - /> - <ListItem - is='label' - role='listitem' - icon='condensed-view' - text={t('Condensed')} - input={<RadioButton mi='x16' onChange={setToCondensed} checked={sidebarViewMode === 'condensed'} />} - /> - <ListItem - is='label' - role='listitem' - icon='user-rounded' - text={t('Avatars')} - input={<ToggleSwitch mie='x16' onChange={handleChangeSidebarDisplayAvatar} checked={sidebarDisplayAvatar} />} - /> - </ul> - </> - ); -} - -export default ViewModeList; diff --git a/apps/meteor/client/components/SortList/index.ts b/apps/meteor/client/components/SortList/index.ts deleted file mode 100644 index eeb9d07165bb3..0000000000000 --- a/apps/meteor/client/components/SortList/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default } from './SortList'; diff --git a/apps/meteor/client/hooks/useHandleMenuAction.tsx b/apps/meteor/client/hooks/useHandleMenuAction.tsx new file mode 100644 index 0000000000000..f799ab464e7b8 --- /dev/null +++ b/apps/meteor/client/hooks/useHandleMenuAction.tsx @@ -0,0 +1,10 @@ +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import type { GenericMenuItemProps } from '../components/GenericMenuItem'; + +export const useHandleMenuAction = (items: GenericMenuItemProps[]) => { + return useMutableCallback((id) => { + const item = items.find((item) => item.id === id); + item?.onClick && item.onClick(); + }); +}; diff --git a/apps/meteor/client/providers/TooltipProvider.tsx b/apps/meteor/client/providers/TooltipProvider.tsx index 2b4d7026ed386..0fc7c996b9f39 100644 --- a/apps/meteor/client/providers/TooltipProvider.tsx +++ b/apps/meteor/client/providers/TooltipProvider.tsx @@ -121,7 +121,7 @@ const TooltipProvider: FC = ({ children }) => { document.body.addEventListener('mouseover', handleMouseOver, { passive: true, }); - document.body.addEventListener('click', dismissOnClick); + document.body.addEventListener('click', dismissOnClick, { capture: true }); return (): void => { contextValue.close(); diff --git a/apps/meteor/client/sidebar/header/actions/Administration.tsx b/apps/meteor/client/sidebar/header/actions/Administration.tsx index 6aac757108616..cb6921f91dda0 100644 --- a/apps/meteor/client/sidebar/header/actions/Administration.tsx +++ b/apps/meteor/client/sidebar/header/actions/Administration.tsx @@ -1,103 +1,22 @@ -import { Sidebar, Dropdown } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { usePermission, useAtLeastOnePermission } from '@rocket.chat/ui-contexts'; +import { Sidebar } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import type { HTMLAttributes, VFC } from 'react'; -import React, { useCallback, useRef } from 'react'; -import { createPortal } from 'react-dom'; +import React from 'react'; -import { AccountBox } from '../../../../app/ui-utils/client'; -import type { IAppAccountBoxItem, AccountBoxItem } from '../../../../app/ui-utils/client/lib/AccountBox'; -import { isAppAccountBoxItem } from '../../../../app/ui-utils/client/lib/AccountBox'; -import { useHasLicenseModule } from '../../../../ee/client/hooks/useHasLicenseModule'; -import AdministrationList from '../../../components/AdministrationList/AdministrationList'; -import AdministrationModelList from '../../../components/AdministrationList/AdministrationModelList'; -import AppsModelList from '../../../components/AdministrationList/AppsModelList'; -import AuditModelList from '../../../components/AdministrationList/AuditModelList'; -import { useReactiveValue } from '../../../hooks/useReactiveValue'; -import { useDropdownVisibility } from '../hooks/useDropdownVisibility'; - -const ADMIN_PERMISSIONS = [ - 'view-statistics', - 'run-import', - 'view-user-administration', - 'view-room-administration', - 'create-invite-links', - 'manage-cloud', - 'view-logs', - 'manage-sounds', - 'view-federation-data', - 'manage-email-inbox', - 'manage-emoji', - 'manage-outgoing-integrations', - 'manage-own-outgoing-integrations', - 'manage-incoming-integrations', - 'manage-own-incoming-integrations', - 'manage-oauth-apps', - 'access-mailer', - 'manage-user-status', - 'access-permissions', - 'access-setting-permissions', - 'view-privileged-setting', - 'edit-privileged-setting', - 'manage-selected-settings', - 'view-engagement-dashboard', - 'view-moderation-console', -]; +import GenericMenu from '../../../components/GenericMenu'; +import type { GenericMenuItemProps } from '../../../components/GenericMenuItem'; +import { useHandleMenuAction } from '../../../hooks/useHandleMenuAction'; +import { useAdministrationMenu } from './hooks/useAdministrationMenu'; const Administration: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => { - const reference = useRef(null); - const target = useRef(null); - - const { isVisible, toggle } = useDropdownVisibility({ reference, target }); - - const getAccountBoxItems = useMutableCallback(() => AccountBox.getItems()); - const accountBoxItems = useReactiveValue(getAccountBoxItems); - - const hasAuditLicense = useHasLicenseModule('auditing') === true; - const hasManageAppsPermission = usePermission('manage-apps'); - const hasAccessMarketplacePermission = usePermission('access-marketplace'); - const hasAdminPermission = useAtLeastOnePermission(ADMIN_PERMISSIONS); - const hasAuditPermission = usePermission('can-audit') && hasAuditLicense; - const hasAuditLogPermission = usePermission('can-audit-log') && hasAuditLicense; - - const appBoxItems = accountBoxItems.filter((item): item is IAppAccountBoxItem => isAppAccountBoxItem(item)); - const adminBoxItems = accountBoxItems.filter((item): item is AccountBoxItem => !isAppAccountBoxItem(item)); - const showAdmin = hasAdminPermission || !!adminBoxItems.length; - const showAudit = hasAuditPermission || hasAuditLogPermission; - const showWorkspace = hasAdminPermission; - const showApps = hasAccessMarketplacePermission || hasManageAppsPermission || !!appBoxItems.length; - - const onDismiss = useCallback((): void => toggle(false), [toggle]); + const t = useTranslation(); - const optionsList = [ - showAdmin && <AdministrationModelList showWorkspace={showWorkspace} accountBoxItems={adminBoxItems} onDismiss={onDismiss} />, - showApps && ( - <AppsModelList - appBoxItems={appBoxItems} - onDismiss={onDismiss} - appsManagementAllowed={hasManageAppsPermission} - showMarketplace={hasAccessMarketplacePermission || hasManageAppsPermission} - /> - ), - showAudit && <AuditModelList showAudit={hasAuditPermission} showAuditLog={hasAuditLogPermission} onDismiss={onDismiss} />, - ].filter(Boolean); + const sections = useAdministrationMenu(); + const items = sections.reduce((acc, { items }) => [...acc, ...items], [] as GenericMenuItemProps[]); - if (!optionsList || optionsList.length === 0) { - return null; - } + const handleAction = useHandleMenuAction(items); - return ( - <> - <Sidebar.TopBar.Action icon='menu' onClick={(): void => toggle()} {...props} ref={reference} /> - {isVisible && - createPortal( - <Dropdown reference={reference} ref={target}> - <AdministrationList optionsList={optionsList} /> - </Dropdown>, - document.body, - )} - </> - ); + return <GenericMenu sections={sections} title={t('Administration')} onAction={handleAction} is={Sidebar.TopBar.Action} {...props} />; }; export default Administration; diff --git a/apps/meteor/client/sidebar/header/actions/CreateRoom.tsx b/apps/meteor/client/sidebar/header/actions/CreateRoom.tsx index 2c9d4f5ed3ee8..465bcc63bc054 100644 --- a/apps/meteor/client/sidebar/header/actions/CreateRoom.tsx +++ b/apps/meteor/client/sidebar/header/actions/CreateRoom.tsx @@ -1,32 +1,30 @@ -import { Sidebar, Dropdown } from '@rocket.chat/fuselage'; -import { useAtLeastOnePermission } from '@rocket.chat/ui-contexts'; +import { Sidebar } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import type { HTMLAttributes, VFC } from 'react'; -import React, { useRef } from 'react'; -import { createPortal } from 'react-dom'; +import React from 'react'; -import { useDropdownVisibility } from '../hooks/useDropdownVisibility'; -import CreateRoomList from './CreateRoomList'; - -const CREATE_ROOM_PERMISSIONS = ['create-c', 'create-p', 'create-d', 'start-discussion', 'start-discussion-other-user']; +import GenericMenu from '../../../components/GenericMenu'; +import type { GenericMenuItemProps } from '../../../components/GenericMenuItem'; +import { useHandleMenuAction } from '../../../hooks/useHandleMenuAction'; +import { useCreateRoom } from './hooks/useCreateRoomMenu'; const CreateRoom: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => { - const reference = useRef(null); - const target = useRef(null); - const { isVisible, toggle } = useDropdownVisibility({ reference, target }); + const t = useTranslation(); + + const sections = useCreateRoom(); + const items = sections.reduce((acc, { items }) => [...acc, ...items], [] as GenericMenuItemProps[]); - const showCreate = useAtLeastOnePermission(CREATE_ROOM_PERMISSIONS); + const handleAction = useHandleMenuAction(items); return ( - <> - {showCreate && <Sidebar.TopBar.Action icon='edit-rounded' onClick={(): void => toggle()} {...props} ref={reference} />} - {isVisible && - createPortal( - <Dropdown reference={reference} ref={target}> - <CreateRoomList closeList={(): void => toggle(false)} /> - </Dropdown>, - document.body, - )} - </> + <GenericMenu + icon='edit-rounded' + sections={sections} + onAction={handleAction} + title={t('Create_new')} + is={Sidebar.TopBar.Action} + {...props} + /> ); }; diff --git a/apps/meteor/client/sidebar/header/actions/CreateRoomList.tsx b/apps/meteor/client/sidebar/header/actions/CreateRoomList.tsx deleted file mode 100644 index f7ae919230282..0000000000000 --- a/apps/meteor/client/sidebar/header/actions/CreateRoomList.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { OptionTitle, OptionDivider } from '@rocket.chat/fuselage'; -import { useSetting, useAtLeastOnePermission, useTranslation } from '@rocket.chat/ui-contexts'; -import type { ReactElement, MouseEvent } from 'react'; -import React from 'react'; - -import CreateDiscussion from '../../../components/CreateDiscussion'; -import ListItem from '../../../components/Sidebar/ListItem'; -import { useIsEnterprise } from '../../../hooks/useIsEnterprise'; -import CreateChannelWithData from '../CreateChannel'; -import CreateDirectMessage from '../CreateDirectMessage'; -import CreateTeam from '../CreateTeam'; -import MatrixFederationSearch from '../MatrixFederationSearch'; -import { useCreateRoomModal } from '../hooks/useCreateRoomModal'; - -const CREATE_CHANNEL_PERMISSIONS = ['create-c', 'create-p']; -const CREATE_TEAM_PERMISSIONS = ['create-team']; -const CREATE_DIRECT_PERMISSIONS = ['create-d']; -const CREATE_DISCUSSION_PERMISSIONS = ['start-discussion', 'start-discussion-other-user']; - -type CreateRoomListProps = { - closeList: () => void; -}; - -const CreateRoomList = ({ closeList }: CreateRoomListProps): ReactElement => { - const t = useTranslation(); - const discussionEnabled = useSetting('Discussion_enabled'); - - const canCreateChannel = useAtLeastOnePermission(CREATE_CHANNEL_PERMISSIONS); - const canCreateTeam = useAtLeastOnePermission(CREATE_TEAM_PERMISSIONS); - const canCreateDirectMessages = useAtLeastOnePermission(CREATE_DIRECT_PERMISSIONS); - const canCreateDiscussion = useAtLeastOnePermission(CREATE_DISCUSSION_PERMISSIONS); - - const createChannel = useCreateRoomModal(CreateChannelWithData); - const createTeam = useCreateRoomModal(CreateTeam); - const createDiscussion = useCreateRoomModal(CreateDiscussion); - const createDirectMessage = useCreateRoomModal(CreateDirectMessage); - const searchFederatedRooms = useCreateRoomModal(MatrixFederationSearch); - - const { data } = useIsEnterprise(); - const isMatrixEnabled = useSetting('Federation_Matrix_enabled') && data?.isEnterprise; - - return ( - <> - <OptionTitle>{t('Create_new')}</OptionTitle> - <ul> - {canCreateChannel && ( - <ListItem - role='listitem' - icon='hashtag' - text={t('Channel')} - onClick={(e: MouseEvent<HTMLElement>): void => { - createChannel(e); - closeList(); - }} - /> - )} - {canCreateTeam && ( - <ListItem - role='listitem' - icon='team' - text={t('Team')} - onClick={(e: MouseEvent<HTMLElement>): void => { - createTeam(e); - closeList(); - }} - /> - )} - {canCreateDirectMessages && ( - <ListItem - role='listitem' - icon='balloon' - text={t('Direct_Messages')} - onClick={(e: MouseEvent<HTMLElement>): void => { - createDirectMessage(e); - closeList(); - }} - /> - )} - {discussionEnabled && canCreateDiscussion && ( - <ListItem - role='listitem' - icon='discussion' - text={t('Discussion')} - onClick={(e: MouseEvent<HTMLElement>): void => { - createDiscussion(e); - closeList(); - }} - /> - )} - {isMatrixEnabled && ( - <> - <OptionDivider /> - <OptionTitle>{t('Explore')}</OptionTitle> - <ListItem - icon='magnifier' - text={t('Federation_Search_federated_rooms')} - onClick={(e: MouseEvent<HTMLElement>): void => { - searchFederatedRooms(e); - closeList(); - }} - /> - </> - )} - </ul> - </> - ); -}; - -export default CreateRoomList; diff --git a/apps/meteor/client/sidebar/header/actions/Sort.tsx b/apps/meteor/client/sidebar/header/actions/Sort.tsx index ac993807b2366..194e920ec4a0d 100644 --- a/apps/meteor/client/sidebar/header/actions/Sort.tsx +++ b/apps/meteor/client/sidebar/header/actions/Sort.tsx @@ -1,27 +1,18 @@ -import { Sidebar, Dropdown } from '@rocket.chat/fuselage'; +import { Sidebar } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import type { VFC, HTMLAttributes } from 'react'; -import React, { useRef } from 'react'; -import { createPortal } from 'react-dom'; +import React from 'react'; -import SortList from '../../../components/SortList'; -import { useDropdownVisibility } from '../hooks/useDropdownVisibility'; +import GenericMenu from '../../../components/GenericMenu'; +import { useSortMenu } from './hooks/useSortMenu'; const Sort: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => { - const reference = useRef(null); - const target = useRef(null); - const { isVisible, toggle } = useDropdownVisibility({ reference, target }); + const t = useTranslation(); + + const sections = useSortMenu(); return ( - <> - <Sidebar.TopBar.Action {...props} icon='sort' onClick={(): void => toggle()} ref={reference} /> - {isVisible && - createPortal( - <Dropdown reference={reference} ref={target}> - <SortList /> - </Dropdown>, - document.body, - )} - </> + <GenericMenu icon='sort' sections={sections} title={t('Display')} selectionMode='multiple' is={Sidebar.TopBar.Action} {...props} /> ); }; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx new file mode 100644 index 0000000000000..b32ae66dde134 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx @@ -0,0 +1,94 @@ +import { useTranslation, useRoute, useMethod, useSetModal, useRole } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import { FlowRouter } from 'meteor/kadira:flow-router'; +import React from 'react'; + +import type { AccountBoxItem } from '../../../../../app/ui-utils/client/lib/AccountBox'; +import type { UpgradeTabVariant } from '../../../../../lib/upgradeTab'; +import { getUpgradeTabLabel, isFullyFeature } from '../../../../../lib/upgradeTab'; +import Emoji from '../../../../components/Emoji'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import RegisterWorkspaceModal from '../../../../views/admin/cloud/modals/RegisterWorkspaceModal'; +import { useUpgradeTabParams } from '../../../../views/hooks/useUpgradeTabParams'; + +type useAdministrationItemProps = { + accountBoxItems: AccountBoxItem[]; + showWorkspace: boolean; +}; +export const useAdministrationItems = ({ accountBoxItems, showWorkspace }: useAdministrationItemProps): GenericMenuItemProps[] => { + const t = useTranslation(); + + const { tabType, trialEndDate, isLoading } = useUpgradeTabParams(); + const shouldShowEmoji = isFullyFeature(tabType); + const label = getUpgradeTabLabel(tabType); + const isAdmin = useRole('admin'); + const setModal = useSetModal(); + + const checkCloudRegisterStatus = useMethod('cloud:checkRegisterStatus'); + const result = useQuery(['admin/cloud/register-status'], async () => checkCloudRegisterStatus()); + const { workspaceRegistered } = result.data || {}; + + const handleRegisterWorkspaceClick = (): void => { + const handleModalClose = (): void => setModal(null); + setModal(<RegisterWorkspaceModal onClose={handleModalClose} />); + }; + + const adminRoute = useRoute('admin-index'); + const upgradeRoute = useRoute('upgrade'); + const cloudRoute = useRoute('cloud'); + const showUpgradeItem = !isLoading && tabType; + + const upgradeItem: GenericMenuItemProps = { + id: 'showUpgradeItem', + content: ( + <> + {t(label)} {shouldShowEmoji && <Emoji emojiHandle=':zap:' />} + </> + ), + icon: 'arrow-stack-up', + onClick: () => { + upgradeRoute.push({ type: tabType as UpgradeTabVariant }, trialEndDate ? { trialEndDate } : undefined); + }, + }; + const adminItem: GenericMenuItemProps = { + id: 'registration', + content: workspaceRegistered ? t('Registration') : t('Register'), + icon: 'cloud-plus', + onClick: () => { + if (workspaceRegistered) { + cloudRoute.push({ context: '/' }); + return; + } + handleRegisterWorkspaceClick(); + }, + }; + const workspaceItem: GenericMenuItemProps = { + id: 'workspace', + content: t('Workspace'), + icon: 'cog', + onClick: () => { + adminRoute.push({ context: '/' }); + }, + }; + + const accountBoxItem: GenericMenuItemProps[] = accountBoxItems.map((item, key) => { + const action = () => { + if (item.href) { + FlowRouter.go(item.href); + } + }; + return { + id: `account-box-item-${key}`, + content: t(item.name), + icon: item.icon, + onClick: action, + }; + }); + + return [ + ...(showUpgradeItem ? [upgradeItem] : []), + ...(isAdmin ? [adminItem] : []), + ...(showWorkspace ? [workspaceItem] : []), + ...(accountBoxItems.length ? accountBoxItem : []), + ]; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationMenu.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationMenu.tsx new file mode 100644 index 0000000000000..5145ac30c0f40 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationMenu.tsx @@ -0,0 +1,75 @@ +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useAtLeastOnePermission, usePermission } from '@rocket.chat/ui-contexts'; + +import { AccountBox } from '../../../../../app/ui-utils/client'; +import type { IAppAccountBoxItem, AccountBoxItem } from '../../../../../app/ui-utils/client/lib/AccountBox'; +import { isAppAccountBoxItem } from '../../../../../app/ui-utils/client/lib/AccountBox'; +import { useHasLicenseModule } from '../../../../../ee/client/hooks/useHasLicenseModule'; +import { useReactiveValue } from '../../../../hooks/useReactiveValue'; +import { useAdministrationItems } from './useAdministrationItems'; +import { useAppsItems } from './useAppsItems'; +import { useAuditItems } from './useAuditItems'; + +const ADMIN_PERMISSIONS = [ + 'view-statistics', + 'run-import', + 'view-user-administration', + 'view-room-administration', + 'create-invite-links', + 'manage-cloud', + 'view-logs', + 'manage-sounds', + 'view-federation-data', + 'manage-email-inbox', + 'manage-emoji', + 'manage-outgoing-integrations', + 'manage-own-outgoing-integrations', + 'manage-incoming-integrations', + 'manage-own-incoming-integrations', + 'manage-oauth-apps', + 'access-mailer', + 'manage-user-status', + 'access-permissions', + 'access-setting-permissions', + 'view-privileged-setting', + 'edit-privileged-setting', + 'manage-selected-settings', + 'view-engagement-dashboard', + 'view-moderation-console', +]; + +export const useAdministrationMenu = () => { + const getAccountBoxItems = useMutableCallback(() => AccountBox.getItems()); + const accountBoxItems = useReactiveValue(getAccountBoxItems); + + const hasAuditLicense = useHasLicenseModule('auditing') === true; + const hasManageAppsPermission = usePermission('manage-apps'); + const hasAccessMarketplacePermission = usePermission('access-marketplace'); + const hasAdminPermission = useAtLeastOnePermission(ADMIN_PERMISSIONS); + const hasAuditPermission = usePermission('can-audit') && hasAuditLicense; + const hasAuditLogPermission = usePermission('can-audit-log') && hasAuditLicense; + + const appBoxItems = accountBoxItems.filter((item): item is IAppAccountBoxItem => isAppAccountBoxItem(item)); + const adminBoxItems = accountBoxItems.filter((item): item is AccountBoxItem => !isAppAccountBoxItem(item)); + + const showAdmin = hasAdminPermission || !!adminBoxItems.length; + const showAudit = hasAuditPermission || hasAuditLogPermission; + const showWorkspace = hasAdminPermission; + const showApps = hasAccessMarketplacePermission || hasManageAppsPermission || !!appBoxItems.length; + + const administrationItems = useAdministrationItems({ accountBoxItems: adminBoxItems, showWorkspace }); + const appItems = useAppsItems({ + appBoxItems, + appsManagementAllowed: hasManageAppsPermission, + showMarketplace: hasAccessMarketplacePermission || hasManageAppsPermission, + }); + const auditItems = useAuditItems({ showAudit: hasAuditPermission, showAuditLog: hasAuditLogPermission }); + + const sections = [ + { title: 'Administration', items: administrationItems, permission: showAdmin }, + { title: 'Apps', items: appItems, permission: showApps }, + { title: 'Audit', items: auditItems, permission: showAudit }, + ]; + + return sections.filter(({ permission }) => permission); +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx new file mode 100644 index 0000000000000..66a5befe0f2b3 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx @@ -0,0 +1,78 @@ +import { Badge, Skeleton } from '@rocket.chat/fuselage'; +import { useTranslation, useRoute } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { triggerActionButtonAction } from '../../../../../app/ui-message/client/ActionManager'; +import type { IAppAccountBoxItem } from '../../../../../app/ui-utils/client/lib/AccountBox'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import { useAppRequestStats } from '../../../../views/marketplace/hooks/useAppRequestStats'; + +type useAppsItemsProps = { + appBoxItems: IAppAccountBoxItem[]; + appsManagementAllowed?: boolean; + showMarketplace?: boolean; +}; + +export const useAppsItems = ({ appBoxItems, appsManagementAllowed, showMarketplace }: useAppsItemsProps): GenericMenuItemProps[] => { + const t = useTranslation(); + + const marketplaceRoute = useRoute('marketplace'); + const page = 'list'; + + const appRequestStats = useAppRequestStats(); + + const marketPlaceItems: GenericMenuItemProps[] = [ + { + id: 'marketplace', + icon: 'store', + content: t('Marketplace'), + onClick: () => marketplaceRoute.push({ context: 'explore', page }), + }, + { + id: 'installed', + icon: 'circle-arrow-down', + content: t('Installed'), + onClick: () => marketplaceRoute.push({ context: 'installed', page }), + }, + ]; + + const appsManagementItem: GenericMenuItemProps = { + id: 'requested-apps', + icon: 'cube', + content: t('Requested'), + onClick: () => { + marketplaceRoute.push({ context: 'requested', page }); + }, + addon: ( + <> + {appRequestStats.isLoading && <Skeleton variant='circle' height={16} width={16} />} + {appRequestStats.isSuccess && appRequestStats.data.data.totalUnseen > 0 && ( + <Badge variant='primary'>{appRequestStats.data.data.totalUnseen}</Badge> + )} + </> + ), + }; + + const appItems: GenericMenuItemProps[] = appBoxItems.map((item: IAppAccountBoxItem, key: number) => { + const action = () => { + triggerActionButtonAction({ + rid: '', + mid: '', + actionId: item.actionId, + appId: item.appId, + payload: { context: item.context }, + }); + }; + return { + id: item.actionId + key, + icon: item.icon as GenericMenuItemProps['icon'], + content: (t.has(item.name) && t(item.name)) || item.name, + onClick: action, + }; + }); + return [ + ...(showMarketplace ? marketPlaceItems : []), + ...(appsManagementAllowed ? [appsManagementItem] : []), + ...(appBoxItems.length ? appItems : []), + ]; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAuditItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAuditItems.tsx new file mode 100644 index 0000000000000..3e9d98d74b188 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAuditItems.tsx @@ -0,0 +1,30 @@ +import { useTranslation, useRoute } from '@rocket.chat/ui-contexts'; + +import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; + +type useAuditItemsProps = { + showAudit: boolean; + showAuditLog: boolean; +}; + +export const useAuditItems = ({ showAudit, showAuditLog }: useAuditItemsProps): GenericMenuItemProps[] => { + const t = useTranslation(); + + const auditHomeRoute = useRoute('audit-home'); + const auditSettingsRoute = useRoute('audit-log'); + + const auditMessageItem: GenericMenuItemProps = { + id: 'messages', + icon: 'document-eye', + content: t('Messages'), + onClick: () => auditHomeRoute.push(), + }; + const auditLogItem: GenericMenuItemProps = { + id: 'auditLog', + icon: 'document-eye', + content: t('Logs'), + onClick: () => auditSettingsRoute.push(), + }; + + return [...(showAudit ? [auditMessageItem] : []), ...(showAuditLog ? [auditLogItem] : [])]; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx new file mode 100644 index 0000000000000..3cdca7adebc41 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx @@ -0,0 +1,83 @@ +import { useTranslation, useSetting, useAtLeastOnePermission } from '@rocket.chat/ui-contexts'; + +import CreateDiscussion from '../../../../components/CreateDiscussion'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import { useIsEnterprise } from '../../../../hooks/useIsEnterprise'; +import CreateChannelWithData from '../../CreateChannel'; +import CreateDirectMessage from '../../CreateDirectMessage'; +import CreateTeam from '../../CreateTeam'; +import MatrixFederationSearch from '../../MatrixFederationSearch'; +import { useCreateRoomModal } from '../../hooks/useCreateRoomModal'; + +const CREATE_CHANNEL_PERMISSIONS = ['create-c', 'create-p']; +const CREATE_TEAM_PERMISSIONS = ['create-team']; +const CREATE_DIRECT_PERMISSIONS = ['create-d']; +const CREATE_DISCUSSION_PERMISSIONS = ['start-discussion', 'start-discussion-other-user']; + +export const useCreateRoomItems = (): GenericMenuItemProps[] => { + const t = useTranslation(); + const discussionEnabled = useSetting('Discussion_enabled'); + + const canCreateChannel = useAtLeastOnePermission(CREATE_CHANNEL_PERMISSIONS); + const canCreateTeam = useAtLeastOnePermission(CREATE_TEAM_PERMISSIONS); + const canCreateDirectMessages = useAtLeastOnePermission(CREATE_DIRECT_PERMISSIONS); + const canCreateDiscussion = useAtLeastOnePermission(CREATE_DISCUSSION_PERMISSIONS); + + const createChannel = useCreateRoomModal(CreateChannelWithData); + const createTeam = useCreateRoomModal(CreateTeam); + const createDiscussion = useCreateRoomModal(CreateDiscussion); + const createDirectMessage = useCreateRoomModal(CreateDirectMessage); + const searchFederatedRooms = useCreateRoomModal(MatrixFederationSearch); + + const { data } = useIsEnterprise(); + const isMatrixEnabled = useSetting('Federation_Matrix_enabled') && data?.isEnterprise; + + const createChannelItem: GenericMenuItemProps = { + id: 'channel', + content: t('Channel'), + icon: 'hashtag', + onClick: () => { + createChannel(); + }, + }; + const createTeamItem: GenericMenuItemProps = { + id: 'team', + content: t('Team'), + icon: 'team', + onClick: () => { + createTeam(); + }, + }; + const createDirectMessageItem: GenericMenuItemProps = { + id: 'direct', + content: t('Direct_Messages'), + icon: 'balloon', + onClick: () => { + createDirectMessage(); + }, + }; + const createDiscussionItem: GenericMenuItemProps = { + id: 'discussion', + content: t('Discussion'), + icon: 'discussion', + onClick: () => { + createDiscussion(); + }, + }; + const matrixFederationSearchItem: GenericMenuItemProps = { + id: 'matrix-federation-search', + content: t('Federation_Search_federated_rooms'), + icon: 'magnifier', + onClick: () => { + searchFederatedRooms(); + }, + }; + + return [ + ...(canCreateChannel ? [createChannelItem] : []), + ...(canCreateTeam ? [createTeamItem] : []), + ...(canCreateDirectMessages ? [createDirectMessageItem] : []), + ...(canCreateDiscussion && discussionEnabled ? [createDiscussionItem] : []), + ...(isMatrixEnabled ? [matrixFederationSearchItem] : []), + ]; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomMenu.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomMenu.tsx new file mode 100644 index 0000000000000..7e4cbe8d138e1 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomMenu.tsx @@ -0,0 +1,24 @@ +import { useAtLeastOnePermission, useSetting } from '@rocket.chat/ui-contexts'; + +import { useIsEnterprise } from '../../../../hooks/useIsEnterprise'; +import { useCreateRoomItems } from './useCreateRoomItems'; +import { useMatrixFederationItems } from './useMatrixFederationItems.tsx'; + +const CREATE_ROOM_PERMISSIONS = ['create-c', 'create-p', 'create-d', 'start-discussion', 'start-discussion-other-user']; + +export const useCreateRoom = () => { + const showCreate = useAtLeastOnePermission(CREATE_ROOM_PERMISSIONS); + + const { data } = useIsEnterprise(); + const isMatrixEnabled = useSetting('Federation_Matrix_enabled') && data?.isEnterprise; + + const createRoomItems = useCreateRoomItems(); + const matrixFederationSearchItems = useMatrixFederationItems({ isMatrixEnabled }); + + const sections = [ + { title: 'Create_new', items: createRoomItems, permission: showCreate }, + { title: 'Explore', items: matrixFederationSearchItems, permission: showCreate && isMatrixEnabled }, + ]; + + return sections.filter((section) => section.permission); +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useGroupingListItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useGroupingListItems.tsx new file mode 100644 index 0000000000000..24629be905e38 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useGroupingListItems.tsx @@ -0,0 +1,43 @@ +import { CheckBox } from '@rocket.chat/fuselage'; +import { useEndpoint, useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useCallback } from 'react'; + +import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; + +export const useGroupingListItems = (): GenericMenuItemProps[] => { + const t = useTranslation(); + + const sidebarGroupByType = useUserPreference<boolean>('sidebarGroupByType'); + const sidebarShowFavorites = useUserPreference<boolean>('sidebarShowFavorites'); + const sidebarShowUnread = useUserPreference<boolean>('sidebarShowUnread'); + + const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); + + const useHandleChange = (key: 'sidebarGroupByType' | 'sidebarShowFavorites' | 'sidebarShowUnread', value: boolean): (() => void) => + useCallback(() => saveUserPreferences({ data: { [key]: value } }), [key, value]); + + const handleChangeGroupByType = useHandleChange('sidebarGroupByType', !sidebarGroupByType); + const handleChangeShoFavorite = useHandleChange('sidebarShowFavorites', !sidebarShowFavorites); + const handleChangeShowUnread = useHandleChange('sidebarShowUnread', !sidebarShowUnread); + + return [ + { + id: 'unread', + content: t('Unread'), + icon: 'flag', + addon: <CheckBox mi='x16' onChange={handleChangeShowUnread} checked={sidebarShowUnread} />, + }, + { + id: 'favorites', + content: t('Favorites'), + icon: 'star', + addon: <CheckBox mi='x16' onChange={handleChangeShoFavorite} checked={sidebarShowFavorites} />, + }, + { + id: 'types', + content: t('Types'), + icon: 'group-by-type', + addon: <CheckBox mi='x16' onChange={handleChangeGroupByType} checked={sidebarGroupByType} />, + }, + ]; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useMatrixFederationItems.tsx.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useMatrixFederationItems.tsx.tsx new file mode 100644 index 0000000000000..76cd6d9458af5 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useMatrixFederationItems.tsx.tsx @@ -0,0 +1,26 @@ +import { useTranslation } from '@rocket.chat/ui-contexts'; + +import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import MatrixFederationSearch from '../../MatrixFederationSearch'; +import { useCreateRoomModal } from '../../hooks/useCreateRoomModal'; + +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/header/actions/hooks/useSortMenu.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useSortMenu.tsx new file mode 100644 index 0000000000000..8a3f6a56e5901 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useSortMenu.tsx @@ -0,0 +1,17 @@ +import { useGroupingListItems } from './useGroupingListItems'; +import { useSortModeItems } from './useSortModeItems'; +import { useViewModeItems } from './useViewModeItems'; + +export const useSortMenu = () => { + const viewModeItems = useViewModeItems(); + const sortModeItems = useSortModeItems(); + const groupingListItems = useGroupingListItems(); + + const sections = [ + { title: 'Display', items: viewModeItems }, + { title: 'Sort_By', items: sortModeItems }, + { title: 'Group_by', items: groupingListItems }, + ]; + + return sections; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useSortModeItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useSortModeItems.tsx new file mode 100644 index 0000000000000..c200fefd56898 --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useSortModeItems.tsx @@ -0,0 +1,46 @@ +import { RadioButton } from '@rocket.chat/fuselage'; +import { useEndpoint, useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useCallback } from 'react'; + +import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import { OmnichannelSortingDisclaimer } from '../../../../components/Omnichannel/OmnichannelSortingDisclaimer'; +import { useOmnichannelEnterpriseEnabled } from '../../../../hooks/omnichannel/useOmnichannelEnterpriseEnabled'; + +export const useSortModeItems = (): GenericMenuItemProps[] => { + const t = useTranslation(); + + const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); + const sidebarSortBy = useUserPreference<'activity' | 'alphabetical'>('sidebarSortby', 'activity'); + const isOmnichannelEnabled = useOmnichannelEnterpriseEnabled(); + + const omniDisclaimerItem = { + id: 'sortByList', + content: <OmnichannelSortingDisclaimer id='sortByList' />, + }; + + const useHandleChange = (value: 'alphabetical' | 'activity'): (() => void) => + useCallback(() => saveUserPreferences({ data: { sidebarSortby: value } }), [value]); + + const setToAlphabetical = useHandleChange('alphabetical'); + const setToActivity = useHandleChange('activity'); + const items: GenericMenuItemProps[] = [ + { + id: 'activity', + content: t('Activity'), + icon: 'clock', + addon: <RadioButton mi='x16' onChange={setToActivity} checked={sidebarSortBy === 'activity'} />, + }, + { + id: 'name', + content: t('Name'), + icon: 'sort-az', + addon: <RadioButton mi='x16' onChange={setToAlphabetical} checked={sidebarSortBy === 'alphabetical'} />, + }, + ]; + + if (isOmnichannelEnabled) { + items.push(omniDisclaimerItem); + } + + return items; +}; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useViewModeItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useViewModeItems.tsx new file mode 100644 index 0000000000000..92da7101baf5a --- /dev/null +++ b/apps/meteor/client/sidebar/header/actions/hooks/useViewModeItems.tsx @@ -0,0 +1,53 @@ +import { RadioButton, ToggleSwitch } from '@rocket.chat/fuselage'; +import { useEndpoint, useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; +import React, { useCallback } from 'react'; + +import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; + +export const useViewModeItems = (): GenericMenuItemProps[] => { + const t = useTranslation(); + + const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); + + const useHandleChange = (value: 'medium' | 'extended' | 'condensed'): (() => void) => + useCallback(() => saveUserPreferences({ data: { sidebarViewMode: value } }), [value]); + + const sidebarViewMode = useUserPreference<'medium' | 'extended' | 'condensed'>('sidebarViewMode', 'extended'); + const sidebarDisplayAvatar = useUserPreference('sidebarDisplayAvatar', false); + + const setToExtended = useHandleChange('extended'); + const setToMedium = useHandleChange('medium'); + const setToCondensed = useHandleChange('condensed'); + + const handleChangeSidebarDisplayAvatar = useCallback( + () => saveUserPreferences({ data: { sidebarDisplayAvatar: !sidebarDisplayAvatar } }), + [saveUserPreferences, sidebarDisplayAvatar], + ); + + return [ + { + id: 'extended', + content: t('Extended'), + icon: 'extended-view', + addon: <RadioButton mi='x16' onChange={setToExtended} checked={sidebarViewMode === 'extended'} />, + }, + { + id: 'medium', + content: t('Medium'), + icon: 'medium-view', + addon: <RadioButton mi='x16' onChange={setToMedium} checked={sidebarViewMode === 'medium'} />, + }, + { + id: 'condensed', + content: t('Condensed'), + icon: 'condensed-view', + addon: <RadioButton mi='x16' onChange={setToCondensed} checked={sidebarViewMode === 'condensed'} />, + }, + { + id: 'avatars', + content: t('Avatars'), + icon: 'user-rounded', + addon: <ToggleSwitch mie='x16' onChange={handleChangeSidebarDisplayAvatar} checked={sidebarDisplayAvatar} />, + }, + ]; +}; diff --git a/apps/meteor/client/sidebar/header/hooks/useCreateRoomModal.tsx b/apps/meteor/client/sidebar/header/hooks/useCreateRoomModal.tsx index 8e14f04ddbc76..2b371ec1b0eff 100644 --- a/apps/meteor/client/sidebar/header/hooks/useCreateRoomModal.tsx +++ b/apps/meteor/client/sidebar/header/hooks/useCreateRoomModal.tsx @@ -1,14 +1,12 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useSetModal } from '@rocket.chat/ui-contexts'; -import type { MouseEvent, FC } from 'react'; +import type { FC } from 'react'; import React from 'react'; -export const useCreateRoomModal = (Component: FC<any>): ((e: MouseEvent<HTMLElement>) => void) => { +export const useCreateRoomModal = (Component: FC<any>): (() => void) => { const setModal = useSetModal(); - return useMutableCallback((e) => { - e.preventDefault(); - + return useMutableCallback(() => { const handleClose = (): void => { setModal(null); }; diff --git a/apps/meteor/tests/e2e/administration-menu.spec.ts b/apps/meteor/tests/e2e/administration-menu.spec.ts index 755862a3ef80e..4e630c60e16e4 100644 --- a/apps/meteor/tests/e2e/administration-menu.spec.ts +++ b/apps/meteor/tests/e2e/administration-menu.spec.ts @@ -16,7 +16,7 @@ test.describe.serial('administration-menu', () => { test('expect open upgrade page', async ({ page }) => { test.skip(IS_EE, 'Community Only'); - await poHomeDiscussion.sidenav.openAdministrationByLabel('Go fully featured'); + await poHomeDiscussion.sidenav.openAdministrationByLabel('Go fully featured ⚡'); await expect(page).toHaveURL('admin/upgrade/go-fully-featured'); }); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts index 348c9fc44554b..8b721895cc5cf 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -41,7 +41,7 @@ export class HomeSidenav { async openAdministrationByLabel(text: string): Promise<void> { await this.page.locator('role=button[name="Administration"]').click(); - await this.page.locator(`li.rcx-option >> text="${text}"`).click(); + await this.page.locator(`role=menuitem[name="${text}"]`).click(); } async openInstalledApps(): Promise<void> { @@ -50,8 +50,8 @@ export class HomeSidenav { } async openNewByLabel(text: string): Promise<void> { - await this.page.locator('[data-qa="sidebar-create"]').click(); - await this.page.locator(`li.rcx-option >> text="${text}"`).click(); + await this.page.locator('role=button[name="Create new"]').click(); + await this.page.locator(`role=menuitem[name="${text}"]`).click(); } async logout(): Promise<void> { diff --git a/apps/meteor/tests/unit/client/sidebar/header/actions/Administration.spec.tsx b/apps/meteor/tests/unit/client/sidebar/header/actions/Administration.spec.tsx deleted file mode 100644 index 918d90d2c5cd7..0000000000000 --- a/apps/meteor/tests/unit/client/sidebar/header/actions/Administration.spec.tsx +++ /dev/null @@ -1,192 +0,0 @@ -import { render, screen, waitFor } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; -import { expect, spy } from 'chai'; -import proxyquire from 'proxyquire'; -import type { ReactElement } from 'react'; -import React, { Fragment } from 'react'; - -const COMPONENT_PATH = '../../../../../../client/sidebar/header/actions/Administration'; -const defaultConfig = { - '@rocket.chat/ui-contexts': { - useAtLeastOnePermission: () => true, - usePermission: () => false, - }, - '@rocket.chat/fuselage-hooks': { - useMutableCallback: (cb: () => null) => cb(), - }, - '../../../../ee/client/hooks/useHasLicenseModule': { - useHasLicenseModule: () => true, - }, - '../../../../app/ui-utils/client': { - AccountBox: { - getItems: () => [], - }, - }, - '../../../../app/ui-utils/client/lib/AccountBox': { - AccountBoxItem: {}, - isAppAccountBoxItem: () => false, - }, - '../../../hooks/useReactiveValue': { - useReactiveValue: (item: unknown) => item, - }, - '../../../components/AdministrationList/AdministrationModelList': () => <p>Administration Model List</p>, - '../../../components/AdministrationList/AppsModelList': () => <p>Apps Model List</p>, - '../../../components/AdministrationList/AuditModelList': () => <p>Audit Model List</p>, - '../hooks/useDropdownVisibility': { - useDropdownVisibility: () => ({ - isVisible: true, - toggle: () => null, - }), - }, - '../../../components/AdministrationList/AdministrationList': ({ optionsList }: { optionsList: ReactElement[] }) => - optionsList?.map((i, j) => <Fragment key={j}>{i}</Fragment>), -}; - -describe('sidebar/header/actions/Administration', () => { - it('should render all model list', async () => { - const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, { - ...defaultConfig, - '@rocket.chat/ui-contexts': { - useAtLeastOnePermission: () => true, - usePermission: () => true, - }, - }).default; - - render(<Administration />); - - expect(screen.getByText('Administration Model List')).to.exist; - expect(screen.getByText('Apps Model List')).to.exist; - expect(screen.getByText('Audit Model List')).to.exist; - }); - - it('should not render administration list when isVisible false', async () => { - const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, { - ...defaultConfig, - '../hooks/useDropdownVisibility': { - useDropdownVisibility: () => ({ - isVisible: false, - toggle: () => null, - }), - }, - }).default; - - render(<Administration />); - - expect(screen.getByRole('button')).to.exist; - expect(screen.queryByText('Administration Model List')).to.not.exist; - }); - - it('should render button if has accountBoxItem', async () => { - const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, { - ...defaultConfig, - '../../../../app/ui-utils/client': { - AccountBox: { - getItems: () => [{}], - }, - }, - '@rocket.chat/ui-contexts': { - useAtLeastOnePermission: () => false, - usePermission: () => false, - }, - }).default; - - render(<Administration />); - - expect(screen.getByRole('button')).to.exist; - expect(screen.getByText('Administration Model List')).to.exist; - }); - - it('should not render button if does not have permission', async () => { - const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, { - ...defaultConfig, - '@rocket.chat/ui-contexts': { - useAtLeastOnePermission: () => false, - usePermission: () => false, - }, - '../../../../app/ui-utils/client/lib/AccountBox': { - AccountBoxItem: {}, - isAppAccountBoxItem: () => false, - }, - '../../../../app/ui-utils/client': { - AccountBox: { - getItems: () => [{}], - }, - }, - }).default; - - render(<Administration />); - - expect(screen.queryByText('button')).to.not.exist; - expect(screen.getByText('Administration Model List')).to.exist; - }); - - it('should render administration model list when has account box item with no permissions', async () => { - const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, { - ...defaultConfig, - '@rocket.chat/ui-contexts': { - useAtLeastOnePermission: () => false, - usePermission: () => false, - }, - '../../../../app/ui-utils/client/lib/AccountBox': { - AccountBoxItem: {}, - isAppAccountBoxItem: () => false, - }, - '../../../../app/ui-utils/client': { - AccountBox: { - getItems: () => [{}], - }, - }, - }).default; - - render(<Administration />); - - expect(screen.getByText('Administration Model List')).to.exist; - expect(screen.queryByText('Apps Model List')).to.not.exist; - expect(screen.queryByText('Audit Model List')).to.not.exist; - }); - - it('should render apps model list when has app account box item and no permissions', async () => { - const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, { - ...defaultConfig, - '@rocket.chat/ui-contexts': { - useAtLeastOnePermission: () => false, - usePermission: () => false, - }, - '../../../../app/ui-utils/client/lib/AccountBox': { - AccountBoxItem: {}, - isAppAccountBoxItem: () => true, - }, - '../../../../app/ui-utils/client': { - AccountBox: { - getItems: () => [{}], - }, - }, - }).default; - - render(<Administration />); - - expect(screen.getByText('Apps Model List')).to.exist; - expect(screen.queryByText('Administration Model List')).to.not.exist; - expect(screen.queryByText('Audit Model List')).to.not.exist; - }); - - context('when clicked', () => { - it('should toggle dropdown', async () => { - const toggle = spy(); - const Administration = proxyquire.noCallThru().load(COMPONENT_PATH, { - ...defaultConfig, - '../hooks/useDropdownVisibility': { - useDropdownVisibility: () => ({ - isVisible: true, - toggle, - }), - }, - }).default; - - render(<Administration />); - - userEvent.click(screen.getByRole('button')); - await waitFor(() => expect(toggle).to.have.been.called()); - }); - }); -}); diff --git a/ee/packages/ui-theming/src/paletteDark.ts b/ee/packages/ui-theming/src/paletteDark.ts index 84b44b8818ced..80a29c07cd2d0 100644 --- a/ee/packages/ui-theming/src/paletteDark.ts +++ b/ee/packages/ui-theming/src/paletteDark.ts @@ -110,7 +110,7 @@ export const palette = [ category: 'Elevation', description: 'Elevation border and shadow levels', list: [ - { name: 'shadow-elevation-border', token: '', color: '#EBECEF' }, + { name: 'shadow-elevation-border', token: '', color: '#2F343D' }, { name: 'shadow-elevation-1', token: '', color: 'rgba(9, 9, 9, 0.35)' }, { name: 'shadow-elevation-2x', token: '', color: 'rgba(9, 9, 9, 0.3)' }, { name: 'shadow-elevation-2y', token: '', color: 'rgba(9, 9, 9, 0.45)' }, diff --git a/yarn.lock b/yarn.lock index 210c2672084bd..89e8ef56d15b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9392,7 +9392,7 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/css-in-js@npm:^0.31.12, @rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.143": +"@rocket.chat/css-in-js@npm:^0.31.12, @rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.146": version: 0.31.23 resolution: "@rocket.chat/css-in-js@npm:0.31.23" dependencies: @@ -9418,7 +9418,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.143": +"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.146": version: 0.31.23 resolution: "@rocket.chat/css-supports@npm:0.31.23" dependencies: @@ -9647,10 +9647,10 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.319": - version: 0.32.0-dev.319 - resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.319" - checksum: 2bc25081e2efd632130d3ae0aecb21744e354fa7299bf283a41aa8fd564e2238508f4aac0958ad33de810c4df328b77aa10755ad2c05692cdcb6c70f417146be +"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.322": + version: 0.32.0-dev.322 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.322" + checksum: 95a8319aaa4f6fd429c62a594b4094136ec57c519201140ca029bbe44d49cb3fd381fb67d7e20f1341308868794dbc9609dad180c4ab38ddcbd89eea3e78451a languageName: node linkType: hard @@ -9710,14 +9710,14 @@ __metadata: linkType: soft "@rocket.chat/fuselage@npm:next": - version: 0.32.0-dev.369 - resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.369" - dependencies: - "@rocket.chat/css-in-js": ~0.31.23-dev.143 - "@rocket.chat/css-supports": ~0.31.23-dev.143 - "@rocket.chat/fuselage-tokens": ~0.32.0-dev.319 - "@rocket.chat/memo": ~0.31.23-dev.143 - "@rocket.chat/styled": ~0.31.23-dev.143 + version: 0.32.0-dev.372 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.372" + dependencies: + "@rocket.chat/css-in-js": ~0.31.23-dev.146 + "@rocket.chat/css-supports": ~0.31.23-dev.146 + "@rocket.chat/fuselage-tokens": ~0.32.0-dev.322 + "@rocket.chat/memo": ~0.31.23-dev.146 + "@rocket.chat/styled": ~0.31.23-dev.146 invariant: ^2.2.4 react-aria: ~3.23.1 react-keyed-flatten-children: ^1.3.0 @@ -9729,7 +9729,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 109d05662debb9dbe38b218d8585953ed4c88b02f3ef7b9c529813e978a93c4af345602078ce237a03d8666d6ce09f91e7b72d2cea1cd4fc5101125b809e5a81 + checksum: 47cdb72c37ee36ebc1dd3daeb8d03950b674dbc3637918dc3d8f6aeead7a56a34db6767e3514e62fc0a9d6a811ef0325cc17c8ffc79532b87f720559ef940ce0 languageName: node linkType: hard @@ -9957,7 +9957,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.143": +"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.146": version: 0.31.23 resolution: "@rocket.chat/memo@npm:0.31.23" checksum: 070debb940749a2e4463cf767dd65c6967cea664a5bd67c22a812d611f6c3c46d6fe4bb0bf329e43dcd927493413add37c45ae3b05ec08f0b24e9d7385caebdd @@ -10762,7 +10762,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.143": +"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.146": version: 0.31.23 resolution: "@rocket.chat/styled@npm:0.31.23" dependencies: From 094f3ba0d5f4cbd4eb9a8129ef96667d05a05427 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Wed, 28 Jun 2023 16:39:34 -0300 Subject: [PATCH 11/79] regression: Apps translations not working randomly (#29659) --- .../ui-message/client/actionButtons/messageAction.ts | 3 +-- .../app/ui-message/client/actionButtons/messageBox.ts | 4 +--- .../app/ui-message/client/actionButtons/tabbar.ts | 3 +-- apps/meteor/client/providers/TranslationProvider.tsx | 11 +++++++++++ apps/meteor/ee/client/apps/orchestrator.ts | 9 --------- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/apps/meteor/app/ui-message/client/actionButtons/messageAction.ts b/apps/meteor/app/ui-message/client/actionButtons/messageAction.ts index e0a0d94a3f4df..29a45ded6a1d4 100644 --- a/apps/meteor/app/ui-message/client/actionButtons/messageAction.ts +++ b/apps/meteor/app/ui-message/client/actionButtons/messageAction.ts @@ -3,7 +3,6 @@ import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; import { Utilities } from '../../../../ee/lib/misc/Utilities'; import { MessageAction } from '../../../ui-utils/client'; import { messageArgs } from '../../../../client/lib/utils/messageArgs'; -import { t } from '../../../utils/lib/i18n'; import { triggerActionButtonAction } from '../ActionManager'; import { applyButtonFilters } from './lib/applyButtonFilters'; @@ -14,7 +13,7 @@ export const onAdded = (button: IUIActionButton): void => MessageAction.addButton({ id: getIdForActionButton(button), icon: '' as any, - label: t(Utilities.getI18nKeyForApp(button.labelI18n, button.appId)) as any, + label: Utilities.getI18nKeyForApp(button.labelI18n, button.appId), context: button.when?.messageActionContext || ['message', 'message-mobile', 'threads', 'starred'], condition({ room }) { return applyButtonFilters(button, room); diff --git a/apps/meteor/app/ui-message/client/actionButtons/messageBox.ts b/apps/meteor/app/ui-message/client/actionButtons/messageBox.ts index 828c6bcaf5772..d07ecbc23ad8c 100644 --- a/apps/meteor/app/ui-message/client/actionButtons/messageBox.ts +++ b/apps/meteor/app/ui-message/client/actionButtons/messageBox.ts @@ -1,11 +1,9 @@ import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { ChatRoom } from '../../../models/client'; import { messageBox } from '../../../ui-utils/client'; import { applyButtonFilters } from './lib/applyButtonFilters'; import { triggerActionButtonAction } from '../ActionManager'; -import { t } from '../../../utils/lib/i18n'; import { Utilities } from '../../../../ee/lib/misc/Utilities'; import { RoomManager } from '../../../../client/lib/RoomManager'; import { asReactiveSource } from '../../../../client/lib/tracker'; @@ -14,7 +12,7 @@ const getIdForActionButton = ({ appId, actionId }: IUIActionButton): string => ` export const onAdded = (button: IUIActionButton): void => // eslint-disable-next-line no-void - void messageBox.actions.add('Apps', t(Utilities.getI18nKeyForApp(button.labelI18n, button.appId)) as TranslationKey, { + void messageBox.actions.add('Apps', Utilities.getI18nKeyForApp(button.labelI18n, button.appId), { id: getIdForActionButton(button), // icon: button.icon || '', condition() { diff --git a/apps/meteor/app/ui-message/client/actionButtons/tabbar.ts b/apps/meteor/app/ui-message/client/actionButtons/tabbar.ts index 1ff5a3809aebe..26352cd9b59a4 100644 --- a/apps/meteor/app/ui-message/client/actionButtons/tabbar.ts +++ b/apps/meteor/app/ui-message/client/actionButtons/tabbar.ts @@ -2,7 +2,6 @@ import type { IUIActionButton } from '@rocket.chat/apps-engine/definition/ui'; import { addAction, deleteAction } from '../../../../client/views/room/lib/Toolbox'; import { Utilities } from '../../../../ee/lib/misc/Utilities'; -import { t } from '../../../utils/lib/i18n'; import { triggerActionButtonAction } from '../ActionManager'; import { applyButtonFilters } from './lib/applyButtonFilters'; @@ -16,7 +15,7 @@ export const onAdded = (button: IUIActionButton): void => id: button.actionId, icon: undefined, // Apps won't provide icons for now order: 300, // Make sure the button only shows up inside the room toolbox - title: t(Utilities.getI18nKeyForApp(button.labelI18n, button.appId)) as any, + title: Utilities.getI18nKeyForApp(button.labelI18n, button.appId), // Filters were applied in the applyButtonFilters function // if the code made it this far, the button should be shown groups: ['group', 'channel', 'live', 'team', 'direct', 'direct_multiple'], diff --git a/apps/meteor/client/providers/TranslationProvider.tsx b/apps/meteor/client/providers/TranslationProvider.tsx index 36b7c8d998456..122579c8e7bbd 100644 --- a/apps/meteor/client/providers/TranslationProvider.tsx +++ b/apps/meteor/client/providers/TranslationProvider.tsx @@ -11,7 +11,9 @@ import type { ReactElement, ReactNode } from 'react'; import React, { useEffect, useMemo } from 'react'; import { I18nextProvider, initReactI18next, useTranslation } from 'react-i18next'; +import { CachedCollectionManager } from '../../app/ui-cached-collection/client'; import { i18n, addSprinfToI18n } from '../../app/utils/lib/i18n'; +import { AppClientOrchestratorInstance } from '../../ee/client/apps/orchestrator'; import { applyCustomTranslations } from '../lib/utils/applyCustomTranslations'; import { filterLanguage } from '../lib/utils/filterLanguage'; import { isRTLScriptLanguage } from '../lib/utils/isRTLScriptLanguage'; @@ -218,6 +220,15 @@ const TranslationProvider = ({ children }: TranslationProviderProps): ReactEleme }); }, [language, loadLocale, availableLanguages]); + useEffect(() => { + const cb = () => { + AppClientOrchestratorInstance.getAppClientManager().initialize(); + AppClientOrchestratorInstance.load(); + }; + CachedCollectionManager.onLogin(cb); + return () => CachedCollectionManager.off('login', cb); + }, []); + return ( <I18nextProvider i18n={i18nextInstance}> <TranslationProviderInner children={children} availableLanguages={availableLanguages} /> diff --git a/apps/meteor/ee/client/apps/orchestrator.ts b/apps/meteor/ee/client/apps/orchestrator.ts index 80a8e2cbf6ef4..41efc3e6ed096 100644 --- a/apps/meteor/ee/client/apps/orchestrator.ts +++ b/apps/meteor/ee/client/apps/orchestrator.ts @@ -5,10 +5,8 @@ import type { IPermission } from '@rocket.chat/apps-engine/definition/permission import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import type { IAppStorageItem } from '@rocket.chat/apps-engine/server/storage/IAppStorageItem'; import type { AppScreenshot, AppRequestFilter, Serialized, AppRequestsStats, PaginatedAppRequests } from '@rocket.chat/core-typings'; -import { Meteor } from 'meteor/meteor'; import { hasAtLeastOnePermission } from '../../../app/authorization/client'; -import { CachedCollectionManager } from '../../../app/ui-cached-collection/client'; import { sdk } from '../../../app/utils/client/lib/SDKClient'; import { dispatchToastMessage } from '../../../client/lib/toast'; import type { App } from '../../../client/views/marketplace/types'; @@ -263,10 +261,3 @@ class AppClientOrchestrator { } export const AppClientOrchestratorInstance = new AppClientOrchestrator(); - -Meteor.startup(() => { - CachedCollectionManager.onLogin(() => { - AppClientOrchestratorInstance.getAppClientManager().initialize(); - AppClientOrchestratorInstance.load(); - }); -}); From 674f95cca9a0c63463c03b6994358cb298f46061 Mon Sep 17 00:00:00 2001 From: Kevin Aleman <kaleman960@gmail.com> Date: Thu, 29 Jun 2023 06:56:05 -0600 Subject: [PATCH 12/79] fix: Dont update a user's livechat status when its status is offline (#29589) Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> --- .changeset/thin-hats-jog.md | 5 +++++ .../server/business-hour/AbstractBusinessHour.ts | 6 +++++- apps/meteor/app/livechat/server/business-hour/index.ts | 10 +++++----- apps/meteor/ee/server/startup/presence.ts | 5 +++-- apps/meteor/server/models/raw/Users.js | 1 + 5 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 .changeset/thin-hats-jog.md diff --git a/.changeset/thin-hats-jog.md b/.changeset/thin-hats-jog.md new file mode 100644 index 0000000000000..2cea8a2312b32 --- /dev/null +++ b/.changeset/thin-hats-jog.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Avoid updating a user's livechat status on login when its status is set to offline diff --git a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts index 7c0ddd007d157..2bafd2f0fa20b 100644 --- a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts +++ b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts @@ -44,11 +44,15 @@ export abstract class AbstractBusinessHourBehavior { return this.UsersRepository.isAgentWithinBusinessHours(agentId); } + // After logout, users are turned not-available by default + // This will turn them available unless they put themselves offline (manual status change) async changeAgentActiveStatus(agentId: string, status: string): Promise<any> { return this.UsersRepository.setLivechatStatusIf( agentId, status, - { livechatStatusSystemModified: true }, + // Why this works: statusDefault is the property set when a user manually changes their status + // So if it's set to offline, we can be sure the user will be offline after login and we can skip the update + { livechatStatusSystemModified: true, statusDefault: { $ne: 'offline' } }, { livechatStatusSystemModified: true }, ); } diff --git a/apps/meteor/app/livechat/server/business-hour/index.ts b/apps/meteor/app/livechat/server/business-hour/index.ts index 02fd2480dce62..8b80e415f7a72 100644 --- a/apps/meteor/app/livechat/server/business-hour/index.ts +++ b/apps/meteor/app/livechat/server/business-hour/index.ts @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; -import { Accounts } from 'meteor/accounts-base'; import { cronJobs } from '@rocket.chat/cron'; +import type { IUser } from '@rocket.chat/core-typings'; +import { Accounts } from 'meteor/accounts-base'; import { BusinessHourManager } from './BusinessHourManager'; import { SingleBusinessHourBehavior } from './Single'; @@ -16,8 +17,7 @@ Meteor.startup(async () => { businessHourManager.registerBusinessHourBehavior(new BusinessHourBehaviorClass()); businessHourManager.registerBusinessHourType(new DefaultBusinessHour()); - Accounts.onLogin( - async ({ user }: { user: any }) => - user?.roles?.includes('livechat-agent') && !user?.roles?.includes('bot') && businessHourManager.onLogin(user._id), - ); + Accounts.onLogin((user: IUser) => { + void (user?.roles?.includes('livechat-agent') && !user?.roles?.includes('bot') && businessHourManager.onLogin(user._id)); + }); }); diff --git a/apps/meteor/ee/server/startup/presence.ts b/apps/meteor/ee/server/startup/presence.ts index 952539ff70077..51cff87cebebc 100644 --- a/apps/meteor/ee/server/startup/presence.ts +++ b/apps/meteor/ee/server/startup/presence.ts @@ -33,9 +33,10 @@ Meteor.startup(function () { if (login.type !== 'resume') { return; } - void Presence.newConnection(login.user._id, login.connection.id, nodeId).then(() => { + void (async function () { + await Presence.newConnection(login.user._id, login.connection.id, nodeId); updateConns(); - }); + })(); }); Accounts.onLogout(function (login: any): void { diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index afcca73f81e89..f43a1522a548e 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -968,6 +968,7 @@ export class UsersRaw extends BaseRaw { setLivechatStatusActiveBasedOnBusinessHours(userId) { const query = { _id: userId, + statusDefault: { $ne: 'offline' }, openBusinessHours: { $exists: true, $not: { $size: 0 }, From 9efc081772142d9ff872b7ea23dfd3b997450bcd Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Thu, 29 Jun 2023 18:28:53 +0530 Subject: [PATCH 13/79] fix: close button of Invite friends Dialog in the Game Center (#28986) --- .../ee/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/ee/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx b/apps/meteor/ee/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx index e5902144dc515..2a03f321d5d68 100644 --- a/apps/meteor/ee/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx +++ b/apps/meteor/ee/client/apps/gameCenter/GameCenterInvitePlayersModal.tsx @@ -54,7 +54,7 @@ const GameCenterInvitePlayersModal = ({ game, onClose }: IGameCenterInvitePlayer return ( <> - <GenericModal onConfirm={sendInvite} title={t('Apps_Game_Center_Invite_Friends')}> + <GenericModal onClose={onClose} onCancel={onClose} onConfirm={sendInvite} title={t('Apps_Game_Center_Invite_Friends')}> <Box mbe='x16'>{t('Invite_Users')}</Box> <Box mbe='x16' display='flex' justifyContent='stretch'> <UserAutoCompleteMultipleFederated value={users} onChange={setUsers} /> From ce99be6b0a7dc2406bc41e1aae24334d1e1a7332 Mon Sep 17 00:00:00 2001 From: Kevin Aleman <kaleman960@gmail.com> Date: Thu, 29 Jun 2023 07:00:06 -0600 Subject: [PATCH 14/79] fix: Omnichannel queue not running for all queues (#29290) Co-authored-by: murtaza98 <murtaza.patrawala@rocket.chat> --- .changeset/spicy-kiwis-invent.md | 5 +++++ apps/meteor/app/livechat/server/lib/RoutingManager.ts | 2 ++ apps/meteor/server/models/raw/LivechatInquiry.ts | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 .changeset/spicy-kiwis-invent.md diff --git a/.changeset/spicy-kiwis-invent.md b/.changeset/spicy-kiwis-invent.md new file mode 100644 index 0000000000000..6cdf5372d52d3 --- /dev/null +++ b/.changeset/spicy-kiwis-invent.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fix: Omnichannel queue not running for all queues diff --git a/apps/meteor/app/livechat/server/lib/RoutingManager.ts b/apps/meteor/app/livechat/server/lib/RoutingManager.ts index aed24ad928dde..55c0bff01c662 100644 --- a/apps/meteor/app/livechat/server/lib/RoutingManager.ts +++ b/apps/meteor/app/livechat/server/lib/RoutingManager.ts @@ -128,6 +128,8 @@ export const RoutingManager: Routing = { if (!agent) { logger.debug(`No agents available. Unable to delegate inquiry ${inquiry._id}`); + // When an inqury reaches here on CE, it will stay here as 'ready' since on CE there's no mechanism to re queue it. + // When reaching this point, managers have to manually transfer the inquiry to another room. This is expected. return LivechatRooms.findOneById(rid); } diff --git a/apps/meteor/server/models/raw/LivechatInquiry.ts b/apps/meteor/server/models/raw/LivechatInquiry.ts index 9bdf7e27b1096..2515aef2082d9 100644 --- a/apps/meteor/server/models/raw/LivechatInquiry.ts +++ b/apps/meteor/server/models/raw/LivechatInquiry.ts @@ -140,7 +140,7 @@ export class LivechatInquiryRaw extends BaseRaw<ILivechatInquiryRecord> implemen const result = await this.col.findOneAndUpdate( { status: LivechatInquiryStatus.QUEUED, - ...(department && { department }), + ...(department ? { department } : { department: { $exists: false } }), $or: [ { locked: true, From f5507ffd33f1a78d0657dba79e41a52100c6936b Mon Sep 17 00:00:00 2001 From: Douglas Fabris <devfabris@gmail.com> Date: Thu, 29 Jun 2023 10:01:35 -0300 Subject: [PATCH 15/79] regression: `showThreadsInMainChannel` preference misaligned (#29669) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../preferences/PreferencesMessagesSection.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.tsx b/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.tsx index 25f143772be67..76aef16dfcd56 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.tsx +++ b/apps/meteor/client/views/account/preferences/PreferencesMessagesSection.tsx @@ -1,5 +1,5 @@ import type { SelectOption } from '@rocket.chat/fuselage'; -import { Accordion, Field, Select, FieldGroup, ToggleSwitch } from '@rocket.chat/fuselage'; +import { Accordion, Field, Select, FieldGroup, ToggleSwitch, Box } from '@rocket.chat/fuselage'; import { useUserPreference, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useMemo } from 'react'; @@ -127,11 +127,13 @@ const PreferencesMessagesSection = ({ onChange, commitRef, ...props }: FormSecti )} {useMemo( () => ( - <Field display='flex' flexDirection='row' justifyContent='spaceBetween' flexGrow={1}> - <Field.Label>{t('Always_show_thread_replies_in_main_channel')}</Field.Label> - <Field.Row> - <ToggleSwitch checked={showThreadsInMainChannel} onChange={handleShowThreadsInMainChannel} /> - </Field.Row> + <Field> + <Box display='flex' flexDirection='row' justifyContent='spaceBetween' flexGrow={1}> + <Field.Label>{t('Always_show_thread_replies_in_main_channel')}</Field.Label> + <Field.Row> + <ToggleSwitch checked={showThreadsInMainChannel} onChange={handleShowThreadsInMainChannel} /> + </Field.Row> + </Box> <Field.Hint>{t('Accounts_Default_User_Preferences_showThreadsInMainChannel_Description')}</Field.Hint> </Field> ), From 5688ff6c83a68997784877e22925c56acd856ae2 Mon Sep 17 00:00:00 2001 From: Swapnil Bankar <swapnilbankar1010@gmail.com> Date: Thu, 29 Jun 2023 18:43:44 +0530 Subject: [PATCH 16/79] fix: Misaligned alignment of prompt when hover on user / room name in room information and user information section (#28627) Co-authored-by: Hugo Costa <hugocarreiracosta@gmail.com> Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com> --- apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx b/apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx index bab4bf530c1d7..298a01bd2045b 100644 --- a/apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx +++ b/apps/meteor/client/components/InfoPanel/InfoPanelTitle.tsx @@ -10,9 +10,9 @@ type InfoPanelTitleProps = { const isValidIcon = (icon: ReactNode): icon is ComponentProps<typeof Icon>['name'] => typeof icon === 'string'; const InfoPanelTitle: FC<InfoPanelTitleProps> = ({ title, icon }) => ( - <Box display='flex' title={title} flexShrink={0} alignItems='center' fontScale='h4' color='default' withTruncatedText> + <Box display='flex' flexShrink={0} alignItems='center' fontScale='h4' color='default' withTruncatedText> {isValidIcon(icon) ? <Icon name={icon} size='x22' /> : icon} - <Box mis='x8' flexGrow={1} withTruncatedText> + <Box mis='x8' withTruncatedText title={title}> {title} </Box> </Box> From c92b606d33fe301657e78993a2195f93d795ee23 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Thu, 29 Jun 2023 10:58:19 -0300 Subject: [PATCH 17/79] regression: Opening the dial pad crashes the GUI (#29670) --- apps/meteor/client/hooks/useDialModal.tsx | 4 +++- apps/meteor/ee/client/voip/modal/DialPad/hooks/useDialPad.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/hooks/useDialModal.tsx b/apps/meteor/client/hooks/useDialModal.tsx index ac7ad1e81514a..ead4c9c070c7e 100644 --- a/apps/meteor/client/hooks/useDialModal.tsx +++ b/apps/meteor/client/hooks/useDialModal.tsx @@ -31,7 +31,9 @@ export const useDialModal = (): DialModalControls => { } setModal( - <Suspense fallback={null}> + // TODO: Revisit Modal's FocusScope which currently does not accept null as children. + // Added dummy div fallback for that reason. + <Suspense fallback={<div />}> <DialPadModal initialValue={initialValue} errorMessage={errorMessage} handleClose={closeDialModal} /> </Suspense>, ); diff --git a/apps/meteor/ee/client/voip/modal/DialPad/hooks/useDialPad.tsx b/apps/meteor/ee/client/voip/modal/DialPad/hooks/useDialPad.tsx index 7f10e1d08ee08..ed28ca24fa398 100644 --- a/apps/meteor/ee/client/voip/modal/DialPad/hooks/useDialPad.tsx +++ b/apps/meteor/ee/client/voip/modal/DialPad/hooks/useDialPad.tsx @@ -45,7 +45,7 @@ export const useDialPad = ({ initialValue, initialErrorMessage }: DialPadProps): const { ref, onChange } = register('PhoneInput'); - const value = watch('PhoneInput'); + const value = watch('PhoneInput', ''); const [disabled, setDisabled] = useState(true); From 90038e4316dc2f0a7cade14c5079fc2264e0fe2e Mon Sep 17 00:00:00 2001 From: Kevin Aleman <kaleman960@gmail.com> Date: Thu, 29 Jun 2023 11:12:19 -0600 Subject: [PATCH 18/79] ci: Add missing `context` property on compose file (#29676) --- docker-compose-ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index ab54ed2d57d2a..e859772f007bf 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -29,6 +29,7 @@ services: platform: linux/amd64 build: dockerfile: ee/apps/authorization-service/Dockerfile + context: . args: SERVICE: authorization-service image: ghcr.io/${LOWERCASE_REPOSITORY}/authorization-service:${DOCKER_TAG} @@ -45,6 +46,7 @@ services: platform: linux/amd64 build: dockerfile: ee/apps/account-service/Dockerfile + context: . args: SERVICE: account-service image: ghcr.io/${LOWERCASE_REPOSITORY}/account-service:${DOCKER_TAG} @@ -61,6 +63,7 @@ services: platform: linux/amd64 build: dockerfile: ee/apps/presence-service/Dockerfile + context: . args: SERVICE: presence-service image: ghcr.io/${LOWERCASE_REPOSITORY}/presence-service:${DOCKER_TAG} @@ -77,6 +80,7 @@ services: platform: linux/amd64 build: dockerfile: ee/apps/ddp-streamer/Dockerfile + context: . args: SERVICE: ddp-streamer image: ghcr.io/${LOWERCASE_REPOSITORY}/ddp-streamer-service:${DOCKER_TAG} @@ -99,6 +103,7 @@ services: platform: linux/amd64 build: dockerfile: ee/apps/stream-hub-service/Dockerfile + context: . args: SERVICE: stream-hub-service image: ghcr.io/${LOWERCASE_REPOSITORY}/stream-hub-service:${DOCKER_TAG} @@ -115,6 +120,7 @@ services: platform: linux/amd64 build: dockerfile: ee/apps/queue-worker/Dockerfile + context: . args: SERVICE: queue-worker image: ghcr.io/${LOWERCASE_REPOSITORY}/queue-worker-service:${DOCKER_TAG} @@ -131,6 +137,7 @@ services: platform: linux/amd64 build: dockerfile: ee/apps/omnichannel-transcript/Dockerfile + context: . args: SERVICE: omnichannel-transcript image: ghcr.io/${LOWERCASE_REPOSITORY}/omnichannel-transcript-service:${DOCKER_TAG} From 2bdddc5615f4826968fa867368acd82c9e6d3969 Mon Sep 17 00:00:00 2001 From: Kevin Aleman <kaleman960@gmail.com> Date: Thu, 29 Jun 2023 12:40:21 -0600 Subject: [PATCH 19/79] regression: `onLogin` hook not destructuring user prop (#29675) --- .changeset/dull-rockets-own.md | 5 +++++ apps/meteor/app/livechat/server/business-hour/index.ts | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 .changeset/dull-rockets-own.md diff --git a/.changeset/dull-rockets-own.md b/.changeset/dull-rockets-own.md new file mode 100644 index 0000000000000..85a61de0c94b1 --- /dev/null +++ b/.changeset/dull-rockets-own.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +regression: `onLogin` hook not destructuring user prop diff --git a/apps/meteor/app/livechat/server/business-hour/index.ts b/apps/meteor/app/livechat/server/business-hour/index.ts index 8b80e415f7a72..36c67bf74d661 100644 --- a/apps/meteor/app/livechat/server/business-hour/index.ts +++ b/apps/meteor/app/livechat/server/business-hour/index.ts @@ -17,7 +17,7 @@ Meteor.startup(async () => { businessHourManager.registerBusinessHourBehavior(new BusinessHourBehaviorClass()); businessHourManager.registerBusinessHourType(new DefaultBusinessHour()); - Accounts.onLogin((user: IUser) => { + Accounts.onLogin(({ user }: { user: IUser }) => { void (user?.roles?.includes('livechat-agent') && !user?.roles?.includes('bot') && businessHourManager.onLogin(user._id)); }); }); From c31f93ed9677e43d947615c5e2ace233c73df7ad Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Fri, 30 Jun 2023 01:31:44 +0530 Subject: [PATCH 20/79] fix: Newly added agent not following business hours (#29529) Co-authored-by: Kevin Aleman <11577696+KevLehman@users.noreply.github.com> --- .changeset/quick-coats-protect.md | 6 + .../app/lib/server/functions/saveUser.js | 7 +- .../business-hour/AbstractBusinessHour.ts | 34 +++++- .../business-hour/BusinessHourManager.ts | 32 ++--- .../livechat/server/business-hour/Helper.ts | 3 + .../livechat/server/business-hour/Single.ts | 2 + .../livechat/server/hooks/afterUserActions.ts | 50 ++++++-- .../server/hooks/saveAnalyticsData.ts | 2 +- .../app/livechat/server/lib/Livechat.js | 9 +- .../lib/{callbackLogger.ts => logger.ts} | 1 + .../app/livechat/server/sendMessageBySMS.ts | 2 +- apps/meteor/app/livechat/server/startup.ts | 8 +- .../server/business-hour/Helper.ts | 32 +++-- .../server/business-hour/Multiple.ts | 112 ++---------------- .../server/hooks/onCloseLivechat.ts | 2 +- .../server/hooks/resumeOnHold.ts | 2 +- .../app/livechat-enterprise/server/startup.ts | 4 + apps/meteor/lib/callbacks.ts | 2 + apps/meteor/server/models/raw/Users.js | 39 +++--- .../tests/data/livechat/businessHours.ts | 20 +++- apps/meteor/tests/data/livechat/users.ts | 6 + .../api/livechat/19-business-hours.ts | 86 +++++++++++++- packages/core-typings/src/ILivechatAgent.ts | 2 + .../model-typings/src/models/IUsersModel.ts | 10 +- 24 files changed, 301 insertions(+), 172 deletions(-) create mode 100644 .changeset/quick-coats-protect.md rename apps/meteor/app/livechat/server/lib/{callbackLogger.ts => logger.ts} (67%) diff --git a/.changeset/quick-coats-protect.md b/.changeset/quick-coats-protect.md new file mode 100644 index 0000000000000..f47a439443eb9 --- /dev/null +++ b/.changeset/quick-coats-protect.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +fix: newly added agent not following business hours diff --git a/apps/meteor/app/lib/server/functions/saveUser.js b/apps/meteor/app/lib/server/functions/saveUser.js index 849a96260dd26..650a9b05bc31d 100644 --- a/apps/meteor/app/lib/server/functions/saveUser.js +++ b/apps/meteor/app/lib/server/functions/saveUser.js @@ -419,11 +419,14 @@ export const saveUser = async function (userId, userData) { await Users.updateOne({ _id: userData._id }, updateUser); - await callbacks.run('afterSaveUser', userData); - // App IPostUserUpdated event hook const userUpdated = await Users.findOneById(userId); + await callbacks.run('afterSaveUser', { + user: userUpdated, + oldUser: oldUserData, + }); + await Apps.triggerEvent(AppEvents.IPostUserUpdated, { user: userUpdated, previousUser: oldUserData, diff --git a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts index 2bafd2f0fa20b..80cdc5aea2961 100644 --- a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts +++ b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts @@ -1,23 +1,28 @@ import moment from 'moment-timezone'; +import { ILivechatAgentStatus } from '@rocket.chat/core-typings'; import type { ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings'; import type { ILivechatBusinessHoursModel, IUsersModel } from '@rocket.chat/model-typings'; import { LivechatBusinessHours, Users } from '@rocket.chat/models'; import type { UpdateFilter } from 'mongodb'; import type { IWorkHoursCronJobsWrapper } from '../../../../server/models/raw/LivechatBusinessHours'; +import { businessHourLogger } from '../lib/logger'; +import { filterBusinessHoursThatMustBeOpened } from './Helper'; export interface IBusinessHourBehavior { findHoursToCreateJobs(): Promise<IWorkHoursCronJobsWrapper[]>; openBusinessHoursByDayAndHour(day: string, hour: string): Promise<void>; closeBusinessHoursByDayAndHour(day: string, hour: string): Promise<void>; onDisableBusinessHours(): Promise<void>; - onAddAgentToDepartment(options?: Record<string, any>): Promise<any>; + onAddAgentToDepartment(options?: { departmentId: string; agentsId: string[] }): Promise<any>; onRemoveAgentFromDepartment(options?: Record<string, any>): Promise<any>; onRemoveDepartment(department?: ILivechatDepartment): Promise<any>; onStartBusinessHours(): Promise<void>; afterSaveBusinessHours(businessHourData: ILivechatBusinessHour): Promise<void>; allowAgentChangeServiceStatus(agentId: string): Promise<boolean>; changeAgentActiveStatus(agentId: string, status: string): Promise<any>; + // If a new agent is created, this callback will be called + onNewAgentCreated(agentId: string): Promise<void>; } export interface IBusinessHourType { @@ -44,9 +49,7 @@ export abstract class AbstractBusinessHourBehavior { return this.UsersRepository.isAgentWithinBusinessHours(agentId); } - // After logout, users are turned not-available by default - // This will turn them available unless they put themselves offline (manual status change) - async changeAgentActiveStatus(agentId: string, status: string): Promise<any> { + async changeAgentActiveStatus(agentId: string, status: ILivechatAgentStatus): Promise<any> { return this.UsersRepository.setLivechatStatusIf( agentId, status, @@ -56,6 +59,29 @@ export abstract class AbstractBusinessHourBehavior { { livechatStatusSystemModified: true }, ); } + + async onNewAgentCreated(agentId: string): Promise<void> { + businessHourLogger.debug(`Executing onNewAgentCreated for agentId: ${agentId}`); + + const defaultBusinessHour = await LivechatBusinessHours.findOneDefaultBusinessHour(); + if (!defaultBusinessHour) { + businessHourLogger.debug(`No default business hour found for agentId: ${agentId}`); + return; + } + + const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([defaultBusinessHour]); + if (!businessHourToOpen.length) { + businessHourLogger.debug( + `No business hour to open found for agentId: ${agentId}. Default business hour is closed. Setting agentId: ${agentId} to status: ${ILivechatAgentStatus.NOT_AVAILABLE}`, + ); + await Users.setLivechatStatus(agentId, ILivechatAgentStatus.NOT_AVAILABLE); + return; + } + + await Users.addBusinessHourByAgentIds([agentId], defaultBusinessHour._id); + + businessHourLogger.debug(`Setting agentId: ${agentId} to status: ${ILivechatAgentStatus.AVAILABLE}`); + } } export abstract class AbstractBusinessHourType { diff --git a/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts b/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts index b128fa64485e8..7e72a17563586 100644 --- a/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts +++ b/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts @@ -7,16 +7,7 @@ import { Users } from '@rocket.chat/models'; import type { IBusinessHourBehavior, IBusinessHourType } from './AbstractBusinessHour'; import { settings } from '../../../settings/server'; import { callbacks } from '../../../../lib/callbacks'; - -const cronJobDayDict: Record<string, number> = { - Sunday: 0, - Monday: 1, - Tuesday: 2, - Wednesday: 3, - Thursday: 4, - Friday: 5, - Saturday: 6, -}; +import { businessHourLogger } from '../lib/logger'; export class BusinessHourManager { private types: Map<string, IBusinessHourType> = new Map(); @@ -35,6 +26,7 @@ export class BusinessHourManager { async startManager(): Promise<void> { await this.createCronJobsForWorkHours(); + businessHourLogger.debug('Cron jobs created, setting up callbacks'); this.setupCallbacks(); await this.behavior.onStartBusinessHours(); } @@ -115,12 +107,19 @@ export class BusinessHourManager { callbacks.priority.HIGH, 'business-hour-livechat-on-save-agent-department', ); + callbacks.add( + 'livechat.onNewAgentCreated', + this.behavior.onNewAgentCreated.bind(this), + callbacks.priority.HIGH, + 'business-hour-livechat-on-agent-created', + ); } private removeCallbacks(): void { callbacks.remove('livechat.removeAgentDepartment', 'business-hour-livechat-on-remove-agent-department'); callbacks.remove('livechat.afterRemoveDepartment', 'business-hour-livechat-after-remove-department'); callbacks.remove('livechat.saveAgentDepartment', 'business-hour-livechat-on-save-agent-department'); + callbacks.remove('livechat.onNewAgentCreated', 'business-hour-livechat-on-agent-created'); } private async createCronJobsForWorkHours(): Promise<void> { @@ -137,12 +136,17 @@ export class BusinessHourManager { await Promise.all(finish.map(({ day, times }) => this.scheduleCronJob(times, day, 'close', this.closeWorkHoursCallback))); } - private async scheduleCronJob(items: string[], day: string, type: string, job: (day: string, hour: string) => void): Promise<void> { + private async scheduleCronJob( + items: string[], + day: string, + type: 'open' | 'close', + job: (day: string, hour: string) => void, + ): Promise<void> { await Promise.all( items.map((hour) => { - const jobName = `${day}/${hour}/${type}`; - const time = moment(hour, 'HH:mm'); - const scheduleAt = `${time.minutes()} ${time.hours()} * * ${cronJobDayDict[day]}`; + const time = moment(hour, 'HH:mm').day(day); + const jobName = `${time.format('dddd')}/${time.format('HH:mm')}/${type}`; + const scheduleAt = `${time.minutes()} ${time.hours()} * * ${time.day()}`; this.addToCache(jobName); return this.cronJobs.add(jobName, scheduleAt, () => job(day, hour)); }), diff --git a/apps/meteor/app/livechat/server/business-hour/Helper.ts b/apps/meteor/app/livechat/server/business-hour/Helper.ts index ba2b48089afb9..ccf0381f0c0ca 100644 --- a/apps/meteor/app/livechat/server/business-hour/Helper.ts +++ b/apps/meteor/app/livechat/server/business-hour/Helper.ts @@ -4,6 +4,7 @@ import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; import { LivechatBusinessHours, Users } from '@rocket.chat/models'; import { createDefaultBusinessHourRow } from './LivechatBusinessHours'; +import { businessHourLogger } from '../lib/logger'; export const filterBusinessHoursThatMustBeOpened = async ( businessHours: ILivechatBusinessHour[], @@ -52,8 +53,10 @@ export const openBusinessHourDefault = async (): Promise<void> => { }, }); const businessHoursToOpenIds = (await filterBusinessHoursThatMustBeOpened(activeBusinessHours)).map((businessHour) => businessHour._id); + businessHourLogger.debug({ msg: 'Opening default business hours', businessHoursToOpenIds }); await Users.openAgentsBusinessHoursByBusinessHourId(businessHoursToOpenIds); await Users.updateLivechatStatusBasedOnBusinessHours(); + businessHourLogger.debug('Done opening default business hours'); }; export const createDefaultBusinessHourIfNotExists = async (): Promise<void> => { diff --git a/apps/meteor/app/livechat/server/business-hour/Single.ts b/apps/meteor/app/livechat/server/business-hour/Single.ts index fed214305e7f9..2996d5f1c2c74 100644 --- a/apps/meteor/app/livechat/server/business-hour/Single.ts +++ b/apps/meteor/app/livechat/server/business-hour/Single.ts @@ -3,6 +3,7 @@ import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; import type { IBusinessHourBehavior } from './AbstractBusinessHour'; import { AbstractBusinessHourBehavior } from './AbstractBusinessHour'; import { openBusinessHourDefault } from './Helper'; +import { businessHourLogger } from '../lib/logger'; export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior implements IBusinessHourBehavior { async openBusinessHoursByDayAndHour(day: string, hour: string): Promise<void> { @@ -25,6 +26,7 @@ export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior imp } async onStartBusinessHours(): Promise<void> { + businessHourLogger.debug('Starting Single Business Hours'); return openBusinessHourDefault(); } diff --git a/apps/meteor/app/livechat/server/hooks/afterUserActions.ts b/apps/meteor/app/livechat/server/hooks/afterUserActions.ts index 8b47b8af13345..3ec0bd81785ef 100644 --- a/apps/meteor/app/livechat/server/hooks/afterUserActions.ts +++ b/apps/meteor/app/livechat/server/hooks/afterUserActions.ts @@ -1,23 +1,51 @@ import type { IUser } from '@rocket.chat/core-typings'; -import { Users } from '@rocket.chat/models'; -import type { UsersUpdateParamsPOST } from '@rocket.chat/rest-typings'; import { callbacks } from '../../../../lib/callbacks'; +import { Livechat } from '../lib/Livechat'; +import { callbackLogger } from '../lib/logger'; -type UserData = UsersUpdateParamsPOST['data'] & { _id: string }; +type IAfterSaveUserProps = { + user: IUser; + oldUser: IUser | null; +}; + +const wasAgent = (user: Pick<IUser, 'roles'> | null) => user?.roles?.includes('livechat-agent'); +const isAgent = (user: Pick<IUser, 'roles'> | null) => user?.roles?.includes('livechat-agent'); + +const handleAgentUpdated = async (userData: IAfterSaveUserProps) => { + const { + user: { _id: userId, username }, + user: newUser, + oldUser, + } = userData; + + if (wasAgent(oldUser) && !isAgent(newUser)) { + callbackLogger.debug('Removing agent', userId); + await Livechat.removeAgent(username); + } -const handleAgentUpdated = async (userData: UserData) => { - if (!userData?.roles?.includes('livechat-agent')) { - await Users.unsetExtension(userData._id); + if (!wasAgent(oldUser) && isAgent(newUser)) { + callbackLogger.debug('Adding agent', userId); + await Livechat.addAgent(username); } }; -const handleDeactivateUser = async (userData: IUser) => { - if (userData?.roles?.includes('livechat-agent')) { - await Users.unsetExtension(userData._id); +const handleDeactivateUser = async (user: IUser) => { + if (wasAgent(user)) { + callbackLogger.debug('Removing agent', user._id); + await Livechat.removeAgent(user.username); } }; -callbacks.add('afterSaveUser', handleAgentUpdated, callbacks.priority.LOW, 'livechat-after-save-user-remove-extension'); +const handleActivateUser = async (user: IUser) => { + if (isAgent(user)) { + callbackLogger.debug('Adding agent', user._id); + await Livechat.addAgent(user.username); + } +}; + +callbacks.add('afterSaveUser', handleAgentUpdated, callbacks.priority.LOW, 'livechat-after-save-user-update-agent'); + +callbacks.add('afterDeactivateUser', handleDeactivateUser, callbacks.priority.LOW, 'livechat-after-deactivate-user-remove-agent'); -callbacks.add('afterDeactivateUser', handleDeactivateUser, callbacks.priority.LOW, 'livechat-after-deactivate-user-remove-extension'); +callbacks.add('afterActivateUser', handleActivateUser, callbacks.priority.LOW, 'livechat-after-activate-user-add-agent'); diff --git a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts index db3dca5f0a845..53a8c180c61ba 100644 --- a/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts +++ b/apps/meteor/app/livechat/server/hooks/saveAnalyticsData.ts @@ -3,7 +3,7 @@ import { LivechatRooms } from '@rocket.chat/models'; import { callbacks } from '../../../../lib/callbacks'; import { normalizeMessageFileUpload } from '../../../utils/server/functions/normalizeMessageFileUpload'; -import { callbackLogger } from '../lib/callbackLogger'; +import { callbackLogger } from '../lib/logger'; callbacks.add( 'afterSaveMessage', diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index 6ba4a653b211a..1fcf39c30a319 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -533,6 +533,9 @@ export const Livechat = { if (await addUserRolesAsync(user._id, ['livechat-agent'])) { await Users.setOperator(user._id, true); await this.setUserStatusLivechat(user._id, user.status !== 'offline' ? 'available' : 'not-available'); + + callbacks.runAsync('livechat.onNewAgentCreated', user._id); + return user; } @@ -571,14 +574,10 @@ export const Livechat = { const { _id } = user; if (await removeUserFromRolesAsync(_id, ['livechat-agent'])) { - await Users.setOperator(_id, false); - await Users.removeLivechatData(_id); - await this.setUserStatusLivechat(_id, 'not-available'); - await Promise.all([ + Users.removeAgent(_id), LivechatDepartmentAgents.removeByAgentId(_id), LivechatVisitors.removeContactManagerByUsername(username), - Users.unsetExtension(_id), ]); return true; } diff --git a/apps/meteor/app/livechat/server/lib/callbackLogger.ts b/apps/meteor/app/livechat/server/lib/logger.ts similarity index 67% rename from apps/meteor/app/livechat/server/lib/callbackLogger.ts rename to apps/meteor/app/livechat/server/lib/logger.ts index 7b56495bba3c2..ee6b2969c5c58 100644 --- a/apps/meteor/app/livechat/server/lib/callbackLogger.ts +++ b/apps/meteor/app/livechat/server/lib/logger.ts @@ -1,3 +1,4 @@ import { Logger } from '../../../../server/lib/logger/Logger'; export const callbackLogger = new Logger('[Omnichannel] Callback'); +export const businessHourLogger = new Logger('Business Hour'); diff --git a/apps/meteor/app/livechat/server/sendMessageBySMS.ts b/apps/meteor/app/livechat/server/sendMessageBySMS.ts index 7df67b44b2fbf..8449c2c7bacf0 100644 --- a/apps/meteor/app/livechat/server/sendMessageBySMS.ts +++ b/apps/meteor/app/livechat/server/sendMessageBySMS.ts @@ -5,7 +5,7 @@ import { OmnichannelIntegration } from '@rocket.chat/core-services'; import { callbacks } from '../../../lib/callbacks'; import { settings } from '../../settings/server'; import { normalizeMessageFileUpload } from '../../utils/server/functions/normalizeMessageFileUpload'; -import { callbackLogger } from './lib/callbackLogger'; +import { callbackLogger } from './lib/logger'; callbacks.add( 'afterSaveMessage', diff --git a/apps/meteor/app/livechat/server/startup.ts b/apps/meteor/app/livechat/server/startup.ts index e268fd7aa6d2f..d8c5ba098d652 100644 --- a/apps/meteor/app/livechat/server/startup.ts +++ b/apps/meteor/app/livechat/server/startup.ts @@ -62,10 +62,14 @@ Meteor.startup(async () => { await createDefaultBusinessHourIfNotExists(); settings.watch<boolean>('Livechat_enable_business_hours', async (value) => { + Livechat.logger.debug(`Changing business hour type to ${value}`); if (value) { - return businessHourManager.startManager(); + await businessHourManager.startManager(); + Livechat.logger.debug(`Business hour manager started`); + return; } - return businessHourManager.stopManager(); + await businessHourManager.stopManager(); + Livechat.logger.debug(`Business hour manager stopped`); }); settings.watch<string>('Livechat_Routing_Method', function (value) { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts index b2e7483561746..6575f13bbde2d 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts @@ -4,11 +4,13 @@ import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; import { LivechatBusinessHours, LivechatDepartment, LivechatDepartmentAgents, Users } from '@rocket.chat/models'; import { isEnterprise } from '../../../license/server/license'; +import { businessHourLogger } from '../../../../../app/livechat/server/lib/logger'; const getAllAgentIdsWithoutDepartment = async (): Promise<string[]> => { const agentIdsWithDepartment = ( await LivechatDepartmentAgents.find({ departmentEnabled: true }, { projection: { agentId: 1 } }).toArray() - ).map((dept: any) => dept.agentId); + ).map((dept) => dept.agentId); + const agentIdsWithoutDepartment = ( await Users.findUsersInRolesWithQuery( 'livechat-agent', @@ -17,7 +19,8 @@ const getAllAgentIdsWithoutDepartment = async (): Promise<string[]> => { }, { projection: { _id: 1 } }, ).toArray() - ).map((user: any) => user._id); + ).map((user) => user._id); + return agentIdsWithoutDepartment; }; @@ -43,7 +46,7 @@ const getAllAgentIdsForDefaultBusinessHour = async (): Promise<string[]> => { return [...new Set([...withoutDepartment, ...withDepartmentNotConnectedToBusinessHour])]; }; -const getAgentIdsToHandle = async (businessHour: Record<string, any>): Promise<string[]> => { +const getAgentIdsToHandle = async (businessHour: Pick<ILivechatBusinessHour, '_id' | 'type'>): Promise<string[]> => { if (businessHour.type === LivechatBusinessHourTypes.DEFAULT) { return getAllAgentIdsForDefaultBusinessHour(); } @@ -51,22 +54,35 @@ const getAgentIdsToHandle = async (businessHour: Record<string, any>): Promise<s await LivechatDepartment.findEnabledByBusinessHourId(businessHour._id, { projection: { _id: 1 }, }).toArray() - ).map((dept: any) => dept._id); + ).map((dept) => dept._id); return ( await LivechatDepartmentAgents.findByDepartmentIds(departmentIds, { projection: { agentId: 1 }, }).toArray() - ).map((dept: any) => dept.agentId); + ).map((dept) => dept.agentId); }; -export const openBusinessHour = async (businessHour: Record<string, any>): Promise<void> => { - const agentIds: string[] = await getAgentIdsToHandle(businessHour); +export const openBusinessHour = async (businessHour: Pick<ILivechatBusinessHour, '_id' | 'type'>): Promise<void> => { + const agentIds = await getAgentIdsToHandle(businessHour); + businessHourLogger.debug({ + msg: 'Opening business hour', + businessHour: businessHour._id, + totalAgents: agentIds.length, + top10AgentIds: agentIds.slice(0, 10), + }); + await Users.addBusinessHourByAgentIds(agentIds, businessHour._id); await Users.updateLivechatStatusBasedOnBusinessHours(); }; -export const closeBusinessHour = async (businessHour: Record<string, any>): Promise<void> => { +export const closeBusinessHour = async (businessHour: Pick<ILivechatBusinessHour, '_id' | 'type'>): Promise<void> => { const agentIds: string[] = await getAgentIdsToHandle(businessHour); + businessHourLogger.debug({ + msg: 'Closing business hour', + businessHour: businessHour._id, + totalAgents: agentIds.length, + top10AgentIds: agentIds.slice(0, 10), + }); await Users.removeBusinessHourByAgentIds(agentIds, businessHour._id); await Users.updateLivechatStatusBasedOnBusinessHours(); }; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts index 2c0db1516f8f2..0fa768453e3ad 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts @@ -1,16 +1,12 @@ import moment from 'moment'; -import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; import type { ILivechatDepartment, ILivechatBusinessHour } from '@rocket.chat/core-typings'; import { LivechatDepartment, LivechatDepartmentAgents } from '@rocket.chat/models'; import type { IBusinessHourBehavior } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; import { AbstractBusinessHourBehavior } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; -import { - filterBusinessHoursThatMustBeOpened, - filterBusinessHoursThatMustBeOpenedByDay, -} from '../../../../../app/livechat/server/business-hour/Helper'; +import { filterBusinessHoursThatMustBeOpened } from '../../../../../app/livechat/server/business-hour/Helper'; import { closeBusinessHour, openBusinessHour, removeBusinessHourByAgentIds } from './Helper'; -import { bhLogger } from '../lib/logger'; +import { businessHourLogger } from '../../../../../app/livechat/server/lib/logger'; interface IBusinessHoursExtraProperties extends ILivechatBusinessHour { timezoneName: string; @@ -23,6 +19,7 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior this.onAddAgentToDepartment = this.onAddAgentToDepartment.bind(this); this.onRemoveAgentFromDepartment = this.onRemoveAgentFromDepartment.bind(this); this.onRemoveDepartment = this.onRemoveDepartment.bind(this); + this.onNewAgentCreated = this.onNewAgentCreated.bind(this); } async onStartBusinessHours(): Promise<void> { @@ -39,6 +36,11 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior }, }); const businessHoursToOpen = await filterBusinessHoursThatMustBeOpened(activeBusinessHours); + businessHourLogger.debug({ + msg: 'Starting Multiple Business Hours', + totalBusinessHoursToOpen: businessHoursToOpen.length, + top10BusinessHoursToOpen: businessHoursToOpen.slice(0, 10), + }); for (const businessHour of businessHoursToOpen) { void this.openBusinessHour(businessHour); } @@ -84,7 +86,7 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior return openBusinessHour(businessHour); } - async onAddAgentToDepartment(options: Record<string, any> = {}): Promise<any> { + async onAddAgentToDepartment(options: { departmentId: string; agentsId: string[] }): Promise<any> { const { departmentId, agentsId } = options; const department = await LivechatDepartment.findOneById<Pick<ILivechatDepartment, 'businessHourId'>>(departmentId, { projection: { businessHourId: 1 }, @@ -132,96 +134,8 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior return this.handleRemoveAgentsFromDepartments(deletedDepartment, agentsIds, options); } - async allowAgentChangeServiceStatus(agentId: string): Promise<boolean> { - const isWithinBushinessHours = await this.UsersRepository.isAgentWithinBusinessHours(agentId); - if (isWithinBushinessHours) { - return true; - } - - bhLogger.debug(`No active business hour found for agent with id: ${agentId} based on user's cache. Attempting to recheck the status`); - - // double check to see if user is actually within business hours - // this is required since the cache of businessHour Ids we maintain within user's collection might be stale - // in many scenario's like, if the agent is created when a business is active, - // or if a normal user is converted to agent when a business hour is active - const currentTime = moment.utc(moment().utc().format('dddd:HH:mm'), 'dddd:HH:mm'); - const day = currentTime.format('dddd'); - const allActiveBusinessHoursForEntireWeek = await this.BusinessHourRepository.findActiveBusinessHours({ - projection: { - workHours: 1, - timezone: 1, - type: 1, - active: 1, - }, - }); - const openedBusinessHours = await filterBusinessHoursThatMustBeOpenedByDay(allActiveBusinessHoursForEntireWeek, day); - if (!openedBusinessHours.length) { - bhLogger.debug(`Business hour status recheck failed for agentId: ${agentId}. No opened business hour found`); - return false; - } - - const agentDepartments = await LivechatDepartmentAgents.find( - { departmentEnabled: true, agentId }, - { projection: { agentId: 1, departmentId: 1 } }, - ).toArray(); - - if (agentDepartments.length) { - // check if any one these departments have a opened business hour linked to it - const departments = await LivechatDepartment.findInIds( - agentDepartments.map(({ departmentId }) => departmentId), - { projection: { _id: 1, businessHourId: 1 } }, - ).toArray(); - - const departmentsWithActiveBH = departments.filter( - ({ businessHourId }) => businessHourId && openedBusinessHours.findIndex(({ _id }) => _id === businessHourId) !== -1, - ); - - if (!departmentsWithActiveBH.length) { - bhLogger.debug( - `No opened business hour found for any of the departments connected to the agent with id: ${agentId}. Now, checking if the default business hour can be used`, - ); - - // check if this agent has any departments that is connected to any non-default business hour - // if no such departments found then check default BH and if it is active, then allow the agent to change service status - const hasAtLeastOneDepartmentWithNonDefaultBH = departments.some(({ businessHourId }) => { - // check if business hour is active - return businessHourId && allActiveBusinessHoursForEntireWeek.findIndex(({ _id }) => _id === businessHourId) !== -1; - }); - if (!hasAtLeastOneDepartmentWithNonDefaultBH) { - const isDefaultBHActive = openedBusinessHours.find(({ type }) => type === LivechatBusinessHourTypes.DEFAULT); - if (isDefaultBHActive?._id) { - await this.UsersRepository.openAgentBusinessHoursByBusinessHourIdsAndAgentId([isDefaultBHActive._id], agentId); - - bhLogger.debug(`Business hour status recheck passed for agentId: ${agentId}. Found default business hour to be active`); - return true; - } - bhLogger.debug(`Business hour status recheck failed for agentId: ${agentId}. Found default business hour to be inactive`); - } - return false; - } - - const activeBusinessHoursForAgent = departmentsWithActiveBH.map(({ businessHourId }) => businessHourId); - await this.UsersRepository.openAgentBusinessHoursByBusinessHourIdsAndAgentId(activeBusinessHoursForAgent, agentId); - - bhLogger.debug( - `Business hour status recheck passed for agentId: ${agentId}. Found following business hours to be active:`, - activeBusinessHoursForAgent, - ); - return true; - } - - // check if default businessHour is active - const isDefaultBHActive = openedBusinessHours.find(({ type }) => type === LivechatBusinessHourTypes.DEFAULT); - if (isDefaultBHActive?._id) { - await this.UsersRepository.openAgentBusinessHoursByBusinessHourIdsAndAgentId([isDefaultBHActive._id], agentId); - - bhLogger.debug(`Business hour status recheck passed for agentId: ${agentId}. Found default business hour to be active`); - return true; - } - - bhLogger.debug(`Business hour status recheck failed for agentId: ${agentId}. No opened business hour found`); - - return false; + allowAgentChangeServiceStatus(agentId: string): Promise<boolean> { + return this.UsersRepository.isAgentWithinBusinessHours(agentId); } private async handleRemoveAgentsFromDepartments(department: Record<string, any>, agentsIds: string[], options: any): Promise<any> { @@ -255,7 +169,7 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior return options; } - private async openBusinessHour(businessHour: Record<string, any>): Promise<void> { + private async openBusinessHour(businessHour: Pick<ILivechatBusinessHour, '_id' | 'type'>): Promise<void> { return openBusinessHour(businessHour); } @@ -270,7 +184,7 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior await removeBusinessHourByAgentIds(agentIds, businessHourId); } - private async closeBusinessHour(businessHour: Record<string, any>): Promise<void> { + private async closeBusinessHour(businessHour: Pick<ILivechatBusinessHour, '_id' | 'type'>): Promise<void> { await closeBusinessHour(businessHour); } } diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts index 59d96b3fac1bd..3f93967ae9b83 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/onCloseLivechat.ts @@ -4,8 +4,8 @@ import { LivechatRooms, Subscriptions } from '@rocket.chat/models'; import { callbacks } from '../../../../../lib/callbacks'; import { settings } from '../../../../../app/settings/server'; import { debouncedDispatchWaitingQueueStatus } from '../lib/Helper'; -import { callbackLogger } from '../../../../../app/livechat/server/lib/callbackLogger'; import { AutoCloseOnHoldScheduler } from '../lib/AutoCloseOnHoldScheduler'; +import { callbackLogger } from '../../../../../app/livechat/server/lib/logger'; type LivechatCloseCallbackParams = { room: IOmnichannelRoom; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/resumeOnHold.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/resumeOnHold.ts index 8682f03852b1f..de6b0861d8788 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/resumeOnHold.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/resumeOnHold.ts @@ -4,8 +4,8 @@ import { LivechatRooms, LivechatVisitors, Users } from '@rocket.chat/models'; import { OmnichannelEEService } from '@rocket.chat/core-services'; import { callbacks } from '../../../../../lib/callbacks'; -import { callbackLogger } from '../../../../../app/livechat/server/lib/callbackLogger'; import { i18n } from '../../../../../server/lib/i18n'; +import { callbackLogger } from '../../../../../app/livechat/server/lib/logger'; const resumeOnHoldCommentAndUser = async (room: IOmnichannelRoom): Promise<{ comment: string; resumedBy: IUser }> => { const { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/startup.ts b/apps/meteor/ee/app/livechat-enterprise/server/startup.ts index 26a7cacce4c4a..7ba2479dd2fb5 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/startup.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/startup.ts @@ -8,6 +8,7 @@ import { MultipleBusinessHoursBehavior } from './business-hour/Multiple'; import { SingleBusinessHourBehavior } from '../../../../app/livechat/server/business-hour/Single'; import { businessHourManager } from '../../../../app/livechat/server/business-hour'; import { resetDefaultBusinessHourIfNeeded } from './business-hour/Helper'; +import { logger } from './lib/logger'; const visitorActivityMonitor = new VisitorInactivityMonitor(); const businessHours = { @@ -31,12 +32,15 @@ Meteor.startup(async function () { await updatePredictedVisitorAbandonment(); }); settings.change<string>('Livechat_business_hour_type', async (value) => { + logger.debug(`Changing business hour type to ${value}`); if (!Object.keys(businessHours).includes(value)) { + logger.error(`Invalid business hour type ${value}`); return; } businessHourManager.registerBusinessHourBehavior(businessHours[value as keyof typeof businessHours]); if (settings.get('Livechat_enable_business_hours')) { await businessHourManager.startManager(); + logger.debug(`Business hour manager started`); } }); diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index 32a4e59a7b4b3..f84b927c89e0f 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -61,6 +61,7 @@ interface EventLikeCallbackSignatures { 'livechat:afterReturnRoomAsInquiry': (params: { room: IRoom }) => void; 'livechat.setUserStatusLivechat': (params: { userId: IUser['_id']; status: OmnichannelAgentStatus }) => void; 'livechat.agentStatusChanged': (params: { userId: IUser['_id']; status: OmnichannelAgentStatus }) => void; + 'livechat.onNewAgentCreated': (agentId: string) => void; 'livechat.afterTakeInquiry': (inq: InquiryWithAgentInfo, agent: { agentId: string; username: string }) => void; 'afterAddedToRoom': (params: { user: IUser; inviter?: IUser }, room: IRoom) => void; 'beforeAddedToRoom': (params: { user: IUser; inviter: IUser }) => void; @@ -91,6 +92,7 @@ interface EventLikeCallbackSignatures { 'afterValidateLogin': (login: { user: IUser }) => void; 'afterJoinRoom': (user: IUser, room: IRoom) => void; 'beforeCreateRoom': (data: { type: IRoom['t']; extraData: { encrypted: boolean } }) => void; + 'afterSaveUser': ({ user, oldUser }: { user: IUser; oldUser: IUser | null }) => void; } /** diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index f43a1522a548e..f8ae05e11d11e 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -842,9 +842,6 @@ export class UsersRaw extends BaseRaw { }; const update = { - $set: { - statusLivechat: 'available', - }, $addToSet: { openBusinessHours: { $each: businessHourIds }, }, @@ -878,9 +875,6 @@ export class UsersRaw extends BaseRaw { }; const update = { - $set: { - statusLivechat: 'available', - }, $addToSet: { openBusinessHours: businessHourId, }, @@ -985,15 +979,14 @@ export class UsersRaw extends BaseRaw { } async isAgentWithinBusinessHours(agentId) { - return ( - (await this.find({ - _id: agentId, - openBusinessHours: { - $exists: true, - $not: { $size: 0 }, - }, - }).count()) > 0 - ); + const query = { + _id: agentId, + openBusinessHours: { + $exists: true, + $not: { $size: 0 }, + }, + }; + return (await this.col.countDocuments(query)) > 0; } removeBusinessHoursFromAllUsers() { @@ -2887,4 +2880,20 @@ export class UsersRaw extends BaseRaw { countRoomMembers(roomId) { return this.col.countDocuments({ __rooms: roomId, active: true }); } + + removeAgent(_id) { + const update = { + $set: { + operator: false, + }, + $unset: { + livechat: 1, + statusLivechat: 1, + extension: 1, + openBusinessHours: 1, + }, + }; + + return this.updateOne({ _id }, update); + } } diff --git a/apps/meteor/tests/data/livechat/businessHours.ts b/apps/meteor/tests/data/livechat/businessHours.ts index 59b232a38d227..73ccdf75d0961 100644 --- a/apps/meteor/tests/data/livechat/businessHours.ts +++ b/apps/meteor/tests/data/livechat/businessHours.ts @@ -1,7 +1,9 @@ -import { ILivechatBusinessHour } from "@rocket.chat/core-typings"; +import { ILivechatBusinessHour, LivechatBusinessHourTypes } from "@rocket.chat/core-typings"; import { api, credentials, methodCall, request } from "../api-data"; import { updateEESetting, updateSetting } from "../permissions.helper" +import { saveBusinessHour } from "./business-hours"; import moment from "moment"; + type ISaveBhApiWorkHour = Omit<ILivechatBusinessHour, '_id' | 'ts' | 'timezone'> & { workHours: { day: string, start: string, finish: string, open: boolean }[] } & { departmentsToApplyBusinessHour?: string } & { timezoneName: string }; export const makeDefaultBusinessHourActiveAndClosed = async () => { @@ -76,6 +78,22 @@ export const disableDefaultBusinessHour = async () => { }); } +export const getDefaultBusinessHour = async (): Promise<ILivechatBusinessHour> => { + const response = await request.get(api('livechat/business-hour')).set(credentials).query({ type: LivechatBusinessHourTypes.DEFAULT }).expect(200); + return response.body.businessHour; +}; + +export const openOrCloseBusinessHour = async (businessHour: ILivechatBusinessHour, open: boolean) => { + const enabledBusinessHour = { + ...businessHour, + timezoneName: businessHour.timezone.name, + workHours: getWorkHours(open), + departmentsToApplyBusinessHour: businessHour.departments?.map((department) => department._id).join(',') || '', + } + + await saveBusinessHour(enabledBusinessHour as any); +} + export const getWorkHours = (open = true): ISaveBhApiWorkHour['workHours'] => { const workHours: ISaveBhApiWorkHour['workHours'] = []; diff --git a/apps/meteor/tests/data/livechat/users.ts b/apps/meteor/tests/data/livechat/users.ts index 401ba463e5751..7a5dc23b4cc02 100644 --- a/apps/meteor/tests/data/livechat/users.ts +++ b/apps/meteor/tests/data/livechat/users.ts @@ -3,6 +3,7 @@ import type { IUser } from "@rocket.chat/core-typings"; import { password } from "../user"; import { createUser, login } from "../users.helper"; import { createAgent, makeAgentAvailable } from "./rooms"; +import { api, credentials, request } from "../api-data"; export const createBotAgent = async (): Promise<{ credentials: { 'X-Auth-Token': string; 'X-User-Id': string; }; @@ -23,3 +24,8 @@ export const createBotAgent = async (): Promise<{ export const getRandomVisitorToken = (): string => faker.string.alphanumeric(17); +export const removeAgent = async (userId: string): Promise<void> => { + await request.delete(api(`livechat/users/agent/${userId}`)) + .set(credentials) + .expect(200); +} diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index eb5393920c2d1..d2e41fa11beca 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -1,14 +1,20 @@ /* eslint-env mocha */ -import { LivechatBusinessHourTypes, LivechatBusinessHourBehaviors } from '@rocket.chat/core-typings'; +import type { ILivechatAgent, ILivechatBusinessHour } from '@rocket.chat/core-typings'; +import { ILivechatAgentStatus, LivechatBusinessHourBehaviors, LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; import { saveBusinessHour } from '../../../data/livechat/business-hours'; -import { updatePermission, updateSetting } from '../../../data/permissions.helper'; +import { updateEESetting, updatePermission, updateSetting } from '../../../data/permissions.helper'; import { IS_EE } from '../../../e2e/config/constants'; +import { createUser, deleteUser, getMe, login } from '../../../data/users.helper'; import { createAgent, makeAgentAvailable } from '../../../data/livechat/rooms'; -import { getWorkHours } from '../../../data/livechat/businessHours'; +import { sleep } from '../../../../lib/utils/sleep'; +import { getDefaultBusinessHour, openOrCloseBusinessHour, getWorkHours } from '../../../data/livechat/businessHours'; +import type { IUserCredentialsHeader } from '../../../data/user'; +import { password } from '../../../data/user'; +import { removeAgent } from '../../../data/livechat/users'; describe('[CE] LIVECHAT - business hours', function () { this.retries(0); @@ -226,4 +232,78 @@ describe('[CE] LIVECHAT - business hours', function () { expect(result).to.have.property('error'); }); }); + + describe('BH behavior upon new agent creation/deletion', () => { + let defaultBH: ILivechatBusinessHour; + let agent: ILivechatAgent; + let agentCredentials: IUserCredentialsHeader; + + before(async () => { + await updateSetting('Livechat_enable_business_hours', true); + await updateEESetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.SINGLE); + // wait for callbacks to run + await sleep(2000); + + defaultBH = await getDefaultBusinessHour(); + await openOrCloseBusinessHour(defaultBH, true); + + agent = await createUser(); + agentCredentials = await login(agent.username, password); + }); + + it('should create a new agent and verify if it is assigned to the default business hour which is open', async () => { + agent = await createAgent(agent.username); + + const latestAgent: ILivechatAgent = await getMe(agentCredentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id); + }); + + it('should create a new agent and verify if it is assigned to the default business hour which is closed', async () => { + await openOrCloseBusinessHour(defaultBH, false); + + const newUser: ILivechatAgent = await createUser(); + const newUserCredentials = await login(newUser.username, password); + await createAgent(newUser.username); + + const latestAgent: ILivechatAgent = await getMe(newUserCredentials); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.undefined; + expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE); + }); + + it('should verify if agent is assigned to BH when it is opened', async () => { + // first verify if agent is not assigned to any BH + let latestAgent: ILivechatAgent = await getMe(agentCredentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(0); + expect(latestAgent.statusLivechat).to.be.equal(ILivechatAgentStatus.NOT_AVAILABLE); + + // now open BH + await openOrCloseBusinessHour(defaultBH, true); + + // verify if agent is assigned to BH + latestAgent = await getMe(agentCredentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBH._id); + + // verify if agent is able to make themselves available + await makeAgentAvailable(agentCredentials as any); + }); + + it('should verify if BH related props are cleared when an agent is deleted', async () => { + await removeAgent(agent._id); + + const latestAgent: ILivechatAgent = await getMe(agentCredentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.undefined; + expect(latestAgent.statusLivechat).to.be.undefined; + }); + + after(async () => { + await deleteUser(agent._id); + }); + }); }); diff --git a/packages/core-typings/src/ILivechatAgent.ts b/packages/core-typings/src/ILivechatAgent.ts index 35e5763e57c6b..68a99c2723d97 100644 --- a/packages/core-typings/src/ILivechatAgent.ts +++ b/packages/core-typings/src/ILivechatAgent.ts @@ -13,4 +13,6 @@ export interface ILivechatAgent extends IUser { livechatCount: number; lastRoutingTime: Date; livechatStatusSystemModified?: boolean; + + openBusinessHours?: string[]; } diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index bc279190ea812..5900b5a140915 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -8,6 +8,7 @@ import type { ILoginToken, IPersonalAccessToken, AtLeast, + ILivechatAgentStatus, } from '@rocket.chat/core-typings'; import type { FindPaginated, IBaseModel } from './IBaseModel'; @@ -92,7 +93,7 @@ export interface IUsersModel extends IBaseModel<IUser> { setLastRoutingTime(userId: any): Promise<number>; - setLivechatStatusIf(userId: any, status: any, conditions?: any, extraFields?: any): Promise<UpdateResult>; + setLivechatStatusIf(userId: string, status: ILivechatAgentStatus, conditions?: any, extraFields?: any): Promise<UpdateResult>; getAgentAndAmountOngoingChats( userId: any, ): Promise<{ agentId: string; username: string; lastAssignTime: Date; lastRoutingTime: Date; queueInfo: { chats: number } }>; @@ -128,7 +129,7 @@ export interface IUsersModel extends IBaseModel<IUser> { openAgentBusinessHoursByBusinessHourIdsAndAgentId(businessHourIds: any, agentId: any): any; - addBusinessHourByAgentIds(agentIds: any, businessHourId: any): any; + addBusinessHourByAgentIds(agentIds: string[], businessHourId: string): any; removeBusinessHourByAgentIds(agentIds: any, businessHourId: any): any; @@ -142,7 +143,7 @@ export interface IUsersModel extends IBaseModel<IUser> { setLivechatStatusActiveBasedOnBusinessHours(userId: any): any; - isAgentWithinBusinessHours(agentId: any): Promise<any>; + isAgentWithinBusinessHours(agentId: string): Promise<boolean>; removeBusinessHoursFromAllUsers(): any; @@ -253,7 +254,7 @@ export interface IUsersModel extends IBaseModel<IUser> { countAgents(): Promise<number>; getNextAgent(ignoreAgentId?: string, extraQuery?: Filter<IUser>): Promise<{ agentId: string; username: string } | null>; getNextBotAgent(ignoreAgentId?: string): Promise<{ agentId: string; username: string } | null>; - setLivechatStatus(userId: string, status: UserStatus): Promise<UpdateResult>; + setLivechatStatus(userId: string, status: ILivechatAgentStatus): Promise<UpdateResult>; setLivechatData(userId: string, data?: Record<string, any>): Promise<UpdateResult>; closeOffice(): Promise<void>; openOffice(): Promise<void>; @@ -374,4 +375,5 @@ export interface IUsersModel extends IBaseModel<IUser> { countRoomMembers(roomId: string): Promise<number>; countRemote(options?: FindOptions<IUser>): Promise<number>; findOneByImportId(importId: string, options?: FindOptions<IUser>): Promise<IUser | null>; + removeAgent(_id: string): Promise<UpdateResult>; } From fa6c1258d8da80f749de7ca10bdca1a0c9eb4c35 Mon Sep 17 00:00:00 2001 From: Diego Sampaio <chinello@gmail.com> Date: Fri, 30 Jun 2023 13:59:55 -0300 Subject: [PATCH 21/79] ci: update EE license (#29684) --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d9c250ebcc045..8c71965aa907c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,8 +32,8 @@ jobs: rc-docker-tag-alpine: '${{ steps.docker.outputs.gh-docker-tag }}.alpine' node-version: ${{ steps.var.outputs.node-version }} # this is 100% intentional, secrets are not available for forks, so ee-tests will always fail - # to avoid this, we are using a dummy license, expiring at 2023-06-30 - enterprise-license: Z2Dg0RC3kyxjuklSE6qfqyvD2xSD+oTYcS9OesJG0523r7rSPjv59LTQqPcp5E61qQYM3MOKoW3mDrurw4h78nVbsfrF2DoJZeNjRFQfIbgwcdPwtmnqPpDvAslszHY16VzM7O7EYqAqp/9mlnRzs1iJY+W3w1r6HWBlVMb9u41bl5HBSpX6Nxw8YxL4mizwOpjxewQbPQvNTLJNAW6w0nCzF5A3CKBhD9fziadedVMLOuXBuR8kIl8zbIAfqpHmL8SvakvQAbZEjWWQshmH+C9CKA5PppkmA8Q1DNWQoVtHSiYDK8RRjAEx+0oGflklzFyhJFDvD+ohZduNtNCgrJmxP5VFrVrLSK4BXgTSwwnaSKa2N+Qx0CmuRfu7nCPc1Cf6h6+k2TXvzkE4Z0ZJnDV1khu611glAr99bHdwF+bMX3XZI66bS8KqnHEukCt5xei25iKJ2xrfmGuiAkAuKHKzBmTEmXM0pGhkfDhA9jhxG3Atoj1A5y8vdrs88voF+UuNFZ6k9sKtdvrWIWClnkatPE+41ggbzCsOhFz07BvRWaEtw2Kenipl4Vtag4qmFpUaUfsuouH99M3gDlysDZO3x5aH8yfzvFeL5WDMvsmdEHNLpHl89WsPCONvx0JjRSdwcCA1NrRuVy1Ncu0S0bRByn7HZqoY9u6HPkXKBxQ= + # to avoid this, we are using a dummy license, expiring at 2024-06-30 + enterprise-license: WMa5i+/t/LZbYOj8u3XUkivRhWBtWO6ycUjaZoVAw2DxMfdyBIAa2gMMI4x7Z2BrTZIZhFEImfOxcXcgD0QbXHGBJaMI+eYG+eofnVWi2VA7RWbpvWTULgPFgyJ4UEFeCOzVjcBLTQbmMSam3u0RlekWJkfAO0KnmLtsaEYNNA2rz1U+CLI/CdNGfdqrBu5PZZbGkH0KEzyIZMaykOjzvX+C6vd7fRxh23HecwhkBbqE8eQsCBt2ad0qC4MoVXsDaSOmSzGW+aXjuXt/9zjvrLlsmWQTSlkrEHdNkdywm0UkGxqz3+CP99n0WggUBioUiChjMuNMoceWvDvmxYP9Ml2NpYU7SnfhjmMFyXOah8ofzv8w509Y7XODvQBz+iB4Co9YnF3vT96HDDQyAV5t4jATE+0t37EAXmwjTi3qqyP7DLGK/revl+mlcwJ5kS4zZBsm1E4519FkXQOZSyWRnPdjqvh4mCLqoispZ49wKvklDvjPxCSP9us6cVXLDg7NTJr/4pfxLPOkvv7qCgugDvlDx17bXpQFPSDxmpw66FLzvb5Id0dkWjOzrRYSXb0bFWoUQjtHFzmcpFkyVhOKrQ9zA9+Zm7vXmU9Y2l2dK79EloOuHMSYAqsPEag8GMW6vI/cT4iIjHGGDePKnD0HblvTEKzql11cfT/abf2IiaY= steps: - uses: Bhacaz/checkout-files@v2 with: From 6ff6ba96609508d114a8a81ae0390247c44c538d Mon Sep 17 00:00:00 2001 From: Diego Sampaio <chinello@gmail.com> Date: Fri, 30 Jun 2023 16:49:37 -0300 Subject: [PATCH 22/79] chore: add change stream option to get full doc (#29627) --- .../meteor/server/database/DatabaseWatcher.ts | 19 ++- .../database/convertChangeStreamPayload.ts | 1 + .../server/database/watchCollections.ts | 58 +++++++--- .../modules/watchers/watchers.module.ts | 108 +++++++++--------- ee/apps/stream-hub-service/src/service.ts | 6 +- 5 files changed, 112 insertions(+), 80 deletions(-) diff --git a/apps/meteor/server/database/DatabaseWatcher.ts b/apps/meteor/server/database/DatabaseWatcher.ts index ff42f9721232e..b0b6058b1e9d6 100644 --- a/apps/meteor/server/database/DatabaseWatcher.ts +++ b/apps/meteor/server/database/DatabaseWatcher.ts @@ -8,7 +8,7 @@ import { MongoClient } from 'mongodb'; import type { Logger } from '../lib/logger/Logger'; import { convertChangeStreamPayload } from './convertChangeStreamPayload'; import { convertOplogPayload } from './convertOplogPayload'; -import { watchCollections } from './watchCollections'; +import { getWatchCollections } from './watchCollections'; const instancePing = parseInt(String(process.env.MULTIPLE_INSTANCES_PING_INTERVAL)) || 10000; @@ -28,6 +28,8 @@ const ignoreChangeStream = ['yes', 'true'].includes(String(process.env.IGNORE_CH const useMeteorOplog = ['yes', 'true'].includes(String(process.env.USE_NATIVE_OPLOG).toLowerCase()); +const useFullDocument = ['yes', 'true'].includes(String(process.env.CHANGESTREAM_FULL_DOCUMENT).toLowerCase()); + export class DatabaseWatcher extends EventEmitter { private db: Db; @@ -44,6 +46,8 @@ export class DatabaseWatcher extends EventEmitter { */ private lastDocTS: Date; + private watchCollections: string[]; + // eslint-disable-next-line @typescript-eslint/naming-convention constructor({ db, _oplogHandle, metrics, logger: LoggerClass }: { db: Db; _oplogHandle?: any; metrics?: any; logger: typeof Logger }) { super(); @@ -55,6 +59,8 @@ export class DatabaseWatcher extends EventEmitter { } async watch(): Promise<void> { + this.watchCollections = getWatchCollections(); + if (useMeteorOplog) { // TODO remove this when updating to Meteor 2.8 this.logger.warn( @@ -121,7 +127,7 @@ export class DatabaseWatcher extends EventEmitter { const stream = cursor.stream(); stream.on('data', (doc) => { - const doesMatter = watchCollections.some((collection) => doc.ns === `${dbName}.${collection}`); + const doesMatter = this.watchCollections.some((collection) => doc.ns === `${dbName}.${collection}`); if (!doesMatter) { return; } @@ -143,7 +149,7 @@ export class DatabaseWatcher extends EventEmitter { this.logger.startup('Using Meteor oplog'); - watchCollections.forEach((collection) => { + this.watchCollections.forEach((collection) => { this._oplogHandle.onOplogEntry({ collection }, (event: any) => { this.emitDoc(collection, convertOplogPayload(event)); }); @@ -152,7 +158,10 @@ export class DatabaseWatcher extends EventEmitter { private watchChangeStream(resumeToken?: unknown): void { try { - const options = resumeToken ? { startAfter: resumeToken } : {}; + const options = { + ...(useFullDocument ? { fullDocument: 'updateLookup' } : {}), + ...(resumeToken ? { startAfter: resumeToken } : {}), + }; let lastEvent: unknown; @@ -166,7 +175,7 @@ export class DatabaseWatcher extends EventEmitter { { $match: { 'operationType': { $in: ['insert', 'update', 'delete'] }, - 'ns.coll': { $in: watchCollections }, + 'ns.coll': { $in: this.watchCollections }, }, }, ], diff --git a/apps/meteor/server/database/convertChangeStreamPayload.ts b/apps/meteor/server/database/convertChangeStreamPayload.ts index 728b7a43111ee..870e5abce381c 100644 --- a/apps/meteor/server/database/convertChangeStreamPayload.ts +++ b/apps/meteor/server/database/convertChangeStreamPayload.ts @@ -39,6 +39,7 @@ export function convertChangeStreamPayload( action: 'update', clientAction: 'updated', id: event.documentKey._id, + data: event.fullDocument, diff, unset, }; diff --git a/apps/meteor/server/database/watchCollections.ts b/apps/meteor/server/database/watchCollections.ts index 48421e9e681c8..8b667e921f1fe 100644 --- a/apps/meteor/server/database/watchCollections.ts +++ b/apps/meteor/server/database/watchCollections.ts @@ -17,21 +17,43 @@ import { LivechatPriority, } from '@rocket.chat/models'; -export const watchCollections = [ - Messages.getCollectionName(), - Users.getCollectionName(), - Subscriptions.getCollectionName(), - LivechatInquiry.getCollectionName(), - LivechatDepartmentAgents.getCollectionName(), - Permissions.getCollectionName(), - Roles.getCollectionName(), - Rooms.getCollectionName(), - LoginServiceConfiguration.getCollectionName(), - InstanceStatus.getCollectionName(), - IntegrationHistory.getCollectionName(), - Integrations.getCollectionName(), - EmailInbox.getCollectionName(), - PbxEvents.getCollectionName(), - Settings.getCollectionName(), - LivechatPriority.getCollectionName(), -]; +const { DBWATCHER_EXCLUDE_COLLECTIONS = '', DBWATCHER_ONLY_COLLECTIONS = '' } = process.env; + +const excludeCollections = DBWATCHER_EXCLUDE_COLLECTIONS.split(',') + .map((collection) => collection.trim()) + .filter(Boolean); + +const onlyCollections = DBWATCHER_ONLY_COLLECTIONS.split(',') + .map((collection) => collection.trim()) + .filter(Boolean); + +export function getWatchCollections(): string[] { + const collections = [ + Messages.getCollectionName(), + Users.getCollectionName(), + Subscriptions.getCollectionName(), + LivechatInquiry.getCollectionName(), + LivechatDepartmentAgents.getCollectionName(), + Permissions.getCollectionName(), + Roles.getCollectionName(), + Rooms.getCollectionName(), + LoginServiceConfiguration.getCollectionName(), + InstanceStatus.getCollectionName(), + IntegrationHistory.getCollectionName(), + Integrations.getCollectionName(), + EmailInbox.getCollectionName(), + PbxEvents.getCollectionName(), + Settings.getCollectionName(), + LivechatPriority.getCollectionName(), + ]; + + if (onlyCollections.length > 0) { + return collections.filter((collection) => onlyCollections.includes(collection)); + } + + if (excludeCollections.length > 0) { + return collections.filter((collection) => !excludeCollections.includes(collection)); + } + + return collections; +} diff --git a/apps/meteor/server/modules/watchers/watchers.module.ts b/apps/meteor/server/modules/watchers/watchers.module.ts index 4ce7432bf124a..c039259d7ddfa 100644 --- a/apps/meteor/server/modules/watchers/watchers.module.ts +++ b/apps/meteor/server/modules/watchers/watchers.module.ts @@ -120,59 +120,61 @@ export function initWatchers(watcher: DatabaseWatcher, broadcast: BroadcastCallb } // Override data cuz we do not publish all fields - const subscription = await Subscriptions.findOneById< - Pick< - ISubscription, - | 't' - | 'ts' - | 'ls' - | 'lr' - | 'name' - | 'fname' - | 'rid' - | 'code' - | 'f' - | 'u' - | 'open' - | 'alert' - | 'roles' - | 'unread' - | 'prid' - | 'userMentions' - | 'groupMentions' - | 'archived' - | 'audioNotificationValue' - | 'desktopNotifications' - | 'mobilePushNotifications' - | 'emailNotifications' - | 'desktopPrefOrigin' - | 'mobilePrefOrigin' - | 'emailPrefOrigin' - | 'unreadAlert' - | '_updatedAt' - | 'blocked' - | 'blocker' - | 'autoTranslate' - | 'autoTranslateLanguage' - | 'disableNotifications' - | 'hideUnreadStatus' - | 'hideMentionStatus' - | 'muteGroupMentions' - | 'ignored' - | 'E2EKey' - | 'E2ESuggestedKey' - | 'tunread' - | 'tunreadGroup' - | 'tunreadUser' - - // Omnichannel fields - | 'department' - | 'v' - | 'onHold' - > - >(id, { - projection: subscriptionFields, - }); + const subscription = + data || + (await Subscriptions.findOneById< + Pick< + ISubscription, + | 't' + | 'ts' + | 'ls' + | 'lr' + | 'name' + | 'fname' + | 'rid' + | 'code' + | 'f' + | 'u' + | 'open' + | 'alert' + | 'roles' + | 'unread' + | 'prid' + | 'userMentions' + | 'groupMentions' + | 'archived' + | 'audioNotificationValue' + | 'desktopNotifications' + | 'mobilePushNotifications' + | 'emailNotifications' + | 'desktopPrefOrigin' + | 'mobilePrefOrigin' + | 'emailPrefOrigin' + | 'unreadAlert' + | '_updatedAt' + | 'blocked' + | 'blocker' + | 'autoTranslate' + | 'autoTranslateLanguage' + | 'disableNotifications' + | 'hideUnreadStatus' + | 'hideMentionStatus' + | 'muteGroupMentions' + | 'ignored' + | 'E2EKey' + | 'E2ESuggestedKey' + | 'tunread' + | 'tunreadGroup' + | 'tunreadUser' + + // Omnichannel fields + | 'department' + | 'v' + | 'onHold' + > + >(id, { + projection: subscriptionFields, + })); if (!subscription) { return; diff --git a/ee/apps/stream-hub-service/src/service.ts b/ee/apps/stream-hub-service/src/service.ts index 81175a3e216a5..adfa40fd4cb66 100755 --- a/ee/apps/stream-hub-service/src/service.ts +++ b/ee/apps/stream-hub-service/src/service.ts @@ -3,9 +3,11 @@ import polka from 'polka'; import { api } from '@rocket.chat/core-services'; import { broker } from '../../../../apps/meteor/ee/server/startup/broker'; +import { DatabaseWatcher } from '../../../../apps/meteor/server/database/DatabaseWatcher'; import { Collections, getCollection, getConnection } from '../../../../apps/meteor/ee/server/services/mongo'; import { registerServiceModels } from '../../../../apps/meteor/ee/server/lib/registerServiceModels'; import { Logger } from '../../../../apps/meteor/server/lib/logger/Logger'; +import { StreamHub } from './StreamHub'; const PORT = process.env.PORT || 3035; @@ -18,10 +20,6 @@ const PORT = process.env.PORT || 3035; api.setBroker(broker); - // need to import service after models are registered - const { StreamHub } = await import('./StreamHub'); - const { DatabaseWatcher } = await import('../../../../apps/meteor/server/database/DatabaseWatcher'); - // TODO having to import Logger to pass as a param is a temporary solution. logger should come from the service (either from broker or api) const watcher = new DatabaseWatcher({ db, logger: Logger }); From ee75fc2e82784f92831e5d841cc1a1477a6d79c2 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Fri, 30 Jun 2023 22:41:23 -0300 Subject: [PATCH 23/79] chore: bump turbo (#29696) --- package.json | 2 +- turbo.json | 4 ++ yarn.lock | 130 ++++++++++++--------------------------------------- 3 files changed, 35 insertions(+), 101 deletions(-) diff --git a/package.json b/package.json index 97f4e17804188..7a79038a3acf1 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@types/chart.js": "^2.9.37", "@types/js-yaml": "^4.0.5", "husky": "^7.0.4", - "turbo": "~1.2.16" + "turbo": "~1.10.7" }, "workspaces": [ "apps/*", diff --git a/turbo.json b/turbo.json index 514ed788ab5dd..e617f46d0bea1 100644 --- a/turbo.json +++ b/turbo.json @@ -39,6 +39,10 @@ "dependsOn": ["^build"], "cache": false }, + "@rocket.chat/meteor#build": { + "dependsOn": ["^build"], + "cache": false + }, "@rocket.chat/i18n#build": { "dependsOn": ["^build"], "cache": false diff --git a/yarn.lock b/yarn.lock index 89e8ef56d15b4..7c98d73a79617 100644 --- a/yarn.lock +++ b/yarn.lock @@ -35948,7 +35948,7 @@ __metadata: "@types/chart.js": ^2.9.37 "@types/js-yaml": ^4.0.5 husky: ^7.0.4 - turbo: ~1.2.16 + turbo: ~1.10.7 languageName: unknown linkType: soft @@ -39339,144 +39339,74 @@ __metadata: languageName: node linkType: hard -"turbo-darwin-64@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-darwin-64@npm:1.2.16" +"turbo-darwin-64@npm:1.10.7": + version: 1.10.7 + resolution: "turbo-darwin-64@npm:1.10.7" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"turbo-darwin-arm64@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-darwin-arm64@npm:1.2.16" +"turbo-darwin-arm64@npm:1.10.7": + version: 1.10.7 + resolution: "turbo-darwin-arm64@npm:1.10.7" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"turbo-freebsd-64@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-freebsd-64@npm:1.2.16" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - -"turbo-freebsd-arm64@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-freebsd-arm64@npm:1.2.16" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - -"turbo-linux-32@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-linux-32@npm:1.2.16" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - -"turbo-linux-64@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-linux-64@npm:1.2.16" +"turbo-linux-64@npm:1.10.7": + version: 1.10.7 + resolution: "turbo-linux-64@npm:1.10.7" conditions: os=linux & cpu=x64 languageName: node linkType: hard -"turbo-linux-arm64@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-linux-arm64@npm:1.2.16" +"turbo-linux-arm64@npm:1.10.7": + version: 1.10.7 + resolution: "turbo-linux-arm64@npm:1.10.7" conditions: os=linux & cpu=arm64 languageName: node linkType: hard -"turbo-linux-arm@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-linux-arm@npm:1.2.16" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - -"turbo-linux-mips64le@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-linux-mips64le@npm:1.2.16" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - -"turbo-linux-ppc64le@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-linux-ppc64le@npm:1.2.16" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - -"turbo-windows-32@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-windows-32@npm:1.2.16" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - -"turbo-windows-64@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-windows-64@npm:1.2.16" +"turbo-windows-64@npm:1.10.7": + version: 1.10.7 + resolution: "turbo-windows-64@npm:1.10.7" conditions: os=win32 & cpu=x64 languageName: node linkType: hard -"turbo-windows-arm64@npm:1.2.16": - version: 1.2.16 - resolution: "turbo-windows-arm64@npm:1.2.16" +"turbo-windows-arm64@npm:1.10.7": + version: 1.10.7 + resolution: "turbo-windows-arm64@npm:1.10.7" conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"turbo@npm:~1.2.16": - version: 1.2.16 - resolution: "turbo@npm:1.2.16" - dependencies: - turbo-darwin-64: 1.2.16 - turbo-darwin-arm64: 1.2.16 - turbo-freebsd-64: 1.2.16 - turbo-freebsd-arm64: 1.2.16 - turbo-linux-32: 1.2.16 - turbo-linux-64: 1.2.16 - turbo-linux-arm: 1.2.16 - turbo-linux-arm64: 1.2.16 - turbo-linux-mips64le: 1.2.16 - turbo-linux-ppc64le: 1.2.16 - turbo-windows-32: 1.2.16 - turbo-windows-64: 1.2.16 - turbo-windows-arm64: 1.2.16 +"turbo@npm:~1.10.7": + version: 1.10.7 + resolution: "turbo@npm:1.10.7" + dependencies: + turbo-darwin-64: 1.10.7 + turbo-darwin-arm64: 1.10.7 + turbo-linux-64: 1.10.7 + turbo-linux-arm64: 1.10.7 + turbo-windows-64: 1.10.7 + turbo-windows-arm64: 1.10.7 dependenciesMeta: turbo-darwin-64: optional: true turbo-darwin-arm64: optional: true - turbo-freebsd-64: - optional: true - turbo-freebsd-arm64: - optional: true - turbo-linux-32: - optional: true turbo-linux-64: optional: true - turbo-linux-arm: - optional: true turbo-linux-arm64: optional: true - turbo-linux-mips64le: - optional: true - turbo-linux-ppc64le: - optional: true - turbo-windows-32: - optional: true turbo-windows-64: optional: true turbo-windows-arm64: optional: true bin: turbo: bin/turbo - checksum: 245b1b28af153edd14ab35a4812a8651b1e7cbae44ce3f15b53e0a73a3cc9c4ea39734f18c966403496c37876dd57053070b5b7eb736a14f1bc7e1bfd36566f3 + checksum: 58329caf13b5fef284ccfc21bd1f841023122371d75d2202be809a385aade84fd886cf5c2093323c115c497d503c9c34e1f7ae09e13d06d1dcb4b649883f60dd languageName: node linkType: hard From baaa38f7f43dcbb47646d1fb3a74aef1d7115b67 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Sat, 1 Jul 2023 17:37:37 -0300 Subject: [PATCH 24/79] fix: Required custom field consider blank space as valid input (#29635) --- .changeset/olive-pears-sell.md | 5 ++ .../src/components/CustomFieldsForm.tsx | 46 +++++++++++-------- 2 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 .changeset/olive-pears-sell.md diff --git a/.changeset/olive-pears-sell.md b/.changeset/olive-pears-sell.md new file mode 100644 index 0000000000000..369f64b04dcfe --- /dev/null +++ b/.changeset/olive-pears-sell.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/ui-client': patch +--- + +Fixed required custom fields considering blank spaces as valid. diff --git a/packages/ui-client/src/components/CustomFieldsForm.tsx b/packages/ui-client/src/components/CustomFieldsForm.tsx index dee63d50106ea..49d4a120e47d5 100644 --- a/packages/ui-client/src/components/CustomFieldsForm.tsx +++ b/packages/ui-client/src/components/CustomFieldsForm.tsx @@ -3,8 +3,9 @@ import type { SelectOption } from '@rocket.chat/fuselage'; import { Field, Select, TextInput } from '@rocket.chat/fuselage'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useCallback, useMemo } from 'react'; import type { Control, FieldValues } from 'react-hook-form'; -import { Controller } from 'react-hook-form'; +import { Controller, useFormState, get } from 'react-hook-form'; type CustomFieldFormProps<T extends FieldValues> = { metadata: CustomFieldMetadata[]; @@ -33,32 +34,41 @@ const CustomField = <T extends FieldValues>({ ...props }: CustomFieldProps<T>) => { const t = useTranslation(); - const { getFieldState } = control; + const { errors } = useFormState({ control }); const Component = FIELD_TYPES[type] ?? null; - const selectOptions = - options.length > 0 && options[0] instanceof Array ? options : options.map((option) => [option, option, defaultValue === option]); + const selectOptions = useMemo( + () => + options.length > 0 && options[0] instanceof Array ? options : options.map((option) => [option, option, defaultValue === option]), + [defaultValue, options], + ); + + const validateRequired = useCallback((value) => (required ? typeof value === 'string' && !!value.trim() : true), [required]); - const getErrorMessage = (error: any) => { - switch (error?.type) { - case 'required': - return t('The_field_is_required', label || name); - case 'minLength': - return t('Min_length_is', props?.minLength); - case 'maxLength': - return t('Max_length_is', props?.maxLength); - } - }; + const getErrorMessage = useCallback( + (error: any) => { + switch (error?.type) { + case 'required': + return t('The_field_is_required', label || name); + case 'minLength': + return t('Min_length_is', props?.minLength); + case 'maxLength': + return t('Max_length_is', props?.maxLength); + } + }, + [label, name, props?.maxLength, props?.minLength, t], + ); - const error = getErrorMessage(getFieldState(name as any).error); + const error = get(errors, name); + const errorMessage = useMemo(() => getErrorMessage(error), [error, getErrorMessage]); return ( <Controller<T, any> name={name} control={control} defaultValue={defaultValue ?? ''} - rules={{ required, minLength: props.minLength, maxLength: props.maxLength }} + rules={{ minLength: props.minLength, maxLength: props.maxLength, validate: { required: validateRequired } }} render={({ field }) => ( <Field rcx-field-group__item> <Field.Label> @@ -66,9 +76,9 @@ const CustomField = <T extends FieldValues>({ {required && '*'} </Field.Label> <Field.Row> - <Component {...props} {...field} error={error} options={selectOptions as SelectOption[]} flexGrow={1} /> + <Component {...props} {...field} error={errorMessage} options={selectOptions as SelectOption[]} flexGrow={1} /> </Field.Row> - <Field.Error>{error}</Field.Error> + <Field.Error>{errorMessage}</Field.Error> </Field> )} /> From 6923838fe9f80e45b836b417dce365f320bd0c5b Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Sun, 2 Jul 2023 15:31:18 -0300 Subject: [PATCH 25/79] fix: livechat url triggers only fire when using the widget (#29673) --- packages/livechat/src/lib/triggers.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/livechat/src/lib/triggers.js b/packages/livechat/src/lib/triggers.js index 5f98dc2aa41d6..9b630e636e673 100644 --- a/packages/livechat/src/lib/triggers.js +++ b/packages/livechat/src/lib/triggers.js @@ -57,6 +57,8 @@ const getAgent = (triggerAction) => { return agentPromise; }; +const isInIframe = () => window.self !== window.top; + class Triggers { constructor() { if (!Triggers.instance) { @@ -172,9 +174,8 @@ class Triggers { trigger.conditions.forEach((condition) => { switch (condition.name) { case 'page-url': - const { parentUrl } = store.state; const hrefRegExp = new RegExp(condition.value, 'g'); - if (parentUrl && hrefRegExp.test(parentUrl)) { + if (this.parentUrl && hrefRegExp.test(this.parentUrl)) { this.fire(trigger); } break; @@ -197,9 +198,7 @@ class Triggers { } processPageUrlTriggers() { - const { parentUrl } = store.state; - - if (!parentUrl) return; + if (!this.parentUrl) return; this._triggers.forEach((trigger) => { if (trigger.skip) return; @@ -208,7 +207,7 @@ class Triggers { if (condition.name !== 'page-url') return; const hrefRegExp = new RegExp(condition.value, 'g'); - if (hrefRegExp.test(parentUrl)) { + if (hrefRegExp.test(this.parentUrl)) { this.fire(trigger); } }); @@ -222,6 +221,10 @@ class Triggers { set enabled(value) { this._enabled = value; } + + get parentUrl() { + return isInIframe() ? store.state.parentUrl : window.location.href; + } } const instance = new Triggers(); From dbc79dd3eefe7eb766087d7783c93623659c8cff Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Mon, 3 Jul 2023 11:00:23 +0530 Subject: [PATCH 26/79] regression: Livechat UiKit button not working (#29652) --- .../meteor/server/models/raw/LivechatRooms.ts | 8 +++---- .../src/livechat/LivechatClientImpl.ts | 10 ++++++++ .../src/livechat/types/LivechatSDK.ts | 5 ++++ packages/livechat/src/lib/uiKit.js | 23 +++++++++++-------- packages/rest-typings/src/apps/index.ts | 13 +++++++++++ 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/apps/meteor/server/models/raw/LivechatRooms.ts b/apps/meteor/server/models/raw/LivechatRooms.ts index e71fbe9185942..f63f12a9cc689 100644 --- a/apps/meteor/server/models/raw/LivechatRooms.ts +++ b/apps/meteor/server/models/raw/LivechatRooms.ts @@ -2044,13 +2044,13 @@ export class LivechatRoomsRaw extends BaseRaw<IOmnichannelRoom> implements ILive 'metrics.response.ft': analyticsData.firstResponseTime, }), }, - $inc: { - ...(analyticsData && { + ...(analyticsData && { + $inc: { 'metrics.response.total': 1, 'metrics.response.tt': analyticsData.responseTime as number, 'metrics.reaction.tt': analyticsData.reactionTime as number, - }), - }, + }, + }), }; // livechat analytics : update last message timestamps diff --git a/packages/ddp-client/src/livechat/LivechatClientImpl.ts b/packages/ddp-client/src/livechat/LivechatClientImpl.ts index 8b62580e50f5c..9274c25c5c269 100644 --- a/packages/ddp-client/src/livechat/LivechatClientImpl.ts +++ b/packages/ddp-client/src/livechat/LivechatClientImpl.ts @@ -324,6 +324,16 @@ export class LivechatClientImpl extends DDPSDK implements LivechatStream, Livech }); } + async sendUiInteraction( + payload: OperationParams<'POST', '/apps/ui.interaction/:id'>, + appId: string, + ): Promise<Serialized<OperationResult<'POST', '/apps/ui.interaction/:id'>>> { + if (!this.token) { + throw new Error('Invalid token'); + } + return this.rest.post(`/apps/ui.interaction/${appId}`, payload, { headers: { 'x-visitor-token': this.token } }); + } + // API DELETE deleteMessage(id: string, { rid }: { rid: string }): Promise<Serialized<OperationResult<'DELETE', '/v1/livechat/message/:_id'>>> { diff --git a/packages/ddp-client/src/livechat/types/LivechatSDK.ts b/packages/ddp-client/src/livechat/types/LivechatSDK.ts index 9e646152f4ae7..171d6dfc24100 100644 --- a/packages/ddp-client/src/livechat/types/LivechatSDK.ts +++ b/packages/ddp-client/src/livechat/types/LivechatSDK.ts @@ -95,4 +95,9 @@ export interface LivechatEndpoints { id: string, args: OperationParams<'PUT', '/v1/livechat/message/:_id'>, ): Promise<Serialized<OperationResult<'PUT', '/v1/livechat/message/:_id'>>>; + + sendUiInteraction( + payload: OperationParams<'POST', '/apps/ui.interaction/:id'>, + appId: string, + ): Promise<Serialized<OperationResult<'POST', '/apps/ui.interaction/:id'>>>; } diff --git a/packages/livechat/src/lib/uiKit.js b/packages/livechat/src/lib/uiKit.js index c7cae9e4db589..dcd9d2e34a584 100644 --- a/packages/livechat/src/lib/uiKit.js +++ b/packages/livechat/src/lib/uiKit.js @@ -104,16 +104,19 @@ export const triggerAction = async ({ appId, type, actionId, rid, mid, viewId, c try { const result = await Promise.race([ - Livechat.rest.post(`/apps/ui.interaction/${appId}`, { - type, - actionId, - rid, - mid, - viewId, - container, - triggerId, - payload, - }), + Livechat.sendUiInteraction( + { + type, + actionId, + rid, + mid, + viewId, + container, + triggerId, + payload, + }, + appId, + ), new Promise((_, reject) => { setTimeout(() => { diff --git a/packages/rest-typings/src/apps/index.ts b/packages/rest-typings/src/apps/index.ts index c70b8d7b60d76..76ad596bb90b5 100644 --- a/packages/rest-typings/src/apps/index.ts +++ b/packages/rest-typings/src/apps/index.ts @@ -251,4 +251,17 @@ export type AppsEndpoints = { app: App; }; }; + + '/apps/ui.interaction/:id': { + POST: (params: { + type: string; + actionId: string; + rid: string; + mid: string; + viewId: string; + container: string; + triggerId: string; + payload: any; + }) => any; + }; }; From eecd9fc99a6a3d7f6156f9c6eaed5db64bba991a Mon Sep 17 00:00:00 2001 From: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Date: Mon, 3 Jul 2023 12:33:56 +0530 Subject: [PATCH 27/79] fix: Omnichannel Tags available to be used in the wrong department (#29169) Co-authored-by: Martin Schoeler <20868078+MartinSchoeler@users.noreply.github.com> --- .changeset/great-brooms-invent.md | 6 ++ .../client/components/Omnichannel/Tags.tsx | 12 ++- .../Omnichannel/hooks/useLivechatTags.ts | 19 ++++ .../Omnichannel/modals/CloseChatModal.tsx | 2 +- .../server/lib/canned-responses.js | 64 ++----------- .../server/api/lib/departments.ts | 38 ++++++++ .../server/api/lib/tags.ts | 49 +++++++++- .../livechat-enterprise/server/api/tags.ts | 4 +- apps/meteor/ee/client/hooks/useTagsList.ts | 4 +- .../tags/AutoCompleteTagsMultiple.js | 4 +- .../omnichannel/tags/CurrentChatTags.tsx | 4 +- .../ee/client/omnichannel/tags/TagsTable.tsx | 1 + .../ee/server/models/raw/LivechatUnit.ts | 2 +- .../server/models/raw/LivechatDepartment.ts | 43 ++++++++- apps/meteor/tests/data/livechat/tags.ts | 26 +++++- .../tests/end-to-end/api/livechat/13-tags.ts | 92 +++++++++++++++++-- .../src/models/ILivechatDepartmentModel.ts | 1 + packages/rest-typings/src/v1/omnichannel.ts | 11 ++- 18 files changed, 301 insertions(+), 81 deletions(-) create mode 100644 .changeset/great-brooms-invent.md create mode 100644 apps/meteor/client/components/Omnichannel/hooks/useLivechatTags.ts create mode 100644 apps/meteor/ee/app/livechat-enterprise/server/api/lib/departments.ts diff --git a/.changeset/great-brooms-invent.md b/.changeset/great-brooms-invent.md new file mode 100644 index 0000000000000..8dc820e8c8396 --- /dev/null +++ b/.changeset/great-brooms-invent.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +fix: Omnichannel Tags available to be used in the wrong department diff --git a/apps/meteor/client/components/Omnichannel/Tags.tsx b/apps/meteor/client/components/Omnichannel/Tags.tsx index dfa1cb713a5f5..3074639cee0d1 100644 --- a/apps/meteor/client/components/Omnichannel/Tags.tsx +++ b/apps/meteor/client/components/Omnichannel/Tags.tsx @@ -1,23 +1,25 @@ import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useEndpoint, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; import type { ChangeEvent, ReactElement } from 'react'; import React, { useState } from 'react'; import { useFormsSubscription } from '../../views/omnichannel/additionalForms'; import { FormSkeleton } from './Skeleton'; +import { useLivechatTags } from './hooks/useLivechatTags'; const Tags = ({ tags = [], handler, error, tagRequired, + department, }: { tags?: string[]; handler: (value: string[]) => void; error?: string; tagRequired?: boolean; + department?: string; }): ReactElement => { const t = useTranslation(); const forms = useFormsSubscription() as any; @@ -27,9 +29,8 @@ const Tags = ({ // Conditional hook was required since the whole formSubscription uses hooks in an incorrect manner const EETagsComponent = useCurrentChatTags?.(); - const getTags = useEndpoint('GET', '/v1/livechat/tags'); - const { data: tagsResult, isInitialLoading } = useQuery(['/v1/livechat/tags'], () => getTags({ text: '' }), { - enabled: Boolean(EETagsComponent), + const { data: tagsResult, isInitialLoading } = useLivechatTags({ + department, }); const dispatchToastMessage = useToastMessageDispatch(); @@ -81,6 +82,7 @@ const Tags = ({ handler(tags.map((tag) => tag.label)); handlePaginatedTagValue(tags); }} + department={department} /> </Field.Row> ) : ( diff --git a/apps/meteor/client/components/Omnichannel/hooks/useLivechatTags.ts b/apps/meteor/client/components/Omnichannel/hooks/useLivechatTags.ts new file mode 100644 index 0000000000000..ac042274a2c74 --- /dev/null +++ b/apps/meteor/client/components/Omnichannel/hooks/useLivechatTags.ts @@ -0,0 +1,19 @@ +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; + +type Props = { + department?: string; + text?: string; +}; + +export const useLivechatTags = (options: Props) => { + const getTags = useEndpoint('GET', '/v1/livechat/tags'); + + const { department, text } = options; + return useQuery(['/v1/livechat/tags', text, department], () => + getTags({ + text: text || '', + ...(department && { department }), + }), + ); +}; diff --git a/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx b/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx index fd145ea9d9e60..c8307e2045f9a 100644 --- a/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx +++ b/apps/meteor/client/components/Omnichannel/modals/CloseChatModal.tsx @@ -150,7 +150,7 @@ const CloseChatModal = ({ <Field.Error>{errors.comment?.message}</Field.Error> </Field> <Field> - <Tags tagRequired={tagRequired} tags={tags} handler={handleTags} /> + <Tags tagRequired={tagRequired} tags={tags} handler={handleTags} {...(department && { department: department._id })} /> <Field.Error>{errors.tags?.message}</Field.Error> </Field> {canSendTranscript && ( diff --git a/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js b/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js index 26f751fe4e99f..9f0872c38f29d 100644 --- a/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js +++ b/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js @@ -1,7 +1,8 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { LivechatDepartmentAgents, CannedResponse, LivechatUnit } from '@rocket.chat/models'; +import { CannedResponse } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; +import { getDepartmentsWhichUserCanAccess } from '../../../livechat-enterprise/server/api/lib/departments'; export async function findAllCannedResponses({ userId }) { // If the user is an admin or livechat manager, get his own responses and all responses from all departments @@ -30,23 +31,8 @@ export async function findAllCannedResponses({ userId }) { }).toArray(); } - // Last scenario: user is an agente, so get his own responses and those from the departments he is in - const departments = await LivechatDepartmentAgents.find( - { - agentId: userId, - }, - { - projection: { - departmentId: 1, - }, - }, - ).toArray(); - - const monitoredDepartments = await LivechatUnit.findMonitoredDepartmentsByMonitorId(userId); - const combinedDepartments = [ - ...departments.map((department) => department.departmentId), - ...monitoredDepartments.map((department) => department._id), - ]; + // Last scenario: user is an agent, so get his own responses and those from the departments he is in + const accessibleDepartments = await getDepartmentsWhichUserCanAccess(userId); return CannedResponse.find({ $or: [ @@ -57,7 +43,7 @@ export async function findAllCannedResponses({ userId }) { { scope: 'department', departmentId: { - $in: combinedDepartments, + $in: accessibleDepartments, }, }, { @@ -71,26 +57,11 @@ export async function findAllCannedResponsesFilter({ userId, shortcut, text, dep let extraFilter = []; // if user cannot see all, filter to private + public + departments user is in if (!(await hasPermissionAsync(userId, 'view-all-canned-responses'))) { - const departments = await LivechatDepartmentAgents.find( - { - agentId: userId, - }, - { - fields: { - departmentId: 1, - }, - }, - ).toArray(); - - const monitoredDepartments = await LivechatUnit.findMonitoredDepartmentsByMonitorId(userId); - const combinedDepartments = [ - ...departments.map((department) => department.departmentId), - ...monitoredDepartments.map((department) => department._id), - ]; + const accessibleDepartments = await getDepartmentsWhichUserCanAccess(userId); - const isDepartmentInScope = (departmentId) => !!combinedDepartments.includes(departmentId); + const isDepartmentInScope = (departmentId) => !!accessibleDepartments.includes(departmentId); - const departmentIds = departmentId && isDepartmentInScope(departmentId) ? [departmentId] : combinedDepartments; + const departmentIds = departmentId && isDepartmentInScope(departmentId) ? [departmentId] : accessibleDepartments; extraFilter = [ { @@ -163,22 +134,7 @@ export async function findOneCannedResponse({ userId, _id }) { return CannedResponse.findOneById(_id); } - const departments = await LivechatDepartmentAgents.find( - { - agentId: userId, - }, - { - fields: { - departmentId: 1, - }, - }, - ).toArray(); - - const monitoredDepartments = await LivechatUnit.findMonitoredDepartmentsByMonitorId(userId); - const combinedDepartments = [ - ...departments.map((department) => department.departmentId), - ...monitoredDepartments.map((department) => department._id), - ]; + const accessibleDepartments = await getDepartmentsWhichUserCanAccess(userId); const filter = { _id, @@ -190,7 +146,7 @@ export async function findOneCannedResponse({ userId, _id }) { { scope: 'department', departmentId: { - $in: combinedDepartments, + $in: accessibleDepartments, }, }, { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/lib/departments.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/lib/departments.ts new file mode 100644 index 0000000000000..2e86aececc58d --- /dev/null +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/lib/departments.ts @@ -0,0 +1,38 @@ +import { LivechatDepartment, LivechatDepartmentAgents, LivechatUnit } from '@rocket.chat/models'; + +import { helperLogger } from '../../lib/logger'; + +export const getDepartmentsWhichUserCanAccess = async (userId: string): Promise<string[]> => { + const departments = await LivechatDepartmentAgents.find( + { + agentId: userId, + }, + { + projection: { + departmentId: 1, + }, + }, + ).toArray(); + + const monitoredDepartments = await LivechatUnit.findMonitoredDepartmentsByMonitorId(userId); + const combinedDepartments = [ + ...departments.map((department) => department.departmentId), + ...monitoredDepartments.map((department) => department._id), + ]; + + return [...new Set(combinedDepartments)]; +}; + +export const hasAccessToDepartment = async (userId: string, departmentId: string): Promise<boolean> => { + const department = await LivechatDepartmentAgents.findOneByAgentIdAndDepartmentId(userId, departmentId); + if (department) { + helperLogger.debug(`User ${userId} has access to department ${departmentId} because they are an agent`); + return true; + } + + const monitorAccess = await LivechatDepartment.checkIfMonitorIsMonitoringDepartmentById(userId, departmentId); + helperLogger.debug( + `User ${userId} ${monitorAccess ? 'has' : 'does not have'} access to department ${departmentId} because they are a monitor`, + ); + return monitorAccess; +}; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/lib/tags.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/lib/tags.ts index 0ebf4df1553e8..7f00206041233 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/api/lib/tags.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/lib/tags.ts @@ -1,7 +1,10 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import { LivechatTag } from '@rocket.chat/models'; import type { ILivechatTag } from '@rocket.chat/core-typings'; -import type { FindOptions } from 'mongodb'; +import type { Filter, FindOptions } from 'mongodb'; + +import { hasPermissionAsync } from '../../../../../../app/authorization/server/functions/hasPermission'; +import { hasAccessToDepartment } from './departments'; type FindTagsParams = { userId: string; @@ -11,6 +14,8 @@ type FindTagsParams = { count: number; sort: FindOptions<ILivechatTag>['sort']; }; + department?: string; + viewAll?: boolean; }; type FindTagsResult = { @@ -27,11 +32,47 @@ type FindTagsByIdParams = { type FindTagsByIdResult = ILivechatTag | null; -export async function findTags({ text, pagination: { offset, count, sort } }: FindTagsParams): Promise<FindTagsResult> { - const query = { - ...(text && { $or: [{ name: new RegExp(escapeRegExp(text), 'i') }, { description: new RegExp(escapeRegExp(text), 'i') }] }), +// If viewAll is true, then all tags will be returned, regardless of department +// If viewAll is false, then all public tags will be returned, and +// if department is specified, then all department tags will be returned +export async function findTags({ + userId, + text, + department, + viewAll, + pagination: { offset, count, sort }, +}: FindTagsParams): Promise<FindTagsResult> { + if (!(await hasPermissionAsync(userId, 'manage-livechat-tags'))) { + if (viewAll) { + viewAll = false; + } + + if (department) { + if (!(await hasAccessToDepartment(userId, department))) { + department = undefined; + } + } + } + + const query: { + $and?: Filter<ILivechatTag>[]; + } = { + $and: [ + ...(text ? [{ $or: [{ name: new RegExp(escapeRegExp(text), 'i') }, { description: new RegExp(escapeRegExp(text), 'i') }] }] : []), + ...(!viewAll + ? [ + { + $or: [{ departments: { $size: 0 } }, ...(department ? [{ departments: department }] : [])], + }, + ] + : []), + ], }; + if (!query?.$and?.length) { + delete query.$and; + } + const { cursor, totalCount } = LivechatTag.findPaginated(query, { sort: sort || { name: 1 }, skip: offset, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts b/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts index 19ef9cf922dc7..4c84a42bd9e63 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/api/tags.ts @@ -9,12 +9,14 @@ API.v1.addRoute( async get() { const { offset, count } = await getPaginationItems(this.queryParams); const { sort } = await this.parseJsonQuery(); - const { text } = this.queryParams; + const { text, viewAll, department } = this.queryParams; return API.v1.success( await findTags({ userId: this.userId, text, + department, + viewAll: viewAll === 'true', pagination: { offset, count, diff --git a/apps/meteor/ee/client/hooks/useTagsList.ts b/apps/meteor/ee/client/hooks/useTagsList.ts index 5c887d28d7139..013a320fbadec 100644 --- a/apps/meteor/ee/client/hooks/useTagsList.ts +++ b/apps/meteor/ee/client/hooks/useTagsList.ts @@ -8,6 +8,7 @@ import { RecordList } from '../../../client/lib/lists/RecordList'; type TagsListOptions = { filter: string; + department?: string; }; export const useTagsList = ( @@ -33,6 +34,7 @@ export const useTagsList = ( text: options.filter, offset: start, count: end + start, + ...(options.department && { department: options.department }), }); return { items: tags.map((tag: any) => { @@ -44,7 +46,7 @@ export const useTagsList = ( itemCount: total, }; }, - [getTags, options.filter], + [getTags, options.filter, options.department], ); const { loadMoreItems, initialItemCount } = useScrollableRecordList(itemsList, fetchData, 25); diff --git a/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js b/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js index 2ae6f893f8123..5f7fe4a4ffe67 100644 --- a/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js +++ b/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js @@ -8,7 +8,7 @@ import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState'; import { useTagsList } from '../../hooks/useTagsList'; const AutoCompleteTagMultiple = (props) => { - const { value, onlyMyTags = false, onChange = () => {} } = props; + const { value, onlyMyTags = false, onChange = () => {}, department } = props; const t = useTranslation(); const [tagsFilter, setTagsFilter] = useState(''); @@ -16,7 +16,7 @@ const AutoCompleteTagMultiple = (props) => { const debouncedTagsFilter = useDebouncedValue(tagsFilter, 500); const { itemsList: tagsList, loadMoreItems: loadMoreTags } = useTagsList( - useMemo(() => ({ filter: debouncedTagsFilter, onlyMyTags }), [debouncedTagsFilter, onlyMyTags]), + useMemo(() => ({ filter: debouncedTagsFilter, onlyMyTags, department }), [debouncedTagsFilter, onlyMyTags, department]), ); const { phase: tagsPhase, items: tagsItems, itemCount: tagsTotal } = useRecordList(tagsList); diff --git a/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx b/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx index 30bae3323231f..7253764c52b64 100644 --- a/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx +++ b/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx @@ -3,8 +3,8 @@ import React from 'react'; import AutoCompleteTagsMultiple from './AutoCompleteTagsMultiple'; -const CurrentChatTags: FC<{ value: Array<string>; handler: () => void }> = ({ value, handler }) => ( - <AutoCompleteTagsMultiple onChange={handler} value={value} /> +const CurrentChatTags: FC<{ value: Array<string>; handler: () => void; department?: string }> = ({ value, handler, department }) => ( + <AutoCompleteTagsMultiple onChange={handler} value={value} department={department} /> ); export default CurrentChatTags; diff --git a/apps/meteor/ee/client/omnichannel/tags/TagsTable.tsx b/apps/meteor/ee/client/omnichannel/tags/TagsTable.tsx index 98f2220fe5bfa..01ab1f0ec637b 100644 --- a/apps/meteor/ee/client/omnichannel/tags/TagsTable.tsx +++ b/apps/meteor/ee/client/omnichannel/tags/TagsTable.tsx @@ -39,6 +39,7 @@ const TagsTable = ({ reload }: { reload: MutableRefObject<() => void> }) => { const query = useMemo( () => ({ + viewAll: 'true' as const, fields: JSON.stringify({ name: 1 }), text: filter, sort: JSON.stringify({ [sortBy]: sortDirection === 'asc' ? 1 : -1 }), diff --git a/apps/meteor/ee/server/models/raw/LivechatUnit.ts b/apps/meteor/ee/server/models/raw/LivechatUnit.ts index c0f32af571213..2006f8200767d 100644 --- a/apps/meteor/ee/server/models/raw/LivechatUnit.ts +++ b/apps/meteor/ee/server/models/raw/LivechatUnit.ts @@ -202,7 +202,7 @@ export class LivechatUnitRaw extends BaseRaw<IOmnichannelBusinessUnit> implement async findMonitoredDepartmentsByMonitorId(monitorId: string): Promise<ILivechatDepartment[]> { const monitoredUnits = await this.findByMonitorId(monitorId); - return LivechatDepartment.findByUnitIds(monitoredUnits, {}).toArray(); + return LivechatDepartment.findActiveByUnitIds(monitoredUnits, {}).toArray(); } countUnits(): Promise<number> { diff --git a/apps/meteor/server/models/raw/LivechatDepartment.ts b/apps/meteor/server/models/raw/LivechatDepartment.ts index 75329e3e6125c..5b1b4010dd381 100644 --- a/apps/meteor/server/models/raw/LivechatDepartment.ts +++ b/apps/meteor/server/models/raw/LivechatDepartment.ts @@ -13,7 +13,7 @@ import type { UpdateFilter, } from 'mongodb'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { LivechatDepartmentAgents } from '@rocket.chat/models'; +import { LivechatDepartmentAgents, LivechatUnitMonitors } from '@rocket.chat/models'; import { BaseRaw } from './BaseRaw'; @@ -353,6 +353,47 @@ export class LivechatDepartmentRaw extends BaseRaw<ILivechatDepartment> implemen return this.find(query, options); } + + checkIfMonitorIsMonitoringDepartmentById(monitorId: string, departmentId: string): Promise<boolean> { + const aggregation = [ + { + $match: { + enabled: true, + _id: departmentId, + }, + }, + { + $lookup: { + from: LivechatUnitMonitors.getCollectionName(), + localField: 'parentId', + foreignField: 'unitId', + as: 'monitors', + pipeline: [ + { + $match: { + monitorId, + }, + }, + ], + }, + }, + { + $match: { + monitors: { + $exists: true, + $ne: [], + }, + }, + }, + { + $project: { + _id: 1, + }, + }, + ]; + + return this.col.aggregate(aggregation).hasNext(); + } } const difference = <T>(arr: T[], arr2: T[]): T[] => { diff --git a/apps/meteor/tests/data/livechat/tags.ts b/apps/meteor/tests/data/livechat/tags.ts index c8cfbd1d4bed2..4ba5c78025cde 100644 --- a/apps/meteor/tests/data/livechat/tags.ts +++ b/apps/meteor/tests/data/livechat/tags.ts @@ -3,7 +3,7 @@ import type { ILivechatTag } from '@rocket.chat/core-typings'; import { credentials, methodCall, request } from '../api-data'; import type { DummyResponse } from './utils'; -export const saveTags = (): Promise<ILivechatTag> => { +export const saveTags = (departments: string[] = []): Promise<ILivechatTag> => { return new Promise((resolve, reject) => { request .post(methodCall(`livechat:saveTag`)) @@ -11,7 +11,29 @@ export const saveTags = (): Promise<ILivechatTag> => { .send({ message: JSON.stringify({ method: 'livechat:saveTag', - params: [undefined, { name: faker.person.firstName(), description: faker.lorem.sentence() }, []], + params: [undefined, { name: faker.person.firstName(), description: faker.lorem.sentence() }, departments], + id: '101', + msg: 'method', + }), + }) + .end((err: Error, res: DummyResponse<string, 'wrapped'>) => { + if (err) { + return reject(err); + } + resolve(JSON.parse(res.body.message).result); + }); + }); +}; + +export const removeTag = (id: string): Promise<void> => { + return new Promise((resolve, reject) => { + request + .post(methodCall(`livechat:removeTag`)) + .set(credentials) + .send({ + message: JSON.stringify({ + method: 'livechat:removeTag', + params: [id], id: '101', msg: 'method', }), diff --git a/apps/meteor/tests/end-to-end/api/livechat/13-tags.ts b/apps/meteor/tests/end-to-end/api/livechat/13-tags.ts index 986e268e26f42..a08e77238caae 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/13-tags.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/13-tags.ts @@ -1,11 +1,13 @@ /* eslint-env mocha */ import { expect } from 'chai'; +import type { ILivechatDepartment, ILivechatTag } from '@rocket.chat/core-typings'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; -import { saveTags } from '../../../data/livechat/tags'; +import { removeTag, saveTags } from '../../../data/livechat/tags'; import { updatePermission, updateSetting } from '../../../data/permissions.helper'; import { IS_EE } from '../../../e2e/config/constants'; +import { createDepartment } from '../../../data/livechat/rooms'; (IS_EE ? describe : describe.skip)('[EE] Livechat - Tags', function () { this.retries(0); @@ -17,26 +19,104 @@ import { IS_EE } from '../../../e2e/config/constants'; }); describe('livechat/tags', () => { + let tagsData: { + caseA: { department: ILivechatDepartment; tag: ILivechatTag }; + caseB: { department: ILivechatDepartment; tag: ILivechatTag }; + casePublic: { tag: ILivechatTag }; + }; it('should throw unauthorized error when the user does not have the necessary permission', async () => { await updatePermission('manage-livechat-tags', []); await updatePermission('view-l-room', []); const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(403); expect(response.body).to.have.property('success', false); + + await updatePermission('manage-livechat-tags', ['admin']); + await updatePermission('view-l-room', ['livechat-agent', 'livechat-manager', 'admin']); + }); + it('should remove all existing tags', async () => { + const allTags = await request + .get(api('livechat/tags')) + .set(credentials) + .query({ viewAll: 'true' }) + .expect('Content-Type', 'application/json') + .expect(200); + + const { tags } = allTags.body; + for await (const tag of tags) { + await removeTag(tag._id); + } + + const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(200); + + expect(response.body).to.have.property('success', true); + expect(response.body).to.have.property('tags').and.to.be.an('array').that.is.empty; + }); + it('should add 3 tags', async () => { + const dA = await createDepartment(); + const tagA = await saveTags([dA._id]); + + const dB = await createDepartment(); + const tagB = await saveTags([dB._id]); + + const publicTag = await saveTags(); + + tagsData = { + caseA: { department: dA, tag: tagA }, + caseB: { department: dB, tag: tagB }, + casePublic: { tag: publicTag }, + }; }); it('should return an array of tags', async () => { - await updatePermission('manage-livechat-tags', ['admin']); - await updatePermission('view-l-room', ['livechat-agent']); - const tag = await saveTags(); + const { tag } = tagsData.caseA; const response = await request .get(api('livechat/tags')) .set(credentials) - .query({ text: tag.name }) + .query({ text: tag.name, viewAll: 'true' }) .expect('Content-Type', 'application/json') .expect(200); expect(response.body).to.have.property('success', true); - expect(response.body.tags).to.be.an('array').with.lengthOf.greaterThan(0); + expect(response.body.tags).to.be.an('array').with.lengthOf(1); expect(response.body.tags[0]).to.have.property('_id', tag._id); }); + it('show return all tags when "viewAll" param is true', async () => { + const response = await request + .get(api('livechat/tags')) + .set(credentials) + .query({ viewAll: 'true' }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body).to.have.property('success', true); + expect(response.body.tags).to.be.an('array').with.lengthOf(3); + + const expectedTags = [tagsData.caseA.tag, tagsData.caseB.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort(); + const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort(); + expect(actualTags).to.deep.equal(expectedTags); + }); + it('should return department tags and public tags when "departmentId" param is provided', async () => { + const { department } = tagsData.caseA; + + const response = await request + .get(api('livechat/tags')) + .set(credentials) + .query({ department: department._id }) + .expect('Content-Type', 'application/json') + .expect(200); + + expect(response.body).to.have.property('success', true); + expect(response.body.tags).to.be.an('array').with.lengthOf(2); + + const expectedTags = [tagsData.caseA.tag, tagsData.casePublic.tag].map((tag) => tag._id).sort(); + const actualTags = response.body.tags.map((tag: ILivechatTag) => tag._id).sort(); + expect(actualTags).to.deep.equal(expectedTags); + }); + it('should return public tags when "departmentId" param is not provided', async () => { + const response = await request.get(api('livechat/tags')).set(credentials).expect('Content-Type', 'application/json').expect(200); + + expect(response.body).to.have.property('success', true); + expect(response.body.tags).to.be.an('array').with.lengthOf(1); + expect(response.body.tags[0]).to.have.property('_id', tagsData.casePublic.tag._id); + }); }); describe('livechat/tags/:tagId', () => { diff --git a/packages/model-typings/src/models/ILivechatDepartmentModel.ts b/packages/model-typings/src/models/ILivechatDepartmentModel.ts index d0f11f2bff95b..8008008780880 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentModel.ts @@ -59,4 +59,5 @@ export interface ILivechatDepartmentModel extends IBaseModel<ILivechatDepartment findOneByIdOrName(_idOrName: string, options?: FindOptions<ILivechatDepartment>): Promise<ILivechatDepartment | null>; findByUnitIds(unitIds: string[], options?: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>; findActiveByUnitIds(unitIds: string[], options?: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>; + checkIfMonitorIsMonitoringDepartmentById(monitorId: string, departmentId: string): Promise<boolean>; } diff --git a/packages/rest-typings/src/v1/omnichannel.ts b/packages/rest-typings/src/v1/omnichannel.ts index 8423c4db1fdd3..257706a0d2703 100644 --- a/packages/rest-typings/src/v1/omnichannel.ts +++ b/packages/rest-typings/src/v1/omnichannel.ts @@ -463,7 +463,7 @@ const LivechatMonitorsListSchema = { export const isLivechatMonitorsListProps = ajv.compile<LivechatMonitorsListProps>(LivechatMonitorsListSchema); -type LivechatTagsListProps = PaginatedRequest<{ text: string }, 'name'>; +type LivechatTagsListProps = PaginatedRequest<{ text: string; viewAll?: 'true' | 'false'; department?: string }, 'name'>; const LivechatTagsListSchema = { type: 'object', @@ -471,6 +471,15 @@ const LivechatTagsListSchema = { text: { type: 'string', }, + department: { + type: 'string', + nullable: true, + }, + viewAll: { + type: 'string', + nullable: true, + enum: ['true', 'false'], + }, count: { type: 'number', nullable: true, From 74aa6770881eb620a2275b84c55465d7552e4597 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Mon, 3 Jul 2023 10:31:23 -0300 Subject: [PATCH 28/79] feat: Add custom OAuth setting to allow merging users to others from distinct services (#28783) * Merge OAuth users with existing users from distinct services when setting is enabled * Add changesets * Fix changesets * Apply requested changes --------- Co-authored-by: Diego Sampaio <chinello@gmail.com> Co-authored-by: Rafael Tapia <rafael.tapia@rocket.chat> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> Co-authored-by: Gabriel Casals <83978645+casalsgh@users.noreply.github.com> --- .changeset/dirty-singers-promise.md | 6 +++++ .../server/custom_oauth_server.js | 9 +++++-- .../lib/server/functions/addOAuthService.ts | 12 +++++++++ .../lib/server/startup/oAuthServicesUpdate.js | 3 +++ .../rocketchat-i18n/i18n/en.i18n.json | 2 ++ apps/meteor/server/startup/migrations/v300.ts | 27 +++++++++++++++++++ packages/rest-typings/src/v1/settings.ts | 1 + 7 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 .changeset/dirty-singers-promise.md create mode 100644 apps/meteor/server/startup/migrations/v300.ts diff --git a/.changeset/dirty-singers-promise.md b/.changeset/dirty-singers-promise.md new file mode 100644 index 0000000000000..62660283d8d46 --- /dev/null +++ b/.changeset/dirty-singers-promise.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/rest-typings": minor +--- + +feat: Add custom OAuth setting to allow merging users to others from distinct services diff --git a/apps/meteor/app/custom-oauth/server/custom_oauth_server.js b/apps/meteor/app/custom-oauth/server/custom_oauth_server.js index 01c8d1bc00e9a..abfdafed6f537 100644 --- a/apps/meteor/app/custom-oauth/server/custom_oauth_server.js +++ b/apps/meteor/app/custom-oauth/server/custom_oauth_server.js @@ -81,6 +81,7 @@ export class CustomOAuth { this.nameField = (options.nameField || '').trim(); this.avatarField = (options.avatarField || '').trim(); this.mergeUsers = options.mergeUsers; + this.mergeUsersDistinctServices = options.mergeUsersDistinctServices; this.rolesClaim = options.rolesClaim || 'roles'; this.accessTokenParam = options.accessTokenParam; this.channelsAdmin = options.channelsAdmin || 'rocket.cat'; @@ -333,9 +334,13 @@ export class CustomOAuth { let user = undefined; if (this.keyField === 'username') { - user = await Users.findOneByUsernameAndServiceNameIgnoringCase(serviceData.username, serviceData._id, serviceName); + user = this.mergeUsersDistinctServices + ? await Users.findOneByUsernameIgnoringCase(serviceData.username) + : await Users.findOneByUsernameAndServiceNameIgnoringCase(serviceData.username, serviceData.id, serviceName); } else if (this.keyField === 'email') { - user = await Users.findOneByEmailAddressAndServiceNameIgnoringCase(serviceData.email, serviceData._id, serviceName); + user = this.mergeUsersDistinctServices + ? await Users.findOneByEmailAddress(serviceData.email) + : await Users.findOneByEmailAddressAndServiceNameIgnoringCase(serviceData.email, serviceData.id, serviceName); } if (!user) { diff --git a/apps/meteor/app/lib/server/functions/addOAuthService.ts b/apps/meteor/app/lib/server/functions/addOAuthService.ts index 0c206a4d1544f..eb28c5a7e3eb2 100644 --- a/apps/meteor/app/lib/server/functions/addOAuthService.ts +++ b/apps/meteor/app/lib/server/functions/addOAuthService.ts @@ -230,6 +230,18 @@ export async function addOAuthService(name: string, values: { [k: string]: strin i18nLabel: 'Accounts_OAuth_Custom_Merge_Users', persistent: true, }); + await settingsRegistry.add(`Accounts_OAuth_Custom-${name}-merge_users_distinct_services`, values.mergeUsersDistinctServices || false, { + type: 'boolean', + group: 'OAuth', + section: `Custom OAuth: ${name}`, + i18nLabel: 'Accounts_OAuth_Custom_Merge_Users_Distinct_Services', + i18nDescription: 'Accounts_OAuth_Custom_Merge_Users_Distinct_Services_Description', + enableQuery: { + _id: `Accounts_OAuth_Custom-${name}-merge_users`, + value: true, + }, + persistent: true, + }); await settingsRegistry.add(`Accounts_OAuth_Custom-${name}-show_button`, values.showButton || true, { type: 'boolean', group: 'OAuth', diff --git a/apps/meteor/app/lib/server/startup/oAuthServicesUpdate.js b/apps/meteor/app/lib/server/startup/oAuthServicesUpdate.js index 58e8222705243..e69d3c900eade 100644 --- a/apps/meteor/app/lib/server/startup/oAuthServicesUpdate.js +++ b/apps/meteor/app/lib/server/startup/oAuthServicesUpdate.js @@ -53,6 +53,7 @@ async function _OAuthServicesUpdate() { data.channelsMap = settings.get(`${key}-groups_channel_map`); data.channelsAdmin = settings.get(`${key}-channels_admin`); data.mergeUsers = settings.get(`${key}-merge_users`); + data.mergeUsersDistinctServices = settings.get(`${key}-merge_users_distinct_services`); data.mapChannels = settings.get(`${key}-map_channels`); data.mergeRoles = settings.get(`${key}-merge_roles`); data.rolesToSync = settings.get(`${key}-roles_to_sync`); @@ -78,6 +79,7 @@ async function _OAuthServicesUpdate() { channelsMap: data.channelsMap, channelsAdmin: data.channelsAdmin, mergeUsers: data.mergeUsers, + mergeUsersDistinctServices: data.mergeUsersDistinctServices, mergeRoles: data.mergeRoles, rolesToSync: data.rolesToSync, accessTokenParam: data.accessTokenParam, @@ -187,6 +189,7 @@ async function customOAuthServicesInit() { channelsMap: process.env[`${serviceKey}_groups_channel_map`], channelsAdmin: process.env[`${serviceKey}_channels_admin`], mergeUsers: process.env[`${serviceKey}_merge_users`] === 'true', + mergeUsersDistinctServices: process.env[`${serviceKey}_merge_users_distinct_services`] === 'true', mapChannels: process.env[`${serviceKey}_map_channels`], mergeRoles: process.env[`${serviceKey}_merge_roles`] === 'true', rolesToSync: process.env[`${serviceKey}_roles_to_sync`], diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 3690cbb353d76..60b02e3ca684c 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -135,6 +135,8 @@ "Accounts_OAuth_Custom_Map_Channels": "Map Roles/Groups to channels", "Accounts_OAuth_Custom_Merge_Roles": "Merge Roles from SSO", "Accounts_OAuth_Custom_Merge_Users": "Merge users", + "Accounts_OAuth_Custom_Merge_Users_Distinct_Services": "Merge users from distinct services", + "Accounts_OAuth_Custom_Merge_Users_Distinct_Services_Description": "When the given key field matches the one of an existing user, allow users from this OAuth service to be merged to existing users regardless of their origin service.", "Accounts_OAuth_Custom_Name_Field": "Name field", "Accounts_OAuth_Custom_Roles_Claim": "Roles/Groups field name", "Accounts_OAuth_Custom_Roles_To_Sync": "Roles to Sync", diff --git a/apps/meteor/server/startup/migrations/v300.ts b/apps/meteor/server/startup/migrations/v300.ts new file mode 100644 index 0000000000000..4e00b2c030750 --- /dev/null +++ b/apps/meteor/server/startup/migrations/v300.ts @@ -0,0 +1,27 @@ +import { Settings } from '@rocket.chat/models'; + +import { settingsRegistry } from '../../../app/settings/server'; +import { addMigration } from '../../lib/migrations'; + +addMigration({ + version: 300, + async up() { + const customOauthServices = await Settings.find({ _id: /Accounts_OAuth_Custom-[^-]+$/ }, { projection: { _id: 1 } }).toArray(); + const serviceNames = customOauthServices.map(({ _id }) => _id.replace('Accounts_OAuth_Custom-', '')); + + for await (const serviceName of serviceNames) { + await settingsRegistry.add(`Accounts_OAuth_Custom-${serviceName}-merge_users_distinct_services`, false, { + type: 'boolean', + group: 'OAuth', + section: `Custom OAuth: ${serviceName}`, + i18nLabel: 'Accounts_OAuth_Custom_Merge_Users_Distinct_Services', + i18nDescription: 'Accounts_OAuth_Custom_Merge_Users_Distinct_Services_Description', + enableQuery: { + _id: `Accounts_OAuth_Custom-${serviceName}-merge_users`, + value: true, + }, + persistent: true, + }); + } + }, +}); diff --git a/packages/rest-typings/src/v1/settings.ts b/packages/rest-typings/src/v1/settings.ts index 694ea9f593189..cbc789e220516 100644 --- a/packages/rest-typings/src/v1/settings.ts +++ b/packages/rest-typings/src/v1/settings.ts @@ -32,6 +32,7 @@ export type OauthCustomConfiguration = { channelsMap: string; channelsAdmin: string; mergeUsers: boolean; + mergeUsersDistinctServices: boolean; mergeRoles: boolean; accessTokenParam: string; showButton: boolean; From 649347846dc3a6b4fd99fca5a87fa72380119b67 Mon Sep 17 00:00:00 2001 From: Hugo Costa <hugocarreiracosta@gmail.com> Date: Mon, 3 Jul 2023 11:20:44 -0300 Subject: [PATCH 29/79] chore: Device Management version info at table and contextual bar (#29606) --- .../DeviceManagementAdminRow.tsx | 3 +++ .../DeviceManagementAdminTable.tsx | 2 ++ .../DeviceManagementInfo/DeviceManagementInfo.tsx | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminRow.tsx b/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminRow.tsx index cb7aae0ba80a4..200eb40686e41 100644 --- a/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminRow.tsx +++ b/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminRow.tsx @@ -17,6 +17,7 @@ type DeviceRowProps = { deviceType?: string; deviceOSName?: string; loginAt: string; + rcVersion?: string; onReload: () => void; }; @@ -28,6 +29,7 @@ const DeviceManagementAdminRow = ({ deviceType = 'browser', deviceOSName = '', loginAt, + rcVersion, onReload, }: DeviceRowProps): ReactElement => { const t = useTranslation(); @@ -69,6 +71,7 @@ const DeviceManagementAdminRow = ({ {deviceName && <Box withTruncatedText>{deviceName}</Box>} </Box> </GenericTableCell> + <GenericTableCell>{rcVersion}</GenericTableCell> <GenericTableCell>{deviceOSName}</GenericTableCell> <GenericTableCell withTruncatedText>{username}</GenericTableCell> {mediaQuery && <GenericTableCell>{formatDateAndTime(loginAt)}</GenericTableCell>} diff --git a/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminTable.tsx b/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminTable.tsx index c9e94518cf081..db1c78f53cedd 100644 --- a/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminTable.tsx +++ b/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementAdminTable/DeviceManagementAdminTable.tsx @@ -55,6 +55,7 @@ const DeviceManagementAdminTable = ({ reloadRef }: { reloadRef: MutableRefObject <GenericTableHeaderCell key={'client'} direction={sortDirection} active={sortBy === 'client'} onClick={setSort} sort='client'> {t('Client')} </GenericTableHeaderCell>, + <GenericTableHeaderCell key={'rcVersion'}>{t('Version')}</GenericTableHeaderCell>, <GenericTableHeaderCell key={'os'} direction={sortDirection} active={sortBy === 'os'} onClick={setSort} sort='os'> {t('OS')} </GenericTableHeaderCell>, @@ -91,6 +92,7 @@ const DeviceManagementAdminTable = ({ reloadRef }: { reloadRef: MutableRefObject deviceName={session?.device?.name} deviceType={session?.device?.type} deviceOSName={session?.device?.os?.name} + rcVersion={session?.device?.version} loginAt={session.loginAt} onReload={reload} /> diff --git a/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementInfo/DeviceManagementInfo.tsx b/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementInfo/DeviceManagementInfo.tsx index 4065057ecea74..89f1af7132a1c 100644 --- a/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementInfo/DeviceManagementInfo.tsx +++ b/apps/meteor/ee/client/views/admin/deviceManagement/DeviceManagementInfo/DeviceManagementInfo.tsx @@ -29,7 +29,7 @@ const DeviceManagementInfo = ({ device, sessionId, loginAt, ip, userId, _user, o const handleDeviceLogout = useDeviceLogout(sessionId, '/v1/sessions/logout'); - const { name: clientName, os } = device || {}; + const { name: clientName, os, version: rcVersion } = device || {}; const { username, name } = _user || {}; const userPresence = usePresence(userId); @@ -48,6 +48,11 @@ const DeviceManagementInfo = ({ device, sessionId, loginAt, ip, userId, _user, o <InfoPanel.Text>{clientName}</InfoPanel.Text> </InfoPanel.Field> + <InfoPanel.Field> + <InfoPanel.Label>{t('Version')}</InfoPanel.Label> + <InfoPanel.Text>{rcVersion || '—'}</InfoPanel.Text> + </InfoPanel.Field> + <InfoPanel.Field> <InfoPanel.Label>{t('OS')}</InfoPanel.Label> <InfoPanel.Text>{`${os?.name || ''} ${os?.version || ''}`}</InfoPanel.Text> From 8ac075833579f0e1429763f1d3ae6b1abc383229 Mon Sep 17 00:00:00 2001 From: Pierre Lehnen <55164754+pierre-lehnen-rc@users.noreply.github.com> Date: Mon, 3 Jul 2023 13:40:50 -0300 Subject: [PATCH 30/79] fix: call-management permission not doing anything (#29524) --- .changeset/call-management-permission.md | 5 +++++ apps/meteor/app/api/server/v1/videoConference.ts | 4 ++++ apps/meteor/app/authorization/server/constant/permissions.ts | 2 +- apps/meteor/app/videobridge/client/tabBar.tsx | 3 ++- 4 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 .changeset/call-management-permission.md diff --git a/.changeset/call-management-permission.md b/.changeset/call-management-permission.md new file mode 100644 index 0000000000000..ad18de8de1ce5 --- /dev/null +++ b/.changeset/call-management-permission.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +fix: Permission to start conference calls was not being considered diff --git a/apps/meteor/app/api/server/v1/videoConference.ts b/apps/meteor/app/api/server/v1/videoConference.ts index bd95433773db8..9fb13b7dbdb9c 100644 --- a/apps/meteor/app/api/server/v1/videoConference.ts +++ b/apps/meteor/app/api/server/v1/videoConference.ts @@ -26,6 +26,10 @@ API.v1.addRoute( return API.v1.failure('invalid-params'); } + if (!(await hasPermissionAsync(userId, 'call-management', roomId))) { + return API.v1.unauthorized(); + } + try { const providerName = videoConfProviders.getActiveProvider(); diff --git a/apps/meteor/app/authorization/server/constant/permissions.ts b/apps/meteor/app/authorization/server/constant/permissions.ts index 053f39c3af0ba..0090d10fd8803 100644 --- a/apps/meteor/app/authorization/server/constant/permissions.ts +++ b/apps/meteor/app/authorization/server/constant/permissions.ts @@ -83,7 +83,7 @@ export const permissions = [ { _id: 'preview-c-room', roles: ['admin', 'user', 'anonymous'] }, { _id: 'view-outside-room', roles: ['admin', 'owner', 'moderator', 'user'] }, { _id: 'view-broadcast-member-list', roles: ['admin', 'owner', 'moderator'] }, - { _id: 'call-management', roles: ['admin', 'owner', 'moderator'] }, + { _id: 'call-management', roles: ['admin', 'owner', 'moderator', 'user'] }, { _id: 'create-invite-links', roles: ['admin', 'owner', 'moderator'] }, { _id: 'view-l-room', diff --git a/apps/meteor/app/videobridge/client/tabBar.tsx b/apps/meteor/app/videobridge/client/tabBar.tsx index 80b4305949424..23fb71b09e35d 100644 --- a/apps/meteor/app/videobridge/client/tabBar.tsx +++ b/apps/meteor/app/videobridge/client/tabBar.tsx @@ -44,6 +44,7 @@ addAction('start-call', ({ room }) => { const isRinging = useVideoConfIsRinging(); const federated = isRoomFederated(room); const canPostReadOnly = usePermission('post-readonly', room._id); + const canStartCall = usePermission('call-management', room._id); const ownUser = room.uids && room.uids.length === 1; @@ -57,7 +58,7 @@ addAction('start-call', ({ room }) => { const live = room?.streamingOptions && room.streamingOptions.type === 'call'; const enabled = enabledDMs || enabledChannel || enabledTeams || enabledGroups || enabledLiveChat; - const enableOption = enabled && (!user?.username || !room.muted?.includes(user.username)); + const enableOption = enabled && canStartCall && (!user?.username || !room.muted?.includes(user.username)); const groups = useStableArray( [ From 0856e8519015be36a6338d975b69df4ee89e3ffe Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Mon, 3 Jul 2023 14:31:16 -0300 Subject: [PATCH 31/79] chore: upgrading fuselage package (#29703) --- yarn.lock | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7c98d73a79617..b3fc2efa93913 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9392,7 +9392,7 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/css-in-js@npm:^0.31.12, @rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.146": +"@rocket.chat/css-in-js@npm:^0.31.12, @rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.151": version: 0.31.23 resolution: "@rocket.chat/css-in-js@npm:0.31.23" dependencies: @@ -9418,7 +9418,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.146": +"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.151": version: 0.31.23 resolution: "@rocket.chat/css-supports@npm:0.31.23" dependencies: @@ -9647,10 +9647,10 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.322": - version: 0.32.0-dev.322 - resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.322" - checksum: 95a8319aaa4f6fd429c62a594b4094136ec57c519201140ca029bbe44d49cb3fd381fb67d7e20f1341308868794dbc9609dad180c4ab38ddcbd89eea3e78451a +"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.327": + version: 0.32.0-dev.327 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.327" + checksum: 93597b59a76829d5074834295c0b13ecf1256433bdedab6ce5f92b50799893ca8f5eceabcdeaba0fbd24df7abd64f4c62fa28f008d8fa90a7ecd869bbd8b0d6a languageName: node linkType: hard @@ -9710,14 +9710,14 @@ __metadata: linkType: soft "@rocket.chat/fuselage@npm:next": - version: 0.32.0-dev.372 - resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.372" - dependencies: - "@rocket.chat/css-in-js": ~0.31.23-dev.146 - "@rocket.chat/css-supports": ~0.31.23-dev.146 - "@rocket.chat/fuselage-tokens": ~0.32.0-dev.322 - "@rocket.chat/memo": ~0.31.23-dev.146 - "@rocket.chat/styled": ~0.31.23-dev.146 + version: 0.32.0-dev.377 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.377" + dependencies: + "@rocket.chat/css-in-js": ~0.31.23-dev.151 + "@rocket.chat/css-supports": ~0.31.23-dev.151 + "@rocket.chat/fuselage-tokens": ~0.32.0-dev.327 + "@rocket.chat/memo": ~0.31.23-dev.151 + "@rocket.chat/styled": ~0.31.23-dev.151 invariant: ^2.2.4 react-aria: ~3.23.1 react-keyed-flatten-children: ^1.3.0 @@ -9729,7 +9729,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 47cdb72c37ee36ebc1dd3daeb8d03950b674dbc3637918dc3d8f6aeead7a56a34db6767e3514e62fc0a9d6a811ef0325cc17c8ffc79532b87f720559ef940ce0 + checksum: e61ddd6ce6dd7ea4ba8d5b5c5f464a82d5600934c7ae05a38f20227308f39b55dbd5b91f9190d494b814efac6e60eb70810ab7d171ff1b482e54a2132573bdda languageName: node linkType: hard @@ -9957,7 +9957,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.146": +"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.151": version: 0.31.23 resolution: "@rocket.chat/memo@npm:0.31.23" checksum: 070debb940749a2e4463cf767dd65c6967cea664a5bd67c22a812d611f6c3c46d6fe4bb0bf329e43dcd927493413add37c45ae3b05ec08f0b24e9d7385caebdd @@ -10762,7 +10762,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.146": +"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.151": version: 0.31.23 resolution: "@rocket.chat/styled@npm:0.31.23" dependencies: From cb4048a911f12d154dc0bfc68747d5204f155881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:31:31 -0300 Subject: [PATCH 32/79] chore: update button-secondary colors (#29708) --- ee/packages/ui-theming/src/paletteDark.ts | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ee/packages/ui-theming/src/paletteDark.ts b/ee/packages/ui-theming/src/paletteDark.ts index 80a29c07cd2d0..6c4a22fa26ccd 100644 --- a/ee/packages/ui-theming/src/paletteDark.ts +++ b/ee/packages/ui-theming/src/paletteDark.ts @@ -131,23 +131,23 @@ export const palette = [ { description: 'Secondary Background', list: [ - { name: 'button-background-secondary-default', token: 'N800', color: '#2F343D' }, - { name: 'button-background-secondary-hover', token: '', color: '#3A404B' }, - { name: 'button-background-secondary-press', token: '', color: '#454C59' }, - { name: 'button-background-secondary-focus', token: 'N800', color: '#2F343D' }, - { name: 'button-background-secondary-keyfocus', token: 'N800', color: '#2F343D' }, - { name: 'button-background-secondary-disabled', token: '', color: '#2F343D' }, + { name: 'button-background-secondary-default', token: 'N800', color: '#353B45' }, + { name: 'button-background-secondary-hover', token: '', color: '#404754' }, + { name: 'button-background-secondary-press', token: '', color: '#4C5362' }, + { name: 'button-background-secondary-focus', token: 'N800', color: '#353B45' }, + { name: 'button-background-secondary-keyfocus', token: 'N800', color: '#353B45' }, + { name: 'button-background-secondary-disabled', token: '', color: '#353B45' }, ], }, { description: 'Secondary Danger Background', list: [ - { name: 'button-background-secondary-danger-default', token: 'N800', color: '#2F343D' }, - { name: 'button-background-secondary-danger-hover', token: '', color: '#3A404B' }, - { name: 'button-background-secondary-danger-press', token: '', color: '#454C59' }, - { name: 'button-background-secondary-danger-focus', token: 'N800', color: '#2F343D' }, - { name: 'button-background-secondary-danger-keyfocus', token: 'N800', color: '#2F343D' }, - { name: 'button-background-secondary-danger-disabled', token: '', color: '#2F343D' }, + { name: 'button-background-secondary-danger-default', token: 'N800', color: '#353B45' }, + { name: 'button-background-secondary-danger-hover', token: '', color: '#404754' }, + { name: 'button-background-secondary-danger-press', token: '', color: '#4C5362' }, + { name: 'button-background-secondary-danger-focus', token: 'N800', color: '#353B45' }, + { name: 'button-background-secondary-danger-keyfocus', token: 'N800', color: '#353B45' }, + { name: 'button-background-secondary-danger-disabled', token: '', color: '#353B45' }, ], }, { From ba1d0c5d6381fcef8900a7ce9cbf77c27fe668dc Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Mon, 3 Jul 2023 18:20:44 -0300 Subject: [PATCH 33/79] fix: Video Conf information block not updating automatically (#29682) Co-authored-by: Diego Sampaio <chinello@gmail.com> --- apps/meteor/server/modules/listeners/listeners.module.ts | 7 +++++++ apps/meteor/server/services/video-conference/service.ts | 6 +----- packages/core-services/src/Events.ts | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index a1898592c5af2..45d8e75952fb8 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -132,6 +132,13 @@ export class ListenersModule { }, ); + service.onEvent('room.video-conference', ({ rid, callId }) => { + /* deprecated */ + (notifications.notifyRoom as any)(rid, callId); + + notifications.notifyRoom(rid, 'videoconf', callId); + }); + service.onEvent('presence.status', ({ user }) => { const { _id, username, name, status, statusText, roles } = user; if (!status || !username) { diff --git a/apps/meteor/server/services/video-conference/service.ts b/apps/meteor/server/services/video-conference/service.ts index ed44cee96e5ce..2543e91a3e47e 100644 --- a/apps/meteor/server/services/video-conference/service.ts +++ b/apps/meteor/server/services/video-conference/service.ts @@ -41,7 +41,6 @@ import { updateCounter } from '../../../app/statistics/server/functions/updateSt import { readSecondaryPreferred } from '../../database/readSecondaryPreferred'; import { availabilityErrors } from '../../../lib/videoConference/constants'; import { callbacks } from '../../../lib/callbacks'; -import { Notifications } from '../../../app/notifications/server'; import { canAccessRoomIdAsync } from '../../../app/authorization/server/functions/canAccessRoom'; import { i18n } from '../../lib/i18n'; @@ -419,10 +418,7 @@ export class VideoConfService extends ServiceClassInternal implements IVideoConf } private notifyVideoConfUpdate(rid: IRoom['_id'], callId: VideoConference['_id']): void { - /* deprecated */ - (Notifications.notifyRoom as any)(rid, callId); - - Notifications.notifyRoom(rid, 'videoconf', callId); + void api.broadcast('room.video-conference', { rid, callId }); } private async endCall(callId: VideoConference['_id']): Promise<void> { diff --git a/packages/core-services/src/Events.ts b/packages/core-services/src/Events.ts index ed04a2404ece5..56187608df000 100644 --- a/packages/core-services/src/Events.ts +++ b/packages/core-services/src/Events.ts @@ -36,6 +36,7 @@ import type { AutoUpdateRecord } from './types/IMeteor'; type ClientAction = 'inserted' | 'updated' | 'removed' | 'changed'; export type EventSignatures = { + 'room.video-conference': (params: { rid: string; callId: string }) => void; 'shutdown': (params: Record<string, string[]>) => void; '$services.changed': (info: { localService: boolean }) => void; 'accounts.login': (info: { userId: string; connection: ISocketConnection }) => void; From e05e581a466609caf03150819c74ce84349fc438 Mon Sep 17 00:00:00 2001 From: Rodrigo Nascimento <rodrigoknascimento@gmail.com> Date: Mon, 3 Jul 2023 18:21:35 -0300 Subject: [PATCH 34/79] chore: Improve performance of getActiveLocalUserCount (#29681) Co-authored-by: Diego Sampaio <chinello@gmail.com> --- apps/meteor/server/models/raw/Users.js | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index f8ae05e11d11e..0cb88a178ccc8 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -2815,19 +2815,17 @@ export class UsersRaw extends BaseRaw { // here getActiveLocalUserCount() { return Promise.all([ + // Count all active users (fast based on index) this.col.countDocuments({ active: true, - type: { - $nin: ['app'], - }, - roles: { $ne: ['guest'] }, }), - this.col.countDocuments({ federated: true, active: true }), + // Count all active that are guests, apps or federated + // Fast based on indexes, usually based on guest index as is usually small this.col.countDocuments({ - isRemote: true, active: true, - roles: { $ne: ['guest'] }, + $or: [{ roles: ['guest'] }, { type: 'app' }, { federated: true }, { isRemote: true }], }), + // Get all active and remove the guests, apps, federated, etc ]).then((results) => results.reduce((a, b) => a - b)); } From ca8d94b94faa64d96efd764af789bc083a09867c Mon Sep 17 00:00:00 2001 From: Hugo Costa <hugocarreiracosta@gmail.com> Date: Tue, 4 Jul 2023 10:01:09 -0300 Subject: [PATCH 35/79] ci: fixing and updating Stale action (#29653) --- .github/workflows/stale.yml | 32 ++++++++++++++------------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 6ffbea0997e4d..b074212964ebc 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -1,24 +1,20 @@ -name: Stale Questions - +name: Close inactive issues on: schedule: - - cron: "0 */6 * * *" + - cron: "0 */6 * * *" jobs: - no-response: + close-issues: runs-on: ubuntu-latest + permissions: + issues: write steps: - - uses: actions/stale@v4 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - days-before-stale: 10 - days-before-close: 4 - only-labels: 'stat: need more info,stat: waiting response' - stale-issue-message: >- - This issue has been marked as stale because there has been - no further activity in the last 10 days. If the issue remains - stale for the next 4 days (a total of two weeks with no activity), - then it will be assumed that the question has been resolved and - the issue will be automatically closed. - stale-issue-label: 'stat: no response' - operations-per-run: 40 + - uses: actions/stale@v5 + with: + days-before-issue-stale: 10 + days-before-issue-close: 4 + any-of-labels: 'stat: need more info,stat: waiting response' + stale-issue-label: "stat: no response" + stale-issue-message: "This issue has been marked as stale because there has been no further activity in the last 10 days. If the issue remains stale for the next 4 days (a total of 14 days with no activity), then it will be assumed that the question has been resolved and the issue will be automatically closed." + close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale." + repo-token: ${{ secrets.GITHUB_TOKEN }} From 3e2d70087dcbadae97ff5841da7d912f42b79706 Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Tue, 4 Jul 2023 11:07:10 -0300 Subject: [PATCH 36/79] fix: avatar is reset in the UI when username is changed (#29685) --- .changeset/green-icons-smash.md | 5 +++++ .../lib/server/functions/saveUserIdentity.ts | 20 +++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) create mode 100644 .changeset/green-icons-smash.md diff --git a/.changeset/green-icons-smash.md b/.changeset/green-icons-smash.md new file mode 100644 index 0000000000000..5419c2c3fa5b2 --- /dev/null +++ b/.changeset/green-icons-smash.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fix: Avatar is reset in the UI when username is changed diff --git a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts index a7c9866478520..37ec3a890bd36 100644 --- a/apps/meteor/app/lib/server/functions/saveUserIdentity.ts +++ b/apps/meteor/app/lib/server/functions/saveUserIdentity.ts @@ -49,6 +49,16 @@ export async function saveUserIdentity({ _id, name: rawName, username: rawUserna // if coming from old username, update all references if (previousUsername) { if (usernameChanged && typeof rawUsername !== 'undefined') { + const fileStore = FileUpload.getStore('Avatars'); + const previousFile = await fileStore.model.findOneByName(previousUsername); + const file = await fileStore.model.findOneByName(username); + if (file) { + await fileStore.model.deleteFile(file._id); + } + if (previousFile) { + await fileStore.model.updateFileNameById(previousFile._id, username); + } + await Messages.updateAllUsernamesByUserId(user._id, username); await Messages.updateUsernameOfEditByUserId(user._id, username); @@ -64,16 +74,6 @@ export async function saveUserIdentity({ _id, name: rawName, username: rawUserna await Subscriptions.setUserUsernameByUserId(user._id, username); await LivechatDepartmentAgents.replaceUsernameOfAgentByUserId(user._id, username); - - const fileStore = FileUpload.getStore('Avatars'); - const previousFile = await fileStore.model.findOneByName(previousUsername); - const file = await fileStore.model.findOneByName(username); - if (file) { - await fileStore.model.deleteFile(file._id); - } - if (previousFile) { - await fileStore.model.updateFileNameById(previousFile._id, username); - } } // update other references if either the name or username has changed From c10137e015b2cfa42aa7976d88e512e2ed0ed34e Mon Sep 17 00:00:00 2001 From: Douglas Fabris <devfabris@gmail.com> Date: Tue, 4 Jul 2023 11:40:48 -0300 Subject: [PATCH 37/79] feat: Outlook Calendar Integration (#27922) --- .vscode/settings.json | 1 + apps/meteor/app/api/server/index.ts | 1 + apps/meteor/app/api/server/v1/calendar.ts | 139 ++++ .../GenericModal.stories.tsx | 0 .../{ => GenericModal}/GenericModal.tsx | 2 +- .../GenericModal/GenericModalSkeleton.tsx | 25 + .../client/components/GenericModal/index.ts | 2 + .../{ => GenericModal}/withDoNotAskAgain.tsx | 2 +- .../lib/utils/window.RocketChatDesktop.d.ts | 14 + apps/meteor/client/main.ts | 1 + .../client/providers/VideoConfProvider.tsx | 4 +- .../notifications/konchatNotifications.ts | 40 +- .../preferences/AccountPreferencesPage.tsx | 1 + .../PreferencesNotificationsSection.tsx | 17 + .../views/conference/ConferencePage.tsx | 4 +- ...erativeModal.ts => useImperativeModal.tsx} | 15 +- .../OutlookCalendarEventModal.tsx | 53 ++ .../OutlookEventsList/OutlookEventItem.tsx | 67 ++ .../OutlookEventItemContent.tsx | 25 + .../OutlookEventsList/OutlookEventsList.tsx | 139 ++++ .../OutlookEventsList/index.ts | 1 + .../outlookCalendar/OutlookEventsRoute.tsx | 25 + .../OutlookSettingItem.tsx | 58 ++ .../OutlookSettingsList.tsx | 87 +++ .../OutlookSettingsList/index.ts | 1 + .../hooks/useOutlookAuthentication.ts | 59 ++ .../hooks/useOutlookCalendarList.ts | 53 ++ .../hooks/useOutlookOpenCall.ts | 18 + .../client/views/outlookCalendar/index.ts | 1 + .../outlookCalendar/lib/NotOnDesktopError.ts | 5 + .../outlookCalendar/lib/syncOutlookEvents.ts | 15 + .../client/views/outlookCalendar/tabBar.ts | 23 + .../hooks/useVideoConfOpenCall.tsx | 18 +- .../ConvertToChannelModal.tsx | 12 +- apps/meteor/ee/app/license/server/bundles.ts | 4 +- apps/meteor/ee/server/configuration/index.ts | 1 + .../server/configuration/outlookCalendar.ts | 13 + .../ee/server/settings/outlookCalendar.ts | 41 ++ .../rocketchat-i18n/i18n/en.i18n.json | 26 + .../server/methods/saveUserPreferences.ts | 2 + apps/meteor/server/models/CalendarEvent.ts | 6 + .../meteor/server/models/raw/CalendarEvent.ts | 124 ++++ apps/meteor/server/models/startup.ts | 1 + .../modules/listeners/listeners.module.ts | 4 + .../server/services/calendar/service.ts | 233 +++++++ apps/meteor/server/services/startup.ts | 2 + apps/meteor/server/settings/accounts.ts | 6 + apps/meteor/tests/data/user.ts | 1 + .../tests/end-to-end/api/00-miscellaneous.js | 1 + .../tests/end-to-end/api/30-calendar.ts | 660 ++++++++++++++++++ packages/core-services/src/Events.ts | 2 + packages/core-services/src/index.ts | 3 + .../src/types/ICalendarService.ts | 15 + packages/core-typings/src/ICalendarEvent.ts | 16 + packages/core-typings/src/INotification.ts | 9 + packages/core-typings/src/index.ts | 1 + packages/cron/src/index.ts | 53 +- packages/model-typings/src/index.ts | 1 + .../src/models/ICalendarEventModel.ts | 16 + packages/models/src/index.ts | 2 + packages/rest-typings/src/index.ts | 4 + .../v1/calendar/CalendarEventCreateProps.ts | 47 ++ .../v1/calendar/CalendarEventDeleteProps.ts | 22 + .../v1/calendar/CalendarEventImportProps.ts | 47 ++ .../src/v1/calendar/CalendarEventInfoProps.ts | 22 + .../src/v1/calendar/CalendarEventListProps.ts | 22 + .../v1/calendar/CalendarEventUpdateProps.ts | 48 ++ .../rest-typings/src/v1/calendar/index.ts | 41 ++ .../v1/users/UsersSetPreferenceParamsPOST.ts | 5 + .../ui-contexts/src/ServerContext/streams.ts | 2 + 70 files changed, 2385 insertions(+), 46 deletions(-) create mode 100644 apps/meteor/app/api/server/v1/calendar.ts rename apps/meteor/client/components/{ => GenericModal}/GenericModal.stories.tsx (100%) rename apps/meteor/client/components/{ => GenericModal}/GenericModal.tsx (98%) create mode 100644 apps/meteor/client/components/GenericModal/GenericModalSkeleton.tsx create mode 100644 apps/meteor/client/components/GenericModal/index.ts rename apps/meteor/client/components/{ => GenericModal}/withDoNotAskAgain.tsx (96%) create mode 100644 apps/meteor/client/lib/utils/window.RocketChatDesktop.d.ts rename apps/meteor/client/views/hooks/{useImperativeModal.ts => useImperativeModal.tsx} (57%) create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookCalendarEventModal.tsx create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventItem.tsx create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventItemContent.tsx create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventsList.tsx create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookEventsList/index.ts create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookEventsRoute.tsx create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookSettingsList/OutlookSettingItem.tsx create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookSettingsList/OutlookSettingsList.tsx create mode 100644 apps/meteor/client/views/outlookCalendar/OutlookSettingsList/index.ts create mode 100644 apps/meteor/client/views/outlookCalendar/hooks/useOutlookAuthentication.ts create mode 100644 apps/meteor/client/views/outlookCalendar/hooks/useOutlookCalendarList.ts create mode 100644 apps/meteor/client/views/outlookCalendar/hooks/useOutlookOpenCall.ts create mode 100644 apps/meteor/client/views/outlookCalendar/index.ts create mode 100644 apps/meteor/client/views/outlookCalendar/lib/NotOnDesktopError.ts create mode 100644 apps/meteor/client/views/outlookCalendar/lib/syncOutlookEvents.ts create mode 100644 apps/meteor/client/views/outlookCalendar/tabBar.ts create mode 100644 apps/meteor/ee/server/configuration/outlookCalendar.ts create mode 100644 apps/meteor/ee/server/settings/outlookCalendar.ts create mode 100644 apps/meteor/server/models/CalendarEvent.ts create mode 100644 apps/meteor/server/models/raw/CalendarEvent.ts create mode 100644 apps/meteor/server/services/calendar/service.ts create mode 100644 apps/meteor/tests/end-to-end/api/30-calendar.ts create mode 100644 packages/core-services/src/types/ICalendarService.ts create mode 100644 packages/core-typings/src/ICalendarEvent.ts create mode 100644 packages/model-typings/src/models/ICalendarEventModel.ts create mode 100644 packages/rest-typings/src/v1/calendar/CalendarEventCreateProps.ts create mode 100644 packages/rest-typings/src/v1/calendar/CalendarEventDeleteProps.ts create mode 100644 packages/rest-typings/src/v1/calendar/CalendarEventImportProps.ts create mode 100644 packages/rest-typings/src/v1/calendar/CalendarEventInfoProps.ts create mode 100644 packages/rest-typings/src/v1/calendar/CalendarEventListProps.ts create mode 100644 packages/rest-typings/src/v1/calendar/CalendarEventUpdateProps.ts create mode 100644 packages/rest-typings/src/v1/calendar/index.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index b2ff9086c4ce4..c9d8891420505 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -20,6 +20,7 @@ "typescript.tsdk": "./node_modules/typescript/lib", "cSpell.words": [ "autotranslate", + "Contextualbar", "fname", "Gazzodown", "katex", diff --git a/apps/meteor/app/api/server/index.ts b/apps/meteor/app/api/server/index.ts index 1cf198eaf9c20..699bfdacb3aaa 100644 --- a/apps/meteor/app/api/server/index.ts +++ b/apps/meteor/app/api/server/index.ts @@ -7,6 +7,7 @@ import './helpers/isUserFromParams'; import './helpers/parseJsonQuery'; import './default/info'; import './v1/assets'; +import './v1/calendar'; import './v1/channels'; import './v1/chat'; import './v1/cloud'; diff --git a/apps/meteor/app/api/server/v1/calendar.ts b/apps/meteor/app/api/server/v1/calendar.ts new file mode 100644 index 0000000000000..494f5ad03e240 --- /dev/null +++ b/apps/meteor/app/api/server/v1/calendar.ts @@ -0,0 +1,139 @@ +import { + isCalendarEventListProps, + isCalendarEventCreateProps, + isCalendarEventImportProps, + isCalendarEventInfoProps, + isCalendarEventUpdateProps, + isCalendarEventDeleteProps, +} from '@rocket.chat/rest-typings'; +import { Calendar } from '@rocket.chat/core-services'; + +import { API } from '../api'; + +API.v1.addRoute( + 'calendar-events.list', + { authRequired: true, validateParams: isCalendarEventListProps, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 1000 } }, + { + async get() { + const { userId } = this; + const { date } = this.queryParams; + + const data = await Calendar.list(userId, new Date(date)); + + return API.v1.success({ data }); + }, + }, +); + +API.v1.addRoute( + 'calendar-events.info', + { authRequired: true, validateParams: isCalendarEventInfoProps, rateLimiterOptions: { numRequestsAllowed: 3, intervalTimeInMS: 1000 } }, + { + async get() { + const { userId } = this; + const { id } = this.queryParams; + + const event = await Calendar.get(id); + + if (!event || event.uid !== userId) { + return API.v1.failure(); + } + + return API.v1.success({ event }); + }, + }, +); + +API.v1.addRoute( + 'calendar-events.create', + { authRequired: true, validateParams: isCalendarEventCreateProps }, + { + async post() { + const { userId: uid } = this; + const { startTime, externalId, subject, description, meetingUrl, reminderMinutesBeforeStart } = this.bodyParams; + + const id = await Calendar.create({ + uid, + startTime: new Date(startTime), + externalId, + subject, + description, + meetingUrl, + reminderMinutesBeforeStart, + }); + + return API.v1.success({ id }); + }, + }, +); + +API.v1.addRoute( + 'calendar-events.import', + { authRequired: true, validateParams: isCalendarEventImportProps }, + { + async post() { + const { userId: uid } = this; + const { startTime, externalId, subject, description, meetingUrl, reminderMinutesBeforeStart } = this.bodyParams; + + const id = await Calendar.import({ + uid, + startTime: new Date(startTime), + externalId, + subject, + description, + meetingUrl, + reminderMinutesBeforeStart, + }); + + return API.v1.success({ id }); + }, + }, +); + +API.v1.addRoute( + 'calendar-events.update', + { authRequired: true, validateParams: isCalendarEventUpdateProps }, + { + async post() { + const { userId } = this; + const { eventId, startTime, subject, description, meetingUrl, reminderMinutesBeforeStart } = this.bodyParams; + + const event = await Calendar.get(eventId); + + if (!event || event.uid !== userId) { + throw new Error('invalid-calendar-event'); + } + + await Calendar.update(eventId, { + startTime: new Date(startTime), + subject, + description, + meetingUrl, + reminderMinutesBeforeStart, + }); + + return API.v1.success(); + }, + }, +); + +API.v1.addRoute( + 'calendar-events.delete', + { authRequired: true, validateParams: isCalendarEventDeleteProps }, + { + async post() { + const { userId } = this; + const { eventId } = this.bodyParams; + + const event = await Calendar.get(eventId); + + if (!event || event.uid !== userId) { + throw new Error('invalid-calendar-event'); + } + + await Calendar.delete(eventId); + + return API.v1.success(); + }, + }, +); diff --git a/apps/meteor/client/components/GenericModal.stories.tsx b/apps/meteor/client/components/GenericModal/GenericModal.stories.tsx similarity index 100% rename from apps/meteor/client/components/GenericModal.stories.tsx rename to apps/meteor/client/components/GenericModal/GenericModal.stories.tsx diff --git a/apps/meteor/client/components/GenericModal.tsx b/apps/meteor/client/components/GenericModal/GenericModal.tsx similarity index 98% rename from apps/meteor/client/components/GenericModal.tsx rename to apps/meteor/client/components/GenericModal/GenericModal.tsx index 64dd981b0c328..09a9598dafc27 100644 --- a/apps/meteor/client/components/GenericModal.tsx +++ b/apps/meteor/client/components/GenericModal/GenericModal.tsx @@ -98,7 +98,7 @@ const GenericModal: FC<GenericModalProps> = ({ {confirmText ?? t('Ok')} </Button> )} - {!wrapperFunction && ( + {!wrapperFunction && onConfirm && ( <Button {...getButtonProps(variant)} onClick={onConfirm} disabled={confirmDisabled}> {confirmText ?? t('Ok')} </Button> diff --git a/apps/meteor/client/components/GenericModal/GenericModalSkeleton.tsx b/apps/meteor/client/components/GenericModal/GenericModalSkeleton.tsx new file mode 100644 index 0000000000000..d56cbdd26a67b --- /dev/null +++ b/apps/meteor/client/components/GenericModal/GenericModalSkeleton.tsx @@ -0,0 +1,25 @@ +import { Skeleton } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import type { ComponentProps } from 'react'; +import React from 'react'; + +import GenericModal from './GenericModal'; + +const GenericModalSkeleton = ({ onClose, ...props }: ComponentProps<typeof GenericModal>) => { + const t = useTranslation(); + + return ( + <GenericModal + {...props} + variant='warning' + onClose={onClose} + title={<Skeleton width='50%' />} + confirmText={t('Cancel')} + onConfirm={onClose} + > + <Skeleton width='full' /> + </GenericModal> + ); +}; + +export default GenericModalSkeleton; diff --git a/apps/meteor/client/components/GenericModal/index.ts b/apps/meteor/client/components/GenericModal/index.ts new file mode 100644 index 0000000000000..5ef8367e6779b --- /dev/null +++ b/apps/meteor/client/components/GenericModal/index.ts @@ -0,0 +1,2 @@ +export * from './GenericModal'; +export { default } from './GenericModal'; diff --git a/apps/meteor/client/components/withDoNotAskAgain.tsx b/apps/meteor/client/components/GenericModal/withDoNotAskAgain.tsx similarity index 96% rename from apps/meteor/client/components/withDoNotAskAgain.tsx rename to apps/meteor/client/components/GenericModal/withDoNotAskAgain.tsx index e855b6a3c9fbc..6d344a8a9674d 100644 --- a/apps/meteor/client/components/withDoNotAskAgain.tsx +++ b/apps/meteor/client/components/GenericModal/withDoNotAskAgain.tsx @@ -3,7 +3,7 @@ import { useUserPreference, useTranslation, useEndpoint } from '@rocket.chat/ui- import type { FC, ReactElement, ComponentType } from 'react'; import React, { useState } from 'react'; -import type { DontAskAgainList } from '../hooks/useDontAskAgain'; +import type { DontAskAgainList } from '../../hooks/useDontAskAgain'; type DoNotAskAgainProps = { onConfirm: (...args: any) => Promise<void> | void; diff --git a/apps/meteor/client/lib/utils/window.RocketChatDesktop.d.ts b/apps/meteor/client/lib/utils/window.RocketChatDesktop.d.ts new file mode 100644 index 0000000000000..73f884dc07ced --- /dev/null +++ b/apps/meteor/client/lib/utils/window.RocketChatDesktop.d.ts @@ -0,0 +1,14 @@ +type OutlookEventsResponse = { status: 'success' | 'canceled' }; + +// eslint-disable-next-line @typescript-eslint/naming-convention +interface Window { + RocketChatDesktop: + | { + openInternalVideoChatWindow?: (url: string, options: undefined) => void; + getOutlookEvents?: (date: Date) => Promise<OutlookEventsResponse>; + setOutlookExchangeUrl?: (url: string, userId: string) => Promise<void>; + hasOutlookCredentials?: () => Promise<boolean>; + clearOutlookCredentials?: () => void; + } + | undefined; +} diff --git a/apps/meteor/client/main.ts b/apps/meteor/client/main.ts index e9dec89db3af6..ed98c06c32975 100644 --- a/apps/meteor/client/main.ts +++ b/apps/meteor/client/main.ts @@ -13,3 +13,4 @@ import './views/admin'; import './views/marketplace'; import './views/account'; import './views/teams'; +import './views/outlookCalendar'; diff --git a/apps/meteor/client/providers/VideoConfProvider.tsx b/apps/meteor/client/providers/VideoConfProvider.tsx index 9410e12c3ae58..f642ab0f24158 100644 --- a/apps/meteor/client/providers/VideoConfProvider.tsx +++ b/apps/meteor/client/providers/VideoConfProvider.tsx @@ -8,11 +8,11 @@ import { VideoConfContext } from '../contexts/VideoConfContext'; import type { DirectCallParams, ProviderCapabilities, CallPreferences } from '../lib/VideoConfManager'; import { VideoConfManager } from '../lib/VideoConfManager'; import VideoConfPopups from '../views/room/contextualBar/VideoConference/VideoConfPopups'; -import { useVideoOpenCall } from '../views/room/contextualBar/VideoConference/hooks/useVideoConfOpenCall'; +import { useVideoConfOpenCall } from '../views/room/contextualBar/VideoConference/hooks/useVideoConfOpenCall'; const VideoConfContextProvider = ({ children }: { children: ReactNode }): ReactElement => { const [outgoing, setOutgoing] = useState<VideoConfPopupPayload | undefined>(); - const handleOpenCall = useVideoOpenCall(); + const handleOpenCall = useVideoConfOpenCall(); useEffect( () => diff --git a/apps/meteor/client/startup/notifications/konchatNotifications.ts b/apps/meteor/client/startup/notifications/konchatNotifications.ts index 20b6ba0580595..8729ee3da2718 100644 --- a/apps/meteor/client/startup/notifications/konchatNotifications.ts +++ b/apps/meteor/client/startup/notifications/konchatNotifications.ts @@ -1,17 +1,22 @@ -import type { AtLeast, ISubscription, IUser } from '@rocket.chat/core-typings'; +import type { AtLeast, ISubscription, IUser, ICalendarNotification } from '@rocket.chat/core-typings'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; +import { lazy } from 'react'; import { CachedChatSubscription } from '../../../app/models/client'; import { Notifications } from '../../../app/notifications/client'; +import { settings } from '../../../app/settings/client'; import { readMessage } from '../../../app/ui-utils/client'; import { KonchatNotification } from '../../../app/ui/client/lib/KonchatNotification'; import { getUserPreference } from '../../../app/utils/client'; import { RoomManager } from '../../lib/RoomManager'; +import { imperativeModal } from '../../lib/imperativeModal'; import { fireGlobalEvent } from '../../lib/utils/fireGlobalEvent'; import { isLayoutEmbedded } from '../../lib/utils/isLayoutEmbedded'; +const OutlookCalendarEventModal = lazy(() => import('../../views/outlookCalendar/OutlookCalendarEventModal')); + const notifyNewRoom = async (sub: AtLeast<ISubscription, 'rid'>): Promise<void> => { const user = Meteor.user() as IUser | null; if (!user || user.status === 'busy') { @@ -41,11 +46,42 @@ function notifyNewMessageAudio(rid?: string): void { } Meteor.startup(() => { + const notifyUserCalendar = async function (notification: ICalendarNotification): Promise<void> { + const user = Meteor.user() as IUser | null; + if (!user || user.status === 'busy') { + return; + } + + const requireInteraction = getUserPreference<boolean>(Meteor.userId(), 'desktopNotificationRequireInteraction'); + + const n = new Notification(notification.title, { + body: notification.text, + tag: notification.payload._id, + silent: true, + requireInteraction, + } as NotificationOptions); + + n.onclick = function () { + this.close(); + window.focus(); + imperativeModal.open({ + component: OutlookCalendarEventModal, + props: { id: notification.payload._id, onClose: imperativeModal.close, onCancel: imperativeModal.close }, + }); + }; + }; + Tracker.autorun(() => { + if (!Meteor.userId() || !settings.get('Outlook_Calendar_Enabled')) { + return Notifications.unUser('calendar'); + } + + Notifications.onUser('calendar', notifyUserCalendar); + }); + Tracker.autorun(() => { if (!Meteor.userId()) { return; } - Notifications.onUser('notification', (notification) => { const openedRoomId = ['channel', 'group', 'direct'].includes(FlowRouter.getRouteName()) ? RoomManager.opened : undefined; diff --git a/apps/meteor/client/views/account/preferences/AccountPreferencesPage.tsx b/apps/meteor/client/views/account/preferences/AccountPreferencesPage.tsx index 115b32ebc57b0..9176dc7732859 100644 --- a/apps/meteor/client/views/account/preferences/AccountPreferencesPage.tsx +++ b/apps/meteor/client/views/account/preferences/AccountPreferencesPage.tsx @@ -46,6 +46,7 @@ type CurrentData = { muteFocusedConversations: boolean; receiveLoginDetectionEmail: boolean; dontAskAgainList: [action: string, label: string][]; + notifyCalendarEvents: boolean; }; export type FormSectionProps = { diff --git a/apps/meteor/client/views/account/preferences/PreferencesNotificationsSection.tsx b/apps/meteor/client/views/account/preferences/PreferencesNotificationsSection.tsx index fc4c174b90b24..6801794169dd0 100644 --- a/apps/meteor/client/views/account/preferences/PreferencesNotificationsSection.tsx +++ b/apps/meteor/client/views/account/preferences/PreferencesNotificationsSection.tsx @@ -30,6 +30,7 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }: Form const userMobileNotifications = useUserPreference('pushNotifications'); const userEmailNotificationMode = useUserPreference('emailNotificationMode') as keyof typeof emailNotificationOptionsLabelMap; const userReceiveLoginDetectionEmail = useUserPreference('receiveLoginDetectionEmail'); + const userNotifyCalendarEvents = useUserPreference('notifyCalendarEvents'); const defaultDesktopNotifications = useSetting( 'Accounts_Default_User_Preferences_desktopNotifications', @@ -42,6 +43,7 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }: Form const loginEmailEnabled = useSetting('Device_Management_Enable_Login_Emails'); const allowLoginEmailPreference = useSetting('Device_Management_Allow_Login_Email_preference'); const showNewLoginEmailPreference = loginEmailEnabled && allowLoginEmailPreference; + const showCalendarPreference = useSetting('Outlook_Calendar_Enabled'); const { values, handlers, commit } = useForm( { @@ -50,6 +52,7 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }: Form pushNotifications: userMobileNotifications, emailNotificationMode: userEmailNotificationMode, receiveLoginDetectionEmail: userReceiveLoginDetectionEmail, + notifyCalendarEvents: userNotifyCalendarEvents, }, onChange, ); @@ -60,12 +63,14 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }: Form pushNotifications, emailNotificationMode, receiveLoginDetectionEmail, + notifyCalendarEvents, } = values as { desktopNotificationRequireInteraction: boolean; desktopNotifications: string; pushNotifications: string; emailNotificationMode: string; receiveLoginDetectionEmail: boolean; + notifyCalendarEvents: boolean; }; const { @@ -74,6 +79,7 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }: Form handlePushNotifications, handleEmailNotificationMode, handleReceiveLoginDetectionEmail, + handleNotifyCalendarEvents, } = handlers; useEffect(() => setNotificationsPermission(window.Notification && Notification.permission), []); @@ -186,6 +192,17 @@ const PreferencesNotificationsSection = ({ onChange, commitRef, ...props }: Form <Field.Hint>{t('Receive_Login_Detection_Emails_Description')}</Field.Hint> </Field> )} + + {showCalendarPreference && ( + <Field> + <Box display='flex' flexDirection='row' justifyContent='space-between' flexGrow={1}> + <Field.Label>{t('Notify_Calendar_Events')}</Field.Label> + <Field.Row> + <ToggleSwitch checked={notifyCalendarEvents} onChange={handleNotifyCalendarEvents} /> + </Field.Row> + </Box> + </Field> + )} </FieldGroup> </Accordion.Item> ); diff --git a/apps/meteor/client/views/conference/ConferencePage.tsx b/apps/meteor/client/views/conference/ConferencePage.tsx index bfc648f043679..b95106efca39b 100644 --- a/apps/meteor/client/views/conference/ConferencePage.tsx +++ b/apps/meteor/client/views/conference/ConferencePage.tsx @@ -3,7 +3,7 @@ import type { ReactElement } from 'react'; import React, { useEffect } from 'react'; import { useUserDisplayName } from '../../hooks/useUserDisplayName'; -import { useVideoOpenCall } from '../room/contextualBar/VideoConference/hooks/useVideoConfOpenCall'; +import { useVideoConfOpenCall } from '../room/contextualBar/VideoConference/hooks/useVideoConfOpenCall'; import PageLoading from '../root/PageLoading'; import ConferencePageError from './ConferencePageError'; @@ -19,7 +19,7 @@ const ConferencePage = (): ReactElement => { const user = useUser(); const defaultRoute = useRoute('/'); const setModal = useSetModal(); - const handleOpenCall = useVideoOpenCall(); + const handleOpenCall = useVideoConfOpenCall(); const userDisplayName = useUserDisplayName({ name: user?.name, username: user?.username }); const { callUrlParam } = getQueryParams(); diff --git a/apps/meteor/client/views/hooks/useImperativeModal.ts b/apps/meteor/client/views/hooks/useImperativeModal.tsx similarity index 57% rename from apps/meteor/client/views/hooks/useImperativeModal.ts rename to apps/meteor/client/views/hooks/useImperativeModal.tsx index 2cc193f2cf156..b2bcf233d45b2 100644 --- a/apps/meteor/client/views/hooks/useImperativeModal.ts +++ b/apps/meteor/client/views/hooks/useImperativeModal.tsx @@ -1,23 +1,24 @@ import type { Dispatch, SetStateAction, ReactNode } from 'react'; -import { createElement, useEffect } from 'react'; +import React, { Suspense, createElement, useEffect } from 'react'; import { imperativeModal } from '../../lib/imperativeModal'; export const useImperativeModal = (setModal: Dispatch<SetStateAction<ReactNode>>): void => { useEffect(() => { - const unsub = imperativeModal.on('update', (descriptor) => { + return imperativeModal.on('update', (descriptor) => { if (descriptor === null) { return setModal(null); } if ('component' in descriptor) { setModal( - createElement(descriptor.component, { - key: Math.random(), - ...descriptor.props, - }), + <Suspense fallback={<div />}> + {createElement(descriptor.component, { + key: Math.random(), + ...descriptor.props, + })} + </Suspense>, ); } }); - return unsub; }, [setModal]); }; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookCalendarEventModal.tsx b/apps/meteor/client/views/outlookCalendar/OutlookCalendarEventModal.tsx new file mode 100644 index 0000000000000..980382717607d --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookCalendarEventModal.tsx @@ -0,0 +1,53 @@ +import { useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import type { ComponentProps } from 'react'; +import React from 'react'; + +import GenericModal from '../../components/GenericModal'; +import GenericModalSkeleton from '../../components/GenericModal/GenericModalSkeleton'; +import OutlookEventItemContent from './OutlookEventsList/OutlookEventItemContent'; +import { useOutlookOpenCall } from './hooks/useOutlookOpenCall'; + +type OutlookCalendarEventModalProps = ComponentProps<typeof GenericModal> & { + id?: string; + subject?: string; + meetingUrl?: string; + description?: string; +}; + +const OutlookCalendarEventModal = ({ id, subject, meetingUrl, description, ...props }: OutlookCalendarEventModalProps) => { + const t = useTranslation(); + const calendarInfoEndpoint = useEndpoint('GET', '/v1/calendar-events.info'); + + const { data, isLoading } = useQuery(['calendar-events.info', id], async () => { + if (!id) { + const event = { event: { subject, meetingUrl, description } }; + return event; + } + + return calendarInfoEndpoint({ id }); + }); + + const openCall = useOutlookOpenCall(data?.event.meetingUrl); + + if (isLoading) { + return <GenericModalSkeleton {...props} />; + } + + return ( + <GenericModal + {...props} + tagline={t('Outlook_calendar_event')} + icon={null} + variant='warning' + title={data?.event.subject} + cancelText={t('Close')} + confirmText={t('Join_call')} + onConfirm={openCall} + > + {data?.event.description ? <OutlookEventItemContent html={data?.event.description} /> : t('No_content_was_provided')} + </GenericModal> + ); +}; + +export default OutlookCalendarEventModal; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventItem.tsx b/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventItem.tsx new file mode 100644 index 0000000000000..f39ef14bf3f31 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventItem.tsx @@ -0,0 +1,67 @@ +import type { ICalendarEvent, Serialized } from '@rocket.chat/core-typings'; +import { css } from '@rocket.chat/css-in-js'; +import { Box, Button, Palette } from '@rocket.chat/fuselage'; +import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; +import OutlookCalendarEventModal from '../OutlookCalendarEventModal'; +import { useOutlookOpenCall } from '../hooks/useOutlookOpenCall'; + +const OutlookEventItem = ({ subject, description, startTime, meetingUrl }: Serialized<ICalendarEvent>) => { + const t = useTranslation(); + const setModal = useSetModal(); + const formatDateAndTime = useFormatDateAndTime(); + const openCall = useOutlookOpenCall(meetingUrl); + + const hovered = css` + &:hover { + cursor: pointer; + } + + &:hover, + &:focus { + background: ${Palette.surface['surface-hover']}; + } + `; + + const handleOpenEvent = () => { + setModal( + <OutlookCalendarEventModal + onClose={() => setModal(null)} + onCancel={() => setModal(null)} + subject={subject} + meetingUrl={meetingUrl} + description={description} + />, + ); + }; + + return ( + <Box + className={hovered} + borderBlockEndWidth={1} + borderBlockEndColor='stroke-extra-light' + borderBlockEndStyle='solid' + pi='x24' + pb='x16' + display='flex' + justifyContent='space-between' + onClick={handleOpenEvent} + > + <Box> + <Box fontScale='h4'>{subject}</Box> + <Box fontScale='c1'>{formatDateAndTime(startTime)}</Box> + </Box> + <Box> + {meetingUrl && ( + <Button onClick={openCall} small> + {t('Join')} + </Button> + )} + </Box> + </Box> + ); +}; + +export default OutlookEventItem; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventItemContent.tsx b/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventItemContent.tsx new file mode 100644 index 0000000000000..33bef064d7b7a --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventItemContent.tsx @@ -0,0 +1,25 @@ +import { Box } from '@rocket.chat/fuselage'; +import DOMPurify from 'dompurify'; +import React from 'react'; + +type SanitizeProps = { + html: string; + options?: { + [key: string]: string; + }; +}; + +const OutlookEventItemContent = ({ html, options }: SanitizeProps) => { + const defaultOptions = { + ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'br'], + ALLOWED_ATTR: ['href'], + }; + + const sanitize = (dirtyHTML: SanitizeProps['html'], options: SanitizeProps['options']) => ({ + __html: DOMPurify.sanitize(dirtyHTML, { ...defaultOptions, ...options }).toString(), + }); + + return <Box wordBreak='break-word' color='default' dangerouslySetInnerHTML={sanitize(html, options)} />; +}; + +export default OutlookEventItemContent; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventsList.tsx b/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventsList.tsx new file mode 100644 index 0000000000000..d6788f0020f8e --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookEventsList/OutlookEventsList.tsx @@ -0,0 +1,139 @@ +import { Box, States, StatesIcon, StatesTitle, StatesSubtitle, ButtonGroup, Button, Icon } from '@rocket.chat/fuselage'; +import { useResizeObserver } from '@rocket.chat/fuselage-hooks'; +import { useTranslation, useSetting } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React from 'react'; +import { Virtuoso } from 'react-virtuoso'; + +import { + ContextualbarHeader, + ContextualbarIcon, + ContextualbarTitle, + ContextualbarClose, + ContextualbarContent, + ContextualbarFooter, + ContextualbarSkeleton, +} from '../../../components/Contextualbar'; +import ScrollableContentWrapper from '../../../components/ScrollableContentWrapper'; +import { getErrorMessage } from '../../../lib/errorHandling'; +import { useOutlookAuthentication } from '../hooks/useOutlookAuthentication'; +import { useMutationOutlookCalendarSync, useOutlookCalendarListForToday } from '../hooks/useOutlookCalendarList'; +import { NotOnDesktopError } from '../lib/NotOnDesktopError'; +import OutlookEventItem from './OutlookEventItem'; + +type OutlookEventsListProps = { + onClose: () => void; + changeRoute: () => void; +}; + +const OutlookEventsList = ({ onClose, changeRoute }: OutlookEventsListProps): ReactElement => { + const t = useTranslation(); + const outlookUrl = useSetting<string>('Outlook_Calendar_Outlook_Url'); + const { authEnabled, isError, error } = useOutlookAuthentication(); + + const hasOutlookMethods = !(isError && error instanceof NotOnDesktopError); + + const syncOutlookCalendar = useMutationOutlookCalendarSync(); + + const calendarListResult = useOutlookCalendarListForToday(); + + const { ref, contentBoxSize: { inlineSize = 378, blockSize = 1 } = {} } = useResizeObserver<HTMLElement>({ + debounceDelay: 200, + }); + + if (calendarListResult.isLoading) { + return <ContextualbarSkeleton />; + } + + const calendarEvents = calendarListResult.data; + const total = calendarEvents?.length || 0; + + return ( + <> + <ContextualbarHeader> + <ContextualbarIcon name='calendar' /> + <ContextualbarTitle>{t('Outlook_calendar')}</ContextualbarTitle> + <ContextualbarClose onClick={onClose} /> + </ContextualbarHeader> + + {hasOutlookMethods && !authEnabled && total === 0 && ( + <> + <ContextualbarContent paddingInline={0} ref={ref} color='default'> + <Box display='flex' flexDirection='column' justifyContent='center' height='100%'> + <States> + <StatesIcon name='user' /> + <StatesTitle>{t('Log_in_to_sync')}</StatesTitle> + </States> + </Box> + </ContextualbarContent> + <ContextualbarFooter> + <ButtonGroup mbs='x8' stretch> + <Button primary disabled={syncOutlookCalendar.isLoading} onClick={() => syncOutlookCalendar.mutate()}> + {syncOutlookCalendar.isLoading ? t('Please_wait') : t('Login')} + </Button> + </ButtonGroup> + </ContextualbarFooter> + </> + )} + + {(authEnabled || !hasOutlookMethods) && ( + <> + <ContextualbarContent paddingInline={0} ref={ref} color='default'> + {(total === 0 || calendarListResult.isError) && ( + <Box display='flex' flexDirection='column' justifyContent='center' height='100%'> + {calendarListResult.isError && ( + <States> + <StatesIcon name='circle-exclamation' variation='danger' /> + <StatesTitle>{t('Something_went_wrong')}</StatesTitle> + <StatesSubtitle>{getErrorMessage(calendarListResult.error)}</StatesSubtitle> + </States> + )} + {!calendarListResult.isError && total === 0 && ( + <States> + <StatesIcon name='calendar' /> + <StatesTitle>{t('No_history')}</StatesTitle> + </States> + )} + </Box> + )} + {calendarListResult.isSuccess && calendarListResult.data.length > 0 && ( + <Box flexGrow={1} flexShrink={1} overflow='hidden' display='flex'> + <Virtuoso + style={{ + height: blockSize, + width: inlineSize, + }} + totalCount={total} + overscan={25} + data={calendarEvents} + components={{ Scroller: ScrollableContentWrapper }} + itemContent={(_index, calendarData): ReactElement => <OutlookEventItem {...calendarData} />} + /> + </Box> + )} + </ContextualbarContent> + <ContextualbarFooter> + <ButtonGroup stretch> + {authEnabled && <Button onClick={changeRoute}>{t('Calendar_settings')}</Button>} + {outlookUrl && ( + <Button onClick={() => window.open(outlookUrl, '_blank')}> + <Icon mie='x4' name='new-window' /> + <Box is='span'>{t('Open_Outlook')}</Box> + </Button> + )} + </ButtonGroup> + {hasOutlookMethods && ( + <ButtonGroup mbs='x8' stretch> + <Button primary disabled={syncOutlookCalendar.isLoading} onClick={() => syncOutlookCalendar.mutate()}> + {syncOutlookCalendar.isLoading ? t('Sync_in_progress') : t('Sync')} + </Button> + </ButtonGroup> + )} + </ContextualbarFooter> + </> + )} + </> + ); +}; + +export default OutlookEventsList; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookEventsList/index.ts b/apps/meteor/client/views/outlookCalendar/OutlookEventsList/index.ts new file mode 100644 index 0000000000000..5f926dd78ce0b --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookEventsList/index.ts @@ -0,0 +1 @@ +export { default } from './OutlookEventsList'; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookEventsRoute.tsx b/apps/meteor/client/views/outlookCalendar/OutlookEventsRoute.tsx new file mode 100644 index 0000000000000..ce73e326f02dd --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookEventsRoute.tsx @@ -0,0 +1,25 @@ +import React, { useState } from 'react'; + +import { useTabBarClose } from '../room/contexts/ToolboxContext'; +import OutlookEventsList from './OutlookEventsList'; +import OutlookSettingsList from './OutlookSettingsList'; + +type OutlookCalendarRoutes = 'list' | 'settings'; + +const CALENDAR_ROUTES: { [key: string]: OutlookCalendarRoutes } = { + LIST: 'list', + SETTINGS: 'settings', +}; + +const OutlookEventsRoute = () => { + const closeTabBar = useTabBarClose(); + const [calendarRoute, setCalendarRoute] = useState<OutlookCalendarRoutes>('list'); + + if (calendarRoute === CALENDAR_ROUTES.SETTINGS) { + return <OutlookSettingsList onClose={closeTabBar} changeRoute={() => setCalendarRoute(CALENDAR_ROUTES.LIST)} />; + } + + return <OutlookEventsList onClose={closeTabBar} changeRoute={() => setCalendarRoute(CALENDAR_ROUTES.SETTINGS)} />; +}; + +export default OutlookEventsRoute; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/OutlookSettingItem.tsx b/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/OutlookSettingItem.tsx new file mode 100644 index 0000000000000..f2b01622c5dfd --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/OutlookSettingItem.tsx @@ -0,0 +1,58 @@ +import { css } from '@rocket.chat/css-in-js'; +import { Box, Button, Palette } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +type OutlookSettingItemProps = { + id: string; + title: string; + subTitle: string; + enabled: boolean; + handleEnable: (value: boolean) => void; +}; + +const OutlookSettingItem = ({ id, title, subTitle, enabled, handleEnable }: OutlookSettingItemProps) => { + const t = useTranslation(); + + const hovered = css` + &:hover, + &:focus { + background: ${Palette.surface['surface-hover']}; + .rcx-message { + background: ${Palette.surface['surface-hover']}; + } + } + `; + + return ( + <Box + borderBlockEndWidth={1} + borderBlockEndColor='stroke-extra-light' + borderBlockEndStyle='solid' + className={hovered} + pi='x24' + pb='x16' + display='flex' + justifyContent='space-between' + > + <Box mie='x8'> + <Box fontScale='h4'>{title}</Box> + <Box fontScale='p2'>{subTitle}</Box> + </Box> + <Box> + {id === 'authentication' && ( + <Button small onClick={() => handleEnable(!enabled)}> + {t('Disable')} + </Button> + )} + {id !== 'authentication' && ( + <Button primary={!enabled} small onClick={() => handleEnable(!enabled)}> + {enabled ? t('Disable') : t('Enable')} + </Button> + )} + </Box> + </Box> + ); +}; + +export default OutlookSettingItem; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/OutlookSettingsList.tsx b/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/OutlookSettingsList.tsx new file mode 100644 index 0000000000000..3ceb891d7f9d1 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/OutlookSettingsList.tsx @@ -0,0 +1,87 @@ +import { ButtonGroup, Button } from '@rocket.chat/fuselage'; +import { useTranslation, useUserPreference, useEndpoint, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import type { ReactElement } from 'react'; +import React, { useCallback } from 'react'; + +import { + ContextualbarHeader, + ContextualbarIcon, + ContextualbarTitle, + ContextualbarClose, + ContextualbarContent, + ContextualbarFooter, +} from '../../../components/Contextualbar'; +import { useOutlookAuthentication, useOutlookAuthenticationMutationLogout } from '../hooks/useOutlookAuthentication'; +import OutlookSettingItem from './OutlookSettingItem'; + +type OutlookSettingsListProps = { + onClose: () => void; + changeRoute: () => void; +}; + +const OutlookSettingsList = ({ onClose, changeRoute }: OutlookSettingsListProps): ReactElement => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); + const notifyCalendarEvents = useUserPreference('notifyCalendarEvents') as boolean; + const { authEnabled } = useOutlookAuthentication(); + const handleDisableAuth = useOutlookAuthenticationMutationLogout(); + + const handleNotifyCalendarEvents = useCallback( + (value: boolean) => { + try { + saveUserPreferences({ data: { notifyCalendarEvents: value } }); + dispatchToastMessage({ type: 'success', message: t('Preferences_saved') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + }, + [saveUserPreferences, dispatchToastMessage, t], + ); + + const calendarSettings = [ + { + id: 'notification', + title: t('Event_notifications'), + subTitle: t('Event_notifications_description'), + enabled: notifyCalendarEvents, + handleEnable: handleNotifyCalendarEvents, + }, + { + id: 'authentication', + title: t('Outlook_authentication'), + subTitle: t('Outlook_authentication_description'), + enabled: authEnabled, + handleEnable: () => + handleDisableAuth.mutate(undefined, { + onSuccess: changeRoute, + }), + }, + ]; + + return ( + <> + <ContextualbarHeader> + <ContextualbarIcon name='calendar' /> + <ContextualbarTitle>{t('Outlook_calendar_settings')}</ContextualbarTitle> + <ContextualbarClose onClick={onClose} /> + </ContextualbarHeader> + <ContextualbarContent paddingInline={0} color='default'> + {calendarSettings.map((setting, index) => { + if (setting.id === 'authentication' && !setting.enabled) { + return; + } + + return <OutlookSettingItem key={index} {...setting} />; + })} + </ContextualbarContent> + <ContextualbarFooter> + <ButtonGroup stretch> + <Button onClick={changeRoute}>{t('Back_to_calendar')}</Button> + </ButtonGroup> + </ContextualbarFooter> + </> + ); +}; + +export default OutlookSettingsList; diff --git a/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/index.ts b/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/index.ts new file mode 100644 index 0000000000000..dc0d56237ce99 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/OutlookSettingsList/index.ts @@ -0,0 +1 @@ +export { default } from './OutlookSettingsList'; diff --git a/apps/meteor/client/views/outlookCalendar/hooks/useOutlookAuthentication.ts b/apps/meteor/client/views/outlookCalendar/hooks/useOutlookAuthentication.ts new file mode 100644 index 0000000000000..d671c051f8dd1 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/hooks/useOutlookAuthentication.ts @@ -0,0 +1,59 @@ +import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +import { NotOnDesktopError } from '../lib/NotOnDesktopError'; + +export const useOutlookAuthentication = () => { + const { + data: authEnabled, + isError, + error, + } = useQuery( + ['outlook', 'auth'], + async () => { + const desktopApp = window.RocketChatDesktop; + if (!desktopApp?.hasOutlookCredentials) { + throw new NotOnDesktopError(); + } + + return Boolean(await desktopApp?.hasOutlookCredentials?.()) || false; + }, + { + onError: (error) => { + console.error(error); + }, + }, + ); + + return { authEnabled: Boolean(authEnabled), isError, error }; +}; + +export const useOutlookAuthenticationMutation = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async () => { + await queryClient.invalidateQueries(['outlook', 'auth']); + }, + }); +}; + +export const useOutlookAuthenticationMutationLogout = () => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const mutation = useOutlookAuthenticationMutation(); + return useMutation({ + mutationFn: async () => { + const desktopApp = window.RocketChatDesktop; + if (!desktopApp?.clearOutlookCredentials) { + throw new NotOnDesktopError(); + } + + await desktopApp.clearOutlookCredentials(); + + return mutation.mutateAsync(); + }, + onSuccess: () => { + dispatchToastMessage({ type: 'success', message: t('Outlook_authentication_disabled') }); + }, + }); +}; diff --git a/apps/meteor/client/views/outlookCalendar/hooks/useOutlookCalendarList.ts b/apps/meteor/client/views/outlookCalendar/hooks/useOutlookCalendarList.ts new file mode 100644 index 0000000000000..0b2c6370d9fa8 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/hooks/useOutlookCalendarList.ts @@ -0,0 +1,53 @@ +import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + +import { syncOutlookEvents } from '../lib/syncOutlookEvents'; +import { useOutlookAuthenticationMutation } from './useOutlookAuthentication'; + +export const useOutlookCalendarListForToday = () => { + return useOutlookCalendarList(new Date()); +}; + +export const useOutlookCalendarList = (date: Date) => { + const calendarData = useEndpoint('GET', '/v1/calendar-events.list'); + + return useQuery( + ['outlook', 'calendar', 'list'], + async () => { + const { data } = await calendarData({ date: date.toISOString() }); + return data; + }, + { + refetchOnWindowFocus: false, + }, + ); +}; + +export const useMutationOutlookCalendarSync = () => { + const t = useTranslation(); + const queryClient = useQueryClient(); + + const checkOutlookCredentials = useOutlookAuthenticationMutation(); + + const dispatchToastMessage = useToastMessageDispatch(); + + const syncMutation = useMutation({ + mutationFn: async () => { + await syncOutlookEvents(); + + await queryClient.invalidateQueries(['outlook', 'calendar', 'list']); + + await checkOutlookCredentials.mutateAsync(); + }, + onSuccess: () => { + dispatchToastMessage({ type: 'success', message: t('Outlook_Sync_Success') }); + }, + onError: (error) => { + if (error instanceof Error && error.message === 'abort') { + return; + } + dispatchToastMessage({ type: 'error', message: t('Outlook_Sync_Failed') }); + }, + }); + return syncMutation; +}; diff --git a/apps/meteor/client/views/outlookCalendar/hooks/useOutlookOpenCall.ts b/apps/meteor/client/views/outlookCalendar/hooks/useOutlookOpenCall.ts new file mode 100644 index 0000000000000..80269ec04b5dd --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/hooks/useOutlookOpenCall.ts @@ -0,0 +1,18 @@ +import { useUser } from '@rocket.chat/ui-contexts'; + +import { useUserDisplayName } from '../../../hooks/useUserDisplayName'; +import { useVideoConfOpenCall } from '../../room/contextualBar/VideoConference/hooks/useVideoConfOpenCall'; + +export const useOutlookOpenCall = (meetingUrl?: string) => { + const user = useUser(); + const handleOpenCall = useVideoConfOpenCall(); + const userDisplayName = useUserDisplayName({ name: user?.name, username: user?.username }); + + const namedMeetingUrl = `${meetingUrl}&name=${userDisplayName}`; + + if (!meetingUrl) { + return; + } + + return () => handleOpenCall(namedMeetingUrl); +}; diff --git a/apps/meteor/client/views/outlookCalendar/index.ts b/apps/meteor/client/views/outlookCalendar/index.ts new file mode 100644 index 0000000000000..cd95da0a7d921 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/index.ts @@ -0,0 +1 @@ +import './tabBar'; diff --git a/apps/meteor/client/views/outlookCalendar/lib/NotOnDesktopError.ts b/apps/meteor/client/views/outlookCalendar/lib/NotOnDesktopError.ts new file mode 100644 index 0000000000000..83a2114657c77 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/lib/NotOnDesktopError.ts @@ -0,0 +1,5 @@ +export class NotOnDesktopError extends Error { + constructor() { + super('Not on desktop'); + } +} diff --git a/apps/meteor/client/views/outlookCalendar/lib/syncOutlookEvents.ts b/apps/meteor/client/views/outlookCalendar/lib/syncOutlookEvents.ts new file mode 100644 index 0000000000000..dc2d55696eb26 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/lib/syncOutlookEvents.ts @@ -0,0 +1,15 @@ +import { NotOnDesktopError } from './NotOnDesktopError'; + +export const syncOutlookEvents = async () => { + const date = new Date(); + const desktopApp = window.RocketChatDesktop; + + if (!desktopApp?.getOutlookEvents) { + throw new NotOnDesktopError(); + } + + const response = await desktopApp.getOutlookEvents(date); + if (response.status === 'canceled') { + throw new Error('abort'); + } +}; diff --git a/apps/meteor/client/views/outlookCalendar/tabBar.ts b/apps/meteor/client/views/outlookCalendar/tabBar.ts new file mode 100644 index 0000000000000..89943c8c08c74 --- /dev/null +++ b/apps/meteor/client/views/outlookCalendar/tabBar.ts @@ -0,0 +1,23 @@ +import { useSetting } from '@rocket.chat/ui-contexts'; +import { lazy, useMemo } from 'react'; + +import { addAction } from '../room/lib/Toolbox'; + +addAction('outlookCalendar', () => { + const outlookCalendarEnabled = useSetting('Outlook_Calendar_Enabled'); + + return useMemo( + () => + outlookCalendarEnabled + ? { + groups: ['channel', 'group', 'team'], + id: 'outlookCalendar', + icon: 'calendar', + title: 'Outlook_calendar', + template: lazy(() => import('./OutlookEventsRoute')), + order: 999, + } + : null, + [outlookCalendarEnabled], + ); +}); diff --git a/apps/meteor/client/views/room/contextualBar/VideoConference/hooks/useVideoConfOpenCall.tsx b/apps/meteor/client/views/room/contextualBar/VideoConference/hooks/useVideoConfOpenCall.tsx index 3f282ca800674..48093f9bcfd5e 100644 --- a/apps/meteor/client/views/room/contextualBar/VideoConference/hooks/useVideoConfOpenCall.tsx +++ b/apps/meteor/client/views/room/contextualBar/VideoConference/hooks/useVideoConfOpenCall.tsx @@ -3,21 +3,14 @@ import React, { useCallback } from 'react'; import VideoConfBlockModal from '../VideoConfBlockModal'; -type WindowMaybeDesktop = typeof window & { - RocketChatDesktop?: { - openInternalVideoChatWindow?: (url: string, options: undefined) => void; - }; -}; - -export const useVideoOpenCall = () => { +export const useVideoConfOpenCall = () => { const setModal = useSetModal(); const handleOpenCall = useCallback( (callUrl: string) => { - const windowMaybeDesktop = window as WindowMaybeDesktop; - if (windowMaybeDesktop.RocketChatDesktop?.openInternalVideoChatWindow) { - windowMaybeDesktop.RocketChatDesktop.openInternalVideoChatWindow(callUrl, undefined); - } else { + const desktopApp = window.RocketChatDesktop; + + if (!desktopApp?.openInternalVideoChatWindow) { const open = () => window.open(callUrl); const popup = open(); @@ -26,7 +19,10 @@ export const useVideoOpenCall = () => { } setModal(<VideoConfBlockModal onClose={(): void => setModal(null)} onConfirm={open} />); + return; } + + desktopApp.openInternalVideoChatWindow(callUrl, undefined); }, [setModal], ); diff --git a/apps/meteor/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx b/apps/meteor/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx index 7acf1a337a044..1cf2698b4834c 100644 --- a/apps/meteor/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx +++ b/apps/meteor/client/views/teams/ConvertToChannelModal/ConvertToChannelModal.tsx @@ -1,10 +1,8 @@ import type { IRoom, Serialized } from '@rocket.chat/core-typings'; -import { Skeleton } from '@rocket.chat/fuselage'; -import { useTranslation } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { useMemo } from 'react'; -import GenericModal from '../../../components/GenericModal'; +import GenericModalSkeleton from '../../../components/GenericModal/GenericModalSkeleton'; import { useEndpointData } from '../../../hooks/useEndpointData'; import { AsyncStatePhase } from '../../../lib/asyncState'; import BaseConvertToChannelModal from './BaseConvertToChannelModal'; @@ -18,18 +16,12 @@ type ConvertToChannelModalProps = { }; const ConvertToChannelModal: FC<ConvertToChannelModalProps> = ({ onClose, onCancel, onConfirm, teamId, userId }) => { - const t = useTranslation(); - const { value, phase } = useEndpointData('/v1/teams.listRoomsOfUser', { params: useMemo(() => ({ teamId, userId, canUserDelete: 'true' }), [teamId, userId]), }); if (phase === AsyncStatePhase.LOADING) { - return ( - <GenericModal variant='warning' onClose={onClose} title={<Skeleton width='50%' />} confirmText={t('Cancel')} onConfirm={onClose}> - <Skeleton width='full' /> - </GenericModal> - ); + return <GenericModalSkeleton onClose={onClose} />; } return <BaseConvertToChannelModal onClose={onClose} onCancel={onCancel} onConfirm={onConfirm} rooms={value?.rooms} />; diff --git a/apps/meteor/ee/app/license/server/bundles.ts b/apps/meteor/ee/app/license/server/bundles.ts index 8eb80d4b8f019..507283b3e60fd 100644 --- a/apps/meteor/ee/app/license/server/bundles.ts +++ b/apps/meteor/ee/app/license/server/bundles.ts @@ -14,7 +14,8 @@ export type BundleFeature = | 'oauth-enterprise' | 'federation' | 'videoconference-enterprise' - | 'message-read-receipt'; + | 'message-read-receipt' + | 'outlook-calendar'; interface IBundle { [key: string]: BundleFeature[]; @@ -38,6 +39,7 @@ const bundles: IBundle = { 'federation', 'videoconference-enterprise', 'message-read-receipt', + 'outlook-calendar', ], pro: [], }; diff --git a/apps/meteor/ee/server/configuration/index.ts b/apps/meteor/ee/server/configuration/index.ts index badf718504ecb..9a7738b23e4a4 100644 --- a/apps/meteor/ee/server/configuration/index.ts +++ b/apps/meteor/ee/server/configuration/index.ts @@ -1,4 +1,5 @@ import './ldap'; import './oauth'; +import './outlookCalendar'; import './saml'; import './videoConference'; diff --git a/apps/meteor/ee/server/configuration/outlookCalendar.ts b/apps/meteor/ee/server/configuration/outlookCalendar.ts new file mode 100644 index 0000000000000..15363ea9e5ae2 --- /dev/null +++ b/apps/meteor/ee/server/configuration/outlookCalendar.ts @@ -0,0 +1,13 @@ +import { Meteor } from 'meteor/meteor'; +import { Calendar } from '@rocket.chat/core-services'; + +import { onLicense } from '../../app/license/server'; +import { addSettings } from '../settings/outlookCalendar'; + +Meteor.startup(() => + onLicense('outlook-calendar', async () => { + addSettings(); + + await Calendar.setupNextNotification(); + }), +); diff --git a/apps/meteor/ee/server/settings/outlookCalendar.ts b/apps/meteor/ee/server/settings/outlookCalendar.ts new file mode 100644 index 0000000000000..02dd51f795108 --- /dev/null +++ b/apps/meteor/ee/server/settings/outlookCalendar.ts @@ -0,0 +1,41 @@ +import { settingsRegistry } from '../../../app/settings/server'; + +export function addSettings(): void { + void settingsRegistry.addGroup('Outlook_Calendar', async function () { + await this.with( + { + enterprise: true, + modules: ['outlook-calendar'], + }, + async function () { + await this.add('Outlook_Calendar_Enabled', false, { + type: 'boolean', + public: true, + invalidValue: false, + }); + + await this.add('Outlook_Calendar_Exchange_Url', '', { + type: 'string', + public: true, + invalidValue: '', + }); + + await this.add('Outlook_Calendar_Outlook_Url', '', { + type: 'string', + public: true, + invalidValue: '', + }); + + await this.add( + 'Calendar_MeetingUrl_Regex', + '(?:[?&]callUrl=([^\n&<]+))|(?:(?:%3F)|(?:%26))callUrl(?:%3D)((?:(?:[^\n&<](?!%26)))+[^\n&<]?)', + { + type: 'string', + public: true, + invalidValue: '', + }, + ); + }, + ); + }); +} diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index 60b02e3ca684c..ec6c27d3799bb 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -727,6 +727,7 @@ "Away": "Away", "Back": "Back", "Back_to_applications": "Back to applications", + "Back_to_calendar": "Back to calendar", "Back_to_chat": "Back to chat", "Back_to_imports": "Back to imports", "Back_to_integration_detail": "Back to the integration detail", @@ -817,6 +818,9 @@ "By": "By", "by": "by", "cache_cleared": "Cache cleared", + "Calendar_MeetingUrl_Regex": "Meeting url Regular Expression", + "Calendar_MeetingUrl_Regex_Description": "Expression used to detect meeting URLs in event descriptions. The first matching group with a valid url will be used. HTML encoded urls will be decoded automatically.", + "Calendar_settings": "Calendar settings", "Call": "Call", "Call_again": "Call again", "Call_back": "Call back", @@ -2072,6 +2076,8 @@ "Esc_to": "Esc to", "Estimated_wait_time": "Estimated wait time", "Estimated_wait_time_in_minutes": "Estimated wait time (time in minutes)", + "Event_notifications": "Event notifications", + "Event_notifications_description": "By disabling this setting you’ll prevent the app from notifying you of upcoming events.", "Event_Trigger": "Event Trigger", "Event_Trigger_Description": "Select which type of event will trigger this Outgoing WebHook Integration", "every_5_minutes": "Once every 5 minutes", @@ -3156,6 +3162,7 @@ "Logged_Out_Banner_Text": "Your workspace admin ended your session on this device. Please log in again to continue.", "Logged_out_of_other_clients_successfully": "Logged out of other clients successfully", "Login": "Login", + "Log_in_to_sync": "Log in to sync", "Login_Attempts": "Failed Login Attempts", "Login_Detected": "Login detected", "Logged_In_Via": "Logged in via", @@ -3612,6 +3619,7 @@ "No_Canned_Responses_Yet-description": "Use canned responses to provide quick and consistent answers to frequently asked questions.", "No_channels_in_team": "No Channels on this Team", "No_channels_yet": "You aren't part of any channels yet", + "No_content_was_provided": "No content was provided", "No_data_found": "No data found", "No_direct_messages_yet": "No Direct Messages.", "No_Discussions_found": "No discussions found", @@ -3679,6 +3687,7 @@ "Notifications_Sound_Volume": "Notifications sound volume", "Notify_active_in_this_room": "Notify active users in this room", "Notify_all_in_this_room": "Notify all in this room", + "Notify_Calendar_Events": "Notify calendar events", "Now_Its_Visible_For_Everyone": "Now it's visible for everyone", "Now_Its_Visible_Only_For_Admins": "Now it's visible only for admins", "NPS_survey_enabled": "Enable NPS Survey", @@ -3779,6 +3788,7 @@ "Open_directory": "Open directory", "Open_Livechats": "Chats in Progress", "Open_menu": "Open_menu", + "Open_Outlook": "Open Outlook", "Open_settings": "Open settings", "Open-source_conference_call_solution": "Open-source conference call solution.", "Open_thread": "Open Thread", @@ -3829,7 +3839,22 @@ "Outgoing": "Outgoing", "Outgoing_WebHook": "Outgoing WebHook", "Outgoing_WebHook_Description": "Get data out of Rocket.Chat in real-time.", + "Outlook_authentication": "Outlook authentication", + "Outlook_authentication_disabled": "Outlook authentication disabled", + + "Outlook_authentication_description": "Disable this to clear any outlook credentials stored in this machine.", + "Outlook_calendar": "Outlook calendar", + "Outlook_calendar_event": "Outlook calendar event", + "Outlook_calendar_settings": "Outlook calendar settings", + "Outlook_Calendar": "Outlook Calendar", + "Outlook_Calendar_Enabled": "Enabled", + "Outlook_Calendar_Exchange_Url": "Exchange URL", + "Outlook_Calendar_Exchange_Url_Description": "Host URL for the EWS api.", + "Outlook_Calendar_Outlook_Url": "Outlook URL", + "Outlook_Calendar_Outlook_Url_Description": "URL used to launch the Outlook web app.", "Output_format": "Output format", + "Outlook_Sync_Failed": "Failed to load outlook events.", + "Outlook_Sync_Success": "Outlook events synchronized.", "Override_URL_to_which_files_are_uploaded_This_url_also_used_for_downloads_unless_a_CDN_is_given": "Override URL to which files are uploaded. This url also used for downloads unless a CDN is given", "Override_Destination_Channel": "Allow to overwrite destination channel in the body parameters", "Owner": "Owner", @@ -4100,6 +4125,7 @@ "Reload": "Reload", "Reload_page": "Reload Page", "Reload_Pages": "Reload Pages", + "Remember_my_credentials": "Remember my credentials", "Remove": "Remove", "Remove_Admin": "Remove Admin", "Remove_Association": "Remove Association", diff --git a/apps/meteor/server/methods/saveUserPreferences.ts b/apps/meteor/server/methods/saveUserPreferences.ts index 9e974673f24a9..0cb1dc922eeaa 100644 --- a/apps/meteor/server/methods/saveUserPreferences.ts +++ b/apps/meteor/server/methods/saveUserPreferences.ts @@ -36,6 +36,7 @@ type UserPreferences = { dontAskAgainList: { action: string; label: string }[]; themeAppearence: 'auto' | 'light' | 'dark'; receiveLoginDetectionEmail: boolean; + notifyCalendarEvents: boolean; }; declare module '@rocket.chat/ui-contexts' { @@ -78,6 +79,7 @@ export const saveUserPreferences = async (settings: Partial<UserPreferences>, us muteFocusedConversations: Match.Optional(Boolean), omnichannelTranscriptEmail: Match.Optional(Boolean), omnichannelTranscriptPDF: Match.Optional(Boolean), + notifyCalendarEvents: Match.Optional(Boolean), }; check(settings, Match.ObjectIncluding(keys)); const user = await Users.findOneById(userId); diff --git a/apps/meteor/server/models/CalendarEvent.ts b/apps/meteor/server/models/CalendarEvent.ts new file mode 100644 index 0000000000000..669ecbbdb91c4 --- /dev/null +++ b/apps/meteor/server/models/CalendarEvent.ts @@ -0,0 +1,6 @@ +import { registerModel } from '@rocket.chat/models'; + +import { db } from '../database/utils'; +import { CalendarEventRaw } from './raw/CalendarEvent'; + +registerModel('ICalendarEventModel', new CalendarEventRaw(db)); diff --git a/apps/meteor/server/models/raw/CalendarEvent.ts b/apps/meteor/server/models/raw/CalendarEvent.ts new file mode 100644 index 0000000000000..7a071c77e2470 --- /dev/null +++ b/apps/meteor/server/models/raw/CalendarEvent.ts @@ -0,0 +1,124 @@ +import type { FindCursor, IndexDescription, Collection, Db, UpdateResult } from 'mongodb'; +import type { ICalendarEvent, IUser, RocketChatRecordDeleted } from '@rocket.chat/core-typings'; +import type { ICalendarEventModel } from '@rocket.chat/model-typings'; + +import { BaseRaw } from './BaseRaw'; + +export class CalendarEventRaw extends BaseRaw<ICalendarEvent> implements ICalendarEventModel { + constructor(db: Db, trash?: Collection<RocketChatRecordDeleted<ICalendarEvent>>) { + super(db, 'calendar_event', trash); + } + + protected modelIndexes(): IndexDescription[] { + return [ + { + key: { startTime: -1, uid: 1, externalId: 1 }, + }, + { + key: { reminderTime: -1, notificationSent: 1 }, + }, + ]; + } + + public async findOneByExternalIdAndUserId( + externalId: Required<ICalendarEvent>['externalId'], + uid: ICalendarEvent['uid'], + ): Promise<ICalendarEvent | null> { + return this.findOne({ + externalId, + uid, + }); + } + + public findByUserIdAndDate(uid: IUser['_id'], date: Date): FindCursor<ICalendarEvent> { + const startTime = new Date(date.toISOString()); + startTime.setHours(0, 0, 0, 0); + + const finalTime = new Date(date.valueOf()); + finalTime.setDate(finalTime.getDate() + 1); + + return this.find( + { + uid, + startTime: { $gte: startTime, $lt: finalTime }, + }, + { + sort: { startTime: 1 }, + }, + ); + } + + public async updateEvent( + eventId: ICalendarEvent['_id'], + { subject, description, startTime, meetingUrl, reminderMinutesBeforeStart, reminderTime }: Partial<ICalendarEvent>, + ): Promise<UpdateResult> { + return this.updateOne( + { _id: eventId }, + { + $set: { + ...(subject !== undefined ? { subject } : {}), + ...(description !== undefined ? { description } : {}), + ...(startTime ? { startTime } : {}), + ...(meetingUrl !== undefined ? { meetingUrl } : {}), + ...(reminderMinutesBeforeStart ? { reminderMinutesBeforeStart } : {}), + ...(reminderTime ? { reminderTime } : {}), + }, + }, + ); + } + + public async findNextNotificationDate(): Promise<Date | null> { + const nextEvent = await this.findOne<Pick<ICalendarEvent, 'reminderTime'>>( + { + reminderTime: { + $gt: new Date(), + }, + notificationSent: false, + }, + { + sort: { + reminderTime: 1, + }, + projection: { + reminderTime: 1, + }, + }, + ); + + return nextEvent?.reminderTime || null; + } + + public findEventsToNotify(notificationTime: Date, minutes: number): FindCursor<ICalendarEvent> { + // Find all the events between notificationTime and +minutes that have not been notified yet + const maxDate = new Date(notificationTime.toISOString()); + maxDate.setMinutes(maxDate.getMinutes() + minutes); + + return this.find( + { + reminderTime: { + $gte: notificationTime, + $lt: maxDate, + }, + notificationSent: false, + }, + { + sort: { + reminderTime: 1, + }, + }, + ); + } + + public async flagNotificationSent(eventId: ICalendarEvent['_id']): Promise<UpdateResult> { + return this.updateOne( + { + _id: eventId, + }, + { + $set: { + notificationSent: true, + }, + }, + ); + } +} diff --git a/apps/meteor/server/models/startup.ts b/apps/meteor/server/models/startup.ts index f9c5fa1be2a03..8e83683869acc 100644 --- a/apps/meteor/server/models/startup.ts +++ b/apps/meteor/server/models/startup.ts @@ -5,6 +5,7 @@ import './AppsPersistence'; import './Avatars'; import './Banners'; import './BannersDismiss'; +import './CalendarEvent'; import './CredentialTokens'; import './CustomSounds'; import './CustomUserStatus'; diff --git a/apps/meteor/server/modules/listeners/listeners.module.ts b/apps/meteor/server/modules/listeners/listeners.module.ts index 45d8e75952fb8..b013d5fe3b56c 100644 --- a/apps/meteor/server/modules/listeners/listeners.module.ts +++ b/apps/meteor/server/modules/listeners/listeners.module.ts @@ -391,6 +391,10 @@ export class ListenersModule { notifications.notifyAllInThisInstance('updateCustomSound', data); }); + service.onEvent('notify.calendar', (uid, data): void => { + notifications.notifyUserInThisInstance(uid, 'calendar', data); + }); + service.onEvent('connector.statuschanged', (enabled): void => { notifications.notifyLoggedInThisInstance('voip.statuschanged', enabled); }); diff --git a/apps/meteor/server/services/calendar/service.ts b/apps/meteor/server/services/calendar/service.ts new file mode 100644 index 0000000000000..bfe357e1566ac --- /dev/null +++ b/apps/meteor/server/services/calendar/service.ts @@ -0,0 +1,233 @@ +import type { UpdateResult, DeleteResult } from 'mongodb'; +import type { IUser, ICalendarEvent } from '@rocket.chat/core-typings'; +import type { InsertionModel } from '@rocket.chat/model-typings'; +import { CalendarEvent } from '@rocket.chat/models'; +import type { ICalendarService } from '@rocket.chat/core-services'; +import { ServiceClassInternal, api } from '@rocket.chat/core-services'; +import { cronJobs } from '@rocket.chat/cron'; + +import { settings } from '../../../app/settings/server'; +import { getUserPreference } from '../../../app/utils/server/lib/getUserPreference'; +import { Logger } from '../../lib/logger/Logger'; + +const logger = new Logger('Calendar'); + +const defaultMinutesForNotifications = 5; + +export class CalendarService extends ServiceClassInternal implements ICalendarService { + protected name = 'calendar'; + + public async create(data: Omit<InsertionModel<ICalendarEvent>, 'reminderTime' | 'notificationSent'>): Promise<ICalendarEvent['_id']> { + const { uid, startTime, subject, description, reminderMinutesBeforeStart, meetingUrl } = data; + + const minutes = reminderMinutesBeforeStart ?? defaultMinutesForNotifications; + const reminderTime = minutes ? this.getShiftedTime(startTime, -minutes) : undefined; + + const insertData: InsertionModel<ICalendarEvent> = { + uid, + startTime, + subject, + description, + meetingUrl, + reminderMinutesBeforeStart: minutes, + reminderTime, + notificationSent: false, + }; + + const insertResult = await CalendarEvent.insertOne(insertData); + await this.setupNextNotification(); + + return insertResult.insertedId; + } + + public async import(data: Omit<InsertionModel<ICalendarEvent>, 'notificationSent'>): Promise<ICalendarEvent['_id']> { + const { externalId } = data; + if (!externalId) { + return this.create(data); + } + + const { uid, startTime, subject, description, reminderMinutesBeforeStart } = data; + const meetingUrl = data.meetingUrl ? data.meetingUrl : await this.parseDescriptionForMeetingUrl(description); + const reminderTime = reminderMinutesBeforeStart ? this.getShiftedTime(startTime, -reminderMinutesBeforeStart) : undefined; + + const updateData: Omit<InsertionModel<ICalendarEvent>, 'uid' | 'notificationSent'> = { + startTime, + subject, + description, + meetingUrl, + reminderMinutesBeforeStart, + reminderTime, + externalId, + }; + + const event = await this.findImportedEvent(externalId, uid); + + if (!event) { + const insertResult = await CalendarEvent.insertOne({ + uid, + notificationSent: false, + ...updateData, + }); + + await this.setupNextNotification(); + return insertResult.insertedId; + } + + const updateResult = await CalendarEvent.updateEvent(event._id, updateData); + if (updateResult.modifiedCount > 0) { + await this.setupNextNotification(); + } + + return event._id; + } + + public async get(eventId: ICalendarEvent['_id']): Promise<ICalendarEvent | null> { + return CalendarEvent.findOne({ _id: eventId }); + } + + public async list(uid: IUser['_id'], date: Date): Promise<ICalendarEvent[]> { + return CalendarEvent.findByUserIdAndDate(uid, date).toArray(); + } + + public async update(eventId: ICalendarEvent['_id'], data: Partial<ICalendarEvent>): Promise<UpdateResult> { + const { startTime, subject, description, reminderMinutesBeforeStart } = data; + const meetingUrl = data.meetingUrl ? data.meetingUrl : await this.parseDescriptionForMeetingUrl(description || ''); + const reminderTime = reminderMinutesBeforeStart && startTime ? this.getShiftedTime(startTime, -reminderMinutesBeforeStart) : undefined; + + const updateData: Partial<ICalendarEvent> = { + startTime, + subject, + description, + meetingUrl, + reminderMinutesBeforeStart, + reminderTime, + }; + + const updateResult = await CalendarEvent.updateEvent(eventId, updateData); + + if (updateResult.modifiedCount > 0) { + await this.setupNextNotification(); + } + + return updateResult; + } + + public async delete(eventId: ICalendarEvent['_id']): Promise<DeleteResult> { + return CalendarEvent.deleteOne({ + _id: eventId, + }); + } + + public async setupNextNotification(): Promise<void> { + return this.doSetupNextNotification(false); + } + + private async doSetupNextNotification(isRecursive: boolean): Promise<void> { + const date = await CalendarEvent.findNextNotificationDate(); + if (!date) { + if (await cronJobs.has('calendar-reminders')) { + await cronJobs.remove('calendar-reminders'); + } + return; + } + + date.setSeconds(0); + if (!isRecursive && date.valueOf() < Date.now()) { + return this.sendCurrentNotifications(date); + } + + await cronJobs.addAtTimestamp('calendar-reminders', date, async () => this.sendCurrentNotifications(date)); + } + + public async sendCurrentNotifications(date: Date): Promise<void> { + const events = await CalendarEvent.findEventsToNotify(date, 1).toArray(); + + for await (const event of events) { + await this.sendEventNotification(event); + + await CalendarEvent.flagNotificationSent(event._id); + } + + await this.doSetupNextNotification(true); + } + + public async sendEventNotification(event: ICalendarEvent): Promise<void> { + if (!(await getUserPreference(event.uid, 'notifyCalendarEvents'))) { + return; + } + + return api.broadcast('notify.calendar', event.uid, { + title: event.subject, + text: event.startTime.toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric', dayPeriod: 'narrow' }), + payload: { + _id: event._id, + }, + }); + } + + public async findImportedEvent( + externalId: Required<ICalendarEvent>['externalId'], + uid: ICalendarEvent['uid'], + ): Promise<ICalendarEvent | null> { + return CalendarEvent.findOneByExternalIdAndUserId(externalId, uid); + } + + public async parseDescriptionForMeetingUrl(description: string): Promise<string | undefined> { + if (!description) { + return; + } + + const defaultPattern = '(?:[?&]callUrl=([^\n&<]+))|(?:(?:%3F)|(?:%26))callUrl(?:%3D)((?:(?:[^\n&<](?!%26)))+[^\n&<]?)'; + const pattern = (settings.get<string>('Calendar_MeetingUrl_Regex') || defaultPattern).trim(); + + if (!pattern) { + return; + } + + const regex: RegExp | undefined = (() => { + try { + return new RegExp(pattern, 'im'); + } catch { + logger.error('Failed to parse regular expression for meeting url.'); + } + })(); + + if (!regex) { + return; + } + + const results = description.match(regex); + if (!results) { + return; + } + + const [, ...urls] = results; + for (const encodedUrl of urls) { + if (!encodedUrl) { + continue; + } + + let url = encodedUrl; + while (!url.includes('://')) { + const decodedUrl = decodeURIComponent(url); + if (decodedUrl === url) { + break; + } + + url = decodedUrl; + } + + if (url.includes('://')) { + return url; + } + } + + return undefined; + } + + private getShiftedTime(time: Date, minutes: number): Date { + const newTime = new Date(time.valueOf()); + newTime.setMinutes(newTime.getMinutes() + minutes); + return newTime; + } +} diff --git a/apps/meteor/server/services/startup.ts b/apps/meteor/server/services/startup.ts index 004d4f25a4b41..2afc502846d0c 100644 --- a/apps/meteor/server/services/startup.ts +++ b/apps/meteor/server/services/startup.ts @@ -6,6 +6,7 @@ import { AnalyticsService } from './analytics/service'; import { AppsEngineService } from './apps-engine/service'; import { AuthorizationLivechat } from '../../app/livechat/server/roomAccessValidator.internalService'; import { BannerService } from './banner/service'; +import { CalendarService } from './calendar/service'; import { LDAPService } from './ldap/service'; import { MediaService } from './image/service'; import { MeteorService } from './meteor/service'; @@ -34,6 +35,7 @@ api.registerService(new AppsEngineService()); api.registerService(new AnalyticsService()); api.registerService(new AuthorizationLivechat()); api.registerService(new BannerService()); +api.registerService(new CalendarService()); api.registerService(new LDAPService()); api.registerService(new MediaService()); api.registerService(new MeteorService()); diff --git a/apps/meteor/server/settings/accounts.ts b/apps/meteor/server/settings/accounts.ts index 76f1c344b94f8..586795cef6387 100644 --- a/apps/meteor/server/settings/accounts.ts +++ b/apps/meteor/server/settings/accounts.ts @@ -686,6 +686,12 @@ export const createAccountSettings = () => public: true, i18nLabel: 'Omnichannel_transcript_email', }); + + await this.add('Accounts_Default_User_Preferences_notifyCalendarEvents', true, { + type: 'boolean', + public: true, + i18nLabel: 'Notify_Calendar_Events', + }); }); await this.section('Avatar', async function () { diff --git a/apps/meteor/tests/data/user.ts b/apps/meteor/tests/data/user.ts index 547809fae4d22..3587f2d495d64 100644 --- a/apps/meteor/tests/data/user.ts +++ b/apps/meteor/tests/data/user.ts @@ -30,6 +30,7 @@ export const preferences = { hideFlexTab: false, sendOnEnter: 'normal', idleTimeLimit: 3600, + notifyCalendarEvents: false, }, }; diff --git a/apps/meteor/tests/end-to-end/api/00-miscellaneous.js b/apps/meteor/tests/end-to-end/api/00-miscellaneous.js index 971ed7d8e7b86..500e3c4cd3324 100644 --- a/apps/meteor/tests/end-to-end/api/00-miscellaneous.js +++ b/apps/meteor/tests/end-to-end/api/00-miscellaneous.js @@ -173,6 +173,7 @@ describe('miscellaneous', function () { 'sidebarDisplayAvatar', 'sidebarGroupByType', 'muteFocusedConversations', + 'notifyCalendarEvents', ].filter((p) => Boolean(p)); expect(res.body).to.have.property('success', true); diff --git a/apps/meteor/tests/end-to-end/api/30-calendar.ts b/apps/meteor/tests/end-to-end/api/30-calendar.ts new file mode 100644 index 0000000000000..9074400610b4a --- /dev/null +++ b/apps/meteor/tests/end-to-end/api/30-calendar.ts @@ -0,0 +1,660 @@ +import { expect } from 'chai'; +import type { Response } from 'supertest'; + +import { createUser, login } from '../../data/users.helper'; +import { getCredentials, api, request, credentials } from '../../data/api-data.js'; +import { password } from '../../data/user'; + +describe('[Calendar Events]', function () { + this.retries(0); + + let user2: Awaited<ReturnType<typeof createUser>> | undefined; + let userCredentials: Awaited<ReturnType<typeof login>> | undefined; + + before((done) => getCredentials(done)); + + before(async () => { + user2 = await createUser(); + userCredentials = await login(user2.username, password); + }); + + describe('[/calendar-events.create]', () => { + it('should successfully create an event in the calendar', async function () { + let eventId: string | undefined; + + await request + .post(api('calendar-events.create')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Subject', + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + eventId = res.body.id; + }); + + after(async function () { + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId }); + }); + }); + + it('should fail to create an event without a start time', async function () { + await request + .post(api('calendar-events.create')) + .set(credentials) + .send({ + subject: 'Subject', + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should fail to create an event without a subject', async function () { + await request + .post(api('calendar-events.create')) + .set(credentials) + .send({ + description: 'Description', + startTime: new Date().toISOString(), + reminderMinutesBeforeStart: 10, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should fail to create an event without a description', async function () { + await request + .post(api('calendar-events.create')) + .set(credentials) + .send({ + subject: 'Subject', + startTime: new Date().toISOString(), + reminderMinutesBeforeStart: 10, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should successfully create an event without reminder information', async function () { + let eventId: string | undefined; + + await request + .post(api('calendar-events.create')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Subject', + description: 'Description', + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + eventId = res.body.id; + }); + + after(async function () { + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId }); + }); + }); + }); + + describe('[/calendar-events.list]', () => { + const testSubject = `calendar-events.list-${Date.now()}`; + const testSubject2 = `calendar-events.list-${Date.now()}`; + let eventId: string | undefined; + let eventId2: string | undefined; + let eventId3: string | undefined; + + before('create sample events', async () => { + await request + .post(api('calendar-events.create')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: testSubject, + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .then((res) => { + eventId = res.body.id; + }); + + await request + .post(api('calendar-events.create')) + .set(credentials) + .send({ + startTime: new Date(Date.now() + 48 * 60 * 60 * 1000).toISOString(), + subject: testSubject2, + description: 'Future Event', + reminderMinutesBeforeStart: 10, + }) + .then((res) => { + eventId2 = res.body.id; + }); + + await request + .post(api('calendar-events.create')) + .set(userCredentials) + .send({ + startTime: new Date().toISOString(), + subject: testSubject, + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .then((res) => { + eventId3 = res.body.id; + }); + }); + + after(async function () { + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId }); + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId: eventId2 }); + await request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId: eventId3 }); + }); + + it('should list only the events with the same date', async function () { + await request + .get(api('calendar-events.list')) + .set(credentials) + .query({ + date: new Date().toISOString(), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('data').that.is.an('array'); + + const events = res.body.data.map((event: any) => event._id); + expect(events).to.be.an('array').that.includes(eventId); + expect(events).to.not.includes(eventId2); + }); + }); + + it('should nost list events from other users', async function () { + await request + .get(api('calendar-events.list')) + .set(userCredentials) + .query({ + date: new Date().toISOString(), + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('data').that.is.an('array'); + + const events = res.body.data.map((event: any) => event._id); + expect(events).to.be.an('array').that.includes(eventId3); + expect(events).to.not.includes(eventId); + }); + }); + }); + + describe('[/calendar-events.info]', () => { + const testSubject = `calendar-events.info-${Date.now()}`; + let eventId: string | undefined; + let eventId2: string | undefined; + + before('create sample events', async () => { + await request + .post(api('calendar-events.create')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: testSubject, + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .then((res) => { + eventId = res.body.id; + }); + + await request + .post(api('calendar-events.create')) + .set(userCredentials) + .send({ + startTime: new Date().toISOString(), + subject: testSubject, + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .then((res) => { + eventId2 = res.body.id; + }); + }); + + after(async function () { + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId }); + await request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId: eventId2 }); + }); + + it('should return the event information', async function () { + await request + .get(api('calendar-events.info')) + .set(credentials) + .query({ + id: eventId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', testSubject); + }); + }); + + it('should return the event information - regular user', async function () { + await request + .get(api('calendar-events.info')) + .set(userCredentials) + .query({ + id: eventId2, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', testSubject); + }); + }); + + it('should fail when querying an invalid event', async function () { + await request + .get(api('calendar-events.info')) + .set(credentials) + .query({ + id: 'something-random', + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should fail when querying an event from another user', async function () { + await request + .get(api('calendar-events.info')) + .set(credentials) + .query({ + id: eventId2, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + }); + + describe('[/calendar-events.import]', () => { + it('should successfully import an event to the calendar', async function () { + let eventId: string | undefined; + const externalId = `calendar-events.import-${Date.now()}`; + + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Subject', + description: 'Description', + reminderMinutesBeforeStart: 10, + externalId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + eventId = res.body.id; + }); + + after(async function () { + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId }); + }); + }); + + it('should fail to import an event without an external id', async function () { + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Subject', + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should fail to import an event without a start time', async function () { + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + subject: 'Subject', + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should fail to import an event without a subject', async function () { + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + description: 'Description', + startTime: new Date().toISOString(), + reminderMinutesBeforeStart: 10, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should fail to import an event without a description', async function () { + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + subject: 'Subject', + startTime: new Date().toISOString(), + reminderMinutesBeforeStart: 10, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should successfully import an event without reminder information', async function () { + let eventId: string | undefined; + + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Subject', + description: 'Description', + externalId: `calendar-events.import-external-id-${Date.now()}`, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + eventId = res.body.id; + }); + + after(async function () { + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId }); + }); + }); + + it('should import a new event even if it was already imported by another user', async function () { + let eventId: string | undefined; + let eventId2: string | undefined; + const externalId = `calendar-events.import-${Date.now()}`; + + after(async function () { + await request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId }); + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId: eventId2 }); + }); + + await request + .post(api('calendar-events.import')) + .set(userCredentials) + .send({ + startTime: new Date().toISOString(), + subject: 'First User', + description: 'Description', + reminderMinutesBeforeStart: 10, + externalId, + }) + .then((res) => { + eventId = res.body.id; + }); + + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Second User', + description: 'Description', + reminderMinutesBeforeStart: 10, + externalId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body.id).to.not.be.equal(eventId); + eventId2 = res.body.id; + }); + + await request + .get(api('calendar-events.info')) + .set(userCredentials) + .query({ id: eventId }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'First User'); + }); + + await request + .get(api('calendar-events.info')) + .set(credentials) + .query({ id: eventId2 }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'Second User'); + }); + }); + + it('should update an event that has the same external id', async function () { + let eventId: string | undefined; + const externalId = `calendar-events.import-twice-${Date.now()}`; + + after(async function () { + await request.post(api('calendar-events.delete')).set(credentials).send({ eventId }); + }); + + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Subject', + description: 'Description', + reminderMinutesBeforeStart: 10, + externalId, + }) + .then((res) => { + eventId = res.body.id; + }); + + await request + .post(api('calendar-events.import')) + .set(credentials) + .send({ + startTime: new Date().toISOString(), + subject: 'New Subject', + description: 'New Description', + reminderMinutesBeforeStart: 15, + externalId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(async (res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('id', eventId); + }); + + await request + .get(api('calendar-events.info')) + .set(credentials) + .query({ id: eventId }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('event').that.is.an('object').with.property('subject', 'New Subject'); + }); + }); + }); + + describe('[/calendar-events.update]', () => { + const testSubject = `calendar-events.update-${Date.now()}`; + let eventId: string | undefined; + + before('create sample events', async () => { + await request + .post(api('calendar-events.create')) + .set(userCredentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Old Subject', + description: 'Old Description', + reminderMinutesBeforeStart: 10, + }) + .then((res) => { + eventId = res.body.id; + }); + }); + + after(async function () { + await request.post(api('calendar-events.delete')).set(userCredentials).send({ eventId }); + }); + + it('should update the event with the new data', async function () { + await request + .post(api('calendar-events.update')) + .set(userCredentials) + .send({ + eventId, + startTime: new Date().toISOString(), + subject: testSubject, + description: 'New Description', + reminderMinutesBeforeStart: 15, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(async (res: Response) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .get(api('calendar-events.info')) + .set(userCredentials) + .query({ id: eventId }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res: Response) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('event').that.is.an('object'); + expect(res.body.event).to.have.property('subject', testSubject); + expect(res.body.event).to.have.property('description', 'New Description'); + }); + }); + + it('should fail to update an event that doesnt exist', async function () { + await request + .post(api('calendar-events.update')) + .set(userCredentials) + .send({ + eventId: 'something-random', + startTime: new Date().toISOString(), + subject: testSubject, + description: 'New Description', + reminderMinutesBeforeStart: 15, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should fail to update an event from another user', async function () { + await request + .post(api('calendar-events.update')) + .set(credentials) + .send({ + eventId, + startTime: new Date().toISOString(), + subject: 'Another Subject', + description: 'Third Description', + reminderMinutesBeforeStart: 20, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + }); + + describe('[/calendar-events.delete]', () => { + let eventId: string | undefined; + + before('create sample events', async () => { + await request + .post(api('calendar-events.create')) + .set(userCredentials) + .send({ + startTime: new Date().toISOString(), + subject: 'Subject', + description: 'Description', + reminderMinutesBeforeStart: 10, + }) + .then((res) => { + eventId = res.body.id; + }); + }); + + it('should fail to delete an event from another user', async function () { + await request + .post(api('calendar-events.delete')) + .set(credentials) + .send({ + eventId, + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should delete the specified event', async function () { + await request + .post(api('calendar-events.delete')) + .set(userCredentials) + .send({ + eventId, + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect(async (res: Response) => { + expect(res.body).to.have.property('success', true); + }); + + await request + .get(api('calendar-events.info')) + .set(userCredentials) + .query({ id: eventId }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + + it('should fail to delete an event that doesnt exist', async function () { + await request + .post(api('calendar-events.delete')) + .set(userCredentials) + .send({ + eventId: 'something-random', + }) + .expect('Content-Type', 'application/json') + .expect(400); + }); + }); +}); diff --git a/packages/core-services/src/Events.ts b/packages/core-services/src/Events.ts index 56187608df000..838e60f84716b 100644 --- a/packages/core-services/src/Events.ts +++ b/packages/core-services/src/Events.ts @@ -27,6 +27,7 @@ import type { UserStatus, ILivechatPriority, VideoConference, + ICalendarNotification, AtLeast, ILivechatInquiryRecord, } from '@rocket.chat/core-typings'; @@ -83,6 +84,7 @@ export type EventSignatures = { ): void; 'notify.deleteCustomSound'(data: { soundData: ICustomSound }): void; 'notify.updateCustomSound'(data: { soundData: ICustomSound }): void; + 'notify.calendar'(uid: string, data: ICalendarNotification): void; 'permission.changed'(data: { clientAction: ClientAction; data: any }): void; 'room'(data: { action: string; room: Partial<IRoom> }): void; 'room.avatarUpdate'(room: Pick<IRoom, '_id' | 'avatarETag'>): void; diff --git a/packages/core-services/src/index.ts b/packages/core-services/src/index.ts index 22f7a82b39149..3352c0d3d3735 100644 --- a/packages/core-services/src/index.ts +++ b/packages/core-services/src/index.ts @@ -36,6 +36,7 @@ import type { IDeviceManagementService } from './types/IDeviceManagementService' import type { IPushService } from './types/IPushService'; import type { IOmnichannelService } from './types/IOmnichannelService'; import type { ITelemetryEvent, TelemetryMap, TelemetryEvents } from './types/ITelemetryEvent'; +import type { ICalendarService } from './types/ICalendarService'; import type { IOmnichannelTranscriptService } from './types/IOmnichannelTranscriptService'; import type { IQueueWorkerService, HealthAggResult } from './types/IQueueWorkerService'; import type { ITranslationService } from './types/ITranslationService'; @@ -108,6 +109,7 @@ export { ISendFileMessageParams, IUploadFileParams, IUploadService, + ICalendarService, IOmnichannelTranscriptService, IQueueWorkerService, HealthAggResult, @@ -140,6 +142,7 @@ export const SAUMonitor = proxifyWithWait<ISAUMonitorService>('sau-monitor'); export const DeviceManagement = proxifyWithWait<IDeviceManagementService>('device-management'); export const VideoConf = proxifyWithWait<IVideoConfService>('video-conference'); export const Upload = proxifyWithWait<IUploadService>('upload'); +export const Calendar = proxifyWithWait<ICalendarService>('calendar'); export const QueueWorker = proxifyWithWait<IQueueWorkerService>('queue-worker'); export const OmnichannelTranscript = proxifyWithWait<IOmnichannelTranscriptService>('omnichannel-transcript'); export const Message = proxifyWithWait<IMessageService>('message'); diff --git a/packages/core-services/src/types/ICalendarService.ts b/packages/core-services/src/types/ICalendarService.ts new file mode 100644 index 0000000000000..114ce85510655 --- /dev/null +++ b/packages/core-services/src/types/ICalendarService.ts @@ -0,0 +1,15 @@ +import type { UpdateResult, DeleteResult } from 'mongodb'; +import type { ICalendarEvent, IUser } from '@rocket.chat/core-typings'; +import type { InsertionModel } from '@rocket.chat/model-typings'; + +export interface ICalendarService { + create(data: Omit<InsertionModel<ICalendarEvent>, 'reminderTime' | 'notificationSent'>): Promise<ICalendarEvent['_id']>; + import(data: Omit<InsertionModel<ICalendarEvent>, 'notificationSent'>): Promise<ICalendarEvent['_id']>; + get(eventId: ICalendarEvent['_id']): Promise<ICalendarEvent | null>; + list(uid: IUser['_id'], date: Date): Promise<ICalendarEvent[]>; + update(eventId: ICalendarEvent['_id'], data: Partial<ICalendarEvent>): Promise<UpdateResult>; + delete(eventId: ICalendarEvent['_id']): Promise<DeleteResult>; + findImportedEvent(externalId: Required<ICalendarEvent>['externalId'], uid: ICalendarEvent['uid']): Promise<ICalendarEvent | null>; + parseDescriptionForMeetingUrl(description: string): Promise<string | undefined>; + setupNextNotification(): Promise<void>; +} diff --git a/packages/core-typings/src/ICalendarEvent.ts b/packages/core-typings/src/ICalendarEvent.ts new file mode 100644 index 0000000000000..6bb0a7bb58e35 --- /dev/null +++ b/packages/core-typings/src/ICalendarEvent.ts @@ -0,0 +1,16 @@ +import type { IRocketChatRecord } from './IRocketChatRecord'; +import type { IUser } from './IUser'; + +export interface ICalendarEvent extends IRocketChatRecord { + startTime: Date; + uid: IUser['_id']; + subject: string; + description: string; + notificationSent: boolean; + + externalId?: string; + meetingUrl?: string; + + reminderMinutesBeforeStart?: number; + reminderTime?: Date; +} diff --git a/packages/core-typings/src/INotification.ts b/packages/core-typings/src/INotification.ts index a5fc9155eba87..46c3341eea23e 100644 --- a/packages/core-typings/src/INotification.ts +++ b/packages/core-typings/src/INotification.ts @@ -1,3 +1,4 @@ +import type { ICalendarEvent } from './ICalendarEvent'; import type { IMessage } from './IMessage'; import type { IRoom } from './IRoom'; @@ -64,3 +65,11 @@ export interface INotificationDesktop { }; }; } + +export interface ICalendarNotification { + title: string; + text: string; + payload: { + _id: ICalendarEvent['_id']; + }; +} diff --git a/packages/core-typings/src/index.ts b/packages/core-typings/src/index.ts index 9ba91eb373b45..8cd004dd09f14 100644 --- a/packages/core-typings/src/index.ts +++ b/packages/core-typings/src/index.ts @@ -122,6 +122,7 @@ export * from './VideoConferenceCapabilities'; export * from './VideoConferenceOptions'; export * from './SpotlightUser'; +export * from './ICalendarEvent'; export * from './search'; export * from './omnichannel'; diff --git a/packages/cron/src/index.ts b/packages/cron/src/index.ts index dc6772a604077..8158971219eae 100644 --- a/packages/cron/src/index.ts +++ b/packages/cron/src/index.ts @@ -36,8 +36,22 @@ const runCronJobFunctionAndPersistResult = async (fn: () => Promise<any>, jobNam } }; +type ReservedJob = { + name: string; + callback: () => any | Promise<any>; +} & ( + | { + schedule: string; + timestamped: false; + } + | { + when: Date; + timestamped: true; + } +); + export class AgendaCronJobs { - private reservedJobs: { name: string; schedule: string; callback: () => any | Promise<any> }[] = []; + private reservedJobs: ReservedJob[] = []; private scheduler: Agenda | undefined; @@ -53,8 +67,12 @@ export class AgendaCronJobs { }); await this.scheduler.start(); - for await (const { name, schedule, callback } of this.reservedJobs) { - await this.add(name, schedule, callback); + for await (const job of this.reservedJobs) { + if (job.timestamped) { + await this.addAtTimestamp(job.name, job.when, job.callback); + } else { + await this.add(job.name, job.schedule, job.callback); + } } this.reservedJobs = []; @@ -62,15 +80,22 @@ export class AgendaCronJobs { public async add(name: string, schedule: string, callback: () => any | Promise<any>): Promise<void> { if (!this.scheduler) { - return this.reserve(name, schedule, callback); + return this.reserve({ name, schedule, callback, timestamped: false }); } - this.scheduler.define(name, async () => { - await runCronJobFunctionAndPersistResult(async () => callback(), name); - }); + await this.define(name, callback); await this.scheduler.every(schedule, name, {}, {}); } + public async addAtTimestamp(name: string, when: Date, callback: () => any | Promise<any>): Promise<void> { + if (!this.scheduler) { + return this.reserve({ name, when, callback, timestamped: true }); + } + + await this.define(name, callback); + await this.scheduler.schedule(when, name, {}); + } + public async remove(name: string): Promise<void> { if (!this.scheduler) { return this.unreserve(name); @@ -87,13 +112,23 @@ export class AgendaCronJobs { return this.scheduler.has({ name: jobName }); } - private async reserve(name: string, schedule: string, callback: () => any | Promise<any>): Promise<void> { - this.reservedJobs = [...this.reservedJobs, { name, schedule, callback }]; + private async reserve(config: ReservedJob): Promise<void> { + this.reservedJobs = [...this.reservedJobs, config]; } private async unreserve(jobName: string): Promise<void> { this.reservedJobs = this.reservedJobs.filter(({ name }) => name !== jobName); } + + private async define(jobName: string, callback: () => any | Promise<any>): Promise<void> { + if (!this.scheduler) { + throw new Error('Scheduler is not running.'); + } + + this.scheduler.define(jobName, async () => { + await runCronJobFunctionAndPersistResult(async () => callback(), jobName); + }); + } } export const cronJobs = new AgendaCronJobs(); diff --git a/packages/model-typings/src/index.ts b/packages/model-typings/src/index.ts index 8eff5184d3d71..eda84bd5a359d 100644 --- a/packages/model-typings/src/index.ts +++ b/packages/model-typings/src/index.ts @@ -67,6 +67,7 @@ export * from './models/IVoipRoomModel'; export * from './models/IWebdavAccountsModel'; export * from './models/IMatrixBridgeRoomModel'; export * from './models/IMatrixBridgeUserModel'; +export * from './models/ICalendarEventModel'; export * from './models/IOmnichannelServiceLevelAgreementsModel'; export * from './models/IAppLogsModel'; export * from './models/IAppsModel'; diff --git a/packages/model-typings/src/models/ICalendarEventModel.ts b/packages/model-typings/src/models/ICalendarEventModel.ts new file mode 100644 index 0000000000000..5f16114766020 --- /dev/null +++ b/packages/model-typings/src/models/ICalendarEventModel.ts @@ -0,0 +1,16 @@ +import type { FindCursor, UpdateResult } from 'mongodb'; +import type { ICalendarEvent, IUser } from '@rocket.chat/core-typings'; + +import type { IBaseModel } from './IBaseModel'; + +export interface ICalendarEventModel extends IBaseModel<ICalendarEvent> { + findByUserIdAndDate(uid: IUser['_id'], date: Date): FindCursor<ICalendarEvent>; + updateEvent(eventId: ICalendarEvent['_id'], eventData: Partial<ICalendarEvent>): Promise<UpdateResult>; + findNextNotificationDate(): Promise<Date | null>; + findEventsToNotify(notificationTime: Date, minutes: number): FindCursor<ICalendarEvent>; + flagNotificationSent(eventId: ICalendarEvent['_id']): Promise<UpdateResult>; + findOneByExternalIdAndUserId( + externalId: Required<ICalendarEvent>['externalId'], + uid: ICalendarEvent['uid'], + ): Promise<ICalendarEvent | null>; +} diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 03820ec7e39b4..fd6a959796731 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -66,6 +66,7 @@ import type { IWebdavAccountsModel, IMatrixBridgedRoomModel, IMatrixBridgedUserModel, + ICalendarEventModel, IOmnichannelServiceLevelAgreementsModel, IAppsModel, IAppsPersistenceModel, @@ -163,6 +164,7 @@ export const VoipRoom = proxify<IVoipRoomModel>('IVoipRoomModel'); export const WebdavAccounts = proxify<IWebdavAccountsModel>('IWebdavAccountsModel'); export const MatrixBridgedRoom = proxify<IMatrixBridgedRoomModel>('IMatrixBridgedRoomModel'); export const MatrixBridgedUser = proxify<IMatrixBridgedUserModel>('IMatrixBridgedUserModel'); +export const CalendarEvent = proxify<ICalendarEventModel>('ICalendarEventModel'); export const OmnichannelServiceLevelAgreements = proxify<IOmnichannelServiceLevelAgreementsModel>( 'IOmnichannelServiceLevelAgreementsModel', ); diff --git a/packages/rest-typings/src/index.ts b/packages/rest-typings/src/index.ts index 44c62e12bdac0..8266e5db67709 100644 --- a/packages/rest-typings/src/index.ts +++ b/packages/rest-typings/src/index.ts @@ -43,6 +43,7 @@ import type { CommandsEndpoints } from './v1/commands'; import type { MeEndpoints } from './v1/me'; import type { SubscriptionsEndpoints } from './v1/subscriptionsEndpoints'; import type { ImportEndpoints } from './v1/import'; +import type { CalendarEndpoints } from './v1/calendar'; import type { FederationEndpoints } from './v1/federation'; import type { ModerationEndpoints } from './v1/moderation'; import type { AuthEndpoints } from './v1/auth'; @@ -93,7 +94,9 @@ export interface Endpoints OAuthAppsEndpoint, SubscriptionsEndpoints, AutoTranslateEndpoints, + ImportEndpoints, FederationEndpoints, + CalendarEndpoints, AuthEndpoints, ImportEndpoints, DefaultEndpoints {} @@ -257,6 +260,7 @@ export * from './v1/e2e/e2eUpdateGroupKeyParamsPOST'; export * from './v1/import'; export * from './v1/voip'; export * from './v1/email-inbox'; +export * from './v1/calendar'; export * from './v1/federation'; export * from './v1/rooms'; export * from './v1/groups'; diff --git a/packages/rest-typings/src/v1/calendar/CalendarEventCreateProps.ts b/packages/rest-typings/src/v1/calendar/CalendarEventCreateProps.ts new file mode 100644 index 0000000000000..5358510c97abe --- /dev/null +++ b/packages/rest-typings/src/v1/calendar/CalendarEventCreateProps.ts @@ -0,0 +1,47 @@ +import type { JSONSchemaType } from 'ajv'; +import Ajv from 'ajv'; + +const ajv = new Ajv(); + +export type CalendarEventCreateProps = { + startTime: string; + externalId?: string; + subject: string; + description: string; + meetingUrl?: string; + reminderMinutesBeforeStart?: number; +}; + +const calendarEventCreatePropsSchema: JSONSchemaType<CalendarEventCreateProps> = { + type: 'object', + properties: { + startTime: { + type: 'string', + nullable: false, + }, + externalId: { + type: 'string', + nullable: true, + }, + subject: { + type: 'string', + nullable: false, + }, + description: { + type: 'string', + nullable: false, + }, + meetingUrl: { + type: 'string', + nullable: true, + }, + reminderMinutesBeforeStart: { + type: 'number', + nullable: true, + }, + }, + required: ['startTime', 'subject', 'description'], + additionalProperties: false, +}; + +export const isCalendarEventCreateProps = ajv.compile(calendarEventCreatePropsSchema); diff --git a/packages/rest-typings/src/v1/calendar/CalendarEventDeleteProps.ts b/packages/rest-typings/src/v1/calendar/CalendarEventDeleteProps.ts new file mode 100644 index 0000000000000..17d17bb8c4414 --- /dev/null +++ b/packages/rest-typings/src/v1/calendar/CalendarEventDeleteProps.ts @@ -0,0 +1,22 @@ +import type { ICalendarEvent } from '@rocket.chat/core-typings'; +import type { JSONSchemaType } from 'ajv'; +import Ajv from 'ajv'; + +const ajv = new Ajv(); + +export type CalendarEventDeleteProps = { + eventId: ICalendarEvent['_id']; +}; + +const calendarEventDeletePropsSchema: JSONSchemaType<CalendarEventDeleteProps> = { + type: 'object', + properties: { + eventId: { + type: 'string', + }, + }, + required: ['eventId'], + additionalProperties: false, +}; + +export const isCalendarEventDeleteProps = ajv.compile(calendarEventDeletePropsSchema); diff --git a/packages/rest-typings/src/v1/calendar/CalendarEventImportProps.ts b/packages/rest-typings/src/v1/calendar/CalendarEventImportProps.ts new file mode 100644 index 0000000000000..955184a36115c --- /dev/null +++ b/packages/rest-typings/src/v1/calendar/CalendarEventImportProps.ts @@ -0,0 +1,47 @@ +import type { JSONSchemaType } from 'ajv'; +import Ajv from 'ajv'; + +const ajv = new Ajv(); + +export type CalendarEventImportProps = { + startTime: string; + externalId: string; + subject: string; + description: string; + meetingUrl?: string; + reminderMinutesBeforeStart?: number; +}; + +const calendarEventImportPropsSchema: JSONSchemaType<CalendarEventImportProps> = { + type: 'object', + properties: { + startTime: { + type: 'string', + nullable: false, + }, + externalId: { + type: 'string', + nullable: false, + }, + subject: { + type: 'string', + nullable: false, + }, + description: { + type: 'string', + nullable: false, + }, + meetingUrl: { + type: 'string', + nullable: true, + }, + reminderMinutesBeforeStart: { + type: 'number', + nullable: true, + }, + }, + required: ['startTime', 'externalId', 'subject', 'description'], + additionalProperties: false, +}; + +export const isCalendarEventImportProps = ajv.compile(calendarEventImportPropsSchema); diff --git a/packages/rest-typings/src/v1/calendar/CalendarEventInfoProps.ts b/packages/rest-typings/src/v1/calendar/CalendarEventInfoProps.ts new file mode 100644 index 0000000000000..c5e00d5374317 --- /dev/null +++ b/packages/rest-typings/src/v1/calendar/CalendarEventInfoProps.ts @@ -0,0 +1,22 @@ +import Ajv from 'ajv'; +import type { JSONSchemaType } from 'ajv'; + +const ajv = new Ajv({ + coerceTypes: true, +}); + +export type CalendarEventInfoProps = { id: string }; + +const calendarEventInfoPropsSchema: JSONSchemaType<CalendarEventInfoProps> = { + type: 'object', + properties: { + id: { + type: 'string', + nullable: false, + }, + }, + required: ['id'], + additionalProperties: false, +}; + +export const isCalendarEventInfoProps = ajv.compile(calendarEventInfoPropsSchema); diff --git a/packages/rest-typings/src/v1/calendar/CalendarEventListProps.ts b/packages/rest-typings/src/v1/calendar/CalendarEventListProps.ts new file mode 100644 index 0000000000000..e035d4faa5993 --- /dev/null +++ b/packages/rest-typings/src/v1/calendar/CalendarEventListProps.ts @@ -0,0 +1,22 @@ +import Ajv from 'ajv'; +import type { JSONSchemaType } from 'ajv'; + +const ajv = new Ajv({ + coerceTypes: true, +}); + +export type CalendarEventListProps = { date: string }; + +const calendarEventListPropsSchema: JSONSchemaType<CalendarEventListProps> = { + type: 'object', + properties: { + date: { + type: 'string', + nullable: false, + }, + }, + required: ['date'], + additionalProperties: false, +}; + +export const isCalendarEventListProps = ajv.compile(calendarEventListPropsSchema); diff --git a/packages/rest-typings/src/v1/calendar/CalendarEventUpdateProps.ts b/packages/rest-typings/src/v1/calendar/CalendarEventUpdateProps.ts new file mode 100644 index 0000000000000..1004cd09990e8 --- /dev/null +++ b/packages/rest-typings/src/v1/calendar/CalendarEventUpdateProps.ts @@ -0,0 +1,48 @@ +import type { ICalendarEvent } from '@rocket.chat/core-typings'; +import type { JSONSchemaType } from 'ajv'; +import Ajv from 'ajv'; + +const ajv = new Ajv(); + +export type CalendarEventUpdateProps = { + eventId: ICalendarEvent['_id']; + startTime: string; + subject: string; + description: string; + meetingUrl?: string; + reminderMinutesBeforeStart?: number; +}; + +const calendarEventUpdatePropsSchema: JSONSchemaType<CalendarEventUpdateProps> = { + type: 'object', + properties: { + eventId: { + type: 'string', + nullable: false, + }, + startTime: { + type: 'string', + nullable: false, + }, + subject: { + type: 'string', + nullable: false, + }, + description: { + type: 'string', + nullable: false, + }, + meetingUrl: { + type: 'string', + nullable: true, + }, + reminderMinutesBeforeStart: { + type: 'number', + nullable: true, + }, + }, + required: ['eventId', 'startTime', 'subject', 'description'], + additionalProperties: false, +}; + +export const isCalendarEventUpdateProps = ajv.compile(calendarEventUpdatePropsSchema); diff --git a/packages/rest-typings/src/v1/calendar/index.ts b/packages/rest-typings/src/v1/calendar/index.ts new file mode 100644 index 0000000000000..de00bd3d632ed --- /dev/null +++ b/packages/rest-typings/src/v1/calendar/index.ts @@ -0,0 +1,41 @@ +import type { ICalendarEvent } from '@rocket.chat/core-typings'; + +import type { CalendarEventCreateProps } from './CalendarEventCreateProps'; +import type { CalendarEventListProps } from './CalendarEventListProps'; +import type { CalendarEventImportProps } from './CalendarEventImportProps'; +import type { CalendarEventInfoProps } from './CalendarEventInfoProps'; +import type { CalendarEventUpdateProps } from './CalendarEventUpdateProps'; +import type { CalendarEventDeleteProps } from './CalendarEventDeleteProps'; + +export * from './CalendarEventCreateProps'; +export * from './CalendarEventDeleteProps'; +export * from './CalendarEventImportProps'; +export * from './CalendarEventInfoProps'; +export * from './CalendarEventUpdateProps'; +export * from './CalendarEventListProps'; + +export type CalendarEndpoints = { + '/v1/calendar-events.create': { + POST: (params: CalendarEventCreateProps) => { id: ICalendarEvent['_id'] }; + }; + + '/v1/calendar-events.list': { + GET: (params: CalendarEventListProps) => { data: ICalendarEvent[] }; + }; + + '/v1/calendar-events.info': { + GET: (params: CalendarEventInfoProps) => { event: ICalendarEvent }; + }; + + '/v1/calendar-events.import': { + POST: (params: CalendarEventImportProps) => { id: ICalendarEvent['_id'] }; + }; + + '/v1/calendar-events.update': { + POST: (params: CalendarEventUpdateProps) => void; + }; + + '/v1/calendar-events.delete': { + POST: (params: CalendarEventDeleteProps) => void; + }; +}; diff --git a/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts index 69ac5440189a0..d820dfe5cb2d7 100644 --- a/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts @@ -41,6 +41,7 @@ export type UsersSetPreferencesParamsPOST = { dontAskAgainList?: Array<{ action: string; label: string }>; themeAppearence?: 'auto' | 'light' | 'dark'; receiveLoginDetectionEmail?: boolean; + notifyCalendarEvents?: boolean; idleTimeLimit?: number; omnichannelTranscriptEmail?: boolean; omnichannelTranscriptPDF?: boolean; @@ -204,6 +205,10 @@ const UsersSetPreferencesParamsPostSchema = { type: 'boolean', nullable: true, }, + notifyCalendarEvents: { + type: 'boolean', + nullable: true, + }, idleTimeLimit: { type: 'number', nullable: true, diff --git a/packages/ui-contexts/src/ServerContext/streams.ts b/packages/ui-contexts/src/ServerContext/streams.ts index 6165e15708d68..5892371afb1d7 100644 --- a/packages/ui-contexts/src/ServerContext/streams.ts +++ b/packages/ui-contexts/src/ServerContext/streams.ts @@ -17,6 +17,7 @@ import type { IOmnichannelCannedResponse, IIntegrationHistory, IUserDataEvent, + ICalendarNotification, IUserStatus, ILivechatInquiryRecord, } from '@rocket.chat/core-typings'; @@ -154,6 +155,7 @@ export interface StreamerEvents { }, ]; }, + { key: `${string}/calendar`; args: [ICalendarNotification] }, ]; 'importers': [ From 18c55b0bbe9df3cac53248dc0e05b173f040bece Mon Sep 17 00:00:00 2001 From: XpertWebApp <68045060+XpertWebApp@users.noreply.github.com> Date: Tue, 4 Jul 2023 23:15:16 +0530 Subject: [PATCH 38/79] fix: sound status goes out of window on popout mode (#28181) --- packages/livechat/src/components/Screen/Header.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/livechat/src/components/Screen/Header.js b/packages/livechat/src/components/Screen/Header.js index 793a5a37f6dc9..9f9e41810c7b6 100644 --- a/packages/livechat/src/components/Screen/Header.js +++ b/packages/livechat/src/components/Screen/Header.js @@ -78,7 +78,7 @@ class ScreenHeader extends Component { </Header.Content> <Tooltip.Container> <Header.Actions> - <Tooltip.Trigger content={notificationsEnabled ? t('sound_is_on') : t('sound_is_off')}> + <Tooltip.Trigger content={notificationsEnabled ? t('sound_is_on') : t('sound_is_off')} placement='bottom-left'> <Header.Action aria-label={notificationsEnabled ? t('disable_notifications') : t('enable_notifications')} onClick={notificationsEnabled ? onDisableNotifications : onEnableNotifications} From 706561aa2731c2bd77dfba95891fa0094d3a235c Mon Sep 17 00:00:00 2001 From: Diego Sampaio <chinello@gmail.com> Date: Tue, 4 Jul 2023 17:54:45 -0300 Subject: [PATCH 39/79] chore: throttle exceptions counter increment (#29719) --- .../server/lib/RocketChat.ErrorHandler.ts | 7 ++++++- apps/meteor/app/lib/server/lib/meteorFixes.js | 10 ++++++++-- apps/meteor/ee/server/startup/seatsCap.ts | 5 +++-- apps/meteor/lib/utils/throttledCounter.ts | 20 +++++++++++++++++++ 4 files changed, 37 insertions(+), 5 deletions(-) create mode 100644 apps/meteor/lib/utils/throttledCounter.ts diff --git a/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts b/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts index 9a4fc28b8f3e9..0aa8c3a53747e 100644 --- a/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts +++ b/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts @@ -3,6 +3,11 @@ import { Settings, Users, Rooms } from '@rocket.chat/models'; import { settings } from '../../../settings/server'; import { sendMessage } from '../../../lib/server'; +import { throttledCounter } from '../../../../lib/utils/throttledCounter'; + +const incException = throttledCounter((counter) => { + Settings.incrementValueById('Uncaught_Exceptions_Count', counter).catch(console.error); +}, 10000); class ErrorHandler { reporting: boolean; @@ -40,7 +45,7 @@ class ErrorHandler { async registerHandlers() { process.on('uncaughtException', async (error) => { - await Settings.incrementValueById('Uncaught_Exceptions_Count'); + incException(); if (!this.reporting) { return; } diff --git a/apps/meteor/app/lib/server/lib/meteorFixes.js b/apps/meteor/app/lib/server/lib/meteorFixes.js index 6b88febda2aa9..39616cac4042b 100644 --- a/apps/meteor/app/lib/server/lib/meteorFixes.js +++ b/apps/meteor/app/lib/server/lib/meteorFixes.js @@ -1,6 +1,8 @@ import { MongoInternals } from 'meteor/mongo'; import { Settings } from '@rocket.chat/models'; +import { throttledCounter } from '../../../../lib/utils/throttledCounter'; + const timeoutQuery = parseInt(process.env.OBSERVERS_CHECK_TIMEOUT) || 2 * 60 * 1000; const interval = parseInt(process.env.OBSERVERS_CHECK_INTERVAL) || 60 * 1000; const debug = Boolean(process.env.OBSERVERS_CHECK_DEBUG); @@ -44,6 +46,10 @@ setInterval(() => { }); }, interval); +const incException = throttledCounter((counter) => { + Settings.incrementValueById('Uncaught_Exceptions_Count', counter).catch(console.error); +}, 10000); + /** * If some promise is rejected and doesn't have a catch (unhandledRejection) it may cause this finally * here https://github.com/meteor/meteor/blob/be6e529a739f47446950e045f4547ee60e5de7ae/packages/mongo/oplog_tailing.js#L348 @@ -58,8 +64,8 @@ setInterval(() => { * we will start respecting this and exit the process to prevent these kind of problems. */ -process.on('unhandledRejection', async (error) => { - await Settings.incrementValueById('Uncaught_Exceptions_Count'); +process.on('unhandledRejection', (error) => { + incException(); console.error('=== UnHandledPromiseRejection ==='); console.error(error); diff --git a/apps/meteor/ee/server/startup/seatsCap.ts b/apps/meteor/ee/server/startup/seatsCap.ts index 188c8dfe93b91..d5ec74919568d 100644 --- a/apps/meteor/ee/server/startup/seatsCap.ts +++ b/apps/meteor/ee/server/startup/seatsCap.ts @@ -1,6 +1,7 @@ import { Meteor } from 'meteor/meteor'; import type { IUser } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; +import { throttle } from 'underscore'; import { callbacks } from '../../../lib/callbacks'; import { canAddNewUser, getMaxActiveUsers, onValidateLicenses } from '../../app/license/server/license'; @@ -79,7 +80,7 @@ callbacks.add( 'check-max-user-seats', ); -async function handleMaxSeatsBanners() { +const handleMaxSeatsBanners = throttle(async function _handleMaxSeatsBanners() { const maxActiveUsers = getMaxActiveUsers(); if (!maxActiveUsers) { @@ -106,7 +107,7 @@ async function handleMaxSeatsBanners() { } else { await enableDangerBanner(); } -} +}, 10000); callbacks.add('afterCreateUser', handleMaxSeatsBanners, callbacks.priority.MEDIUM, 'handle-max-seats-banners'); diff --git a/apps/meteor/lib/utils/throttledCounter.ts b/apps/meteor/lib/utils/throttledCounter.ts new file mode 100644 index 0000000000000..6580699b36303 --- /dev/null +++ b/apps/meteor/lib/utils/throttledCounter.ts @@ -0,0 +1,20 @@ +import { throttle } from 'underscore'; + +export function throttledCounter(fn: (counter: number) => unknown, wait: number) { + let counter = 0; + + const throttledFn = throttle( + () => { + fn(counter); + + counter = 0; + }, + wait, + { leading: false }, + ); + + return () => { + counter++; + throttledFn(); + }; +} From 4ffd7fb2092b6e099f1bf6e6414ca4a40111ef33 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Tue, 4 Jul 2023 18:21:00 -0300 Subject: [PATCH 40/79] chore: device management improve readability and performance (#29712) --- .../ee/server/lib/deviceManagement/session.ts | 160 +++++++++--------- .../ee/server/startup/deviceManagement.ts | 7 +- apps/meteor/server/models/raw/Users.js | 10 ++ .../model-typings/src/models/IUsersModel.ts | 1 + 4 files changed, 101 insertions(+), 77 deletions(-) diff --git a/apps/meteor/ee/server/lib/deviceManagement/session.ts b/apps/meteor/ee/server/lib/deviceManagement/session.ts index 22ded104cd4df..f7f67a1c93ca1 100644 --- a/apps/meteor/ee/server/lib/deviceManagement/session.ts +++ b/apps/meteor/ee/server/lib/deviceManagement/session.ts @@ -1,14 +1,13 @@ import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { UAParser } from 'ua-parser-js'; -import type { ISocketConnection, IUser } from '@rocket.chat/core-typings'; +import type { ISocketConnection } from '@rocket.chat/core-typings'; import { Users } from '@rocket.chat/models'; import * as Mailer from '../../../../app/mailer/server/api'; import { settings } from '../../../../app/settings/server'; import { UAParserDesktop, UAParserMobile } from '../../../../app/statistics/server/lib/UAParserCustom'; import { deviceManagementEvents } from '../../../../server/services/device-management/events'; -import { hasLicense } from '../../../app/license/server/license'; import { getUserPreference } from '../../../../app/utils/server'; import { t } from '../../../../app/utils/lib/i18n'; @@ -23,88 +22,97 @@ Meteor.startup(() => { const uaParser = async ( uaString: ISocketConnection['httpHeaders']['user-agent'], ): Promise<UAParser.IResult & { app?: { name: string; version: string; bundle: string } }> => { - let rcAgent = {}; - if (uaString && UAParserMobile.isMobileApp(uaString)) { - rcAgent = UAParserMobile.uaObject(uaString); - } - - if (uaString && UAParserDesktop.isDesktopApp(uaString)) { - rcAgent = UAParserDesktop.uaObject(uaString); - } - const ua = new UAParser(uaString); - return { ...ua.getResult(), ...rcAgent }; + return { + ...ua.getResult(), + ...(uaString && UAParserMobile.isMobileApp(uaString) && UAParserMobile.uaObject(uaString)), + ...(uaString && UAParserDesktop.isDesktopApp(uaString) && UAParserDesktop.uaObject(uaString)), + }; }; -export const listenSessionLogin = async (): Promise<void> => { - deviceManagementEvents.on('device-login', async ({ userId, connection }) => { - if (!hasLicense('device-management')) return; +export const listenSessionLogin = () => { + return deviceManagementEvents.on('device-login', async ({ userId, connection }) => { + const deviceEnabled = settings.get('Device_Management_Enable_Login_Emails'); + + if (!deviceEnabled) { + return; + } + + if (connection.loginToken) { + return; + } + + const user = await Users.findOneByIdWithEmailAddress(userId, { + projection: { 'name': 1, 'username': 1, 'emails': 1, 'settings.preferences.receiveLoginDetectionEmail': 1 }, + }); + + if (!user?.emails?.length) { + return; + } + + const userReceiveLoginEmailPreference = + !settings.get('Device_Management_Allow_Login_Email_preference') || + (await getUserPreference(userId, 'receiveLoginDetectionEmail', true)); - const user = await Users.findOneById<IUser>(userId, { projection: { name: 1, username: 1, emails: 1 } }); - if (user?.emails?.length && !connection.loginToken) { - const { - name, - username, - emails: [{ address: email }], - } = user; - const { browser, os, device, cpu, app } = await uaParser(connection.httpHeaders['user-agent']); + if (!userReceiveLoginEmailPreference) { + return; + } - const mailData = { - name, - username, - browserInfo: `${browser.name} ${browser.version}`, - osInfo: `${os.name}`, - deviceInfo: `${device.type || t('Device_Management_Device_Unknown')} ${device.vendor || ''} ${device.model || ''} ${ - cpu.architecture || '' - }`, - ipInfo: connection.clientAddress, - userAgent: '', - }; + const { + name, + username, + emails: [{ address: email }], + } = user; + const { browser, os, device, cpu, app } = await uaParser(connection.httpHeaders['user-agent']); - switch (device.type) { - case 'mobile': - case 'tablet': - case 'smarttv': - mailData.browserInfo = `${browser.name} ${browser.version}`; - mailData.osInfo = `${os.name}`; - mailData.deviceInfo = `${device.type} ${device.vendor || ''} ${device.model || ''} ${cpu.architecture || ''}`; - break; - case 'mobile-app': - mailData.browserInfo = `Rocket.Chat App ${app?.bundle || app?.version}`; - mailData.osInfo = `${os.name}`; - mailData.deviceInfo = 'Mobile App'; - break; - case 'desktop-app': - mailData.browserInfo = `Rocket.Chat ${app?.name || browser.name} ${app?.bundle || app?.version || browser.version}`; - mailData.osInfo = `${os.name}`; - mailData.deviceInfo = `Desktop App ${cpu.architecture || ''}`; - break; - default: - mailData.userAgent = connection.httpHeaders['user-agent'] || ''; - break; - } + const mailData = { + name, + username, + browserInfo: `${browser.name} ${browser.version}`, + osInfo: `${os.name}`, + deviceInfo: `${device.type || t('Device_Management_Device_Unknown')} ${device.vendor || ''} ${device.model || ''} ${ + cpu.architecture || '' + }`, + ipInfo: connection.clientAddress, + userAgent: '', + }; - try { - const userReceiveLoginEmailPreference = settings.get('Device_Management_Allow_Login_Email_preference') - ? await getUserPreference(userId, 'receiveLoginDetectionEmail', true) - : true; - const shouldSendLoginEmail = settings.get('Device_Management_Enable_Login_Emails') && userReceiveLoginEmailPreference; + switch (device.type) { + case 'mobile': + case 'tablet': + case 'smarttv': + mailData.browserInfo = `${browser.name} ${browser.version}`; + mailData.osInfo = `${os.name}`; + mailData.deviceInfo = `${device.type} ${device.vendor || ''} ${device.model || ''} ${cpu.architecture || ''}`; + break; + case 'mobile-app': + mailData.browserInfo = `Rocket.Chat App ${app?.bundle || app?.version}`; + mailData.osInfo = `${os.name}`; + mailData.deviceInfo = 'Mobile App'; + break; + case 'desktop-app': + mailData.browserInfo = `Rocket.Chat ${app?.name || browser.name} ${app?.bundle || app?.version || browser.version}`; + mailData.osInfo = `${os.name}`; + mailData.deviceInfo = `Desktop App ${cpu.architecture || ''}`; + break; + default: + mailData.userAgent = connection.httpHeaders['user-agent'] || ''; + break; + } - if (shouldSendLoginEmail) { - await Mailer.send({ - to: `${name} <${email}>`, - from: Accounts.emailTemplates.from, - subject: settings.get('Device_Management_Email_Subject'), - html: mailTemplates, - data: mailData, - }); - } - } catch ({ message }: any) { - throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${message}`, { - method: 'listenSessionLogin', - message, - }); - } + try { + await Mailer.send({ + to: `${name} <${email}>`, + from: Accounts.emailTemplates.from, + subject: settings.get('Device_Management_Email_Subject'), + html: mailTemplates, + data: mailData, + }); + } catch ({ message }: any) { + throw new Meteor.Error('error-email-send-failed', `Error trying to send email: ${message}`, { + method: 'listenSessionLogin', + message, + }); } }); }; diff --git a/apps/meteor/ee/server/startup/deviceManagement.ts b/apps/meteor/ee/server/startup/deviceManagement.ts index 6e9ba85406881..a9a1c805f72d5 100644 --- a/apps/meteor/ee/server/startup/deviceManagement.ts +++ b/apps/meteor/ee/server/startup/deviceManagement.ts @@ -1,6 +1,7 @@ import { onToggledFeature } from '../../app/license/server/license'; import { addSettings } from '../settings/deviceManagement'; +let stopListening: (() => void) | undefined; onToggledFeature('device-management', { up: async () => { const { createPermissions, createEmailTemplates } = await import('../lib/deviceManagement/startup'); @@ -9,6 +10,10 @@ onToggledFeature('device-management', { await addSettings(); await createPermissions(); await createEmailTemplates(); - await listenSessionLogin(); + stopListening = await listenSessionLogin(); + }, + down: async () => { + stopListening?.(); + stopListening = undefined; }, }); diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index 0cb88a178ccc8..996c3abaebcd1 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -1250,6 +1250,16 @@ export class UsersRaw extends BaseRaw { return this.findOne({ 'services.password.reset.token': token }, options); } + findOneByIdWithEmailAddress(userId, options) { + return this.findOne( + { + _id: userId, + emails: { $exists: true, $ne: [] }, + }, + options, + ); + } + setFederationAvatarUrlById(userId, federationAvatarUrl) { return this.updateOne( { diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 5900b5a140915..70b161acb6f3a 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -17,6 +17,7 @@ export interface IUsersModel extends IBaseModel<IUser> { addRolesByUserId(uid: IUser['_id'], roles: IRole['_id'][]): Promise<UpdateResult>; findUsersInRoles<T = IUser>(roles: IRole['_id'][], scope?: null, options?: any): FindCursor<T>; findPaginatedUsersInRoles<T = IUser>(roles: IRole['_id'][], options?: any): FindPaginated<FindCursor<T>>; + findOneByIdWithEmailAddress(uid: IUser['_id'], options?: FindOptions<IUser>): Promise<IUser | null>; findOneByUsername<T = IUser>(username: string, options?: any): Promise<T>; findOneAgentById<T = ILivechatAgent>(_id: string, options: any): Promise<T>; findUsersInRolesWithQuery<T = IUser>(roles: IRole['_id'] | IRole['_id'][], query: any, options: any): FindCursor<T>; From c7ff0557bc91a12ace327f4d13cbab837bc615dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Tue, 4 Jul 2023 18:52:53 -0300 Subject: [PATCH 41/79] chore: `MenuV2` on `UserDropdown` (#29672) Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com> --- .../{ => GenericMenu}/GenericMenu.tsx | 34 +++- .../{ => GenericMenu}/GenericMenuItem.tsx | 10 +- .../GenericMenu/hooks/useHandleMenuAction.tsx | 13 ++ .../OmnichannelSortingDisclaimer.tsx | 24 +-- .../client/hooks/useHandleMenuAction.tsx | 10 - .../sidebar/header/UserAvatarButton.tsx | 78 -------- .../sidebar/header/UserAvatarWithStatus.tsx | 57 ++++++ .../client/sidebar/header/UserDropdown.tsx | 183 ------------------ .../meteor/client/sidebar/header/UserMenu.tsx | 31 +++ .../client/sidebar/header/UserMenuHeader.tsx | 43 ++++ .../sidebar/header/actions/Administration.tsx | 9 +- .../sidebar/header/actions/CreateRoom.tsx | 18 +- .../client/sidebar/header/actions/Sort.tsx | 2 +- .../actions/hooks/useAdministrationItems.tsx | 2 +- .../header/actions/hooks/useAppsItems.tsx | 2 +- .../header/actions/hooks/useAuditItems.tsx | 2 +- .../actions/hooks/useCreateRoomItems.tsx | 2 +- .../actions/hooks/useGroupingListItems.tsx | 2 +- .../hooks/useMatrixFederationItems.tsx.tsx | 2 +- .../header/actions/hooks/useSortModeItems.tsx | 26 +-- .../header/actions/hooks/useViewModeItems.tsx | 2 +- .../sidebar/header/hooks/useAccountItems.tsx | 33 ++++ .../hooks/useCustomStatusModalHandler.tsx | 14 ++ .../sidebar/header/hooks/useStatusItems.tsx | 77 ++++++++ .../sidebar/header/hooks/useThemeItems.tsx | 33 ++++ .../sidebar/header/hooks/useUserMenu.tsx | 31 +++ apps/meteor/client/sidebar/header/index.tsx | 5 +- ...nichannel-auto-onhold-chat-closing.spec.ts | 2 +- ...nel-changing-room-priority-and-sla.spec.ts | 2 +- .../omnichannel/omnichannel-takeChat.spec.ts | 4 +- ...channel-transfer-to-another-agents.spec.ts | 8 +- .../page-objects/fragments/home-sidenav.ts | 4 +- 32 files changed, 413 insertions(+), 352 deletions(-) rename apps/meteor/client/components/{ => GenericMenu}/GenericMenu.tsx (53%) rename apps/meteor/client/components/{ => GenericMenu}/GenericMenuItem.tsx (57%) create mode 100644 apps/meteor/client/components/GenericMenu/hooks/useHandleMenuAction.tsx delete mode 100644 apps/meteor/client/hooks/useHandleMenuAction.tsx delete mode 100644 apps/meteor/client/sidebar/header/UserAvatarButton.tsx create mode 100644 apps/meteor/client/sidebar/header/UserAvatarWithStatus.tsx delete mode 100644 apps/meteor/client/sidebar/header/UserDropdown.tsx create mode 100644 apps/meteor/client/sidebar/header/UserMenu.tsx create mode 100644 apps/meteor/client/sidebar/header/UserMenuHeader.tsx create mode 100644 apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx create mode 100644 apps/meteor/client/sidebar/header/hooks/useCustomStatusModalHandler.tsx create mode 100644 apps/meteor/client/sidebar/header/hooks/useStatusItems.tsx create mode 100644 apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx create mode 100644 apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx diff --git a/apps/meteor/client/components/GenericMenu.tsx b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx similarity index 53% rename from apps/meteor/client/components/GenericMenu.tsx rename to apps/meteor/client/components/GenericMenu/GenericMenu.tsx index 21a413a866447..5a9ad9a0877c7 100644 --- a/apps/meteor/client/components/GenericMenu.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx @@ -1,20 +1,21 @@ -import type { Icon } from '@rocket.chat/fuselage'; +import type { IconButton } from '@rocket.chat/fuselage'; import { MenuItem, MenuSection, MenuV2 } from '@rocket.chat/fuselage'; import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { ComponentProps } from 'react'; +import type { ComponentProps, ReactNode } from 'react'; import React from 'react'; import type { GenericMenuItemProps } from './GenericMenuItem'; import GenericMenuItem from './GenericMenuItem'; +import { useHandleMenuAction } from './hooks/useHandleMenuAction'; type GenericMenuCommonProps = { - icon?: ComponentProps<typeof Icon>['name']; + icon?: ComponentProps<typeof IconButton>['icon']; title: string; }; type GenericMenuConditionalProps = | { sections?: { - title?: string; + title?: ReactNode; items: GenericMenuItemProps[]; permission?: boolean | '' | 0 | null | undefined; }[]; @@ -27,18 +28,29 @@ type GenericMenuConditionalProps = type GenericMenuProps = GenericMenuCommonProps & GenericMenuConditionalProps & Omit<ComponentProps<typeof MenuV2>, 'children'>; -const GenericMenu = ({ title, icon = 'menu', ...props }: GenericMenuProps) => { +const GenericMenu = ({ title, icon = 'menu', onAction, ...props }: GenericMenuProps) => { const t = useTranslation(); const sections = 'sections' in props && props.sections; const items = 'items' in props && props.items; + const itemsList = sections ? sections.reduce((acc, { items }) => [...acc, ...items], [] as GenericMenuItemProps[]) : items || []; + + const disabledKeys = itemsList.filter(({ disabled }) => disabled).map(({ id }) => id); + const handleAction = useHandleMenuAction(itemsList || []); + return ( <> {sections && ( - <MenuV2 icon={icon} title={t.has(title) ? t(title) : title} {...props}> + <MenuV2 + icon={icon} + title={t.has(title) ? t(title) : title} + onAction={onAction || handleAction} + {...(disabledKeys && { disabledKeys })} + {...props} + > {sections.map(({ title, items }, key) => ( - <MenuSection title={title && (t.has(title) ? t(title) : title)} items={items} key={`${title}-${key}`}> + <MenuSection title={typeof title === 'string' && t.has(title) ? t(title) : title} items={items} key={`${title}-${key}`}> {(item) => ( <MenuItem key={item.id}> <GenericMenuItem {...item} /> @@ -49,7 +61,13 @@ const GenericMenu = ({ title, icon = 'menu', ...props }: GenericMenuProps) => { </MenuV2> )} {items && ( - <MenuV2 icon={icon} title={t.has(title) ? t(title) : title} {...props}> + <MenuV2 + icon={icon} + title={t.has(title) ? t(title) : title} + onAction={onAction || handleAction} + {...(disabledKeys && { disabledKeys })} + {...props} + > {items.map((item) => ( <MenuItem key={item.id}> <GenericMenuItem {...item} /> diff --git a/apps/meteor/client/components/GenericMenuItem.tsx b/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx similarity index 57% rename from apps/meteor/client/components/GenericMenuItem.tsx rename to apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx index ad546e0473efc..ca56b79de2583 100644 --- a/apps/meteor/client/components/GenericMenuItem.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx @@ -1,18 +1,22 @@ -import { MenuItemContent, MenuItemIcon, MenuItemInput } from '@rocket.chat/fuselage'; +import { MenuItemColumn, MenuItemContent, MenuItemIcon, MenuItemInput } from '@rocket.chat/fuselage'; import type { ComponentProps, ReactNode } from 'react'; import React from 'react'; export type GenericMenuItemProps = { - id?: string; + id: string; icon?: ComponentProps<typeof MenuItemIcon>['name']; content?: ReactNode; addon?: ReactNode; onClick?: () => void; + status?: ReactNode; + disabled?: boolean; + description?: ReactNode; }; -const GenericMenuItem = ({ icon, content, addon }: GenericMenuItemProps) => ( +const GenericMenuItem = ({ icon, content, addon, status }: GenericMenuItemProps) => ( <> {icon && <MenuItemIcon name={icon} />} + {status && <MenuItemColumn>{status}</MenuItemColumn>} {content && <MenuItemContent>{content}</MenuItemContent>} {addon && <MenuItemInput>{addon}</MenuItemInput>} </> diff --git a/apps/meteor/client/components/GenericMenu/hooks/useHandleMenuAction.tsx b/apps/meteor/client/components/GenericMenu/hooks/useHandleMenuAction.tsx new file mode 100644 index 0000000000000..c67c8acd3c5e8 --- /dev/null +++ b/apps/meteor/client/components/GenericMenu/hooks/useHandleMenuAction.tsx @@ -0,0 +1,13 @@ +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; + +import type { GenericMenuItemProps } from '../GenericMenuItem'; + +export const useHandleMenuAction = (items: GenericMenuItemProps[], close?: () => void) => { + return useMutableCallback((id) => { + const item = items.find((item) => item.id === id && !!item.onClick); + if (item) { + item.onClick?.(); + close?.(); + } + }); +}; diff --git a/apps/meteor/client/components/Omnichannel/OmnichannelSortingDisclaimer.tsx b/apps/meteor/client/components/Omnichannel/OmnichannelSortingDisclaimer.tsx index 11c5f5ec8ab9c..89e1a508b3680 100644 --- a/apps/meteor/client/components/Omnichannel/OmnichannelSortingDisclaimer.tsx +++ b/apps/meteor/client/components/Omnichannel/OmnichannelSortingDisclaimer.tsx @@ -1,14 +1,12 @@ import { OmnichannelSortingMechanismSettingType as OmniSortingType } from '@rocket.chat/core-typings'; -import { Box } from '@rocket.chat/fuselage'; import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState } from 'react'; -type OmnichannelSortingDisclaimerProps = { - id?: string; -}; +import { useOmnichannelEnterpriseEnabled } from '../../hooks/omnichannel/useOmnichannelEnterpriseEnabled'; + +export const useOmnichannelSortingDisclaimer = () => { + const isOmnichannelEnabled = useOmnichannelEnterpriseEnabled(); -export const OmnichannelSortingDisclaimer = (props: OmnichannelSortingDisclaimerProps) => { - const t = useTranslation(); const sortingMechanism = useSetting<OmniSortingType>('Omnichannel_sorting_mechanism') || OmniSortingType.Timestamp; const [{ [sortingMechanism]: type }] = useState({ @@ -17,13 +15,17 @@ export const OmnichannelSortingDisclaimer = (props: OmnichannelSortingDisclaimer [OmniSortingType.Timestamp]: '', } as const); + return isOmnichannelEnabled ? type : ''; +}; + +export const OmnichannelSortingDisclaimer = () => { + const t = useTranslation(); + + const type = useOmnichannelSortingDisclaimer(); + if (!type) { return null; } - return ( - <Box is='small' display='block' padding='4px 12px' fontSize='14px' lineHeight='20px' maxWidth='180px' {...props}> - {t('Omnichannel_sorting_disclaimer', { sortingMechanism: t(type) })} - </Box> - ); + return <>{t('Omnichannel_sorting_disclaimer', { sortingMechanism: t(type) })}</>; }; diff --git a/apps/meteor/client/hooks/useHandleMenuAction.tsx b/apps/meteor/client/hooks/useHandleMenuAction.tsx deleted file mode 100644 index f799ab464e7b8..0000000000000 --- a/apps/meteor/client/hooks/useHandleMenuAction.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; - -import type { GenericMenuItemProps } from '../components/GenericMenuItem'; - -export const useHandleMenuAction = (items: GenericMenuItemProps[]) => { - return useMutableCallback((id) => { - const item = items.find((item) => item.id === id); - item?.onClick && item.onClick(); - }); -}; diff --git a/apps/meteor/client/sidebar/header/UserAvatarButton.tsx b/apps/meteor/client/sidebar/header/UserAvatarButton.tsx deleted file mode 100644 index e22b7c8deca04..0000000000000 --- a/apps/meteor/client/sidebar/header/UserAvatarButton.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { css } from '@rocket.chat/css-in-js'; -import { Box, Dropdown } from '@rocket.chat/fuselage'; -import { useSetting, useUser } from '@rocket.chat/ui-contexts'; -import type { ReactElement } from 'react'; -import React, { memo, useRef } from 'react'; -import { createPortal } from 'react-dom'; - -import { UserStatus } from '../../components/UserStatus'; -import UserAvatar from '../../components/avatar/UserAvatar'; -import UserDropdown from './UserDropdown'; -import { useDropdownVisibility } from './hooks/useDropdownVisibility'; - -const anon = { - _id: '', - username: 'Anonymous', - status: 'online', - statusText: '', - avatarETag: undefined, -} as const; - -const UserAvatarButton = (): ReactElement => { - const user = useUser(); - - const { status = !user ? 'online' : 'offline', username, avatarETag, statusText } = user || anon; - const presenceDisabled = useSetting<boolean>('Presence_broadcast_disabled'); - - // const allowAnonymousRead = useSetting('Accounts_AllowAnonymousRead'); - - const reference = useRef(null); - const target = useRef(null); - const { isVisible, toggle } = useDropdownVisibility({ reference, target }); - - return ( - <> - <Box - position='relative' - ref={reference} - onClick={(): void => toggle()} - className={css` - cursor: pointer; - `} - data-qa='sidebar-avatar-button' - > - {username && <UserAvatar size='x24' username={username} etag={avatarETag} />} - <Box - className={css` - bottom: 0; - right: 0; - `} - justifyContent='center' - alignItems='center' - display='flex' - overflow='hidden' - size={12} - borderWidth='default' - position='absolute' - bg='surface-tint' - borderColor='extra-light' - borderRadius='full' - mie='neg-x2' - mbe='neg-x2' - > - <UserStatus small status={presenceDisabled ? 'disabled' : status} statusText={statusText} /> - </Box> - </Box> - {user && - isVisible && - createPortal( - <Dropdown reference={reference} ref={target}> - <UserDropdown user={user} onClose={(): void => toggle(false)} /> - </Dropdown>, - document.body, - )} - </> - ); -}; - -export default memo(UserAvatarButton); diff --git a/apps/meteor/client/sidebar/header/UserAvatarWithStatus.tsx b/apps/meteor/client/sidebar/header/UserAvatarWithStatus.tsx new file mode 100644 index 0000000000000..f1abaa6a62693 --- /dev/null +++ b/apps/meteor/client/sidebar/header/UserAvatarWithStatus.tsx @@ -0,0 +1,57 @@ +import { css } from '@rocket.chat/css-in-js'; +import { Box } from '@rocket.chat/fuselage'; +import { useSetting, useUser } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { UserStatus } from '../../components/UserStatus'; +import UserAvatar from '../../components/avatar/UserAvatar'; + +const anon = { + _id: '', + username: 'Anonymous', + status: 'online', + statusText: '', + avatarETag: undefined, +} as const; + +const UserAvatarWithStatus = () => { + const user = useUser(); + const presenceDisabled = useSetting<boolean>('Presence_broadcast_disabled'); + + const { status = !user ? 'online' : 'offline', username, avatarETag, statusText } = user || anon; + + return ( + <Box + position='relative' + className={css` + cursor: pointer; + `} + aria-label='User menu' + data-qa='sidebar-avatar-button' + > + {username && <UserAvatar size='x24' username={username} etag={avatarETag} />} + <Box + className={css` + bottom: 0; + right: 0; + `} + justifyContent='center' + alignItems='center' + display='flex' + overflow='hidden' + size={12} + borderWidth='default' + position='absolute' + bg='surface-tint' + borderColor='extra-light' + borderRadius='full' + mie='neg-x2' + mbe='neg-x2' + > + <UserStatus small status={presenceDisabled ? 'disabled' : status} statusText={statusText} /> + </Box> + </Box> + ); +}; + +export default UserAvatarWithStatus; diff --git a/apps/meteor/client/sidebar/header/UserDropdown.tsx b/apps/meteor/client/sidebar/header/UserDropdown.tsx deleted file mode 100644 index 8a75891455da7..0000000000000 --- a/apps/meteor/client/sidebar/header/UserDropdown.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import type { IUser, ValueOf } from '@rocket.chat/core-typings'; -import { UserStatus as UserStatusEnum } from '@rocket.chat/core-typings'; -import { - Box, - Margins, - Option, - OptionColumn, - OptionContent, - OptionDivider, - OptionIcon, - OptionInput, - OptionTitle, - RadioButton, -} from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { useLayout, useRoute, useLogout, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; -import { useThemeMode } from '@rocket.chat/ui-theming/src/hooks/useThemeMode'; -import type { ReactElement } from 'react'; -import React from 'react'; - -import { AccountBox } from '../../../app/ui-utils/client'; -import { userStatus } from '../../../app/user-status/client'; -import { callbacks } from '../../../lib/callbacks'; -import MarkdownText from '../../components/MarkdownText'; -import { UserStatus } from '../../components/UserStatus'; -import UserAvatar from '../../components/avatar/UserAvatar'; -import { useUserDisplayName } from '../../hooks/useUserDisplayName'; -import { imperativeModal } from '../../lib/imperativeModal'; -import { useStatusDisabledModal } from '../../views/admin/customUserStatus/hooks/useStatusDisabledModal'; -import EditStatusModal from './EditStatusModal'; - -const isDefaultStatus = (id: string): boolean => (Object.values(UserStatusEnum) as string[]).includes(id); - -const isDefaultStatusName = (_name: string, id: string): _name is UserStatusEnum => isDefaultStatus(id); - -const setStatus = (status: (typeof userStatus.list)['']): void => { - AccountBox.setStatus(status.statusType, !isDefaultStatus(status.id) ? status.name : ''); - void callbacks.run('userStatusManuallySet', status); -}; - -const translateStatusName = (t: ReturnType<typeof useTranslation>, status: (typeof userStatus.list)['']): string => { - if (isDefaultStatusName(status.name, status.id)) { - return t(status.name as TranslationKey); - } - - return status.name; -}; - -type UserDropdownProps = { - user: Pick<IUser, 'username' | 'name' | 'avatarETag' | 'status' | 'statusText'>; - onClose: () => void; -}; - -const UserDropdown = ({ user, onClose }: UserDropdownProps): ReactElement => { - const t = useTranslation(); - const accountRoute = useRoute('account-index'); - const logout = useLogout(); - const { isMobile } = useLayout(); - const presenceDisabled = useSetting<boolean>('Presence_broadcast_disabled'); - const handleStatusDisabledModal = useStatusDisabledModal(); - - const [selectedTheme, setTheme] = useThemeMode(); - - const { username, avatarETag, status, statusText } = user; - - const displayName = useUserDisplayName(user); - - const filterInvisibleStatus = !useSetting('Accounts_AllowInvisibleStatusOption') - ? (status: ValueOf<(typeof userStatus)['list']>): boolean => status.name !== 'invisible' - : (): boolean => true; - - const handleCustomStatus = useMutableCallback((e) => { - e.preventDefault(); - imperativeModal.open({ - component: EditStatusModal, - props: { userStatus: status, userStatusText: statusText, onClose: imperativeModal.close }, - }); - onClose(); - }); - - const handleMyAccount = useMutableCallback(() => { - accountRoute.push({}); - onClose(); - }); - - const handleLogout = useMutableCallback(() => { - logout(); - onClose(); - }); - - return ( - <Box display='flex' flexDirection='column' w={!isMobile ? '244px' : undefined}> - <Box pi='x12' display='flex' flexDirection='row' alignItems='center'> - <Box mie='x4'> - <UserAvatar size='x36' username={username || ''} etag={avatarETag} /> - </Box> - <Box mis='x4' display='flex' overflow='hidden' flexDirection='column' fontScale='p2' mb='neg-x4' flexGrow={1} flexShrink={1}> - <Box withTruncatedText w='full' display='flex' alignItems='center' flexDirection='row'> - <Margins inline='x4'> - <UserStatus status={presenceDisabled ? 'disabled' : status} /> - <Box is='span' withTruncatedText display='inline-block' fontWeight='700'> - {displayName} - </Box> - </Margins> - </Box> - <Box color='hint'> - <MarkdownText - withTruncatedText - parseEmoji={true} - content={statusText || t(status ?? 'offline')} - variant='inlineWithoutBreaks' - /> - </Box> - </Box> - </Box> - <OptionDivider /> - <OptionTitle>{t('Status')}</OptionTitle> - {presenceDisabled && ( - <Box fontScale='p2' mi='x12' mb='x4'> - <Box mbe='x4'>{t('User_status_disabled')}</Box> - <Box is='a' color='info' onClick={handleStatusDisabledModal}> - {t('Learn_more')} - </Box> - </Box> - )} - {Object.values(userStatus.list) - .filter(filterInvisibleStatus) - .map((status, i) => { - const name = status.localizeName ? translateStatusName(t, status) : status.name; - const modifier = status.statusType || user.status; - - return ( - <Option - key={i} - disabled={presenceDisabled} - onClick={(): void => { - setStatus(status); - onClose(); - }} - > - <OptionColumn> - <UserStatus status={modifier} /> - </OptionColumn> - <OptionContent> - <MarkdownText content={name} parseEmoji={true} variant='inline' /> - </OptionContent> - </Option> - ); - })} - <Option icon='emoji' label={`${t('Custom_Status')}...`} onClick={handleCustomStatus} disabled={presenceDisabled}></Option> - <OptionDivider /> - - <OptionTitle>{t('Theme')}</OptionTitle> - <Option is='label' role='listitem'> - <OptionIcon name='sun' /> - <OptionContent>{t('Theme_light')}</OptionContent> - <OptionInput> - <RadioButton checked={selectedTheme === 'light'} onChange={setTheme('light')} m='x4' /> - </OptionInput> - </Option> - <Option is='label' role='listitem'> - <OptionIcon name='moon' /> - <OptionContent>{t('Theme_dark')}</OptionContent> - <OptionInput> - <RadioButton checked={selectedTheme === 'dark'} onChange={setTheme('dark')} m='x4' /> - </OptionInput> - </Option> - <Option is='label' role='listitem'> - <OptionIcon name='desktop' /> - <OptionContent>{t('Theme_match_system')}</OptionContent> - <OptionInput> - <RadioButton checked={selectedTheme === 'auto'} onChange={setTheme('auto')} m='x4' /> - </OptionInput> - </Option> - <OptionDivider /> - <Option icon='user' label={t('My_Account')} onClick={handleMyAccount}></Option> - <Option icon='sign-out' label={t('Logout')} onClick={handleLogout}></Option> - </Box> - ); -}; - -export default UserDropdown; diff --git a/apps/meteor/client/sidebar/header/UserMenu.tsx b/apps/meteor/client/sidebar/header/UserMenu.tsx new file mode 100644 index 0000000000000..6347f6fad92d0 --- /dev/null +++ b/apps/meteor/client/sidebar/header/UserMenu.tsx @@ -0,0 +1,31 @@ +import type { IUser } from '@rocket.chat/core-typings'; +import React, { useState, memo } from 'react'; + +import GenericMenu from '../../components/GenericMenu/GenericMenu'; +import type { GenericMenuItemProps } from '../../components/GenericMenu/GenericMenuItem'; +import { useHandleMenuAction } from '../../components/GenericMenu/hooks/useHandleMenuAction'; +import UserAvatarWithStatus from './UserAvatarWithStatus'; +import { useUserMenu } from './hooks/useUserMenu'; + +const UserMenu = ({ user }: { user: IUser }) => { + const [isOpen, setIsOpen] = useState(false); + + const sections = useUserMenu(user); + const items = sections.reduce((acc, { items }) => [...acc, ...items], [] as GenericMenuItemProps[]); + + const handleAction = useHandleMenuAction(items, () => setIsOpen(false)); + + return ( + <GenericMenu + icon={<UserAvatarWithStatus />} + selectionMode='multiple' + sections={sections} + title='User menu' + onAction={handleAction} + isOpen={isOpen} + onOpenChange={setIsOpen} + /> + ); +}; + +export default memo(UserMenu); diff --git a/apps/meteor/client/sidebar/header/UserMenuHeader.tsx b/apps/meteor/client/sidebar/header/UserMenuHeader.tsx new file mode 100644 index 0000000000000..b56fe35c7ec89 --- /dev/null +++ b/apps/meteor/client/sidebar/header/UserMenuHeader.tsx @@ -0,0 +1,43 @@ +import type { IUser } from '@rocket.chat/core-typings'; +import { Box, Margins } from '@rocket.chat/fuselage'; +import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import MarkdownText from '../../components/MarkdownText'; +import { UserStatus } from '../../components/UserStatus'; +import UserAvatar from '../../components/avatar/UserAvatar'; +import { useUserDisplayName } from '../../hooks/useUserDisplayName'; + +const UserMenuHeader = ({ user }: { user: IUser }) => { + const t = useTranslation(); + const presenceDisabled = useSetting<boolean>('Presence_broadcast_disabled'); + const displayName = useUserDisplayName(user); + + return ( + <Box display='flex' flexDirection='row' alignItems='center' minWidth='x208' mbe='neg-x4' mbs='neg-x8'> + <Box mie='x4'> + <UserAvatar size='x36' username={user?.username || ''} etag={user?.avatarETag} /> + </Box> + <Box mis='x4' display='flex' overflow='hidden' flexDirection='column' fontScale='p2' mb='neg-x4' flexGrow={1} flexShrink={1}> + <Box withTruncatedText w='full' display='flex' alignItems='center' flexDirection='row'> + <Margins inline='x4'> + <UserStatus status={presenceDisabled ? 'disabled' : user.status} /> + <Box is='span' withTruncatedText display='inline-block' fontWeight='700'> + {displayName} + </Box> + </Margins> + </Box> + <Box color='hint'> + <MarkdownText + withTruncatedText + parseEmoji={true} + content={user?.statusText || t(user?.status ?? 'offline')} + variant='inlineWithoutBreaks' + /> + </Box> + </Box> + </Box> + ); +}; + +export default UserMenuHeader; diff --git a/apps/meteor/client/sidebar/header/actions/Administration.tsx b/apps/meteor/client/sidebar/header/actions/Administration.tsx index cb6921f91dda0..d2e51f1913709 100644 --- a/apps/meteor/client/sidebar/header/actions/Administration.tsx +++ b/apps/meteor/client/sidebar/header/actions/Administration.tsx @@ -3,20 +3,15 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import type { HTMLAttributes, VFC } from 'react'; import React from 'react'; -import GenericMenu from '../../../components/GenericMenu'; -import type { GenericMenuItemProps } from '../../../components/GenericMenuItem'; -import { useHandleMenuAction } from '../../../hooks/useHandleMenuAction'; +import GenericMenu from '../../../components/GenericMenu/GenericMenu'; import { useAdministrationMenu } from './hooks/useAdministrationMenu'; const Administration: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => { const t = useTranslation(); const sections = useAdministrationMenu(); - const items = sections.reduce((acc, { items }) => [...acc, ...items], [] as GenericMenuItemProps[]); - const handleAction = useHandleMenuAction(items); - - return <GenericMenu sections={sections} title={t('Administration')} onAction={handleAction} is={Sidebar.TopBar.Action} {...props} />; + return <GenericMenu sections={sections} title={t('Administration')} is={Sidebar.TopBar.Action} {...props} />; }; export default Administration; diff --git a/apps/meteor/client/sidebar/header/actions/CreateRoom.tsx b/apps/meteor/client/sidebar/header/actions/CreateRoom.tsx index 465bcc63bc054..5289a1a9d8a55 100644 --- a/apps/meteor/client/sidebar/header/actions/CreateRoom.tsx +++ b/apps/meteor/client/sidebar/header/actions/CreateRoom.tsx @@ -3,29 +3,15 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import type { HTMLAttributes, VFC } from 'react'; import React from 'react'; -import GenericMenu from '../../../components/GenericMenu'; -import type { GenericMenuItemProps } from '../../../components/GenericMenuItem'; -import { useHandleMenuAction } from '../../../hooks/useHandleMenuAction'; +import GenericMenu from '../../../components/GenericMenu/GenericMenu'; import { useCreateRoom } from './hooks/useCreateRoomMenu'; const CreateRoom: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => { const t = useTranslation(); const sections = useCreateRoom(); - const items = sections.reduce((acc, { items }) => [...acc, ...items], [] as GenericMenuItemProps[]); - const handleAction = useHandleMenuAction(items); - - return ( - <GenericMenu - icon='edit-rounded' - sections={sections} - onAction={handleAction} - title={t('Create_new')} - is={Sidebar.TopBar.Action} - {...props} - /> - ); + return <GenericMenu icon='edit-rounded' sections={sections} title={t('Create_new')} is={Sidebar.TopBar.Action} {...props} />; }; export default CreateRoom; diff --git a/apps/meteor/client/sidebar/header/actions/Sort.tsx b/apps/meteor/client/sidebar/header/actions/Sort.tsx index 194e920ec4a0d..330ad6b233e6a 100644 --- a/apps/meteor/client/sidebar/header/actions/Sort.tsx +++ b/apps/meteor/client/sidebar/header/actions/Sort.tsx @@ -3,7 +3,7 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; import type { VFC, HTMLAttributes } from 'react'; import React from 'react'; -import GenericMenu from '../../../components/GenericMenu'; +import GenericMenu from '../../../components/GenericMenu/GenericMenu'; import { useSortMenu } from './hooks/useSortMenu'; const Sort: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => { diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx index b32ae66dde134..3c8dada44e51f 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx @@ -7,7 +7,7 @@ import type { AccountBoxItem } from '../../../../../app/ui-utils/client/lib/Acco import type { UpgradeTabVariant } from '../../../../../lib/upgradeTab'; import { getUpgradeTabLabel, isFullyFeature } from '../../../../../lib/upgradeTab'; import Emoji from '../../../../components/Emoji'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; import RegisterWorkspaceModal from '../../../../views/admin/cloud/modals/RegisterWorkspaceModal'; import { useUpgradeTabParams } from '../../../../views/hooks/useUpgradeTabParams'; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx index 66a5befe0f2b3..a6c77c55d2848 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAppsItems.tsx @@ -4,7 +4,7 @@ import React from 'react'; import { triggerActionButtonAction } from '../../../../../app/ui-message/client/ActionManager'; import type { IAppAccountBoxItem } from '../../../../../app/ui-utils/client/lib/AccountBox'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; import { useAppRequestStats } from '../../../../views/marketplace/hooks/useAppRequestStats'; type useAppsItemsProps = { diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAuditItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAuditItems.tsx index 3e9d98d74b188..3ed3ba94dcb99 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useAuditItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAuditItems.tsx @@ -1,6 +1,6 @@ import { useTranslation, useRoute } from '@rocket.chat/ui-contexts'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; type useAuditItemsProps = { showAudit: boolean; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx index 3cdca7adebc41..65dd9e29726ba 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useCreateRoomItems.tsx @@ -1,7 +1,7 @@ import { useTranslation, useSetting, useAtLeastOnePermission } from '@rocket.chat/ui-contexts'; import CreateDiscussion from '../../../../components/CreateDiscussion'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; import { useIsEnterprise } from '../../../../hooks/useIsEnterprise'; import CreateChannelWithData from '../../CreateChannel'; import CreateDirectMessage from '../../CreateDirectMessage'; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useGroupingListItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useGroupingListItems.tsx index 24629be905e38..07223513fae9e 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useGroupingListItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useGroupingListItems.tsx @@ -2,7 +2,7 @@ import { CheckBox } from '@rocket.chat/fuselage'; import { useEndpoint, useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; export const useGroupingListItems = (): GenericMenuItemProps[] => { const t = useTranslation(); diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useMatrixFederationItems.tsx.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useMatrixFederationItems.tsx.tsx index 76cd6d9458af5..b3ac63f137738 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useMatrixFederationItems.tsx.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useMatrixFederationItems.tsx.tsx @@ -1,6 +1,6 @@ import { useTranslation } from '@rocket.chat/ui-contexts'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; import MatrixFederationSearch from '../../MatrixFederationSearch'; import { useCreateRoomModal } from '../../hooks/useCreateRoomModal'; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useSortModeItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useSortModeItems.tsx index c200fefd56898..8adc0580cff84 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useSortModeItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useSortModeItems.tsx @@ -2,45 +2,39 @@ import { RadioButton } from '@rocket.chat/fuselage'; import { useEndpoint, useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; -import { OmnichannelSortingDisclaimer } from '../../../../components/Omnichannel/OmnichannelSortingDisclaimer'; -import { useOmnichannelEnterpriseEnabled } from '../../../../hooks/omnichannel/useOmnichannelEnterpriseEnabled'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; +import { + OmnichannelSortingDisclaimer, + useOmnichannelSortingDisclaimer, +} from '../../../../components/Omnichannel/OmnichannelSortingDisclaimer'; export const useSortModeItems = (): GenericMenuItemProps[] => { const t = useTranslation(); const saveUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); const sidebarSortBy = useUserPreference<'activity' | 'alphabetical'>('sidebarSortby', 'activity'); - const isOmnichannelEnabled = useOmnichannelEnterpriseEnabled(); - - const omniDisclaimerItem = { - id: 'sortByList', - content: <OmnichannelSortingDisclaimer id='sortByList' />, - }; + const isOmnichannelEnabled = useOmnichannelSortingDisclaimer(); const useHandleChange = (value: 'alphabetical' | 'activity'): (() => void) => useCallback(() => saveUserPreferences({ data: { sidebarSortby: value } }), [value]); const setToAlphabetical = useHandleChange('alphabetical'); const setToActivity = useHandleChange('activity'); - const items: GenericMenuItemProps[] = [ + + return [ { id: 'activity', content: t('Activity'), icon: 'clock', addon: <RadioButton mi='x16' onChange={setToActivity} checked={sidebarSortBy === 'activity'} />, + description: sidebarSortBy === 'activity' && isOmnichannelEnabled && <OmnichannelSortingDisclaimer />, }, { id: 'name', content: t('Name'), icon: 'sort-az', addon: <RadioButton mi='x16' onChange={setToAlphabetical} checked={sidebarSortBy === 'alphabetical'} />, + description: sidebarSortBy === 'alphabetical' && isOmnichannelEnabled && <OmnichannelSortingDisclaimer />, }, ]; - - if (isOmnichannelEnabled) { - items.push(omniDisclaimerItem); - } - - return items; }; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useViewModeItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useViewModeItems.tsx index 92da7101baf5a..17f052fefca16 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useViewModeItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useViewModeItems.tsx @@ -2,7 +2,7 @@ import { RadioButton, ToggleSwitch } from '@rocket.chat/fuselage'; import { useEndpoint, useUserPreference, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; -import type { GenericMenuItemProps } from '../../../../components/GenericMenuItem'; +import type { GenericMenuItemProps } from '../../../../components/GenericMenu/GenericMenuItem'; export const useViewModeItems = (): GenericMenuItemProps[] => { const t = useTranslation(); diff --git a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx new file mode 100644 index 0000000000000..b46923a562e80 --- /dev/null +++ b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx @@ -0,0 +1,33 @@ +import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; +import { useLogout, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; + +import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; + +export const useAccountItems = (): GenericMenuItemProps[] => { + const t = useTranslation(); + const accountRoute = useRoute('account-index'); + const logout = useLogout(); + + const handleMyAccount = useMutableCallback(() => { + accountRoute.push({}); + }); + + const handleLogout = useMutableCallback(() => { + logout(); + }); + + return [ + { + id: 'my-account', + icon: 'user', + content: t('My_Account'), + onClick: handleMyAccount, + }, + { + id: 'logout', + icon: 'sign-out', + content: t('Logout'), + onClick: handleLogout, + }, + ]; +}; diff --git a/apps/meteor/client/sidebar/header/hooks/useCustomStatusModalHandler.tsx b/apps/meteor/client/sidebar/header/hooks/useCustomStatusModalHandler.tsx new file mode 100644 index 0000000000000..f0f863f8efaba --- /dev/null +++ b/apps/meteor/client/sidebar/header/hooks/useCustomStatusModalHandler.tsx @@ -0,0 +1,14 @@ +import { useSetModal, useUser } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import EditStatusModal from '../EditStatusModal'; + +export const useCustomStatusModalHandler = () => { + const user = useUser(); + const setModal = useSetModal(); + + return () => { + const handleModalClose = () => setModal(null); + setModal(<EditStatusModal userStatus={user?.status} userStatusText={user?.statusText} onClose={handleModalClose} />); + }; +}; diff --git a/apps/meteor/client/sidebar/header/hooks/useStatusItems.tsx b/apps/meteor/client/sidebar/header/hooks/useStatusItems.tsx new file mode 100644 index 0000000000000..8621846456f19 --- /dev/null +++ b/apps/meteor/client/sidebar/header/hooks/useStatusItems.tsx @@ -0,0 +1,77 @@ +import type { IUser, ValueOf } from '@rocket.chat/core-typings'; +import { UserStatus as UserStatusEnum } from '@rocket.chat/core-typings'; +import { Box } from '@rocket.chat/fuselage'; +import type { TranslationKey } from '@rocket.chat/ui-contexts'; +import { useSetting, useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { AccountBox } from '../../../../app/ui-utils/client'; +import { userStatus } from '../../../../app/user-status/client'; +import { callbacks } from '../../../../lib/callbacks'; +import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; +import MarkdownText from '../../../components/MarkdownText'; +import { UserStatus } from '../../../components/UserStatus'; +import { useStatusDisabledModal } from '../../../views/admin/customUserStatus/hooks/useStatusDisabledModal'; +import { useCustomStatusModalHandler } from './useCustomStatusModalHandler'; + +const isDefaultStatus = (id: string): boolean => (Object.values(UserStatusEnum) as string[]).includes(id); +const isDefaultStatusName = (_name: string, id: string): _name is UserStatusEnum => isDefaultStatus(id); +const translateStatusName = (t: ReturnType<typeof useTranslation>, status: (typeof userStatus.list)['']): string => { + if (isDefaultStatusName(status.name, status.id)) { + return t(status.name as TranslationKey); + } + + return status.name; +}; + +export const useStatusItems = (user: IUser): GenericMenuItemProps[] => { + const t = useTranslation(); + const presenceDisabled = useSetting<boolean>('Presence_broadcast_disabled'); + + const setStatus = (status: (typeof userStatus.list)['']): void => { + AccountBox.setStatus(status.statusType, !isDefaultStatus(status.id) ? status.name : ''); + void callbacks.run('userStatusManuallySet', status); + }; + + const filterInvisibleStatus = !useSetting('Accounts_AllowInvisibleStatusOption') + ? (status: ValueOf<(typeof userStatus)['list']>): boolean => status.name !== 'invisible' + : (): boolean => true; + + const handleCustomStatus = useCustomStatusModalHandler(); + + const handleStatusDisabledModal = useStatusDisabledModal(); + + const presenceDisabledItem = { + id: 'presence-disabled', + content: ( + <Box fontScale='p2'> + <Box mbe='x4' wordBreak='break-word' style={{ whiteSpace: 'normal' }}> + {t('User_status_disabled')} + </Box> + <Box is='a' color='info' onClick={handleStatusDisabledModal}> + {t('Learn_more')} + </Box> + </Box> + ), + }; + + const statusItems = Object.values(userStatus.list) + .filter(filterInvisibleStatus) + .map((status) => { + const name = status.localizeName ? translateStatusName(t, status) : status.name; + const modifier = status.statusType || user?.status; + return { + id: status.id, + status: <UserStatus status={modifier} />, + content: <MarkdownText content={name} parseEmoji={true} variant='inline' />, + onClick: () => setStatus(status), + disabled: presenceDisabled, + }; + }); + + return [ + ...(presenceDisabled ? [presenceDisabledItem] : []), + ...statusItems, + { id: 'custom-status', icon: 'emoji', content: t('Custom_Status'), onClick: handleCustomStatus, disabled: presenceDisabled }, + ]; +}; diff --git a/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx b/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx new file mode 100644 index 0000000000000..d571acde1624c --- /dev/null +++ b/apps/meteor/client/sidebar/header/hooks/useThemeItems.tsx @@ -0,0 +1,33 @@ +import { RadioButton } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useThemeMode } from '@rocket.chat/ui-theming/src/hooks/useThemeMode'; +import React from 'react'; + +import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; + +export const useThemeItems = (): GenericMenuItemProps[] => { + const t = useTranslation(); + + const [selectedTheme, setTheme] = useThemeMode(); + + return [ + { + id: 'light', + icon: 'sun', + content: t('Theme_light'), + addon: <RadioButton checked={selectedTheme === 'light'} onChange={setTheme('light')} m='x4' />, + }, + { + id: 'dark', + icon: 'moon', + content: t('Theme_dark'), + addon: <RadioButton checked={selectedTheme === 'dark'} onChange={setTheme('dark')} m='x4' />, + }, + { + id: 'auto', + icon: 'desktop', + content: t('Theme_match_system'), + addon: <RadioButton checked={selectedTheme === 'auto'} onChange={setTheme('auto')} m='x4' />, + }, + ]; +}; diff --git a/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx b/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx new file mode 100644 index 0000000000000..4458deb26f421 --- /dev/null +++ b/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx @@ -0,0 +1,31 @@ +import type { IUser } from '@rocket.chat/core-typings'; +import React from 'react'; + +import UserMenuHeader from '../UserMenuHeader'; +import { useAccountItems } from './useAccountItems'; +import { useStatusItems } from './useStatusItems'; +import { useThemeItems } from './useThemeItems'; + +export const useUserMenu = (user: IUser) => { + const statusItems = useStatusItems(user); + const themeItems = useThemeItems(); + const accountItems = useAccountItems(); + + return [ + { + title: <UserMenuHeader user={user} />, + items: [], + }, + { + title: 'Status', + items: statusItems, + }, + { + title: 'Theme', + items: themeItems, + }, + { + items: accountItems, + }, + ]; +}; diff --git a/apps/meteor/client/sidebar/header/index.tsx b/apps/meteor/client/sidebar/header/index.tsx index 79790341a24ea..1027474aaec22 100644 --- a/apps/meteor/client/sidebar/header/index.tsx +++ b/apps/meteor/client/sidebar/header/index.tsx @@ -3,7 +3,8 @@ import { useUser, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; -import UserAvatarButton from './UserAvatarButton'; +import UserAvatarWithStatus from './UserAvatarWithStatus'; +import UserMenu from './UserMenu'; import Administration from './actions/Administration'; import CreateRoom from './actions/CreateRoom'; import Directory from './actions/Directory'; @@ -24,7 +25,7 @@ const HeaderWithData = (): ReactElement => { style: { flexShrink: 0 }, }} > - <UserAvatarButton /> + {user ? <UserMenu user={user} /> : <UserAvatarWithStatus />} <Sidebar.TopBar.Actions> <Home title={t('Home')} /> <Search title={t('Search')} /> diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts index e3ed090689c5f..e413caaa54362 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-auto-onhold-chat-closing.spec.ts @@ -38,7 +38,7 @@ test.describe('omnichannel-auto-onhold-chat-closing', () => { test.beforeEach(async ({ page, api }) => { // make "user-1" online - await agent.poHomeChannel.sidenav.switchStatus('online'); + await agent.poHomeChannel.sidenav.switchStatus('Online online'); // start a new chat for each test newVisitor = { diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-changing-room-priority-and-sla.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-changing-room-priority-and-sla.spec.ts index d4cc6484975e7..dc9f745c2740e 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-changing-room-priority-and-sla.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-changing-room-priority-and-sla.spec.ts @@ -41,7 +41,7 @@ test.describe('omnichannel-changing-room-priority-and-sla', () => { const { page } = await createAuxContext(browser, Users.admin); agent = { page, poHomeChannel: new HomeChannel(page) }; - await agent.poHomeChannel.sidenav.switchStatus('online'); + await agent.poHomeChannel.sidenav.switchStatus('Online online'); }); test.afterAll(async ({ api }) => { diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts index 4738158ca23c2..78e0a1362da9a 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-takeChat.spec.ts @@ -35,7 +35,7 @@ test.describe('omnichannel-takeChat', () => { test.beforeEach(async ({ page, api }) => { // make "user-1" online - await agent.poHomeChannel.sidenav.switchStatus('online'); + await agent.poHomeChannel.sidenav.switchStatus('Online online'); // start a new chat for each test newVisitor = { @@ -62,7 +62,7 @@ test.describe('omnichannel-takeChat', () => { test('expect "user1" to not able able to take chat from queue in-case its user status is offline', async () => { // make "user-1" offline - await agent.poHomeChannel.sidenav.switchStatus('offline'); + await agent.poHomeChannel.sidenav.switchStatus('Offline offline'); await agent.poHomeChannel.sidenav.openQueuedOmnichannelChat(newVisitor.name); await expect(agent.poHomeChannel.content.takeOmnichannelChatButton).toBeVisible(); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-transfer-to-another-agents.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-transfer-to-another-agents.spec.ts index 3c74065a9a84e..6e3e274bb4369 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-transfer-to-another-agents.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-transfer-to-another-agents.spec.ts @@ -40,8 +40,8 @@ test.describe('omnichannel-transfer-to-another-agent', () => { }); test.beforeEach(async ({ page, api }) => { // make "user-1" online & "user-2" offline so that chat can be automatically routed to "user-1" - await agent1.poHomeOmnichannel.sidenav.switchStatus('online'); - await agent2.poHomeOmnichannel.sidenav.switchStatus('offline'); + await agent1.poHomeOmnichannel.sidenav.switchStatus('Online online'); + await agent2.poHomeOmnichannel.sidenav.switchStatus('Offline offline'); // start a new chat for each test newVisitor = { @@ -62,7 +62,7 @@ test.describe('omnichannel-transfer-to-another-agent', () => { }); await test.step('Expect to not be able to transfer chat to "user-2" when that user is offline', async () => { - await agent2.poHomeOmnichannel.sidenav.switchStatus('offline'); + await agent2.poHomeOmnichannel.sidenav.switchStatus('Offline offline'); await agent1.poHomeOmnichannel.content.btnForwardChat.click(); await agent1.poHomeOmnichannel.content.inputModalAgentUserName.type('user2'); @@ -72,7 +72,7 @@ test.describe('omnichannel-transfer-to-another-agent', () => { }); await test.step('Expect to be able to transfer an omnichannel to conversation to agent 2 as agent 1 when agent 2 is online', async () => { - await agent2.poHomeOmnichannel.sidenav.switchStatus('online'); + await agent2.poHomeOmnichannel.sidenav.switchStatus('Online online'); await agent1.poHomeOmnichannel.sidenav.getSidebarItemByName(newVisitor.name).click(); await agent1.poHomeOmnichannel.content.btnForwardChat.click(); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts index 8b721895cc5cf..3d2ddd3921d6f 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -59,9 +59,9 @@ export class HomeSidenav { await this.page.locator('//*[contains(@class, "rcx-option__content") and contains(text(), "Logout")]').click(); } - async switchStatus(status: 'offline' | 'online'): Promise<void> { + async switchStatus(status: 'Offline offline' | 'Online online'): Promise<void> { await this.page.locator('[data-qa="sidebar-avatar-button"]').click(); - await this.page.locator(`//li[@class="rcx-option"]//div[contains(text(), "${status}")]`).click(); + await this.page.locator(`role=menuitemcheckbox[name="${status}"]`).click(); } async openChat(name: string): Promise<void> { From 93fff202ee031394d7652a00a8d25b651c37db9c Mon Sep 17 00:00:00 2001 From: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Date: Tue, 4 Jul 2023 20:59:53 -0300 Subject: [PATCH 42/79] fix(meteor): `room-opened` event not dispatching when navigating cached rooms (#29718) --- .changeset/hip-hornets-fail.md | 5 +++++ apps/meteor/client/views/room/hooks/useOpenRoom.ts | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 .changeset/hip-hornets-fail.md diff --git a/.changeset/hip-hornets-fail.md b/.changeset/hip-hornets-fail.md new file mode 100644 index 0000000000000..5bd0c0d15199b --- /dev/null +++ b/.changeset/hip-hornets-fail.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fixed `room-opened` event not dispatching when navigating cached rooms diff --git a/apps/meteor/client/views/room/hooks/useOpenRoom.ts b/apps/meteor/client/views/room/hooks/useOpenRoom.ts index 8b9f5fcdf75ab..288767ccde532 100644 --- a/apps/meteor/client/views/room/hooks/useOpenRoom.ts +++ b/apps/meteor/client/views/room/hooks/useOpenRoom.ts @@ -1,6 +1,7 @@ import type { IRoom, RoomType } from '@rocket.chat/core-typings'; import { useMethod, useRoute, useSetting, useUser } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; +import { useRef } from 'react'; import { ChatRoom, ChatSubscription } from '../../../../app/models/client'; import { LegacyRoomManager } from '../../../../app/ui-utils/client'; @@ -20,6 +21,8 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st const openRoom = useMethod('openRoom'); const directRoute = useRoute('direct'); + const unsubscribeFromRoomOpenedEvent = useRef<() => void>(() => undefined); + return useQuery( // we need to add uid and username here because `user` is not loaded all at once (see UserProvider -> Meteor.user()) ['rooms', { type, reference }, { uid: user?._id, username: user?.username }] as const, @@ -63,14 +66,15 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st throw new RoomNotFoundError(undefined, { rid: room._id }); } + unsubscribeFromRoomOpenedEvent.current(); + unsubscribeFromRoomOpenedEvent.current = RoomManager.once('opened', () => fireGlobalEvent('room-opened', omit(room, 'usernames'))); + LegacyRoomManager.open({ typeName: type + reference, rid: room._id }); if (room._id === RoomManager.opened) { return { rid: room._id }; } - fireGlobalEvent('room-opened', omit(room, 'usernames')); - // update user's room subscription const sub = ChatSubscription.findOne({ rid: room._id }); if (sub && !sub.open) { From bec9dfdf88a80716cb7b32a6d484d4294ecf984c Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Wed, 5 Jul 2023 11:00:32 -0300 Subject: [PATCH 43/79] regression: VoIP call UI becoming non responsive (#29717) --- .../providers/CallProvider/CallProvider.tsx | 51 ++++--------------- .../CallProvider/hooks/useVoipSounds.ts | 29 +++++++++++ 2 files changed, 39 insertions(+), 41 deletions(-) create mode 100644 apps/meteor/client/providers/CallProvider/hooks/useVoipSounds.ts diff --git a/apps/meteor/client/providers/CallProvider/CallProvider.tsx b/apps/meteor/client/providers/CallProvider/CallProvider.tsx index 5464e9707e461..22ef506ad481a 100644 --- a/apps/meteor/client/providers/CallProvider/CallProvider.tsx +++ b/apps/meteor/client/providers/CallProvider/CallProvider.tsx @@ -1,12 +1,4 @@ -import type { - IVoipRoom, - IUser, - VoipEventDataSignature, - ICallerInfo, - ICallDetails, - ILivechatVisitor, - Serialized, -} from '@rocket.chat/core-typings'; +import type { IVoipRoom, VoipEventDataSignature, ICallerInfo, ICallDetails, ILivechatVisitor, Serialized } from '@rocket.chat/core-typings'; import { VoipClientEvents, isVoipEventAgentCalled, @@ -37,8 +29,6 @@ import React, { useMemo, useRef, useCallback, useEffect, useState } from 'react' import { createPortal } from 'react-dom'; import type { OutgoingByeRequest } from 'sip.js/lib/core'; -import { CustomSounds } from '../../../app/custom-sounds/client'; -import { getUserPreference } from '../../../app/utils/client'; import { isOutboundClient, useVoipClient } from '../../../ee/client/hooks/useVoipClient'; import { parseOutboundPhoneNumber } from '../../../ee/client/lib/voip/parseOutboundPhoneNumber'; import { WrapUpCallModal } from '../../../ee/client/voip/components/modals/WrapUpCallModal'; @@ -47,30 +37,7 @@ import { CallContext, useIsVoipEnterprise } from '../../contexts/CallContext'; import { useDialModal } from '../../hooks/useDialModal'; import { roomCoordinator } from '../../lib/rooms/roomCoordinator'; import type { QueueAggregator } from '../../lib/voip/QueueAggregator'; - -type VoipSound = 'telephone' | 'outbound-call-ringing' | 'call-ended'; - -const startRingback = (user: IUser, soundId: VoipSound, loop = true): void => { - const audioVolume = getUserPreference(user, 'notificationsSoundVolume', 100) as number; - CustomSounds.play(soundId, { - volume: Number((audioVolume / 100).toPrecision(2)), - loop, - }); -}; - -const stopRingBackById = (soundId: VoipSound): void => { - const sound = CustomSounds.getSound(soundId); - CustomSounds.pause(soundId); - CustomSounds.remove(sound); -}; - -const stopTelephoneRingback = (): void => stopRingBackById('telephone'); -const stopOutboundCallRinging = (): void => stopRingBackById('outbound-call-ringing'); - -const stopAllRingback = (): void => { - stopTelephoneRingback(); - stopOutboundCallRinging(); -}; +import { useVoipSounds } from './hooks/useVoipSounds'; type NetworkState = 'online' | 'offline'; @@ -103,6 +70,8 @@ export const CallProvider: FC = ({ children }) => { const { openDialModal } = useDialModal(); + const voipSounds = useVoipSounds(); + const closeRoom = useCallback( async (data = {}): Promise<void> => { roomInfo && @@ -359,7 +328,7 @@ export const CallProvider: FC = ({ children }) => { if (!callDetails.callInfo) { return; } - stopAllRingback(); + voipSounds.stopAll(); if (callDetails.userState !== UserState.UAC) { return; } @@ -400,16 +369,16 @@ export const CallProvider: FC = ({ children }) => { }; const onRinging = (): void => { - startRingback(user, 'outbound-call-ringing'); + voipSounds.play('outbound-call-ringing'); }; const onIncomingCallRinging = (): void => { - startRingback(user, 'telephone'); + voipSounds.play('telephone'); }; const onCallTerminated = (): void => { - startRingback(user, 'call-ended', false); - stopAllRingback(); + voipSounds.play('call-ended', false); + voipSounds.stopAll(); }; const onCallFailed = (reason: 'Not Found' | 'Address Incomplete' | 'Request Terminated' | string): void => { @@ -458,7 +427,7 @@ export const CallProvider: FC = ({ children }) => { result.voipClient?.off('callfailed', onCallFailed); } }; - }, [createRoom, dispatchEvent, networkStatus, openDialModal, result.voipClient, t, user]); + }, [createRoom, dispatchEvent, networkStatus, openDialModal, result.voipClient, voipSounds, t, user]); const contextValue: CallContextValue = useMemo(() => { if (!voipEnabled) { diff --git a/apps/meteor/client/providers/CallProvider/hooks/useVoipSounds.ts b/apps/meteor/client/providers/CallProvider/hooks/useVoipSounds.ts new file mode 100644 index 0000000000000..44e9f19e72e66 --- /dev/null +++ b/apps/meteor/client/providers/CallProvider/hooks/useVoipSounds.ts @@ -0,0 +1,29 @@ +import { useCustomSound, useUser } from '@rocket.chat/ui-contexts'; +import { useMemo } from 'react'; + +import { getUserPreference } from '../../../../app/utils/client'; + +type VoipSound = 'telephone' | 'outbound-call-ringing' | 'call-ended'; + +export const useVoipSounds = () => { + const { play, pause } = useCustomSound(); + const user = useUser(); + + return useMemo( + () => ({ + play: (soundId: VoipSound, loop = true) => { + const audioVolume = getUserPreference(user, 'notificationsSoundVolume', 100) as number; + play(soundId, { + volume: Number((audioVolume / 100).toPrecision(2)), + loop, + }); + }, + stop: (soundId: VoipSound) => pause(soundId), + stopAll: () => { + pause('telephone'); + pause('outbound-call-ringing'); + }, + }), + [play, pause, user], + ); +}; From e2d6aefc41d04fc477c60562f281b6adf743f865 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Wed, 5 Jul 2023 11:46:20 -0300 Subject: [PATCH 44/79] regression: Livechat not unsubscribing after the chat is closed (#29722) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../app/livechat/server/roomAccessValidator.compatibility.ts | 2 +- packages/ddp-client/src/livechat/LivechatClientImpl.ts | 5 +++++ packages/livechat/src/lib/room.js | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/livechat/server/roomAccessValidator.compatibility.ts b/apps/meteor/app/livechat/server/roomAccessValidator.compatibility.ts index 0bad086e738b7..5da49ccd19949 100644 --- a/apps/meteor/app/livechat/server/roomAccessValidator.compatibility.ts +++ b/apps/meteor/app/livechat/server/roomAccessValidator.compatibility.ts @@ -35,7 +35,7 @@ export const validators: OmnichannelRoomAccessValidator[] = [ } } - return extraData?.visitorToken && room.v && room.v.token === extraData.visitorToken; + return extraData?.visitorToken && room.v && room.v.token === extraData.visitorToken && room.open === true; }, async function (room, user) { if (!user?._id) { diff --git a/packages/ddp-client/src/livechat/LivechatClientImpl.ts b/packages/ddp-client/src/livechat/LivechatClientImpl.ts index 9274c25c5c269..8005528b35c8f 100644 --- a/packages/ddp-client/src/livechat/LivechatClientImpl.ts +++ b/packages/ddp-client/src/livechat/LivechatClientImpl.ts @@ -359,6 +359,11 @@ export class LivechatClientImpl extends DDPSDK implements LivechatStream, Livech return this.rest.put(`/v1/livechat/message/${id}`, params); } + unsubscribeAll(): Promise<unknown> { + const subscriptions = Array.from(this.client.subscriptions.keys()); + return Promise.all(subscriptions.map((subscription) => this.client.unsubscribe(subscription))); + } + static create(url: string, retryOptions = { retryCount: 3, retryTime: 10000 }): LivechatClientImpl { // TODO: Decide what to do with the EJSON objects const ddp = new DDPDispatcher(); diff --git a/packages/livechat/src/lib/room.js b/packages/livechat/src/lib/room.js index d80cbd4da88f3..adf727248ad65 100644 --- a/packages/livechat/src/lib/room.js +++ b/packages/livechat/src/lib/room.js @@ -17,6 +17,8 @@ import { handleTranscript } from './transcript'; const commands = new Commands(); export const closeChat = async ({ transcriptRequested } = {}) => { + Livechat.unsubscribeAll(); + if (!transcriptRequested) { await handleTranscript(); } From dbdf45b0e59c81582274b640c286c8240aa2beda Mon Sep 17 00:00:00 2001 From: Tiago Evangelista Pinto <tiago.evangelista@rocket.chat> Date: Wed, 5 Jul 2023 12:01:53 -0300 Subject: [PATCH 45/79] feat: Introduce contextualBar surface renderer for UiKit blocks (#29697) --- .changeset/popular-donkeys-laugh.md | 7 ++ .../views/room/contextualBar/Apps/Apps.tsx | 10 +-- .../src/contexts/SurfaceContext.ts | 7 +- .../src/contexts/kitContext.ts | 2 +- .../src/surfaces/ContextualBarSurface.tsx | 18 +++++ .../FuselageContextualBarRenderer.tsx | 15 +++++ .../fuselage-ui-kit/src/surfaces/index.ts | 7 ++ .../src/utils/UiKitComponent.tsx | 13 +++- packages/uikit-playground/package.json | 1 + .../Display/Surface/ContextualBarSurface.tsx | 67 +++++++++++++++++++ .../Preview/Display/Surface/Surface.tsx | 2 + .../src/Components/SurfaceSelect/options.ts | 1 + yarn.lock | 14 ++++ 13 files changed, 155 insertions(+), 9 deletions(-) create mode 100644 .changeset/popular-donkeys-laugh.md create mode 100644 packages/fuselage-ui-kit/src/surfaces/ContextualBarSurface.tsx create mode 100644 packages/fuselage-ui-kit/src/surfaces/FuselageContextualBarRenderer.tsx create mode 100644 packages/uikit-playground/src/Components/Preview/Display/Surface/ContextualBarSurface.tsx diff --git a/.changeset/popular-donkeys-laugh.md b/.changeset/popular-donkeys-laugh.md new file mode 100644 index 0000000000000..4fed70427f7aa --- /dev/null +++ b/.changeset/popular-donkeys-laugh.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/fuselage-ui-kit": minor +"@rocket.chat/uikit-playground": minor +--- + +feat: Introduce contextualBar surface renderer for UiKit blocks diff --git a/apps/meteor/client/views/room/contextualBar/Apps/Apps.tsx b/apps/meteor/client/views/room/contextualBar/Apps/Apps.tsx index 1c353ff9a3233..0c83a4097016e 100644 --- a/apps/meteor/client/views/room/contextualBar/Apps/Apps.tsx +++ b/apps/meteor/client/views/room/contextualBar/Apps/Apps.tsx @@ -1,6 +1,6 @@ import type { IUIKitSurface } from '@rocket.chat/apps-engine/definition/uikit'; import { ButtonGroup, Button, Box, Avatar } from '@rocket.chat/fuselage'; -import { UiKitComponent, UiKitModal, modalParser } from '@rocket.chat/fuselage-ui-kit'; +import { UiKitComponent, UiKitContextualBar, contextualBarParser } from '@rocket.chat/fuselage-ui-kit'; import type { LayoutBlock } from '@rocket.chat/ui-kit'; import { BlockContext } from '@rocket.chat/ui-kit'; import React from 'react'; @@ -27,24 +27,24 @@ const Apps = ({ view, onSubmit, onClose, onCancel, appId }: AppsProps): JSX.Elem <> <ContextualbarHeader> <Avatar url={getURL(`/api/apps/${appId}/icon`)} /> - <ContextualbarTitle>{modalParser.text(view.title, BlockContext.NONE, 0)}</ContextualbarTitle> + <ContextualbarTitle>{contextualBarParser.text(view.title, BlockContext.NONE, 0)}</ContextualbarTitle> {onClose && <ContextualbarClose onClick={onClose} />} </ContextualbarHeader> <ContextualbarScrollableContent> <Box is='form' method='post' action='#' onSubmit={onSubmit}> - <UiKitComponent render={UiKitModal} blocks={view.blocks as LayoutBlock[]} /> + <UiKitComponent render={UiKitContextualBar} blocks={view.blocks as LayoutBlock[]} /> </Box> </ContextualbarScrollableContent> <ContextualbarFooter> <ButtonGroup align='end'> {view.close && ( <Button danger={view.close.style === 'danger'} onClick={onCancel}> - {modalParser.text(view.close.text, BlockContext.NONE, 0)} + {contextualBarParser.text(view.close.text, BlockContext.NONE, 0)} </Button> )} {view.submit && ( <Button {...getButtonStyle(view)} onClick={onSubmit}> - {modalParser.text(view.submit.text, BlockContext.NONE, 1)} + {contextualBarParser.text(view.submit.text, BlockContext.NONE, 1)} </Button> )} </ButtonGroup> diff --git a/packages/fuselage-ui-kit/src/contexts/SurfaceContext.ts b/packages/fuselage-ui-kit/src/contexts/SurfaceContext.ts index 62bd57bd4bf9d..21d56b91e4574 100644 --- a/packages/fuselage-ui-kit/src/contexts/SurfaceContext.ts +++ b/packages/fuselage-ui-kit/src/contexts/SurfaceContext.ts @@ -1,6 +1,11 @@ import { createContext, useContext } from 'react'; -type SurfaceContextValue = 'attachment' | 'banner' | 'message' | 'modal'; +type SurfaceContextValue = + | 'attachment' + | 'banner' + | 'message' + | 'modal' + | 'contextualBar'; export const SurfaceContext = createContext<SurfaceContextValue>('message'); diff --git a/packages/fuselage-ui-kit/src/contexts/kitContext.ts b/packages/fuselage-ui-kit/src/contexts/kitContext.ts index 6eebb8cea7a68..f4a0687e5af08 100644 --- a/packages/fuselage-ui-kit/src/contexts/kitContext.ts +++ b/packages/fuselage-ui-kit/src/contexts/kitContext.ts @@ -49,6 +49,6 @@ export const useUiKitStateValue = <T extends string | number | undefined>( return { value: (values && (values[actionId]?.value as T)) ?? initialValue, - error: errors && errors[actionId], + error: errors?.[actionId], }; }; diff --git a/packages/fuselage-ui-kit/src/surfaces/ContextualBarSurface.tsx b/packages/fuselage-ui-kit/src/surfaces/ContextualBarSurface.tsx new file mode 100644 index 0000000000000..b419dea19e8a2 --- /dev/null +++ b/packages/fuselage-ui-kit/src/surfaces/ContextualBarSurface.tsx @@ -0,0 +1,18 @@ +import { Margins } from '@rocket.chat/fuselage'; +import type { ReactElement, ReactNode } from 'react'; + +import { Surface } from './Surface'; + +type ContextualBarSurfaceProps = { + children?: ReactNode; +}; + +const ContextualBarSurface = ({ + children, +}: ContextualBarSurfaceProps): ReactElement => ( + <Surface type='contextualBar'> + <Margins blockEnd='x16'>{children}</Margins> + </Surface> +); + +export default ContextualBarSurface; diff --git a/packages/fuselage-ui-kit/src/surfaces/FuselageContextualBarRenderer.tsx b/packages/fuselage-ui-kit/src/surfaces/FuselageContextualBarRenderer.tsx new file mode 100644 index 0000000000000..ebe6453ba3608 --- /dev/null +++ b/packages/fuselage-ui-kit/src/surfaces/FuselageContextualBarRenderer.tsx @@ -0,0 +1,15 @@ +import { FuselageSurfaceRenderer } from './FuselageSurfaceRenderer'; + +export class FuselageContextualBarSurfaceRenderer extends FuselageSurfaceRenderer { + public constructor() { + super([ + 'actions', + 'context', + 'divider', + 'image', + 'input', + 'section', + 'preview', + ]); + } +} diff --git a/packages/fuselage-ui-kit/src/surfaces/index.ts b/packages/fuselage-ui-kit/src/surfaces/index.ts index 25114be862d0f..489fe921ddb59 100644 --- a/packages/fuselage-ui-kit/src/surfaces/index.ts +++ b/packages/fuselage-ui-kit/src/surfaces/index.ts @@ -5,11 +5,14 @@ import ModalSurface from './ModalSurface'; import { createSurfaceRenderer } from './createSurfaceRenderer'; import { FuselageMessageSurfaceRenderer } from './MessageSurfaceRenderer'; import { FuselageModalSurfaceRenderer } from './FuselageModalSurfaceRenderer'; +import { FuselageContextualBarSurfaceRenderer } from './FuselageContextualBarRenderer'; +import ContextualBarSurface from './ContextualBarSurface'; // export const attachmentParser = new FuselageSurfaceRenderer(); export const bannerParser = new FuselageSurfaceRenderer(); export const messageParser = new FuselageMessageSurfaceRenderer(); export const modalParser = new FuselageModalSurfaceRenderer(); +export const contextualBarParser = new FuselageContextualBarSurfaceRenderer(); // export const UiKitAttachment = createSurfaceRenderer(AttachmentSurface, attachmentParser); export const UiKitBanner = createSurfaceRenderer(BannerSurface, bannerParser); @@ -18,3 +21,7 @@ export const UiKitMessage = createSurfaceRenderer( messageParser ); export const UiKitModal = createSurfaceRenderer(ModalSurface, modalParser); +export const UiKitContextualBar = createSurfaceRenderer( + ContextualBarSurface, + contextualBarParser +); diff --git a/packages/fuselage-ui-kit/src/utils/UiKitComponent.tsx b/packages/fuselage-ui-kit/src/utils/UiKitComponent.tsx index f43057a6abc5d..3d1c5a12afeee 100644 --- a/packages/fuselage-ui-kit/src/utils/UiKitComponent.tsx +++ b/packages/fuselage-ui-kit/src/utils/UiKitComponent.tsx @@ -1,10 +1,19 @@ import type * as UiKit from '@rocket.chat/ui-kit'; import type { ReactElement } from 'react'; -import type { UiKitBanner, UiKitMessage, UiKitModal } from '../surfaces'; +import type { + UiKitBanner, + UiKitContextualBar, + UiKitMessage, + UiKitModal, +} from '../surfaces'; type UiKitComponentProps = { - render: typeof UiKitBanner | typeof UiKitMessage | typeof UiKitModal; + render: + | typeof UiKitBanner + | typeof UiKitMessage + | typeof UiKitModal + | typeof UiKitContextualBar; blocks: UiKit.LayoutBlock[]; }; diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json index a309352c04f44..c60b02b9ca00a 100644 --- a/packages/uikit-playground/package.json +++ b/packages/uikit-playground/package.json @@ -26,6 +26,7 @@ "@rocket.chat/ui-contexts": "workspace:~", "codemirror": "^6.0.1", "eslint4b-prebuilt": "^6.7.2", + "rc-scrollbars": "^1.1.6", "react": "^17.0.2", "react-beautiful-dnd": "^13.1.1", "react-dom": "^17.0.2", diff --git a/packages/uikit-playground/src/Components/Preview/Display/Surface/ContextualBarSurface.tsx b/packages/uikit-playground/src/Components/Preview/Display/Surface/ContextualBarSurface.tsx new file mode 100644 index 0000000000000..e7cbc469e19c6 --- /dev/null +++ b/packages/uikit-playground/src/Components/Preview/Display/Surface/ContextualBarSurface.tsx @@ -0,0 +1,67 @@ +import { + Avatar, + Box, + Button, + ButtonGroup, + Contextualbar, + ContextualbarAction, + ContextualbarFooter, + ContextualbarHeader, + ContextualbarTitle, + Margins, +} from '@rocket.chat/fuselage'; +import { Scrollbars } from 'rc-scrollbars'; +import { useLayoutSizes } from '@rocket.chat/ui-contexts'; + +import DraggableList from '../../../Draggable/DraggableList'; +import type { DraggableListProps } from '../../../Draggable/DraggableList'; + +const ContextualBarSurface = ({ blocks, onDragEnd }: DraggableListProps) => ( + <Contextualbar height='100%' width={useLayoutSizes().contextualBar} position='absolute'> + <ContextualbarHeader> + <Avatar url='data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==' /> + <ContextualbarTitle>{'Contextual Bar'}</ContextualbarTitle> + <ContextualbarAction data-qa='ContextualbarActionClose' title='Close' name='cross' /> + </ContextualbarHeader> + + <Box height='100%' p='12px'> + <Box + height='100%' + display='flex' + flexShrink={1} + flexDirection='column' + flexGrow={1} + overflow='hidden' + > + <Scrollbars + autoHide + autoHideTimeout={2000} + autoHideDuration={500} + style={{ + width: '100%', + height: '100%', + flexGrow: 1, + willChange: 'transform', + overflowY: 'hidden', + }} + renderThumbVertical={({ style, ...props }): JSX.Element => ( + <div {...props} style={{ ...style, backgroundColor: 'rgba(0, 0, 0, 0.5)', borderRadius: '7px' }} /> + )} + > + <Margins blockEnd='x16'> + <DraggableList surface={3} blocks={blocks} onDragEnd={onDragEnd} /> + </Margins> + </Scrollbars> + </Box> + </Box> + + <ContextualbarFooter> + <ButtonGroup stretch> + <Button>Cancel</Button> + <Button primary>Submit</Button> + </ButtonGroup> + </ContextualbarFooter> + </Contextualbar> +); + +export default ContextualBarSurface; diff --git a/packages/uikit-playground/src/Components/Preview/Display/Surface/Surface.tsx b/packages/uikit-playground/src/Components/Preview/Display/Surface/Surface.tsx index 5c21fe2d36e27..1022dad938635 100644 --- a/packages/uikit-playground/src/Components/Preview/Display/Surface/Surface.tsx +++ b/packages/uikit-playground/src/Components/Preview/Display/Surface/Surface.tsx @@ -8,6 +8,7 @@ import type { Block } from '../../../Draggable/DraggableList'; import BannerSurface from './BannerSurface'; import MessageSurface from './MessageSurface'; import ModalSurface from './ModalSurface'; +import ContextualBarSurface from './ContextualBarSurface'; import { reorder } from './Reorder'; const Surface: FC = () => { @@ -39,6 +40,7 @@ const Surface: FC = () => { '1': () => <MessageSurface blocks={uniqueBlocks} onDragEnd={onDragEnd} />, '2': () => <BannerSurface blocks={uniqueBlocks} onDragEnd={onDragEnd} />, '3': () => <ModalSurface blocks={uniqueBlocks} onDragEnd={onDragEnd} />, + '4': () => <ContextualBarSurface blocks={uniqueBlocks} onDragEnd={onDragEnd}/>, }; return ( <Box pb='40px' pi='x20'> diff --git a/packages/uikit-playground/src/Components/SurfaceSelect/options.ts b/packages/uikit-playground/src/Components/SurfaceSelect/options.ts index 8fdf49677cfd1..315b864a8feb4 100644 --- a/packages/uikit-playground/src/Components/SurfaceSelect/options.ts +++ b/packages/uikit-playground/src/Components/SurfaceSelect/options.ts @@ -4,6 +4,7 @@ const options: SelectOption[] = [ ['1', 'Message Preview'], ['2', 'Banner Preview'], ['3', 'Modal Preview'], + ['4', 'ContextualBar Preview'], ]; export default options; diff --git a/yarn.lock b/yarn.lock index b3fc2efa93913..8cc8eada135cb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11021,6 +11021,7 @@ __metadata: eslint-plugin-react-hooks: ^4.6.0 eslint-plugin-react-refresh: ^0.4.1 eslint4b-prebuilt: ^6.7.2 + rc-scrollbars: ^1.1.6 react: ^17.0.2 react-beautiful-dnd: ^13.1.1 react-dom: ^17.0.2 @@ -34530,6 +34531,19 @@ __metadata: languageName: node linkType: hard +"rc-scrollbars@npm:^1.1.6": + version: 1.1.6 + resolution: "rc-scrollbars@npm:1.1.6" + dependencies: + dom-css: ^2.1.0 + raf: ^3.4.1 + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: 6d511fc14c1f932319ecef13de78e64453af349b2eec962b4d6f515e4bca4ca1b5555b6b875c1b5cae193b15069aff16cd7fa02550084b0e58bbeb398f74b80a + languageName: node + linkType: hard + "rc@npm:^1.0.1, rc@npm:^1.1.6, rc@npm:^1.2.7": version: 1.2.8 resolution: "rc@npm:1.2.8" From ef4dcbd3da3d89b9c287105f2043a94d908a7cdf Mon Sep 17 00:00:00 2001 From: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> Date: Wed, 5 Jul 2023 13:40:05 -0300 Subject: [PATCH 46/79] chore: Add missing migration to index (#29728) --- apps/meteor/server/startup/migrations/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/meteor/server/startup/migrations/index.ts b/apps/meteor/server/startup/migrations/index.ts index 17f4c47bbc1ca..f85acdf5aa931 100644 --- a/apps/meteor/server/startup/migrations/index.ts +++ b/apps/meteor/server/startup/migrations/index.ts @@ -33,4 +33,5 @@ import './v296'; import './v297'; import './v298'; import './v299'; +import './v300'; import './xrun'; From d45310c17492a01894c4fc790092cbaa880c4fe1 Mon Sep 17 00:00:00 2001 From: Kevin Aleman <kaleman960@gmail.com> Date: Wed, 5 Jul 2023 10:40:36 -0600 Subject: [PATCH 47/79] chore: `migrations` folder to `arch` team (#29729) --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bfc726ac2b43d..2ad64fe9cb966 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,6 +20,7 @@ /apps/meteor/app/sms @RocketChat/omnichannel /apps/meteor/server @RocketChat/backend /apps/meteor/server/models @RocketChat/Architecture +apps/meteor/server/startup/migrations @RocketChat/Architecture /apps/meteor/packages/rocketchat-livechat @RocketChat/omnichannel /apps/meteor/server/services/voip @RocketChat/omnichannel /apps/meteor/server/services/omnichannel-voip @RocketChat/omnichannel From 6fe38a487b4547053240366c1678aab50ed5575d Mon Sep 17 00:00:00 2001 From: Ayush Sharma <89914602+ayush3160@users.noreply.github.com> Date: Wed, 5 Jul 2023 23:35:32 +0530 Subject: [PATCH 48/79] fix: Time format in quoted message according to user preference (#28912) Co-authored-by: Debdut Chakraborty <debdut.chakraborty@rocket.chat> Co-authored-by: Hugo Costa <hugocarreiracosta@gmail.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .changeset/timeFormat.md | 5 +++ .../content/attachments/QuoteAttachment.tsx | 4 +-- apps/meteor/client/hooks/useTimeAgo.ts | 33 +++++++++++++++---- 3 files changed, 33 insertions(+), 9 deletions(-) create mode 100644 .changeset/timeFormat.md diff --git a/.changeset/timeFormat.md b/.changeset/timeFormat.md new file mode 100644 index 0000000000000..447dc244fcbfd --- /dev/null +++ b/.changeset/timeFormat.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +Fixed different time formats at different places diff --git a/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx b/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx index b7885563565a0..67232cbd441f8 100644 --- a/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx +++ b/apps/meteor/client/components/message/content/attachments/QuoteAttachment.tsx @@ -36,7 +36,7 @@ type QuoteAttachmentProps = { }; export const QuoteAttachment = ({ attachment }: QuoteAttachmentProps): ReactElement => { - const format = useTimeAgo(); + const formatTime = useTimeAgo(); return ( <> @@ -61,7 +61,7 @@ export const QuoteAttachment = ({ attachment }: QuoteAttachmentProps): ReactElem fontScale='c1' {...(attachment.message_link ? { is: 'a', href: attachment.message_link, color: 'hint' } : { color: 'hint' })} > - {format(attachment.ts)} + {formatTime(attachment.ts)} </Box> )} </AttachmentAuthor> diff --git a/apps/meteor/client/hooks/useTimeAgo.ts b/apps/meteor/client/hooks/useTimeAgo.ts index d2abb57017d12..81c5f732d042c 100644 --- a/apps/meteor/client/hooks/useTimeAgo.ts +++ b/apps/meteor/client/hooks/useTimeAgo.ts @@ -1,22 +1,41 @@ -import { useTranslation } from '@rocket.chat/ui-contexts'; +import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts'; import moment from 'moment'; import { useCallback } from 'react'; -export const useTimeAgo = (): ((time: Date | number | string) => string) => - useCallback((time) => moment(time).calendar(null, { sameDay: 'LT', lastWeek: 'dddd LT', sameElse: 'LL' }), []); +import { t } from '../../app/utils/lib/i18n'; + +const dayFormat = ['h:mm A', 'H:mm'] as const; + +export const useTimeAgo = (): ((time: Date | number | string) => string) => { + const clockMode = useUserPreference<1 | 2>('clockMode'); + const timeFormat = useSetting('Message_TimeFormat') as string; + const format = clockMode !== undefined ? dayFormat[clockMode - 1] : timeFormat; + return useCallback( + (time) => + moment(time).calendar(null, { + sameDay: format, + lastDay: moment().localeData().calendar('lastDay').replace('LT', format), + lastWeek: `dddd ${format}`, + sameElse: 'LL', + }), + [format], + ); +}; export const useShortTimeAgo = (): ((time: Date | string | number) => string) => { - const t = useTranslation(); + const clockMode = useUserPreference<1 | 2>('clockMode'); + const timeFormat = useSetting('Message_TimeFormat') as string; + const format = clockMode !== undefined ? dayFormat[clockMode - 1] : timeFormat; return useCallback( (time) => moment(time).calendar(null, { - sameDay: 'LT', + sameDay: format, lastDay: `[${t('Yesterday')}]`, lastWeek: 'dddd', sameElse(now) { /* Using only this.isBefore(): - + ERRORS: Cannot invoke an object which is possibly 'undefined'. This expression is not callable. @@ -29,6 +48,6 @@ export const useShortTimeAgo = (): ((time: Date | string | number) => string) => return 'MMM Do'; }, }), - [], + [format], ); }; From 5e387a1b2e9855e8c8bc2096ac5700da8a7ea9d4 Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Wed, 5 Jul 2023 23:38:28 +0530 Subject: [PATCH 49/79] fix: Toggle message box formatting toolbar on click (#29727) Co-authored-by: gabriellsh <40830821+gabriellsh@users.noreply.github.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .changeset/fuzzy-parents-drop.md | 5 +++++ .../FormattingToolbarDropdown.tsx | 8 +++++++- 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 .changeset/fuzzy-parents-drop.md diff --git a/.changeset/fuzzy-parents-drop.md b/.changeset/fuzzy-parents-drop.md new file mode 100644 index 0000000000000..a0f8c56ea58f1 --- /dev/null +++ b/.changeset/fuzzy-parents-drop.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Fix Toggle message box formatting toolbar on click diff --git a/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBoxFormattingToolbar/FormattingToolbarDropdown.tsx b/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBoxFormattingToolbar/FormattingToolbarDropdown.tsx index e44c2521616ac..bdee3ce6d7d18 100644 --- a/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBoxFormattingToolbar/FormattingToolbarDropdown.tsx +++ b/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBoxFormattingToolbar/FormattingToolbarDropdown.tsx @@ -34,7 +34,13 @@ const FormattingToolbarDropdown = ({ composer, items, ...props }: FormattingTool }; return ( - <Option key={index} onClick={handleFormattingAction}> + <Option + key={index} + onClick={() => { + handleFormattingAction(); + toggle(); + }} + > <OptionIcon name={'icon' in formatter ? formatter.icon : 'link'} /> <OptionContent>{t(formatter.label)}</OptionContent> </Option> From 4da8801830b8b1a9d13b169358c2aff5843230ba Mon Sep 17 00:00:00 2001 From: Tasso Evangelista <tasso.evangelista@rocket.chat> Date: Wed, 5 Jul 2023 15:21:13 -0300 Subject: [PATCH 50/79] chore: Use FlowRoute through a facade (#29507) --- apps/meteor/app/analytics/client/index.ts | 1 - .../meteor/app/analytics/client/loadScript.ts | 16 +- .../app/analytics/client/trackEvents.js | 244 ---------- .../server/functions/sendMail.ts | 6 +- .../client/actionButton.ts | 4 +- .../app/slashcommands-open/client/client.ts | 6 +- .../app/threads/client/flextab/threadlist.tsx | 2 +- .../client/messageAction/replyInThread.ts | 12 +- .../app/ui-message/client/ActionManager.js | 11 +- .../app/ui-utils/client/lib/AccountBox.ts | 4 +- .../ui-utils/client/lib/LegacyRoomManager.ts | 22 +- .../client/lib/messageActionDefault.ts | 4 +- .../app/ui-utils/client/lib/readMessages.ts | 2 +- .../app/ui/client/lib/KonchatNotification.ts | 32 +- .../client/components/GazzodownText.tsx | 14 +- .../client/components/NotFoundState.tsx | 8 +- .../Sidebar/SidebarItemsAssembler.tsx | 1 - .../Sidebar/SidebarNavigationItem.tsx | 9 +- .../client/hooks/useAnalyticsEventTracking.ts | 258 ++++++++++ apps/meteor/client/hooks/useDefaultRoute.ts | 22 - apps/meteor/client/hooks/useEmbeddedLayout.ts | 4 +- apps/meteor/client/importPackages.ts | 1 - apps/meteor/client/lib/appLayout.tsx | 16 +- .../client/lib/chats/flows/replyBroadcast.ts | 6 +- apps/meteor/client/lib/createRouteGroup.tsx | 196 +++----- apps/meteor/client/lib/createSidebarItems.ts | 7 +- .../lib/errors/VisitorDoesNotExistError.ts | 7 + .../client/lib/rooms/roomCoordinator.tsx | 48 +- .../client/lib/rooms/roomTypes/direct.ts | 2 +- .../client/lib/rooms/roomTypes/livechat.ts | 2 +- .../client/lib/rooms/roomTypes/private.ts | 2 +- .../client/lib/rooms/roomTypes/public.ts | 2 +- .../meteor/client/lib/rooms/roomTypes/voip.ts | 2 +- apps/meteor/client/lib/tracker.ts | 8 +- apps/meteor/client/lib/userData.ts | 4 +- apps/meteor/client/lib/utils/goToRoomById.ts | 6 +- .../client/lib/utils/isLayoutEmbedded.ts | 4 +- .../client/lib/utils/legacyJumpToMessage.ts | 29 +- .../setMessageJumpQueryStringParameter.ts | 13 +- .../providers/CallProvider/CallProvider.tsx | 9 +- .../client/providers/LayoutProvider.tsx | 10 +- .../client/providers/RouterProvider.tsx | 252 +++++++--- apps/meteor/client/sidebar/Item/Medium.tsx | 4 +- apps/meteor/client/sidebar/RoomMenu.tsx | 8 +- .../sidebar/header/actions/Directory.tsx | 12 +- .../client/sidebar/header/actions/Home.tsx | 6 +- .../actions/hooks/useAdministrationItems.tsx | 6 +- apps/meteor/client/startup/e2e.ts | 4 +- apps/meteor/client/startup/iframeCommands.ts | 15 +- apps/meteor/client/startup/loginViaQuery.ts | 23 +- .../notifications/konchatNotifications.ts | 6 +- .../client/startup/reloadRoomAfterLogin.ts | 7 +- apps/meteor/client/startup/routes.tsx | 448 ++++++++---------- apps/meteor/client/startup/setupWizard.ts | 4 +- .../stories/contexts/RouterContextMock.tsx | 4 +- .../client/views/account/AccountRouter.tsx | 21 +- .../client/views/account/AccountSidebar.tsx | 6 +- apps/meteor/client/views/account/routes.tsx | 33 ++ .../client/views/account/sidebarItems.ts | 12 +- .../views/admin/AdministrationRouter.tsx | 58 ++- .../views/admin/import/ImportHistoryPage.tsx | 11 +- .../admin/import/ImportOperationSummary.js | 9 +- .../views/admin/import/ImportProgressPage.tsx | 21 +- .../views/admin/import/NewImportPage.js | 26 +- .../views/admin/import/PrepareImportPage.js | 46 +- .../FederationModal/InviteUsers.tsx | 7 +- .../admin/moderation/MessageReportInfo.tsx | 3 +- apps/meteor/client/views/admin/routes.tsx | 98 ++++ .../admin/settings/SettingsGroupCard.tsx | 26 +- .../views/admin/sidebar/AdminSidebar.tsx | 6 +- .../client/views/admin/sidebar/UpgradeTab.tsx | 24 +- .../meteor/client/views/admin/sidebarItems.ts | 36 +- .../admin/upgrade/UpgradePage/UpgradePage.tsx | 4 +- .../views/conference/ConferencePage.tsx | 2 +- .../client/views/directory/DirectoryPage.tsx | 35 +- apps/meteor/client/views/home/HomePage.tsx | 34 +- .../client/views/home/cards/JoinRoomsCard.tsx | 6 +- .../meteor/client/views/invite/InvitePage.tsx | 6 +- .../client/views/invite/SecretURLPage.tsx | 8 +- .../AppDetailsPage/AppDetailsPage.tsx | 25 +- .../AppDetailsPage/AppDetailsPageTabs.tsx | 31 +- .../views/marketplace/AppInstallPage.js | 41 +- .../client/views/marketplace/AppMenu.js | 32 +- .../views/marketplace/AppsList/AppRow.tsx | 26 +- .../views/marketplace/AppsPage/AppsPage.tsx | 10 +- .../marketplace/AppsPage/AppsPageContent.tsx | 18 +- .../views/marketplace/MarketplaceSidebar.tsx | 8 +- .../client/views/marketplace/routes.tsx | 13 + .../client/views/marketplace/sidebarItems.tsx | 10 +- apps/meteor/client/views/meet/MeetPage.tsx | 16 +- apps/meteor/client/views/meet/MeetRoute.tsx | 63 +++ .../views/oauth/OAuthAuthorizationPage.tsx | 6 +- .../components/AuthorizationFormPage.tsx | 2 +- .../views/omnichannel/OmnichannelRouter.tsx | 29 +- .../directory/CallsContextualBarDirectory.tsx | 4 +- .../directory/OmnichannelDirectoryPage.tsx | 38 +- .../contacts/contextualBar/ContactInfo.tsx | 49 +- .../meteor/client/views/omnichannel/routes.ts | 73 +++ .../sidebar/OmnichannelSidebar.tsx | 8 +- .../client/views/omnichannel/sidebarItems.ts | 24 +- .../room/Header/Omnichannel/BackButton.tsx | 16 +- .../Omnichannel/OmnichannelRoomHeader.tsx | 20 +- .../QuickActions/hooks/useQuickActions.tsx | 10 +- .../Header/Omnichannel/VoipRoomHeader.tsx | 19 +- .../MessageList/hooks/useJumpToMessage.ts | 16 +- .../room/MessageList/hooks/useMessages.ts | 2 +- .../providers/MessageListProvider.tsx | 4 +- apps/meteor/client/views/room/RoomRoute.tsx | 29 ++ .../views/room/components/body/RoomBody.tsx | 31 +- .../Info/EditRoomInfo/EditChannel.js | 6 +- .../Info/hooks/actions/useRoomDelete.tsx | 6 +- .../Info/hooks/actions/useRoomHide.tsx | 6 +- .../Info/hooks/actions/useRoomLeave.tsx | 6 +- .../Threads/hooks/useGetMessageByID.ts | 2 +- .../hooks/useLegacyThreadMessageJump.ts | 30 +- .../views/room/hooks/useAppsContextualBar.ts | 5 +- .../client/views/room/hooks/useGoToRoom.ts | 10 +- .../client/views/room/hooks/useGoToThread.ts | 26 +- .../views/room/hooks/useGoToThreadList.ts | 27 +- .../client/views/room/hooks/useOpenRoom.ts | 4 +- .../actions/useRedirectModerationConsole.ts | 2 +- .../views/room/providers/RoomProvider.tsx | 10 +- .../views/room/providers/ToolboxProvider.tsx | 47 +- apps/meteor/client/views/root/AppLayout.tsx | 13 +- apps/meteor/client/views/root/IndexRoute.tsx | 42 ++ apps/meteor/client/views/root/LoginRoute.tsx | 14 + .../client/views/root/LoginTokenRoute.tsx | 25 + .../root/MainLayout/LayoutWithSidebar.tsx | 5 +- .../views/setupWizard/hooks/useRouteLock.ts | 8 +- .../contextualBar/info/TeamsInfoWithData.js | 10 +- apps/meteor/definition/IRoomTypeConfig.ts | 11 +- .../definition/externals/meteor/http.d.ts | 53 +++ .../externals/meteor/kadira-flow-router.d.ts | 82 ++-- .../definition/externals/meteor/meteor.d.ts | 2 + .../client/views/livechatSideNavItems.js | 12 +- .../contextualBar/CannedResponse/index.tsx | 16 +- apps/meteor/ee/client/omnichannel/routes.ts | 21 + apps/meteor/ee/client/startup/audit.tsx | 94 ++-- .../ee/client/startup/deviceManagement.ts | 15 +- .../ee/client/startup/engagementDashboard.ts | 9 + .../EngagementDashboardRoute.tsx | 47 +- .../client/views/audit/hooks/useAuditTab.ts | 2 +- apps/meteor/lib/callbacks.ts | 14 +- apps/meteor/lib/rooms/roomTypes/direct.ts | 9 + apps/meteor/lib/rooms/roomTypes/livechat.ts | 9 + apps/meteor/lib/rooms/roomTypes/private.ts | 9 + apps/meteor/lib/rooms/roomTypes/public.ts | 9 + apps/meteor/lib/rooms/roomTypes/voip.ts | 9 + apps/meteor/lib/utils/generatePath.ts | 13 + apps/meteor/package.json | 2 +- .../omnichannel-contact-center.spec.ts | 2 +- .../omnichannel-departaments.spec.ts | 26 +- .../fragments/omnichannel-sidenav.ts | 12 +- .../page-objects/omnichannel-departments.ts | 5 + .../tests/mocks/client/RouterContextMock.tsx | 147 ++++-- .../views/notFound/NotFoundPage.spec.tsx | 11 +- apps/meteor/tsconfig.json | 2 +- packages/ui-contexts/.eslintrc.json | 2 +- packages/ui-contexts/package.json | 1 + packages/ui-contexts/src/RouterContext.ts | 128 +++-- .../ui-contexts/src/hooks/useCurrentRoute.ts | 12 - .../src/hooks/useCurrentRoutePath.ts | 21 + packages/ui-contexts/src/hooks/useLogout.ts | 6 +- .../src/hooks/useQueryStringParameter.ts | 12 - packages/ui-contexts/src/hooks/useRoute.ts | 25 +- .../src/hooks/useRouteParameter.ts | 10 +- .../ui-contexts/src/hooks/useRoutePath.ts | 20 - packages/ui-contexts/src/hooks/useRouteUrl.ts | 20 - packages/ui-contexts/src/hooks/useRouter.ts | 5 + .../src/hooks/useSearchParameter.ts | 15 + .../src/hooks/useSearchParameters.ts | 9 + packages/ui-contexts/src/index.ts | 10 +- .../src/ResetPassword/ResetPasswordPage.tsx | 6 +- yarn.lock | 1 + 174 files changed, 2602 insertions(+), 1714 deletions(-) delete mode 100644 apps/meteor/app/analytics/client/index.ts delete mode 100644 apps/meteor/app/analytics/client/trackEvents.js create mode 100644 apps/meteor/client/hooks/useAnalyticsEventTracking.ts delete mode 100644 apps/meteor/client/hooks/useDefaultRoute.ts create mode 100644 apps/meteor/client/lib/errors/VisitorDoesNotExistError.ts create mode 100644 apps/meteor/client/views/meet/MeetRoute.tsx create mode 100644 apps/meteor/client/views/room/RoomRoute.tsx create mode 100644 apps/meteor/client/views/root/IndexRoute.tsx create mode 100644 apps/meteor/client/views/root/LoginRoute.tsx create mode 100644 apps/meteor/client/views/root/LoginTokenRoute.tsx create mode 100644 apps/meteor/definition/externals/meteor/http.d.ts create mode 100644 apps/meteor/lib/utils/generatePath.ts delete mode 100644 packages/ui-contexts/src/hooks/useCurrentRoute.ts create mode 100644 packages/ui-contexts/src/hooks/useCurrentRoutePath.ts delete mode 100644 packages/ui-contexts/src/hooks/useQueryStringParameter.ts delete mode 100644 packages/ui-contexts/src/hooks/useRoutePath.ts delete mode 100644 packages/ui-contexts/src/hooks/useRouteUrl.ts create mode 100644 packages/ui-contexts/src/hooks/useRouter.ts create mode 100644 packages/ui-contexts/src/hooks/useSearchParameter.ts create mode 100644 packages/ui-contexts/src/hooks/useSearchParameters.ts diff --git a/apps/meteor/app/analytics/client/index.ts b/apps/meteor/app/analytics/client/index.ts deleted file mode 100644 index dbce9bb4aa219..0000000000000 --- a/apps/meteor/app/analytics/client/index.ts +++ /dev/null @@ -1 +0,0 @@ -import './trackEvents'; diff --git a/apps/meteor/app/analytics/client/loadScript.ts b/apps/meteor/app/analytics/client/loadScript.ts index af90cd8ea2926..47d4a10f764ae 100644 --- a/apps/meteor/app/analytics/client/loadScript.ts +++ b/apps/meteor/app/analytics/client/loadScript.ts @@ -7,15 +7,15 @@ import { useReactiveValue } from '../../../client/hooks/useReactiveValue'; declare global { // eslint-disable-next-line @typescript-eslint/naming-convention interface Window { - _paq: [string, ...unknown[]][]; + _paq?: [string, ...unknown[]][]; GoogleAnalyticsObject: unknown; - ga: qa; + ga?: qa; } type qa = { (...args: unknown[]): void; - l: number; - q: unknown[]; + l?: number; + q?: unknown[]; }; } @@ -60,17 +60,17 @@ export const useAnalytics = (): void => { (i[r] = i[r] || function () { - (i[r].q = i[r].q || []).push(arguments); + ((i[r] as any).q = (i[r] as any).q || []).push(arguments); }), - (i[r].l = new Date().getTime()); + ((i[r] as any).l = new Date().getTime()); (a = s.createElement(o)), (m = s.getElementsByTagName(o)[0]); a.async = 1; a.src = g; m.parentNode.insertBefore(a, m); })(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga'); - window.ga('create', googleId, 'auto'); - window.ga('send', 'pageview'); + window.ga?.('create', googleId, 'auto'); + window.ga?.('send', 'pageview'); } /* eslint-enable */ }, [googleId, uid]); diff --git a/apps/meteor/app/analytics/client/trackEvents.js b/apps/meteor/app/analytics/client/trackEvents.js deleted file mode 100644 index 1d274f03aac4f..0000000000000 --- a/apps/meteor/app/analytics/client/trackEvents.js +++ /dev/null @@ -1,244 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import { Tracker } from 'meteor/tracker'; - -import { settings } from '../../settings/client'; -import { callbacks } from '../../../lib/callbacks'; -import { ChatRoom } from '../../models/client'; - -function trackEvent(category, action, label) { - if (window._paq) { - window._paq.push(['trackEvent', category, action, label]); - } - if (window.ga) { - window.ga('send', 'event', category, action, label); - } -} - -if (!window._paq || window.ga) { - // Trigger the trackPageView manually as the page views are only loaded when the loadScript.js code is executed - FlowRouter.triggers.enter([ - (route) => { - if (window._paq) { - const http = location.protocol; - const slashes = http.concat('//'); - const host = slashes.concat(window.location.hostname); - window._paq.push(['setCustomUrl', host + route.path]); - window._paq.push(['trackPageView']); - } - if (window.ga) { - window.ga('send', 'pageview', route.path); - } - }, - ]); - - // Login page has manual switches - callbacks.add( - 'loginPageStateChange', - (state) => { - trackEvent('Navigation', 'Login Page State Change', state); - }, - callbacks.priority.MEDIUM, - 'analytics-login-state-change', - ); - - // Messsages - callbacks.add( - 'afterSaveMessage', - (message) => { - if ((window._paq || window.ga) && settings.get('Analytics_features_messages')) { - const room = ChatRoom.findOne({ _id: message.rid }); - trackEvent('Message', 'Send', `${room.name} (${room._id})`); - } - }, - 2000, - 'trackEvents', - ); - - // Rooms - callbacks.add( - 'afterCreateChannel', - (owner, room) => { - if (settings.get('Analytics_features_rooms')) { - trackEvent('Room', 'Create', `${room.name} (${room._id})`); - } - }, - callbacks.priority.MEDIUM, - 'analytics-after-create-channel', - ); - - callbacks.add( - 'roomNameChanged', - (room) => { - if (settings.get('Analytics_features_rooms')) { - trackEvent('Room', 'Changed Name', `${room.name} (${room._id})`); - } - }, - callbacks.priority.MEDIUM, - 'analytics-room-name-changed', - ); - - callbacks.add( - 'roomTopicChanged', - (room) => { - if (settings.get('Analytics_features_rooms')) { - trackEvent('Room', 'Changed Topic', `${room.name} (${room._id})`); - } - }, - callbacks.priority.MEDIUM, - 'analytics-room-topic-changed', - ); - - callbacks.add( - 'roomAnnouncementChanged', - (room) => { - if (settings.get('Analytics_features_rooms')) { - trackEvent('Room', 'Changed Announcement', `${room.name} (${room._id})`); - } - }, - callbacks.priority.MEDIUM, - 'analytics-room-announcement-changed', - ); - - callbacks.add( - 'roomTypeChanged', - (room) => { - if (settings.get('Analytics_features_rooms')) { - trackEvent('Room', 'Changed Room Type', `${room.name} (${room._id})`); - } - }, - callbacks.priority.MEDIUM, - 'analytics-room-type-changed', - ); - - callbacks.add( - 'archiveRoom', - (room) => { - if (settings.get('Analytics_features_rooms')) { - trackEvent('Room', 'Archived', `${room.name} (${room._id})`); - } - }, - callbacks.priority.MEDIUM, - 'analytics-archive-room', - ); - - callbacks.add( - 'unarchiveRoom', - (room) => { - if (settings.get('Analytics_features_rooms')) { - trackEvent('Room', 'Unarchived', `${room.name} (${room._id})`); - } - }, - callbacks.priority.MEDIUM, - 'analytics-unarchive-room', - ); - - // Users - // Track logins and associate user ids with piwik - (() => { - let oldUserId = null; - - Tracker.autorun(() => { - const newUserId = Meteor.userId(); - if (oldUserId === null && newUserId) { - if (window._paq && settings.get('Analytics_features_users')) { - trackEvent('User', 'Login', newUserId); - window._paq.push(['setUserId', newUserId]); - } - } else if (newUserId === null && oldUserId) { - if (window._paq && settings.get('Analytics_features_users')) { - trackEvent('User', 'Logout', oldUserId); - } - } - oldUserId = Meteor.userId(); - }); - })(); - - callbacks.add( - 'userRegistered', - () => { - if (settings.get('Analytics_features_users')) { - trackEvent('User', 'Registered'); - } - }, - callbacks.priority.MEDIUM, - 'piwik-user-resitered', - ); - - callbacks.add( - 'usernameSet', - () => { - if (settings.get('Analytics_features_users')) { - trackEvent('User', 'Username Set'); - } - }, - callbacks.priority.MEDIUM, - 'piweik-username-set', - ); - - callbacks.add( - 'userPasswordReset', - () => { - if (settings.get('Analytics_features_users')) { - trackEvent('User', 'Reset Password'); - } - }, - callbacks.priority.MEDIUM, - 'piwik-user-password-reset', - ); - - callbacks.add( - 'userConfirmationEmailRequested', - () => { - if (settings.get('Analytics_features_users')) { - trackEvent('User', 'Confirmation Email Requested'); - } - }, - callbacks.priority.MEDIUM, - 'piwik-user-confirmation-email-requested', - ); - - callbacks.add( - 'userForgotPasswordEmailRequested', - () => { - if (settings.get('Analytics_features_users')) { - trackEvent('User', 'Forgot Password Email Requested'); - } - }, - callbacks.priority.MEDIUM, - 'piwik-user-forgot-password-email-requested', - ); - - callbacks.add( - 'userStatusManuallySet', - (status) => { - if (settings.get('Analytics_features_users')) { - trackEvent('User', 'Status Manually Changed', status); - } - }, - callbacks.priority.MEDIUM, - 'analytics-user-status-manually-set', - ); - - callbacks.add( - 'userAvatarSet', - (service) => { - if (settings.get('Analytics_features_users')) { - trackEvent('User', 'Avatar Changed', service); - } - }, - callbacks.priority.MEDIUM, - 'analytics-user-avatar-set', - ); - - callbacks.add( - 'roomAvatarChanged', - (room) => { - if (settings.get('Analytics_features_rooms')) { - trackEvent('Room', 'Changed Avatar', `${room.name} (${room._id})`); - } - }, - callbacks.priority.MEDIUM, - 'analytics-room-avatar-changed', - ); -} diff --git a/apps/meteor/app/mail-messages/server/functions/sendMail.ts b/apps/meteor/app/mail-messages/server/functions/sendMail.ts index 47acb6da1d9da..881b5eb7f7066 100644 --- a/apps/meteor/app/mail-messages/server/functions/sendMail.ts +++ b/apps/meteor/app/mail-messages/server/functions/sendMail.ts @@ -1,6 +1,5 @@ import { Meteor } from 'meteor/meteor'; import EJSON from 'ejson'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { escapeHTML } from '@rocket.chat/string-helpers'; import type { Filter } from 'mongodb'; import type { IUser } from '@rocket.chat/core-typings'; @@ -9,6 +8,7 @@ import { Users } from '@rocket.chat/models'; import { placeholders } from '../../../utils/server'; import { SystemLogger } from '../../../../server/lib/logger/system'; import * as Mailer from '../../../mailer/server/api'; +import { generatePath } from '../../../../lib/utils/generatePath'; export const sendMail = async function ({ from, @@ -44,7 +44,7 @@ export const sendMail = async function ({ const email = `${user.name} <${user.emails?.[0].address}>`; const html = placeholders.replace(body, { unsubscribe: Meteor.absoluteUrl( - FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { + generatePath('mailer/unsubscribe/:_id/:createdAt', { _id: user._id, createdAt: user.createdAt?.getTime().toString() || '', }), @@ -70,7 +70,7 @@ export const sendMail = async function ({ const html = placeholders.replace(body, { unsubscribe: Meteor.absoluteUrl( - FlowRouter.path('mailer/unsubscribe/:_id/:createdAt', { + generatePath('mailer/unsubscribe/:_id/:createdAt', { _id: user._id, createdAt: user.createdAt?.getTime().toString() || '', }), diff --git a/apps/meteor/app/message-mark-as-unread/client/actionButton.ts b/apps/meteor/app/message-mark-as-unread/client/actionButton.ts index 60d78c888549b..070d1ea750a40 100644 --- a/apps/meteor/app/message-mark-as-unread/client/actionButton.ts +++ b/apps/meteor/app/message-mark-as-unread/client/actionButton.ts @@ -1,5 +1,4 @@ import { Meteor } from 'meteor/meteor'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { LegacyRoomManager, MessageAction } from '../../ui-utils/client'; import { messageArgs } from '../../../client/lib/utils/messageArgs'; @@ -7,6 +6,7 @@ import { ChatSubscription } from '../../models/client'; import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; import { dispatchToastMessage } from '../../../client/lib/toast'; import { sdk } from '../../utils/client/lib/SDKClient'; +import { router } from '../../../client/providers/RouterProvider'; Meteor.startup(() => { MessageAction.addButton({ @@ -27,7 +27,7 @@ Meteor.startup(() => { return; } await LegacyRoomManager.close(subscription.t + subscription.name); - return FlowRouter.go('home'); + return router.navigate('/home'); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } diff --git a/apps/meteor/app/slashcommands-open/client/client.ts b/apps/meteor/app/slashcommands-open/client/client.ts index 3bf88010b721a..696806619965b 100644 --- a/apps/meteor/app/slashcommands-open/client/client.ts +++ b/apps/meteor/app/slashcommands-open/client/client.ts @@ -1,11 +1,11 @@ import type { RoomType, ISubscription, SlashCommandCallbackParams } from '@rocket.chat/core-typings'; import type { Mongo } from 'meteor/mongo'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { roomCoordinator } from '../../../client/lib/rooms/roomCoordinator'; import { slashCommands } from '../../utils/lib/slashCommand'; import { Subscriptions, ChatSubscription } from '../../models/client'; import { sdk } from '../../utils/client/lib/SDKClient'; +import { router } from '../../../client/providers/RouterProvider'; slashCommands.add({ command: 'open', @@ -26,7 +26,7 @@ slashCommands.add({ const subscription = ChatSubscription.findOne(query); if (subscription) { - roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); + roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters()); } if (type && type.indexOf('d') === -1) { @@ -38,7 +38,7 @@ slashCommands.add({ if (!subscription) { return; } - roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); + roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters()); } catch (err: unknown) { // noop } diff --git a/apps/meteor/app/threads/client/flextab/threadlist.tsx b/apps/meteor/app/threads/client/flextab/threadlist.tsx index 82fb993379f04..b1ca3b7b7a613 100644 --- a/apps/meteor/app/threads/client/flextab/threadlist.tsx +++ b/apps/meteor/app/threads/client/flextab/threadlist.tsx @@ -45,7 +45,7 @@ addAction('thread', (options) => { const unread = tunread > 99 ? '99+' : tunread; const variant = getVariant(tunreadUser, tunreadGroup); return ( - <HeaderToolboxAction {...props}> + <HeaderToolboxAction key={props.id} {...props}> {!!unread && <HeaderToolboxActionBadge variant={variant}>{unread}</HeaderToolboxActionBadge>} </HeaderToolboxAction> ); diff --git a/apps/meteor/app/threads/client/messageAction/replyInThread.ts b/apps/meteor/app/threads/client/messageAction/replyInThread.ts index 82b166310c273..632fbc426e4fc 100644 --- a/apps/meteor/app/threads/client/messageAction/replyInThread.ts +++ b/apps/meteor/app/threads/client/messageAction/replyInThread.ts @@ -1,11 +1,11 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { settings } from '../../../settings/client'; import { MessageAction } from '../../../ui-utils/client'; import { messageArgs } from '../../../../client/lib/utils/messageArgs'; import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; +import { router } from '../../../../client/providers/RouterProvider'; Meteor.startup(function () { Tracker.autorun(() => { @@ -20,9 +20,13 @@ Meteor.startup(function () { action(e, props) { const { message = messageArgs(this).msg } = props; e.stopPropagation(); - FlowRouter.setParams({ - tab: 'thread', - context: message.tmid || message._id, + router.navigate({ + name: router.getRouteName()!, + params: { + ...router.getRouteParameters(), + tab: 'thread', + context: message.tmid || message._id, + }, }); }, condition({ subscription, room }) { diff --git a/apps/meteor/app/ui-message/client/ActionManager.js b/apps/meteor/app/ui-message/client/ActionManager.js index 6e2aadd1babed..48165b99f4366 100644 --- a/apps/meteor/app/ui-message/client/ActionManager.js +++ b/apps/meteor/app/ui-message/client/ActionManager.js @@ -1,6 +1,5 @@ import { UIKitIncomingInteractionType } from '@rocket.chat/apps-engine/definition/uikit'; import { Meteor } from 'meteor/meteor'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { Random } from '@rocket.chat/random'; import { Emitter } from '@rocket.chat/emitter'; import { UIKitInteractionTypes } from '@rocket.chat/core-typings'; @@ -13,6 +12,7 @@ import { dispatchToastMessage } from '../../../client/lib/toast'; import { imperativeModal } from '../../../client/lib/imperativeModal'; import UiKitModal from '../../../client/views/modal/uikit/UiKitModal'; import { sdk } from '../../utils/client/lib/SDKClient'; +import { router } from '../../../client/providers/RouterProvider'; const events = new Emitter(); @@ -124,7 +124,14 @@ const handlePayloadUserInteraction = (type, { /* appId,*/ triggerId, ...data }) }, }); - FlowRouter.setParams({ tab: 'app', context: viewId }); + router.navigate({ + name: router.getRouteName(), + params: { + ...router.getRouteParameters(), + tab: 'app', + context: viewId, + }, + }); return UIKitInteractionTypes.CONTEXTUAL_BAR_OPEN; } diff --git a/apps/meteor/app/ui-utils/client/lib/AccountBox.ts b/apps/meteor/app/ui-utils/client/lib/AccountBox.ts index b337ae4e98e57..3ed3a60b3bf61 100644 --- a/apps/meteor/app/ui-utils/client/lib/AccountBox.ts +++ b/apps/meteor/app/ui-utils/client/lib/AccountBox.ts @@ -2,7 +2,7 @@ import type { IUIActionButton, IUActionButtonWhen } from '@rocket.chat/apps-engi import type { UserStatus } from '@rocket.chat/core-typings'; import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; -import type { TranslationKey } from '@rocket.chat/ui-contexts'; +import type { TranslationKey, LocationPathname } from '@rocket.chat/ui-contexts'; import type { Icon } from '@rocket.chat/fuselage'; import type { ComponentProps } from 'react'; @@ -22,7 +22,7 @@ export interface IAppAccountBoxItem extends IUIActionButton { export type AccountBoxItem = { name: TranslationKey; icon: ComponentProps<typeof Icon>['name']; - href: string; + href: LocationPathname; sideNav?: string; condition: () => boolean; }; diff --git a/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts b/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts index fb110c7b1e4f6..f85fecd74ac14 100644 --- a/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts +++ b/apps/meteor/app/ui-utils/client/lib/LegacyRoomManager.ts @@ -1,7 +1,7 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { Tracker } from 'meteor/tracker'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import type { IMessage, IRoom } from '@rocket.chat/core-typings'; +import type { Mongo } from 'meteor/mongo'; import { fireGlobalEvent } from '../../../../client/lib/utils/fireGlobalEvent'; import { upsertMessage, RoomHistoryManager } from './RoomHistoryManager'; @@ -13,6 +13,7 @@ import { RoomManager } from '../../../../client/lib/RoomManager'; import { roomCoordinator } from '../../../../client/lib/rooms/roomCoordinator'; import { Notifications } from '../../../notifications/client'; import { sdk } from '../../../utils/client/lib/SDKClient'; +import { router } from '../../../../client/providers/RouterProvider'; const maxRoomsOpen = parseInt(getConfig('maxRoomsOpen') ?? '5') || 5; @@ -87,15 +88,18 @@ const handleTrackSettingsChange = (msg: IMessage) => { void Tracker.nonreactive(async () => { if (msg.t === 'room_changed_privacy') { - const type = FlowRouter.current().route?.name === 'channel' ? 'c' : 'p'; - await close(type + FlowRouter.getParam('name')); + const type = router.getRouteName() === 'channel' ? 'c' : 'p'; + await close(type + router.getRouteParameters().name); const subscription = ChatSubscription.findOne({ rid: msg.rid }); if (!subscription) { throw new Error('Subscription not found'); } - const route = subscription.t === 'c' ? 'channel' : 'group'; - FlowRouter.go(route, { name: subscription.name }, FlowRouter.current().queryParams); + router.navigate({ + pattern: subscription.t === 'c' ? '/channel/:name/:tab?/:context?' : '/group/:name/:tab?/:context?', + params: { name: subscription.name }, + search: router.getSearchParameters(), + }); } if (msg.t === 'r') { @@ -103,9 +107,9 @@ const handleTrackSettingsChange = (msg: IMessage) => { if (!room) { throw new Error('Room not found'); } - if (room.name !== FlowRouter.getParam('name')) { - await close(room.t + FlowRouter.getParam('name')); - roomCoordinator.openRouteLink(room.t, room, FlowRouter.current().queryParams); + if (room.name !== router.getRouteParameters().name) { + await close(room.t + router.getRouteParameters().name); + roomCoordinator.openRouteLink(room.t, room, router.getSearchParameters()); } } }); @@ -174,7 +178,7 @@ const computation = Tracker.autorun(() => { ChatMessage.update({ tmid: msg._id }, { $unset: { tmid: 1 } }, { multi: true }); }); Notifications.onRoom(record.rid, 'deleteMessageBulk', ({ rid, ts, excludePinned, ignoreDiscussion, users }) => { - const query: Mongo.Query<IMessage> = { rid, ts }; + const query: Mongo.Selector<IMessage> = { rid, ts }; if (excludePinned) { query.pinned = { $ne: true }; } diff --git a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts index 18ae57c68df4f..6d3f856e2b9c9 100644 --- a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts +++ b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts @@ -1,4 +1,3 @@ -import { FlowRouter } from 'meteor/kadira:flow-router'; import moment from 'moment'; import { Meteor } from 'meteor/meteor'; import type { IMessage } from '@rocket.chat/core-typings'; @@ -15,6 +14,7 @@ import ReactionList from '../../../../client/views/room/modals/ReactionListModal import ReportMessageModal from '../../../../client/views/room/modals/ReportMessageModal'; import { dispatchToastMessage } from '../../../../client/lib/toast'; import { t } from '../../../utils/lib/i18n'; +import { router } from '../../../../client/providers/RouterProvider'; const getMainMessageText = (message: IMessage): IMessage => { const newMessage = { ...message }; @@ -36,7 +36,7 @@ Meteor.startup(async function () { 'd', { name: message.u.username }, { - ...FlowRouter.current().queryParams, + ...router.getSearchParameters(), reply: message._id, }, ); diff --git a/apps/meteor/app/ui-utils/client/lib/readMessages.ts b/apps/meteor/app/ui-utils/client/lib/readMessages.ts index 7d1cbc86af314..6a09a503552f0 100644 --- a/apps/meteor/app/ui-utils/client/lib/readMessages.ts +++ b/apps/meteor/app/ui-utils/client/lib/readMessages.ts @@ -150,7 +150,7 @@ class ReadMessage extends Emitter { $gt: lastReadRecord.ts, }, 'u._id': { - $ne: Meteor.userId(), + $ne: Meteor.userId() ?? undefined, }, }, { diff --git a/apps/meteor/app/ui/client/lib/KonchatNotification.ts b/apps/meteor/app/ui/client/lib/KonchatNotification.ts index 4561b198f040e..38e9a807fd8c9 100644 --- a/apps/meteor/app/ui/client/lib/KonchatNotification.ts +++ b/apps/meteor/app/ui/client/lib/KonchatNotification.ts @@ -1,6 +1,5 @@ import type { IMessage, IRoom, IUser, RoomType } from '@rocket.chat/core-typings'; import { ReactiveVar } from 'meteor/reactive-var'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { Random } from '@rocket.chat/random'; import { Meteor } from 'meteor/meteor'; @@ -14,6 +13,7 @@ import { getAvatarAsPng } from '../../../../client/lib/utils/getAvatarAsPng'; import { stripTags } from '../../../../lib/utils/stringUtils'; import { RoomManager } from '../../../../client/lib/RoomManager'; import { sdk } from '../../../utils/client/lib/SDKClient'; +import { router } from '../../../../client/providers/RouterProvider'; declare global { // eslint-disable-next-line @typescript-eslint/naming-convention @@ -111,41 +111,41 @@ class KonchatNotification { switch (notification.payload?.type) { case 'd': - return FlowRouter.go( - 'direct', - { + return router.navigate({ + pattern: '/direct/:rid/:tab?/:context?', + params: { rid: notification.payload.rid, ...(notification.payload.tmid && { tab: 'thread', context: notification.payload.tmid, }), }, - { ...FlowRouter.current().queryParams, jump: notification.payload._id }, - ); + search: { ...router.getSearchParameters(), jump: notification.payload._id }, + }); case 'c': - return FlowRouter.go( - 'channel', - { + return router.navigate({ + pattern: '/channel/:name/:tab?/:context?', + params: { name: notification.payload.name, ...(notification.payload.tmid && { tab: 'thread', context: notification.payload.tmid, }), }, - { ...FlowRouter.current().queryParams, jump: notification.payload._id }, - ); + search: { ...router.getSearchParameters(), jump: notification.payload._id }, + }); case 'p': - return FlowRouter.go( - 'group', - { + return router.navigate({ + pattern: '/group/:name/:tab?/:context?', + params: { name: notification.payload.name, ...(notification.payload.tmid && { tab: 'thread', context: notification.payload.tmid, }), }, - { ...FlowRouter.current().queryParams, jump: notification.payload._id }, - ); + search: { ...router.getSearchParameters(), jump: notification.payload._id }, + }); } }; } diff --git a/apps/meteor/client/components/GazzodownText.tsx b/apps/meteor/client/components/GazzodownText.tsx index 96a8981c193df..e9a0f39ddb5f8 100644 --- a/apps/meteor/client/components/GazzodownText.tsx +++ b/apps/meteor/client/components/GazzodownText.tsx @@ -2,9 +2,9 @@ import type { IRoom } from '@rocket.chat/core-typings'; import type { ChannelMention, UserMention } from '@rocket.chat/gazzodown'; import { MarkupInteractionContext } from '@rocket.chat/gazzodown'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { RouterContext, useLayout, useUserPreference } from '@rocket.chat/ui-contexts'; +import { useLayout, useRouter, useUserPreference } from '@rocket.chat/ui-contexts'; import type { UIEvent } from 'react'; -import React, { useContext, useCallback, memo, useMemo } from 'react'; +import React, { useCallback, memo, useMemo } from 'react'; import { detectEmoji } from '../lib/utils/detectEmoji'; import { fireGlobalEvent } from '../lib/utils/fireGlobalEvent'; @@ -79,18 +79,22 @@ const GazzodownText = ({ mentions, channels, searchText, children }: GazzodownTe ); const goToRoom = useGoToRoom(); - const router = useContext(RouterContext); const { isEmbedded } = useLayout(); const resolveChannelMention = useCallback((mention: string) => channels?.find(({ name }) => name === mention), [channels]); + const router = useRouter(); + const onChannelMentionClick = useCallback( ({ _id: rid }: ChannelMention) => (event: UIEvent): void => { if (isEmbedded) { fireGlobalEvent('click-mention-link', { - path: router.getRoutePath('channel', { name: rid }), + path: router.buildRoutePath({ + pattern: '/channel/:name/:tab?/:context?', + params: { name: rid }, + }), channel: rid, }); } @@ -98,7 +102,7 @@ const GazzodownText = ({ mentions, channels, searchText, children }: GazzodownTe event.stopPropagation(); goToRoom(rid); }, - [isEmbedded, goToRoom, router], + [router, isEmbedded, goToRoom], ); return ( diff --git a/apps/meteor/client/components/NotFoundState.tsx b/apps/meteor/client/components/NotFoundState.tsx index 2e6653f41e745..421fa1a9ca87c 100644 --- a/apps/meteor/client/components/NotFoundState.tsx +++ b/apps/meteor/client/components/NotFoundState.tsx @@ -1,5 +1,5 @@ import { Box, States, StatesAction, StatesActions, StatesIcon, StatesSubtitle, StatesTitle } from '@rocket.chat/fuselage'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -10,10 +10,10 @@ type NotFoundProps = { const NotFoundState = ({ title, subtitle }: NotFoundProps): ReactElement => { const t = useTranslation(); - const homeRoute = useRoute('home'); + const router = useRouter(); - const handleGoHomeClick = (): void => { - homeRoute.push(); + const handleGoHomeClick = () => { + router.navigate('/home'); }; return ( diff --git a/apps/meteor/client/components/Sidebar/SidebarItemsAssembler.tsx b/apps/meteor/client/components/Sidebar/SidebarItemsAssembler.tsx index 32ed00f4ccc3a..3f08bcb121cb2 100644 --- a/apps/meteor/client/components/Sidebar/SidebarItemsAssembler.tsx +++ b/apps/meteor/client/components/Sidebar/SidebarItemsAssembler.tsx @@ -22,7 +22,6 @@ const SidebarItemsAssembler: FC<SidebarItemsAssemblerProps> = ({ items, currentP {isSidebarItem(props) ? ( <SidebarNavigationItem permissionGranted={props.permissionGranted} - pathGroup={props.pathGroup || ''} pathSection={props.href ?? props.pathSection ?? ''} icon={props.icon} label={t((props.i18nLabel || props.name) as Parameters<typeof t>[0])} diff --git a/apps/meteor/client/components/Sidebar/SidebarNavigationItem.tsx b/apps/meteor/client/components/Sidebar/SidebarNavigationItem.tsx index c596ce5d4515b..ed6cd3e91d3be 100644 --- a/apps/meteor/client/components/Sidebar/SidebarNavigationItem.tsx +++ b/apps/meteor/client/components/Sidebar/SidebarNavigationItem.tsx @@ -1,14 +1,12 @@ import { Box, Icon, Tag } from '@rocket.chat/fuselage'; import type { IconProps } from '@rocket.chat/fuselage'; -import { useRoutePath } from '@rocket.chat/ui-contexts'; import type { FC, ReactElement } from 'react'; -import React, { memo, useMemo } from 'react'; +import React, { memo } from 'react'; import SidebarGenericItem from './SidebarGenericItem'; type SidebarNavigationItemProps = { permissionGranted?: (() => boolean) | boolean; - pathGroup: string; pathSection: string; icon?: IconProps['name']; label?: string; @@ -20,7 +18,6 @@ type SidebarNavigationItemProps = { const SidebarNavigationItem: FC<SidebarNavigationItemProps> = ({ permissionGranted, - pathGroup, pathSection, icon, label, @@ -30,9 +27,7 @@ const SidebarNavigationItem: FC<SidebarNavigationItemProps> = ({ // eslint-disable-next-line @typescript-eslint/naming-convention badge: Badge, }) => { - const params = useMemo(() => ({ group: pathGroup }), [pathGroup]); - const routePath = useRoutePath(pathSection, params); - const path = externalUrl ? pathSection : routePath; + const path = pathSection; const isActive = !!path && currentPath?.includes(path as string); if (permissionGranted === false || (typeof permissionGranted === 'function' && !permissionGranted())) { diff --git a/apps/meteor/client/hooks/useAnalyticsEventTracking.ts b/apps/meteor/client/hooks/useAnalyticsEventTracking.ts new file mode 100644 index 0000000000000..78e078ef0070d --- /dev/null +++ b/apps/meteor/client/hooks/useAnalyticsEventTracking.ts @@ -0,0 +1,258 @@ +import { useRouter, useSetting, useUserId } from '@rocket.chat/ui-contexts'; +import { useEffect } from 'react'; + +import { callbacks } from '../../lib/callbacks'; + +function trackEvent(category: string, action: string, label?: unknown) { + const { _paq, ga } = window; + + _paq?.push(['trackEvent', category, action, label]); + ga?.('send', 'event', category, action, label); +} + +export const useAnalyticsEventTracking = () => { + const router = useRouter(); + + useEffect( + () => + router.subscribeToRouteChange(() => { + const { _paq, ga } = window; + + if (_paq) { + const http = location.protocol; + const slashes = http.concat('//'); + const host = slashes.concat(location.hostname); + _paq.push(['setCustomUrl', host + router.getLocationPathname()]); + _paq.push(['trackPageView']); + } + + ga?.('send', 'pageview', router.getLocationPathname()); + }), + [router], + ); + + useEffect(() => { + callbacks.add( + 'loginPageStateChange', + (state) => { + trackEvent('Navigation', 'Login Page State Change', state); + }, + callbacks.priority.MEDIUM, + 'analytics-login-state-change', + ); + + return () => { + callbacks.remove('loginPageStateChange', 'analytics-login-state-change'); + }; + }, []); + + const featuresMessages = useSetting('Analytics_features_messages', true); + + useEffect(() => { + if (!featuresMessages) { + return; + } + + callbacks.add( + 'afterSaveMessage', + (_message, room, _uid) => { + trackEvent('Message', 'Send', `${room.name} (${room._id})`); + }, + callbacks.priority.LOW, + 'trackEvents', + ); + + return () => { + callbacks.remove('afterSaveMessage', 'trackEvents'); + }; + }, [featuresMessages]); + + const featuresRooms = useSetting('Analytics_features_rooms', true); + + useEffect(() => { + if (!featuresRooms) { + return; + } + + callbacks.add( + 'afterCreateChannel', + (_owner, room) => { + trackEvent('Room', 'Create', `${room.name} (${room._id})`); + }, + callbacks.priority.MEDIUM, + 'analytics-after-create-channel', + ); + + callbacks.add( + 'roomNameChanged', + (room) => { + trackEvent('Room', 'Changed Name', `${room.name} (${room._id})`); + }, + callbacks.priority.MEDIUM, + 'analytics-room-name-changed', + ); + + callbacks.add( + 'roomTopicChanged', + (room) => { + trackEvent('Room', 'Changed Topic', `${room.name} (${room._id})`); + }, + callbacks.priority.MEDIUM, + 'analytics-room-topic-changed', + ); + + callbacks.add( + 'roomAnnouncementChanged', + (room) => { + trackEvent('Room', 'Changed Announcement', `${room.name} (${room._id})`); + }, + callbacks.priority.MEDIUM, + 'analytics-room-announcement-changed', + ); + + callbacks.add( + 'roomTypeChanged', + (room) => { + trackEvent('Room', 'Changed Room Type', `${room.name} (${room._id})`); + }, + callbacks.priority.MEDIUM, + 'analytics-room-type-changed', + ); + + callbacks.add( + 'archiveRoom', + (room) => { + trackEvent('Room', 'Archived', `${room.name} (${room._id})`); + }, + callbacks.priority.MEDIUM, + 'analytics-archive-room', + ); + + callbacks.add( + 'unarchiveRoom', + (room) => { + trackEvent('Room', 'Unarchived', `${room.name} (${room._id})`); + }, + callbacks.priority.MEDIUM, + 'analytics-unarchive-room', + ); + + callbacks.add( + 'roomAvatarChanged', + (room) => { + trackEvent('Room', 'Changed Avatar', `${room.name} (${room._id})`); + }, + callbacks.priority.MEDIUM, + 'analytics-room-avatar-changed', + ); + + return () => { + callbacks.remove('afterCreateChannel', 'analytics-after-create-channel'); + callbacks.remove('roomNameChanged', 'analytics-room-name-changed'); + callbacks.remove('roomTopicChanged', 'analytics-room-topic-changed'); + callbacks.remove('roomAnnouncementChanged', 'analytics-room-announcement-changed'); + callbacks.remove('roomTypeChanged', 'analytics-room-type-changed'); + callbacks.remove('archiveRoom', 'analytics-archive-room'); + callbacks.remove('unarchiveRoom', 'analytics-unarchive-room'); + callbacks.remove('roomAvatarChanged', 'analytics-room-avatar-changed'); + }; + }, [featuresRooms]); + + const featuresUsers = useSetting('Analytics_features_users', true); + + useEffect(() => { + if (!featuresUsers) { + return; + } + + callbacks.add( + 'userRegistered', + () => { + trackEvent('User', 'Registered'); + }, + callbacks.priority.MEDIUM, + 'piwik-user-resitered', + ); + + callbacks.add( + 'usernameSet', + () => { + trackEvent('User', 'Username Set'); + }, + callbacks.priority.MEDIUM, + 'piweik-username-set', + ); + + callbacks.add( + 'userPasswordReset', + () => { + trackEvent('User', 'Reset Password'); + }, + callbacks.priority.MEDIUM, + 'piwik-user-password-reset', + ); + + callbacks.add( + 'userConfirmationEmailRequested', + () => { + trackEvent('User', 'Confirmation Email Requested'); + }, + callbacks.priority.MEDIUM, + 'piwik-user-confirmation-email-requested', + ); + + callbacks.add( + 'userForgotPasswordEmailRequested', + () => { + trackEvent('User', 'Forgot Password Email Requested'); + }, + callbacks.priority.MEDIUM, + 'piwik-user-forgot-password-email-requested', + ); + + callbacks.add( + 'userStatusManuallySet', + (status) => { + trackEvent('User', 'Status Manually Changed', status); + }, + callbacks.priority.MEDIUM, + 'analytics-user-status-manually-set', + ); + + callbacks.add( + 'userAvatarSet', + (service) => { + trackEvent('User', 'Avatar Changed', service); + }, + callbacks.priority.MEDIUM, + 'analytics-user-avatar-set', + ); + + return () => { + callbacks.remove('userRegistered', 'piwik-user-resitered'); + callbacks.remove('usernameSet', 'piweik-username-set'); + callbacks.remove('userPasswordReset', 'piwik-user-password-reset'); + callbacks.remove('userConfirmationEmailRequested', 'piwik-user-confirmation-email-requested'); + callbacks.remove('userForgotPasswordEmailRequested', 'piwik-user-forgot-password-email-requested'); + callbacks.remove('userStatusManuallySet', 'analytics-user-status-manually-set'); + callbacks.remove('userAvatarSet', 'analytics-user-avatar-set'); + }; + }, [featuresUsers]); + + const uid = useUserId(); + + useEffect(() => { + if (!featuresUsers) { + return; + } + + const { _paq } = window; + + trackEvent('User', 'Login', uid); + _paq?.push(['setUserId', uid]); + + return () => { + trackEvent('User', 'Logout', uid); + }; + }, [featuresUsers, uid]); +}; diff --git a/apps/meteor/client/hooks/useDefaultRoute.ts b/apps/meteor/client/hooks/useDefaultRoute.ts deleted file mode 100644 index 9ed3e23d0b0f9..0000000000000 --- a/apps/meteor/client/hooks/useDefaultRoute.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { useRoute } from '@rocket.chat/ui-contexts'; - -import type { Item, SidebarDivider, SidebarItem } from '../lib/createSidebarItems'; - -const isSidebarDivider = (sidebarItem: SidebarItem): sidebarItem is SidebarDivider => { - return (sidebarItem as SidebarDivider).divider === true; -}; - -const firstSidebarPage = (sidebarItem: SidebarItem): sidebarItem is Item => { - if (isSidebarDivider(sidebarItem)) { - return false; - } - - return Boolean(sidebarItem.permissionGranted?.()); -}; - -export const useDefaultRoute = (getSidebarItems: () => SidebarItem[], fallbackRoute: string): ReturnType<typeof useRoute> => { - const defaultRouteHref = getSidebarItems().find(firstSidebarPage)?.href || fallbackRoute; - const defaultRoute = useRoute(defaultRouteHref); - - return defaultRoute; -}; diff --git a/apps/meteor/client/hooks/useEmbeddedLayout.ts b/apps/meteor/client/hooks/useEmbeddedLayout.ts index d326e221eb943..1f411377b3e01 100644 --- a/apps/meteor/client/hooks/useEmbeddedLayout.ts +++ b/apps/meteor/client/hooks/useEmbeddedLayout.ts @@ -1,3 +1,3 @@ -import { useQueryStringParameter } from '@rocket.chat/ui-contexts'; +import { useSearchParameter } from '@rocket.chat/ui-contexts'; -export const useEmbeddedLayout = (): boolean => useQueryStringParameter('layout') === 'embedded'; +export const useEmbeddedLayout = (): boolean => useSearchParameter('layout') === 'embedded'; diff --git a/apps/meteor/client/importPackages.ts b/apps/meteor/client/importPackages.ts index 4f404eb143fbe..eb2852f3fc94f 100644 --- a/apps/meteor/client/importPackages.ts +++ b/apps/meteor/client/importPackages.ts @@ -1,6 +1,5 @@ import '../app/cors/client'; import '../app/2fa/client'; -import '../app/analytics/client'; import '../app/apple/client'; import '../app/authorization/client'; import '../app/autotranslate/client'; diff --git a/apps/meteor/client/lib/appLayout.tsx b/apps/meteor/client/lib/appLayout.tsx index 6a2fac6df44a4..d189c7ce1c824 100644 --- a/apps/meteor/client/lib/appLayout.tsx +++ b/apps/meteor/client/lib/appLayout.tsx @@ -22,20 +22,24 @@ class AppLayoutSubscription extends Emitter<{ update: void }> { } render(element: ReactElement): void { - this.setCurrentValue( + this.setCurrentValue(this.wrap(element)); + } + + renderStandalone(element: ReactElement): void { + this.setCurrentValue(element); + } + + wrap(element: ReactElement): ReactElement { + return ( <> <ConnectionStatusBar /> <BannerRegion /> {element} <PortalsWrapper /> <ModalRegion /> - </>, + </> ); } - - renderStandalone(element: ReactElement): void { - this.setCurrentValue(element); - } } export const appLayout = new AppLayoutSubscription(); diff --git a/apps/meteor/client/lib/chats/flows/replyBroadcast.ts b/apps/meteor/client/lib/chats/flows/replyBroadcast.ts index ad7be7ec989de..69e857ee57cb3 100644 --- a/apps/meteor/client/lib/chats/flows/replyBroadcast.ts +++ b/apps/meteor/client/lib/chats/flows/replyBroadcast.ts @@ -1,17 +1,15 @@ import type { IMessage } from '@rocket.chat/core-typings'; -import { FlowRouter } from 'meteor/kadira:flow-router'; +import { router } from '../../../providers/RouterProvider'; import { roomCoordinator } from '../../rooms/roomCoordinator'; import type { ChatAPI } from '../ChatAPI'; export const replyBroadcast = async (_chat: ChatAPI, message: IMessage) => { - const queryStringParams = FlowRouter.current().queryParams; - roomCoordinator.openRouteLink( 'd', { name: message.u.username }, { - ...queryStringParams, + ...router.getSearchParameters(), reply: message._id, }, ); diff --git a/apps/meteor/client/lib/createRouteGroup.tsx b/apps/meteor/client/lib/createRouteGroup.tsx index 8f8691f82966a..e418ecc56e998 100644 --- a/apps/meteor/client/lib/createRouteGroup.tsx +++ b/apps/meteor/client/lib/createRouteGroup.tsx @@ -1,153 +1,81 @@ -import type { Context, Current, Group, RouteOptions } from 'meteor/kadira:flow-router'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Tracker } from 'meteor/tracker'; -import type { ElementType, ReactNode } from 'react'; -import React from 'react'; +import { type IRouterPaths, type RouteName, type RouterPathPattern } from '@rocket.chat/ui-contexts'; +import React, { type ElementType, type ReactNode } from 'react'; +import { router } from '../providers/RouterProvider'; import MainLayout from '../views/root/MainLayout'; import { appLayout } from './appLayout'; -let oldRoute: Current; +type GroupName = 'omnichannel' | 'marketplace' | 'account' | 'admin'; -const registerLazyComponentRoute = ( - routeGroup: Group, - RouterComponent: ElementType<{ - children?: ReactNode; - }>, - path: string, - { - component: RouteComponent, - props, - ready = true, - ...rest - }: RouteOptions & { - component: ElementType; - props?: Record<string, unknown>; - ready?: boolean; - }, -): [register: () => void, unregister: () => void] => { - const enabled = new ReactiveVar(ready ? true : undefined); - let computation: Tracker.Computation | undefined; - - const handleEnter = (_context: Context, _redirect: (pathDef: string) => void, stop: () => void): void => { - const _enabled = Tracker.nonreactive(() => enabled.get()); - if (_enabled === false) { - stop(); - return; - } +type GroupPrefix<TGroupName extends GroupName> = IRouterPaths[`${TGroupName}-index`]['pattern']; - computation = Tracker.autorun(() => { - const _enabled = enabled.get(); - - if (_enabled === false) { - FlowRouter.go('/'); - } - }); - }; - - const handleExit = (context: Context): void => { - computation?.stop(); - - if (!context.oldRoute) { - return; - } +type RouteNamesOf<TGroupName extends GroupName> = Extract< + | keyof { + [TRouteName in RouteName as IRouterPaths[TRouteName]['pattern'] extends `${GroupPrefix<TGroupName>}/${string}` + ? TRouteName + : never]: never; + } + | `${GroupName}-index`, + RouteName +>; - if (context.route.group?.name === context.oldRoute?.group?.name) { - return; - } - - oldRoute?.route?.name && FlowRouter.go(oldRoute.route.name, oldRoute.params, oldRoute.queryParams); - }; - - routeGroup.route(path, { - ...rest, - triggersEnter: [handleEnter, ...(rest.triggersEnter ?? [])], - triggersExit: [handleExit, ...(rest.triggersExit ?? [])], - action() { - appLayout.render( - <MainLayout> - <RouterComponent> - <RouteComponent {...props} /> - </RouterComponent> - </MainLayout>, - ); - }, - }); - - return [(): void => enabled.set(true), (): void => enabled.set(false)]; -}; +type TrimPrefix<T extends string, P extends string> = T extends `${P}${infer U}` ? U : T; -export const createRouteGroup = ( - name: string, - prefix: string, +export const createRouteGroup = <TGroupName extends GroupName>( + name: TGroupName, + prefix: GroupPrefix<TGroupName>, RouterComponent: ElementType<{ children?: ReactNode; }>, -): { - ( - path: string, - options: RouteOptions & { - component: ElementType; - props?: Record<string, unknown>; - ready?: boolean; +) => { + router.defineRoutes([ + { + path: prefix, + id: `${name}-index`, + element: appLayout.wrap( + <MainLayout> + <RouterComponent /> + </MainLayout>, + ), }, - ): [register: () => void, unregister: () => void]; - (path: string, options: RouteOptions): void; -} => { - const routeGroup = FlowRouter.group({ - name, - prefix, - }); - - function registerRoute( - path: string, - options: RouteOptions & { + ]); + + return <TRouteName extends RouteNamesOf<TGroupName>>( + path: TrimPrefix<IRouterPaths[TRouteName]['pattern'], GroupPrefix<TGroupName>>, + { + name, + component: RouteComponent, + props, + ready = true, + }: { + name: TRouteName; component: ElementType; props?: Record<string, unknown>; ready?: boolean; }, - ): [register: () => void, unregister: () => void]; - function registerRoute(path: string, options: RouteOptions): void; - function registerRoute( - path: string, - options: - | RouteOptions - | (RouteOptions & { - component: ElementType; - props?: Record<string, unknown>; - ready?: boolean; - }), - ): [register: () => void, unregister: () => void] | void { - if ('component' in options) { - return registerLazyComponentRoute(routeGroup, RouterComponent, path, options); - } - - routeGroup.route(path, options); - } + ): [register: () => void, unregister: () => void] => { + let unregister: (() => void) | undefined; + + const register = () => { + unregister = router.defineRoutes([ + { + path: `${prefix}${path}` as RouterPathPattern, + id: name, + element: appLayout.wrap( + <MainLayout> + <RouterComponent> + <RouteComponent {...props} /> + </RouterComponent> + </MainLayout>, + ), + }, + ]); + }; - registerRoute('/', { - name: `${name}-index`, - action() { - appLayout.render( - <MainLayout> - <RouterComponent /> - </MainLayout>, - ); - }, - }); + if (ready) { + register(); + } - return registerRoute; + return [register, () => unregister?.()]; + }; }; - -Tracker.autorun( - (() => { - let oldName: string; - return () => { - const name = FlowRouter.getRouteName(); - if (oldName !== name) { - oldRoute = FlowRouter.current(); - } - }; - })(), -); diff --git a/apps/meteor/client/lib/createSidebarItems.ts b/apps/meteor/client/lib/createSidebarItems.ts index 7ce4a5250155a..8d836cc293ceb 100644 --- a/apps/meteor/client/lib/createSidebarItems.ts +++ b/apps/meteor/client/lib/createSidebarItems.ts @@ -1,14 +1,14 @@ import type { IconProps } from '@rocket.chat/fuselage'; +import type { LocationPathname } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; export type Item = { i18nLabel: string; - href?: string; + href?: LocationPathname | `https://go.rocket.chat/i/${string}`; icon?: IconProps['name']; tag?: 'Alpha' | 'Beta'; permissionGranted?: () => boolean; pathSection?: string; - pathGroup?: string; name?: string; externalUrl?: boolean; badge?: () => ReactElement; @@ -17,6 +17,9 @@ export type SidebarDivider = { divider: boolean; i18nLabel: string }; export type SidebarItem = Item | SidebarDivider; export const isSidebarItem = (item: SidebarItem): item is Item => !('divider' in item); +export const isGoRocketChatLink = (link: string): link is `https://go.rocket.chat/i/${string}` => + link.startsWith('https://go.rocket.chat/i/'); + export const createSidebarItems = ( initialItems: SidebarItem[] = [], ): { diff --git a/apps/meteor/client/lib/errors/VisitorDoesNotExistError.ts b/apps/meteor/client/lib/errors/VisitorDoesNotExistError.ts new file mode 100644 index 0000000000000..646e17b17ff9f --- /dev/null +++ b/apps/meteor/client/lib/errors/VisitorDoesNotExistError.ts @@ -0,0 +1,7 @@ +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/rooms/roomCoordinator.tsx b/apps/meteor/client/lib/rooms/roomCoordinator.tsx index a09d2a9bcdc2a..9a2e3d14ab475 100644 --- a/apps/meteor/client/lib/rooms/roomCoordinator.tsx +++ b/apps/meteor/client/lib/rooms/roomCoordinator.tsx @@ -1,6 +1,7 @@ import type { IRoom, RoomType, IUser, AtLeast, ValueOf, ISubscription } from '@rocket.chat/core-typings'; import { isRoomFederated } from '@rocket.chat/core-typings'; -import { FlowRouter } from 'meteor/kadira:flow-router'; +import type { RouteName } from '@rocket.chat/ui-contexts'; +import { Meteor } from 'meteor/meteor'; import React from 'react'; import { hasPermission } from '../../../app/authorization/client'; @@ -16,7 +17,8 @@ import type { IRoomTypeClientConfig, } from '../../../definition/IRoomTypeConfig'; import { RoomCoordinator } from '../../../lib/rooms/coordinator'; -import RoomOpener from '../../views/room/RoomOpener'; +import { router } from '../../providers/RouterProvider'; +import RoomRoute from '../../views/room/RoomRoute'; import MainLayout from '../../views/root/MainLayout/MainLayout'; import { appLayout } from '../appLayout'; @@ -70,7 +72,12 @@ class RoomCoordinatorClient extends RoomCoordinator { return this.roomTypes[roomType].directives as IRoomTypeClientDirectives; } - public openRouteLink(roomType: RoomType, subData: RoomIdentification, queryParams?: Record<string, string>): void { + public openRouteLink( + roomType: RoomType, + subData: RoomIdentification, + queryParams?: Record<string, string>, + options: { replace?: boolean } = {}, + ): void { const config = this.getRoomTypeConfig(roomType); if (!config?.route) { return; @@ -87,7 +94,14 @@ class RoomCoordinatorClient extends RoomCoordinator { return; } - FlowRouter.go(config.route.name, routeData, queryParams); + router.navigate( + { + pattern: config.route.path ?? '/home', + params: routeData, + search: queryParams, + }, + options, + ); } public isLivechatRoom(roomType: string): boolean { @@ -161,7 +175,7 @@ class RoomCoordinatorClient extends RoomCoordinator { return true; } - private validateRoute(route: IRoomTypeRouteConfig): void { + private validateRoute<TRouteName extends RouteName>(route: IRoomTypeRouteConfig<TRouteName>): void { const { name, path, link } = route; if (typeof name !== 'string' || name.length === 0) { @@ -199,18 +213,17 @@ class RoomCoordinatorClient extends RoomCoordinator { route: { name, path }, } = roomConfig; const { extractOpenRoomParams } = directives; - FlowRouter.route(path, { - name, - action: (params) => { - const { type, ref } = extractOpenRoomParams(params ?? {}); - - appLayout.render( + router.defineRoutes([ + { + path, + id: name, + element: appLayout.wrap( <MainLayout> - <RoomOpener type={type} reference={ref} /> + <RoomRoute extractOpenRoomParams={extractOpenRoomParams} /> </MainLayout>, - ); + ), }, - }); + ]); } } @@ -225,7 +238,12 @@ class RoomCoordinatorClient extends RoomCoordinator { return false; } - return FlowRouter.url(config.route.name, routeData); + return Meteor.absoluteUrl( + router.buildRoutePath({ + name: config.route.name, + params: routeData, + }), + ); } public isRouteNameKnown(routeName: string): boolean { diff --git a/apps/meteor/client/lib/rooms/roomTypes/direct.ts b/apps/meteor/client/lib/rooms/roomTypes/direct.ts index 575ec1534b91c..776c309c85447 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/direct.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/direct.ts @@ -142,7 +142,7 @@ roomCoordinator.add( }, extractOpenRoomParams({ rid }) { - return { type: 'd', ref: rid }; + return { type: 'd', reference: rid }; }, findRoom(identifier) { diff --git a/apps/meteor/client/lib/rooms/roomTypes/livechat.ts b/apps/meteor/client/lib/rooms/roomTypes/livechat.ts index 82f2a48a9a9ff..26919ac83b99e 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/livechat.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/livechat.ts @@ -81,7 +81,7 @@ roomCoordinator.add( }, extractOpenRoomParams({ id }) { - return { type: 'l', ref: id }; + return { type: 'l', reference: id }; }, } as AtLeast<IRoomTypeClientDirectives, 'roomName'>, ); diff --git a/apps/meteor/client/lib/rooms/roomTypes/private.ts b/apps/meteor/client/lib/rooms/roomTypes/private.ts index b1d34a92c4ad5..b99e172e83960 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/private.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/private.ts @@ -106,7 +106,7 @@ roomCoordinator.add( }, extractOpenRoomParams({ name }) { - return { type: 'p', ref: name }; + return { type: 'p', reference: name }; }, findRoom(identifier) { diff --git a/apps/meteor/client/lib/rooms/roomTypes/public.ts b/apps/meteor/client/lib/rooms/roomTypes/public.ts index 4dd38eb95881e..cf9d4c1a978c7 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/public.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/public.ts @@ -105,7 +105,7 @@ roomCoordinator.add( }, extractOpenRoomParams({ name }) { - return { type: 'c', ref: name }; + return { type: 'c', reference: name }; }, findRoom(identifier) { diff --git a/apps/meteor/client/lib/rooms/roomTypes/voip.ts b/apps/meteor/client/lib/rooms/roomTypes/voip.ts index 258103651e0ff..30b07aaec1eec 100644 --- a/apps/meteor/client/lib/rooms/roomTypes/voip.ts +++ b/apps/meteor/client/lib/rooms/roomTypes/voip.ts @@ -45,7 +45,7 @@ roomCoordinator.add( }, extractOpenRoomParams({ id }) { - return { type: 'v', ref: id }; + return { type: 'v', reference: id }; }, } as AtLeast<IRoomTypeClientDirectives, 'roomName'>, ); diff --git a/apps/meteor/client/lib/tracker.ts b/apps/meteor/client/lib/tracker.ts index 5c069ec4da4d4..2e4e6aab89f4a 100644 --- a/apps/meteor/client/lib/tracker.ts +++ b/apps/meteor/client/lib/tracker.ts @@ -6,12 +6,10 @@ export const asReactiveSource = <T>(subscribe: (cb: () => void) => () => void, g } const computation = Tracker.currentComputation; - const unsubscribe = subscribe(() => computation.invalidate()); - // const id = new Error().stack?.split('\n')[2].trim(); - // console.log('sub', id); - computation.onInvalidate(() => { + const unsubscribe = subscribe(() => computation?.invalidate()); + + computation?.onInvalidate(() => { unsubscribe(); - // console.log('unsub', id); }); return getSnapshot(); diff --git a/apps/meteor/client/lib/userData.ts b/apps/meteor/client/lib/userData.ts index ba6d62303280f..9e67f4034b1d2 100644 --- a/apps/meteor/client/lib/userData.ts +++ b/apps/meteor/client/lib/userData.ts @@ -46,7 +46,7 @@ const updateUser = (userData: IUser): void => { Object.keys(user).forEach((key) => { delete userData[key as keyof IUser]; }); - Users.update({ _id: user._id }, { $set: userData }); + Users.update({ _id: user._id }, { $set: { ...userData } }); }; let cancel: undefined | (() => void); @@ -69,7 +69,7 @@ export const synchronizeUserData = async (uid: IUser['_id']): Promise<RawUserDat break; case 'updated': - Users.upsert({ _id: uid }, { $set: data.diff, $unset: data.unset }); + Users.upsert({ _id: uid }, { $set: data.diff, $unset: data.unset as any }); break; case 'removed': diff --git a/apps/meteor/client/lib/utils/goToRoomById.ts b/apps/meteor/client/lib/utils/goToRoomById.ts index d55d47e37a594..2bb5936d55109 100644 --- a/apps/meteor/client/lib/utils/goToRoomById.ts +++ b/apps/meteor/client/lib/utils/goToRoomById.ts @@ -1,8 +1,8 @@ import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; import { memoize } from '@rocket.chat/memo'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { ChatSubscription } from '../../../app/models/client'; +import { router } from '../../providers/RouterProvider'; import { roomCoordinator } from '../rooms/roomCoordinator'; import { callWithErrorHandling } from './callWithErrorHandling'; @@ -16,10 +16,10 @@ export const goToRoomById = async (rid: IRoom['_id']): Promise<void> => { const subscription: ISubscription | undefined = ChatSubscription.findOne({ rid }); if (subscription) { - roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams); + roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters()); return; } const room = await getRoomById(rid); - roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, FlowRouter.current().queryParams); + roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, router.getSearchParameters()); }; diff --git a/apps/meteor/client/lib/utils/isLayoutEmbedded.ts b/apps/meteor/client/lib/utils/isLayoutEmbedded.ts index 07743a49f72e7..540ccf1296034 100644 --- a/apps/meteor/client/lib/utils/isLayoutEmbedded.ts +++ b/apps/meteor/client/lib/utils/isLayoutEmbedded.ts @@ -1,3 +1,3 @@ -import { FlowRouter } from 'meteor/kadira:flow-router'; +import { router } from '../../providers/RouterProvider'; -export const isLayoutEmbedded = (): boolean => FlowRouter.getQueryParam('layout') === 'embedded'; +export const isLayoutEmbedded = (): boolean => router.getSearchParameters().layout === 'embedded'; diff --git a/apps/meteor/client/lib/utils/legacyJumpToMessage.ts b/apps/meteor/client/lib/utils/legacyJumpToMessage.ts index 580224444f542..3888e672bdc7c 100644 --- a/apps/meteor/client/lib/utils/legacyJumpToMessage.ts +++ b/apps/meteor/client/lib/utils/legacyJumpToMessage.ts @@ -1,9 +1,9 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { isThreadMessage } from '@rocket.chat/core-typings'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { ChatRoom } from '../../../app/models/client'; import { RoomHistoryManager } from '../../../app/ui-utils/client'; +import { router } from '../../providers/RouterProvider'; import { RoomManager } from '../RoomManager'; import { goToRoomById } from './goToRoomById'; @@ -14,24 +14,27 @@ export const legacyJumpToMessage = async (message: IMessage) => { } if (isThreadMessage(message) || message.tcount) { - const { route, queryParams, params } = FlowRouter.current(); + const { tab, context } = router.getRouteParameters(); - if (params.tab === 'thread' && (params.context === message.tmid || params.context === message._id)) { + if (tab === 'thread' && (context === message.tmid || context === message._id)) { return; } - FlowRouter.go( - route?.name ?? '/', + router.navigate( { - tab: 'thread', - context: message.tmid || message._id, - rid: message.rid, - name: ChatRoom.findOne({ _id: message.rid })?.name ?? '', - }, - { - ...queryParams, - msg: message._id, + name: router.getRouteName()!, + params: { + tab: 'thread', + context: message.tmid || message._id, + rid: message.rid, + name: ChatRoom.findOne({ _id: message.rid })?.name ?? '', + }, + search: { + ...router.getSearchParameters(), + msg: message._id, + }, }, + { replace: false }, ); return; } diff --git a/apps/meteor/client/lib/utils/setMessageJumpQueryStringParameter.ts b/apps/meteor/client/lib/utils/setMessageJumpQueryStringParameter.ts index fbb8794e6b404..74055930ef3bb 100644 --- a/apps/meteor/client/lib/utils/setMessageJumpQueryStringParameter.ts +++ b/apps/meteor/client/lib/utils/setMessageJumpQueryStringParameter.ts @@ -1,6 +1,15 @@ import type { IMessage } from '@rocket.chat/core-typings'; -import { FlowRouter } from 'meteor/kadira:flow-router'; + +import { router } from '../../providers/RouterProvider'; export const setMessageJumpQueryStringParameter = async (msg: IMessage['_id'] | null) => { - FlowRouter.setQueryParams({ msg }); + const { msg: _, ...search } = router.getSearchParameters(); + + router.navigate( + { + pathname: router.getLocationPathname(), + search: msg ? { ...search, msg } : search, + }, + { replace: true }, + ); }; diff --git a/apps/meteor/client/providers/CallProvider/CallProvider.tsx b/apps/meteor/client/providers/CallProvider/CallProvider.tsx index 22ef506ad481a..0de0a3fda0c32 100644 --- a/apps/meteor/client/providers/CallProvider/CallProvider.tsx +++ b/apps/meteor/client/providers/CallProvider/CallProvider.tsx @@ -13,7 +13,7 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { Random } from '@rocket.chat/random'; import type { Device, IExperimentalHTMLAudioElement } from '@rocket.chat/ui-contexts'; import { - useRoute, + useRouter, useUser, useSetting, useEndpoint, @@ -23,7 +23,6 @@ import { useSetModal, useTranslation, } from '@rocket.chat/ui-contexts'; -// import { useRoute, useUser, useSetting, useEndpoint, useStream, useSetModal } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { useMemo, useRef, useCallback, useEffect, useState } from 'react'; import { createPortal } from 'react-dom'; @@ -56,7 +55,7 @@ export const CallProvider: FC = ({ children }) => { const result = useVoipClient(); const user = useUser(); - const homeRoute = useRoute('home'); + const router = useRouter(); const setOutputMediaDevice = useSetOutputMediaDevice(); const setInputMediaDevice = useSetInputMediaDevice(); @@ -80,14 +79,14 @@ export const CallProvider: FC = ({ children }) => { token: roomInfo.v.token || '', options: { comment: data?.comment, tags: data?.tags }, })); - homeRoute.push({}); + router.navigate('/home'); const queueAggregator = result.voipClient?.getAggregator(); if (queueAggregator) { queueAggregator.callEnded(); } }, - [homeRoute, result?.voipClient, roomInfo, voipCloseRoomEndpoint], + [router, result?.voipClient, roomInfo, voipCloseRoomEndpoint], ); const openWrapUpModal = useCallback((): void => { diff --git a/apps/meteor/client/providers/LayoutProvider.tsx b/apps/meteor/client/providers/LayoutProvider.tsx index 85a33fab7a234..95b6b04f9f530 100644 --- a/apps/meteor/client/providers/LayoutProvider.tsx +++ b/apps/meteor/client/providers/LayoutProvider.tsx @@ -1,12 +1,12 @@ import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; -import { LayoutContext, useQueryStringParameter, useRoute, useSetting } from '@rocket.chat/ui-contexts'; +import { LayoutContext, useRouter, useSearchParameter, useSetting } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { useMemo, useState, useEffect } from 'react'; const LayoutProvider: FC = ({ children }) => { const showTopNavbarEmbeddedLayout = Boolean(useSetting('UI_Show_top_navbar_embedded_layout')); const [isCollapsed, setIsCollapsed] = useState(false); - const layout = useQueryStringParameter('layout'); + const layout = useSearchParameter('layout'); const isEmbedded = layout === 'embedded'; const breakpoints = useBreakpoints(); // ["xs", "sm", "md", "lg", "xl", xxl"] @@ -16,7 +16,7 @@ const LayoutProvider: FC = ({ children }) => { setIsCollapsed(isMobile); }, [isMobile]); - const routeHome = useRoute('home'); + const router = useRouter(); return ( <LayoutContext.Provider @@ -31,7 +31,7 @@ const LayoutProvider: FC = ({ children }) => { toggle: () => setIsCollapsed((isCollapsed) => !isCollapsed), collapse: () => setIsCollapsed(true), expand: () => setIsCollapsed(false), - close: () => (isEmbedded ? setIsCollapsed(true) : routeHome.push()), + close: () => (isEmbedded ? setIsCollapsed(true) : router.navigate('/home')), }, size: { sidebar: '240px', @@ -42,7 +42,7 @@ const LayoutProvider: FC = ({ children }) => { // eslint-disable-next-line no-nested-ternary contextualBarPosition: breakpoints.includes('sm') ? (breakpoints.includes('lg') ? 'relative' : 'absolute') : 'fixed', }), - [isMobile, isEmbedded, showTopNavbarEmbeddedLayout, isCollapsed, breakpoints, routeHome], + [isMobile, isEmbedded, showTopNavbarEmbeddedLayout, isCollapsed, breakpoints, router], )} /> ); diff --git a/apps/meteor/client/providers/RouterProvider.tsx b/apps/meteor/client/providers/RouterProvider.tsx index 66bb0aedf93ba..af0448ce34e8c 100644 --- a/apps/meteor/client/providers/RouterProvider.tsx +++ b/apps/meteor/client/providers/RouterProvider.tsx @@ -1,88 +1,208 @@ -import type { RouterContextValue } from '@rocket.chat/ui-contexts'; +import type { + RouterContextValue, + RouteName, + LocationPathname, + RouteParameters, + SearchParameters, + To, + RouteObject, +} from '@rocket.chat/ui-contexts'; import { RouterContext } from '@rocket.chat/ui-contexts'; +import type { LocationSearch } from '@rocket.chat/ui-contexts/src/RouterContext'; import { FlowRouter } from 'meteor/kadira:flow-router'; import { Tracker } from 'meteor/tracker'; import type { FC } from 'react'; import React from 'react'; -import { createSubscription } from '../lib/createSubscription'; - -const queryRoutePath = ( - name: Parameters<RouterContextValue['queryRoutePath']>[0], - parameters: Parameters<RouterContextValue['queryRoutePath']>[1], - queryStringParameters: Parameters<RouterContextValue['queryRoutePath']>[2], -): ReturnType<RouterContextValue['queryRoutePath']> => createSubscription(() => FlowRouter.path(name, parameters, queryStringParameters)); - -const queryRouteUrl = ( - name: Parameters<RouterContextValue['queryRouteUrl']>[0], - parameters: Parameters<RouterContextValue['queryRouteUrl']>[1], - queryStringParameters: Parameters<RouterContextValue['queryRouteUrl']>[2], -): ReturnType<RouterContextValue['queryRouteUrl']> => createSubscription(() => FlowRouter.url(name, parameters, queryStringParameters)); - -const pushRoute = ( - name: Parameters<RouterContextValue['pushRoute']>[0], - parameters: Parameters<RouterContextValue['pushRoute']>[1], - queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>, -): ReturnType<RouterContextValue['pushRoute']> => { - const queryParams = - typeof queryStringParameters === 'function' ? queryStringParameters(FlowRouter.current().queryParams) : queryStringParameters; - FlowRouter.go(name, parameters, queryParams); +import { appLayout } from '../lib/appLayout'; +import { queueMicrotask } from '../lib/utils/queueMicrotask'; + +FlowRouter.wait(); + +FlowRouter.notFound = { + action: () => undefined, }; -const replaceRoute = ( - name: Parameters<RouterContextValue['replaceRoute']>[0], - parameters: Parameters<RouterContextValue['replaceRoute']>[1], - queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>, -): ReturnType<RouterContextValue['replaceRoute']> => { - FlowRouter.withReplaceState(() => { - const queryParams = - typeof queryStringParameters === 'function' ? queryStringParameters(FlowRouter.current().queryParams) : queryStringParameters; - FlowRouter.go(name, parameters, queryParams); - }); +const subscribers = new Set<() => void>(); + +const listenToRouteChange = () => { + FlowRouter.watchPathChange(); + subscribers.forEach((onRouteChange) => onRouteChange()); }; -const queryRouteParameter = ( - name: Parameters<RouterContextValue['replaceRoute']>[0], -): ReturnType<RouterContextValue['queryRouteParameter']> => createSubscription(() => FlowRouter.getParam(name)); +let computation: Tracker.Computation | undefined; -const queryQueryStringParameter = ( - name: Parameters<RouterContextValue['queryQueryStringParameter']>[0], -): ReturnType<RouterContextValue['queryQueryStringParameter']> => createSubscription(() => FlowRouter.getQueryParam(name)); +queueMicrotask(() => { + computation = Tracker.autorun(listenToRouteChange); +}); -const queryCurrentRoute = (): ReturnType<RouterContextValue['queryCurrentRoute']> => - createSubscription(() => { - FlowRouter.watchPathChange(); - const { route, params, queryParams } = FlowRouter.current(); - return [route?.name, params, queryParams, route?.group?.name]; - }); +const subscribeToRouteChange = (onRouteChange: () => void): (() => void) => { + subscribers.add(onRouteChange); + + computation?.invalidate(); + + return () => { + subscribers.delete(onRouteChange); + + if (subscribers.size === 0) { + queueMicrotask(() => computation?.stop()); + } + }; +}; + +const getLocationPathname = () => FlowRouter.current().path as LocationPathname; + +const getLocationSearch = () => location.search as LocationSearch; + +const getRouteParameters = () => (FlowRouter.current().params ?? {}) as RouteParameters; + +const getSearchParameters = () => (FlowRouter.current().queryParams ?? {}) as SearchParameters; + +const getRouteName = () => FlowRouter.current().route?.name as RouteName | undefined; + +const encodeSearchParameters = (searchParameters: SearchParameters) => { + const search = new URLSearchParams(); + + for (const [key, value] of Object.entries(searchParameters)) { + search.append(key, value); + } + + const searchString = search.toString(); + + return searchString ? `?${searchString}` : ''; +}; + +const buildRoutePath = (to: To): LocationPathname | `${LocationPathname}?${LocationSearch}` => { + if (typeof to === 'string') { + return to; + } + + if ('pathname' in to) { + const { pathname, search = {} } = to; + return (pathname + encodeSearchParameters(search)) as LocationPathname | `${LocationPathname}?${LocationSearch}`; + } + + if ('pattern' in to) { + const { pattern, params = {}, search = {} } = to; + return Tracker.nonreactive(() => FlowRouter.path(pattern, params, search)) as + | LocationPathname + | `${LocationPathname}?${LocationSearch}`; + } + + if ('name' in to) { + const { name, params = {}, search = {} } = to; + return Tracker.nonreactive(() => FlowRouter.path(name, params, search)) as LocationPathname | `${LocationPathname}?${LocationSearch}`; + } + + throw new Error('Invalid route'); +}; + +const navigate = ( + toOrDelta: To | number, + options?: { + replace?: boolean; + }, +) => { + if (typeof toOrDelta === 'number') { + history.go(toOrDelta); + return; + } + + const path = buildRoutePath(toOrDelta); + const state = { path }; -const setQueryString = (paramsOrFn: Record<string, string | null> | ((prev: Record<string, string>) => Record<string, string>)): void => { - if (typeof paramsOrFn === 'function') { - const prevParams = FlowRouter.current().queryParams; - const emptyParams = Object.fromEntries(Object.entries(prevParams).map(([key]) => [key, null])); - const newParams = paramsOrFn(prevParams); - FlowRouter.setQueryParams({ ...emptyParams, ...newParams }); + if (options?.replace) { + history.replaceState(state, '', path); + } else { + history.pushState(state, '', path); + } + + dispatchEvent(new PopStateEvent('popstate', { state })); +}; + +const routes: RouteObject[] = []; +const routesSubscribers = new Set<() => void>(); + +const updateFlowRouter = () => { + if (FlowRouter._initialized) { + FlowRouter._updateCallbacks(); + FlowRouter._page.dispatch({ path: FlowRouter._current.path, params: {} }); return; } - FlowRouter.setQueryParams(paramsOrFn); + FlowRouter.initialize(); +}; + +const defineRoutes = (routes: RouteObject[]) => { + const flowRoutes = routes.map((route) => { + if (route.path === '*') { + FlowRouter.notFound = { + action: () => appLayout.renderStandalone(<>{route.element}</>), + }; + + return FlowRouter.notFound; + } + + return FlowRouter.route(route.path, { + name: route.id, + action: () => appLayout.renderStandalone(<>{route.element}</>), + }); + }); + + routes.push(...routes); + const index = routes.length - 1; + + updateFlowRouter(); + routesSubscribers.forEach((onRoutesChange) => onRoutesChange()); + + return () => { + flowRoutes.forEach((flowRoute) => { + FlowRouter._routes = FlowRouter._routes.filter((r) => r !== flowRoute); + if ('name' in flowRoute && flowRoute.name) { + delete FlowRouter._routesMap[flowRoute.name]; + } else { + FlowRouter.notFound = { + action: () => appLayout.renderStandalone(<></>), + }; + } + }); + + if (index !== -1) { + routes.splice(index, 1); + } + + updateFlowRouter(); + routesSubscribers.forEach((onRoutesChange) => onRoutesChange()); + }; +}; + +const getRoutes = () => routes; + +const subscribeToRoutesChange = (onRoutesChange: () => void): (() => void) => { + routesSubscribers.add(onRoutesChange); + + onRoutesChange(); + + return () => { + routesSubscribers.delete(onRoutesChange); + }; }; -const getRoutePath = (name: string, parameters?: Record<string, string>, queryStringParameters?: Record<string, string>) => - Tracker.nonreactive(() => FlowRouter.path(name, parameters, queryStringParameters)); - -const contextValue = { - queryRoutePath, - queryRouteUrl, - pushRoute, - replaceRoute, - queryRouteParameter, - queryQueryStringParameter, - queryCurrentRoute, - setQueryString, - getRoutePath, +/** @deprecated */ +export const router: RouterContextValue = { + subscribeToRouteChange, + getLocationPathname, + getLocationSearch, + getRouteParameters, + getSearchParameters, + getRouteName, + buildRoutePath, + navigate, + defineRoutes, + getRoutes, + subscribeToRoutesChange, }; -const RouterProvider: FC = ({ children }) => <RouterContext.Provider children={children} value={contextValue} />; +const RouterProvider: FC = ({ children }) => <RouterContext.Provider children={children} value={router} />; export default RouterProvider; diff --git a/apps/meteor/client/sidebar/Item/Medium.tsx b/apps/meteor/client/sidebar/Item/Medium.tsx index 180787ba6041e..16de09d151f8d 100644 --- a/apps/meteor/client/sidebar/Item/Medium.tsx +++ b/apps/meteor/client/sidebar/Item/Medium.tsx @@ -30,12 +30,12 @@ const Medium: VFC<MediumProps> = ({ icon, title = '', avatar, actions, href, bad }; return ( - <Sidebar.Item {...props} {...({ href } as any)} clickable={!!href}> + <Sidebar.Item {...props} href={href} clickable={!!href}> {avatar && <Sidebar.Item.Avatar>{avatar}</Sidebar.Item.Avatar>} <Sidebar.Item.Content> <Sidebar.Item.Wrapper> {icon} - <Sidebar.Item.Title data-qa='sidebar-item-title' className={(unread && 'rcx-sidebar-item--highlighted') as string}> + <Sidebar.Item.Title data-qa='sidebar-item-title' className={unread ? 'rcx-sidebar-item--highlighted' : undefined}> {title} </Sidebar.Item.Title> </Sidebar.Item.Wrapper> diff --git a/apps/meteor/client/sidebar/RoomMenu.tsx b/apps/meteor/client/sidebar/RoomMenu.tsx index 09903e66b59e4..be8d889f3065c 100644 --- a/apps/meteor/client/sidebar/RoomMenu.tsx +++ b/apps/meteor/client/sidebar/RoomMenu.tsx @@ -3,9 +3,9 @@ import { Option, Menu } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import type { TranslationKey, Fields } from '@rocket.chat/ui-contexts'; import { + useRouter, useSetModal, useToastMessageDispatch, - useRoute, useUserSubscription, useSetting, usePermission, @@ -77,7 +77,7 @@ const RoomMenu = ({ const closeModal = useMutableCallback(() => setModal()); - const router = useRoute('home'); + const router = useRouter(); const subscription = useUserSubscription(rid, fields); const canFavorite = useSetting('Favorite_Rooms'); @@ -115,7 +115,7 @@ const RoomMenu = ({ try { await leaveRoom({ roomId: rid }); if (roomOpen) { - router.push({}); + router.navigate('/home'); } LegacyRoomManager.close(rid); } catch (error) { @@ -184,7 +184,7 @@ const RoomMenu = ({ } LegacyRoomManager.close(subscription.t + subscription.name); - router.push({}); + router.navigate('/home'); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } diff --git a/apps/meteor/client/sidebar/header/actions/Directory.tsx b/apps/meteor/client/sidebar/header/actions/Directory.tsx index 76ae51042ac68..cdfdcfc1eaf1f 100644 --- a/apps/meteor/client/sidebar/header/actions/Directory.tsx +++ b/apps/meteor/client/sidebar/header/actions/Directory.tsx @@ -1,15 +1,17 @@ import { Sidebar } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useLayout, useRoute } from '@rocket.chat/ui-contexts'; -import type { HTMLAttributes, VFC } from 'react'; +import { useLayout, useRouter } from '@rocket.chat/ui-contexts'; +import type { HTMLAttributes } from 'react'; import React from 'react'; -const Directory: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => { - const directoryRoute = useRoute('directory'); +type DirectoryProps = Omit<HTMLAttributes<HTMLElement>, 'is'>; + +const Directory = (props: DirectoryProps) => { + const router = useRouter(); const { sidebar } = useLayout(); const handleDirectory = useMutableCallback(() => { sidebar.toggle(); - directoryRoute.push({}); + router.navigate('/directory'); }); return <Sidebar.TopBar.Action {...props} icon='notebook-hashtag' onClick={handleDirectory} />; diff --git a/apps/meteor/client/sidebar/header/actions/Home.tsx b/apps/meteor/client/sidebar/header/actions/Home.tsx index 9450bd4c12aba..2c4bbec40f516 100644 --- a/apps/meteor/client/sidebar/header/actions/Home.tsx +++ b/apps/meteor/client/sidebar/header/actions/Home.tsx @@ -1,16 +1,16 @@ import { Sidebar } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useLayout, useRoute, useSetting } from '@rocket.chat/ui-contexts'; +import { useRouter, useLayout, useSetting } from '@rocket.chat/ui-contexts'; import type { HTMLAttributes, VFC } from 'react'; import React from 'react'; const SidebarHeaderActionHome: VFC<Omit<HTMLAttributes<HTMLElement>, 'is'>> = (props) => { - const homeRoute = useRoute('home'); + const router = useRouter(); const { sidebar } = useLayout(); const showHome = useSetting('Layout_Show_Home_Button'); const handleHome = useMutableCallback(() => { sidebar.toggle(); - homeRoute.push({}); + router.navigate('/home'); }); return showHome ? <Sidebar.TopBar.Action {...props} icon='home' onClick={handleHome} /> : null; diff --git a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx index 3c8dada44e51f..97bcb291a37e9 100644 --- a/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx +++ b/apps/meteor/client/sidebar/header/actions/hooks/useAdministrationItems.tsx @@ -1,6 +1,5 @@ -import { useTranslation, useRoute, useMethod, useSetModal, useRole } from '@rocket.chat/ui-contexts'; +import { useTranslation, useRoute, useMethod, useSetModal, useRole, useRouter } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import React from 'react'; import type { AccountBoxItem } from '../../../../../app/ui-utils/client/lib/AccountBox'; @@ -16,6 +15,7 @@ type useAdministrationItemProps = { showWorkspace: boolean; }; export const useAdministrationItems = ({ accountBoxItems, showWorkspace }: useAdministrationItemProps): GenericMenuItemProps[] => { + const router = useRouter(); const t = useTranslation(); const { tabType, trialEndDate, isLoading } = useUpgradeTabParams(); @@ -74,7 +74,7 @@ export const useAdministrationItems = ({ accountBoxItems, showWorkspace }: useAd const accountBoxItem: GenericMenuItemProps[] = accountBoxItems.map((item, key) => { const action = () => { if (item.href) { - FlowRouter.go(item.href); + router.navigate(item.href); } }; return { diff --git a/apps/meteor/client/startup/e2e.ts b/apps/meteor/client/startup/e2e.ts index c58e2ad3caf06..5b3f84c189f92 100644 --- a/apps/meteor/client/startup/e2e.ts +++ b/apps/meteor/client/startup/e2e.ts @@ -1,5 +1,4 @@ import type { AtLeast, IMessage, ISubscription } from '@rocket.chat/core-typings'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; @@ -11,6 +10,7 @@ import { onClientBeforeSendMessage } from '../lib/onClientBeforeSendMessage'; import { onClientMessageReceived } from '../lib/onClientMessageReceived'; import { isLayoutEmbedded } from '../lib/utils/isLayoutEmbedded'; import { waitUntilFind } from '../lib/utils/waitUntilFind'; +import { router } from '../providers/RouterProvider'; Meteor.startup(() => { Tracker.autorun(() => { @@ -18,7 +18,7 @@ Meteor.startup(() => { return; } - const adminEmbedded = isLayoutEmbedded() && FlowRouter.current().path.startsWith('/admin'); + const adminEmbedded = isLayoutEmbedded() && router.getLocationPathname().startsWith('/admin'); if (!adminEmbedded && settings.get('E2E_Enable') && window.crypto) { e2e.startClient(); diff --git a/apps/meteor/client/startup/iframeCommands.ts b/apps/meteor/client/startup/iframeCommands.ts index 8cfc1a9a24a42..3ecf578587bbb 100644 --- a/apps/meteor/client/startup/iframeCommands.ts +++ b/apps/meteor/client/startup/iframeCommands.ts @@ -1,6 +1,6 @@ import type { UserStatus, IUser } from '@rocket.chat/core-typings'; import { escapeRegExp } from '@rocket.chat/string-helpers'; -import { FlowRouter } from 'meteor/kadira:flow-router'; +import type { LocationPathname } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; import { ServiceConfiguration } from 'meteor/service-configuration'; @@ -10,6 +10,7 @@ import { sdk } from '../../app/utils/client/lib/SDKClient'; import { callbacks } from '../../lib/callbacks'; import { capitalize, ltrim, rtrim } from '../../lib/utils/stringUtils'; import { baseURI } from '../lib/baseURI'; +import { router } from '../providers/RouterProvider'; import { add, remove } from '../views/room/lib/Toolbox/IframeButtons'; const commands = { @@ -24,8 +25,14 @@ const commands = { return ret; }, {} as Record<string, string>); - const newPath = newUrl.pathname.replace(new RegExp(`^${escapeRegExp(__meteor_runtime_config__.ROOT_URL_PATH_PREFIX)}`), ''); - FlowRouter.go(newPath, undefined, { ...FlowRouter.current().queryParams, ...newParams }); + const newPath = newUrl.pathname.replace( + new RegExp(`^${escapeRegExp(__meteor_runtime_config__.ROOT_URL_PATH_PREFIX)}`), + '', + ) as LocationPathname; + router.navigate({ + pathname: newPath, + search: { ...router.getSearchParameters(), ...newParams }, + }); }, 'set-user-status'(data: { status: UserStatus }) { @@ -75,7 +82,7 @@ const commands = { } void callbacks.run('afterLogoutCleanUp', user); sdk.call('logoutCleanUp', user as unknown as IUser); - return FlowRouter.go('home'); + return router.navigate('/home'); }); }, diff --git a/apps/meteor/client/startup/loginViaQuery.ts b/apps/meteor/client/startup/loginViaQuery.ts index 5c7fcbf8e7d5a..f22fb80d1c713 100644 --- a/apps/meteor/client/startup/loginViaQuery.ts +++ b/apps/meteor/client/startup/loginViaQuery.ts @@ -1,20 +1,31 @@ -import { FlowRouter } from 'meteor/kadira:flow-router'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; +import { router } from '../providers/RouterProvider'; + Meteor.startup(() => { Tracker.afterFlush(() => { - const resumeToken = FlowRouter.getQueryParam('resumeToken'); + const { resumeToken } = router.getSearchParameters(); if (!resumeToken) { return; } Meteor.loginWithToken(resumeToken, () => { - if (FlowRouter.getRouteName()) { - FlowRouter.setQueryParams({ resumeToken: null, userId: null }); - return; + const routeName = router.getRouteName(); + + if (!routeName) { + router.navigate('/home'); } - FlowRouter.go('/home'); + + const { resumeToken: _, userId: __, ...search } = router.getSearchParameters(); + + router.navigate( + { + pathname: router.getLocationPathname(), + search, + }, + { replace: true }, + ); }); }); }); diff --git a/apps/meteor/client/startup/notifications/konchatNotifications.ts b/apps/meteor/client/startup/notifications/konchatNotifications.ts index 8729ee3da2718..80f60ab68ff21 100644 --- a/apps/meteor/client/startup/notifications/konchatNotifications.ts +++ b/apps/meteor/client/startup/notifications/konchatNotifications.ts @@ -1,5 +1,4 @@ import type { AtLeast, ISubscription, IUser, ICalendarNotification } from '@rocket.chat/core-typings'; -import { FlowRouter } from 'meteor/kadira:flow-router'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { lazy } from 'react'; @@ -14,6 +13,7 @@ import { RoomManager } from '../../lib/RoomManager'; import { imperativeModal } from '../../lib/imperativeModal'; import { fireGlobalEvent } from '../../lib/utils/fireGlobalEvent'; import { isLayoutEmbedded } from '../../lib/utils/isLayoutEmbedded'; +import { router } from '../../providers/RouterProvider'; const OutlookCalendarEventModal = lazy(() => import('../../views/outlookCalendar/OutlookCalendarEventModal')); @@ -23,7 +23,7 @@ const notifyNewRoom = async (sub: AtLeast<ISubscription, 'rid'>): Promise<void> return; } - if ((!FlowRouter.getParam('name') || FlowRouter.getParam('name') !== sub.name) && !sub.ls && sub.alert === true) { + if ((!router.getRouteParameters().name || router.getRouteParameters().name !== sub.name) && !sub.ls && sub.alert === true) { KonchatNotification.newRoom(sub.rid); } }; @@ -83,7 +83,7 @@ Meteor.startup(() => { return; } Notifications.onUser('notification', (notification) => { - const openedRoomId = ['channel', 'group', 'direct'].includes(FlowRouter.getRouteName()) ? RoomManager.opened : undefined; + const openedRoomId = ['channel', 'group', 'direct'].includes(router.getRouteName()!) ? RoomManager.opened : undefined; // This logic is duplicated in /client/startup/unread.coffee. const hasFocus = readMessage.isEnable(); diff --git a/apps/meteor/client/startup/reloadRoomAfterLogin.ts b/apps/meteor/client/startup/reloadRoomAfterLogin.ts index 99f1e88f804bb..a2788616d732e 100644 --- a/apps/meteor/client/startup/reloadRoomAfterLogin.ts +++ b/apps/meteor/client/startup/reloadRoomAfterLogin.ts @@ -1,9 +1,9 @@ -import { FlowRouter } from 'meteor/kadira:flow-router'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { LegacyRoomManager } from '../../app/ui-utils/client'; import { roomCoordinator } from '../lib/rooms/roomCoordinator'; +import { router } from '../providers/RouterProvider'; Meteor.startup(() => { // Reload rooms after login @@ -14,13 +14,14 @@ Meteor.startup(() => { currentUsername = user?.username; LegacyRoomManager.closeAllRooms(); // Reload only if the current route is a channel route - const routeName = FlowRouter.current().route?.name; + const routeName = router.getRouteName(); if (!routeName) { return; } const roomType = roomCoordinator.getRouteNameIdentifier(routeName); if (roomType) { - FlowRouter.reload(); + router; // TODO: fix this + // router.navigate(0); } } }); diff --git a/apps/meteor/client/startup/routes.tsx b/apps/meteor/client/startup/routes.tsx index 78363d1d91cf6..d6139c93cd782 100644 --- a/apps/meteor/client/startup/routes.tsx +++ b/apps/meteor/client/startup/routes.tsx @@ -1,271 +1,233 @@ -import type { IUser } from '@rocket.chat/core-typings'; -import { Accounts } from 'meteor/accounts-base'; -import { FlowRouter } from 'meteor/kadira:flow-router'; -import { Meteor } from 'meteor/meteor'; -import { Tracker } from 'meteor/tracker'; -import React, { lazy } from 'react'; +import React, { createElement, lazy, useEffect } from 'react'; -import { KonchatNotification } from '../../app/ui/client/lib/KonchatNotification'; -import { sdk } from '../../app/utils/client/lib/SDKClient'; -import { t } from '../../app/utils/lib/i18n'; import { appLayout } from '../lib/appLayout'; -import { dispatchToastMessage } from '../lib/toast'; +import { router } from '../providers/RouterProvider'; import MainLayout from '../views/root/MainLayout'; -const PageLoading = lazy(() => import('../views/root/PageLoading')); +const IndexRoute = lazy(() => import('../views/root/IndexRoute')); +const MeetRoute = lazy(() => import('../views/meet/MeetRoute')); const HomePage = lazy(() => import('../views/home/HomePage')); +const DirectoryPage = lazy(() => import('../views/directory')); +const OmnichannelDirectoryPage = lazy(() => import('../views/omnichannel/directory/OmnichannelDirectoryPage')); +const OmnichannelQueueList = lazy(() => import('../views/omnichannel/queueList')); +const CMSPage = lazy(() => import('@rocket.chat/web-ui-registration').then(({ CMSPage }) => ({ default: CMSPage }))); +const SecretURLPage = lazy(() => import('../views/invite/SecretURLPage')); const InvitePage = lazy(() => import('../views/invite/InvitePage')); const ConferenceRoute = lazy(() => import('../views/conference/ConferenceRoute')); - -const SecretURLPage = lazy(() => import('../views/invite/SecretURLPage')); -const CMSPage = lazy(() => import('@rocket.chat/web-ui-registration').then(({ CMSPage }) => ({ default: CMSPage }))); +const SetupWizardRoute = lazy(() => import('../views/setupWizard/SetupWizardRoute')); +const MailerUnsubscriptionPage = lazy(() => import('../views/mailer/MailerUnsubscriptionPage')); +const LoginTokenRoute = lazy(() => import('../views/root/LoginTokenRoute')); const ResetPasswordPage = lazy(() => import('@rocket.chat/web-ui-registration').then(({ ResetPasswordPage }) => ({ default: ResetPasswordPage })), ); - -const MailerUnsubscriptionPage = lazy(() => import('../views/mailer/MailerUnsubscriptionPage')); -const SetupWizardRoute = lazy(() => import('../views/setupWizard/SetupWizardRoute')); -const NotFoundPage = lazy(() => import('../views/notFound/NotFoundPage')); -const MeetPage = lazy(() => import('../views/meet/MeetPage')); - -const DirectoryPage = lazy(() => import('../views/directory')); -const OmnichannelDirectoryPage = lazy(() => import('../views/omnichannel/directory/OmnichannelDirectoryPage')); -const OmnichannelQueueList = lazy(() => import('../views/omnichannel/queueList')); - const OAuthAuthorizationPage = lazy(() => import('../views/oauth/OAuthAuthorizationPage')); const OAuthErrorPage = lazy(() => import('../views/oauth/OAuthErrorPage')); +const NotFoundPage = lazy(() => import('../views/notFound/NotFoundPage')); -FlowRouter.wait(); - -FlowRouter.route('/', { - name: 'index', - action() { - appLayout.render( - <MainLayout> - <PageLoading /> - </MainLayout>, - ); - - if (!Meteor.userId()) { - return FlowRouter.go('home'); - } - - Tracker.autorun((c) => { - if (FlowRouter.subsReady() === true) { - setTimeout(async () => { - const user = Meteor.user() as IUser | null; - if (user?.defaultRoom) { - const room = user.defaultRoom.split('/'); - FlowRouter.go(room[0], { name: room[1] }, FlowRouter.current().queryParams); - } else { - FlowRouter.go('home'); - } - }, 0); - c.stop(); - } - }); - }, -}); - -FlowRouter.route('/login', { - name: 'login', - - action() { - FlowRouter.go('home'); - }, -}); - -FlowRouter.route('/meet/:rid', { - name: 'meet', - - async action(_params, queryParams) { - if (queryParams?.token !== undefined) { - // visitor login - const result = await sdk.rest.get(`/v1/livechat/visitor/${queryParams.token}`); - if ('visitor' in result) { - appLayout.render(<MeetPage />); - return; - } - - dispatchToastMessage({ type: 'error', message: t('Visitor_does_not_exist') }); - return; - } - - if (!Meteor.userId()) { - FlowRouter.go('home'); - return; - } - - appLayout.render(<MeetPage />); - }, -}); - -FlowRouter.route('/home', { - name: 'home', - - action(_params, queryParams) { - KonchatNotification.getDesktopPermission(); - if (queryParams?.saml_idp_credentialToken !== undefined) { - const token = queryParams.saml_idp_credentialToken; - FlowRouter.setQueryParams({ - saml_idp_credentialToken: null, - }); - (Meteor as any).loginWithSamlToken(token, (error?: unknown) => { - if (error) { - dispatchToastMessage({ type: 'error', message: error }); - } - - appLayout.render( - <MainLayout> - <HomePage /> - </MainLayout>, - ); - }); - - return; - } - - appLayout.render( +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'index': { + pathname: '/'; + pattern: '/'; + }; + 'login': { + pathname: '/login'; + pattern: '/login'; + }; + 'meet': { + pathname: `/meet/${string}`; + pattern: '/meet/:rid'; + }; + 'home': { + pathname: '/home'; + pattern: '/home'; + }; + 'directory': { + pathname: `/directory${`/${'users' | 'channels' | 'teams' | 'external'}` | ''}`; + pattern: '/directory/:tab?'; + }; + 'omnichannel-directory': { + pathname: `/omnichannel-directory${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}${ + | `/${string}` + | ''}`; + pattern: '/omnichannel-directory/:page?/:bar?/:id?/:tab?/:context?'; + }; + 'livechat-queue': { + pathname: '/livechat-queue'; + pattern: '/livechat-queue'; + }; + 'terms-of-service': { + pathname: '/terms-of-service'; + pattern: '/terms-of-service'; + }; + 'privacy-policy': { + pathname: '/privacy-policy'; + pattern: '/privacy-policy'; + }; + 'legal-notice': { + pathname: '/legal-notice'; + pattern: '/legal-notice'; + }; + 'register-secret-url': { + pathname: `/register/${string}`; + pattern: '/register/:hash'; + }; + 'invite': { + pathname: `/invite/${string}`; + pattern: '/invite/:hash'; + }; + 'conference': { + pathname: `/conference/${string}`; + pattern: '/conference/:id'; + }; + 'setup-wizard': { + pathname: `/setup-wizard${`/${string}` | ''}`; + pattern: '/setup-wizard/:step?'; + }; + 'mailer-unsubscribe': { + pathname: `/mailer/unsubscribe/${string}/${string}`; + pattern: '/mailer/unsubscribe/:_id/:createdAt'; + }; + 'tokenLogin': { + pathname: `/login-token/${string}`; + pattern: '/login-token/:token'; + }; + 'resetPassword': { + pathname: `/reset-password/${string}`; + pattern: '/reset-password/:token'; + }; + 'oauth/authorize': { + pathname: `/oauth/authorize`; + pattern: '/oauth/authorize'; + }; + 'oauth/error': { + pathname: `/oauth/error/${string}`; + pattern: '/oauth/error/:error'; + }; + } +} + +router.defineRoutes([ + { + path: '/', + id: 'index', + element: appLayout.wrap(<IndexRoute />), + }, + { + path: '/login', + id: 'login', + element: createElement(() => { + useEffect(() => { + router.navigate('/home'); + }, []); + + return null; + }), + }, + { + path: '/meet/:rid', + id: 'meet', + element: appLayout.wrap(<MeetRoute />), + }, + { + path: '/home', + id: 'home', + element: appLayout.wrap( <MainLayout> <HomePage /> </MainLayout>, - ); + ), }, -}); - -FlowRouter.route('/directory/:tab?', { - name: 'directory', - action: () => { - appLayout.render( + { + path: '/directory/:tab?', + id: 'directory', + element: appLayout.wrap( <MainLayout> <DirectoryPage /> </MainLayout>, - ); + ), }, -}); - -FlowRouter.route('/omnichannel-directory/:page?/:bar?/:id?/:tab?/:context?', { - name: 'omnichannel-directory', - action: () => { - appLayout.render( + { + path: '/omnichannel-directory/:page?/:bar?/:id?/:tab?/:context?', + id: 'omnichannel-directory', + element: appLayout.wrap( <MainLayout> <OmnichannelDirectoryPage /> </MainLayout>, - ); + ), }, -}); - -FlowRouter.route('/livechat-queue', { - name: 'livechat-queue', - action: () => { - appLayout.render( + { + path: '/livechat-queue', + id: 'livechat-queue', + element: appLayout.wrap( <MainLayout> <OmnichannelQueueList /> </MainLayout>, - ); - }, -}); - -FlowRouter.route('/terms-of-service', { - name: 'terms-of-service', - action: () => { - appLayout.render(<CMSPage page='Layout_Terms_of_Service' />); - }, -}); - -FlowRouter.route('/privacy-policy', { - name: 'privacy-policy', - action: () => { - appLayout.render(<CMSPage page='Layout_Privacy_Policy' />); - }, -}); - -FlowRouter.route('/legal-notice', { - name: 'legal-notice', - action: () => { - appLayout.render(<CMSPage page='Layout_Legal_Notice' />); - }, -}); - -FlowRouter.route('/register/:hash', { - name: 'register-secret-url', - action: () => { - appLayout.render(<SecretURLPage />); - }, -}); - -FlowRouter.route('/invite/:hash', { - name: 'invite', - action: () => { - appLayout.render(<InvitePage />); - }, -}); - -FlowRouter.route('/conference/:id', { - name: 'conference', - action: () => { - appLayout.render(<ConferenceRoute />); - }, -}); - -FlowRouter.route('/setup-wizard/:step?', { - name: 'setup-wizard', - action: () => { - appLayout.renderStandalone(<SetupWizardRoute />); - }, -}); - -FlowRouter.route('/mailer/unsubscribe/:_id/:createdAt', { - name: 'mailer-unsubscribe', - action: () => { - appLayout.render(<MailerUnsubscriptionPage />); - }, -}); - -FlowRouter.route('/login-token/:token', { - name: 'tokenLogin', - action(params) { - Accounts.callLoginMethod({ - methodArguments: [ - { - loginToken: params?.token, - }, - ], - userCallback(error) { - console.error(error); - FlowRouter.go('/'); - }, - }); - }, -}); - -FlowRouter.route('/reset-password/:token', { - name: 'resetPassword', - action() { - appLayout.render(<ResetPasswordPage />); - }, -}); - -FlowRouter.route('/oauth/authorize', { - name: 'oauth/authorize', - action() { - appLayout.render(<OAuthAuthorizationPage />); - }, -}); - -FlowRouter.route('/oauth/error/:error', { - name: 'oauth/error', - action() { - appLayout.render(<OAuthErrorPage />); - }, -}); - -FlowRouter.notFound = { - action: (): void => { - appLayout.render(<NotFoundPage />); - }, -}; - -Meteor.startup(() => { - FlowRouter.initialize(); -}); + ), + }, + { + path: '/terms-of-service', + id: 'terms-of-service', + element: appLayout.wrap(<CMSPage page='Layout_Terms_of_Service' />), + }, + { + path: '/privacy-policy', + id: 'privacy-policy', + element: appLayout.wrap(<CMSPage page='Layout_Privacy_Policy' />), + }, + { + path: '/legal-notice', + id: 'legal-notice', + element: appLayout.wrap(<CMSPage page='Layout_Legal_Notice' />), + }, + { + path: '/register/:hash', + id: 'register-secret-url', + element: appLayout.wrap(<SecretURLPage />), + }, + { + path: '/invite/:hash', + id: 'invite', + element: appLayout.wrap(<InvitePage />), + }, + { + path: '/conference/:id', + id: 'conference', + element: appLayout.wrap(<ConferenceRoute />), + }, + { + path: '/setup-wizard/:step?', + id: 'setup-wizard', + element: <SetupWizardRoute />, + }, + { + path: '/mailer/unsubscribe/:_id/:createdAt', + id: 'mailer-unsubscribe', + element: appLayout.wrap(<MailerUnsubscriptionPage />), + }, + { + path: '/login-token/:token', + id: 'tokenLogin', + element: appLayout.wrap(<LoginTokenRoute />), + }, + { + path: '/reset-password/:token', + id: 'resetPassword', + element: appLayout.wrap(<ResetPasswordPage />), + }, + { + path: '/oauth/authorize', + id: 'oauth/authorize', + element: appLayout.wrap(<OAuthAuthorizationPage />), + }, + { + path: '/oauth/error/:error', + id: 'oauth/error', + element: appLayout.wrap(<OAuthErrorPage />), + }, + { + path: '*', + id: 'not-found', + element: <NotFoundPage />, + }, +]); diff --git a/apps/meteor/client/startup/setupWizard.ts b/apps/meteor/client/startup/setupWizard.ts index 8c67674d1b022..463b1629fd358 100644 --- a/apps/meteor/client/startup/setupWizard.ts +++ b/apps/meteor/client/startup/setupWizard.ts @@ -1,9 +1,9 @@ -import { FlowRouter } from 'meteor/kadira:flow-router'; import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; import { hasRole } from '../../app/authorization/client'; import { settings } from '../../app/settings/client'; +import { router } from '../providers/RouterProvider'; Meteor.startup(() => { Tracker.autorun(() => { @@ -14,7 +14,7 @@ Meteor.startup(() => { const mustRedirect = (!userId && setupWizardState === 'pending') || isWizardInProgress; if (mustRedirect) { - FlowRouter.go('setup-wizard'); + router.navigate('/setup-wizard'); } }); }); diff --git a/apps/meteor/client/stories/contexts/RouterContextMock.tsx b/apps/meteor/client/stories/contexts/RouterContextMock.tsx index 1d97ddc73eb1e..9c0a9183d5896 100644 --- a/apps/meteor/client/stories/contexts/RouterContextMock.tsx +++ b/apps/meteor/client/stories/contexts/RouterContextMock.tsx @@ -15,8 +15,8 @@ const RouterContextMock = ({ children }: RouterContextMockProps): ReactElement = const value = useMemo( (): ContextType<typeof RouterContext> => ({ ...parent, - pushRoute: (name, parameters, queryStringParameters): void => { - logAction('pushRoute', name, parameters, queryStringParameters); + navigate: (...args): void => { + logAction('navigate', ...args); }, }), [parent], diff --git a/apps/meteor/client/views/account/AccountRouter.tsx b/apps/meteor/client/views/account/AccountRouter.tsx index 0934aa974b25b..8196a877f98cc 100644 --- a/apps/meteor/client/views/account/AccountRouter.tsx +++ b/apps/meteor/client/views/account/AccountRouter.tsx @@ -1,4 +1,4 @@ -import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts'; +import { useRouter } from '@rocket.chat/ui-contexts'; import type { ReactElement, ReactNode } from 'react'; import React, { Suspense, useEffect } from 'react'; @@ -11,16 +11,19 @@ type AccountRouterProps = { }; const AccountRouter = ({ children }: AccountRouterProps): ReactElement => { - const [routeName] = useCurrentRoute(); - const defaultRoute = useRoute('profile'); + const router = useRouter(); - useEffect(() => { - if (routeName !== 'account-index') { - return; - } + useEffect( + () => + router.subscribeToRouteChange(() => { + if (router.getRouteName() !== 'account-index') { + return; + } - defaultRoute.replace(); - }, [routeName, defaultRoute]); + router.navigate('/account/profile', { replace: true }); + }), + [router], + ); return children ? ( <> diff --git a/apps/meteor/client/views/account/AccountSidebar.tsx b/apps/meteor/client/views/account/AccountSidebar.tsx index 3e5971227bde8..e9e1b5e9c5cfe 100644 --- a/apps/meteor/client/views/account/AccountSidebar.tsx +++ b/apps/meteor/client/views/account/AccountSidebar.tsx @@ -1,4 +1,4 @@ -import { useRoutePath, useCurrentRoute, useTranslation, useLayout } from '@rocket.chat/ui-contexts'; +import { useCurrentRoutePath, useTranslation, useLayout } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { memo } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; @@ -14,9 +14,7 @@ const AccountSidebar: FC = () => { const { sidebar } = useLayout(); - const currentRoute = useCurrentRoute(); - const [currentRouteName, currentRouteParams, currentQueryStringParams] = currentRoute; - const currentPath = useRoutePath(currentRouteName || '', currentRouteParams, currentQueryStringParams); + const currentPath = useCurrentRoutePath(); // TODO: uplift this provider return ( diff --git a/apps/meteor/client/views/account/routes.tsx b/apps/meteor/client/views/account/routes.tsx index 17bfc15252833..d31287fa7a570 100644 --- a/apps/meteor/client/views/account/routes.tsx +++ b/apps/meteor/client/views/account/routes.tsx @@ -2,6 +2,39 @@ import { lazy } from 'react'; import { createRouteGroup } from '../../lib/createRouteGroup'; +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'account-index': { + pathname: '/account'; + pattern: '/account'; + }; + 'preferences': { + pathname: '/account/preferences'; + pattern: '/account/preferences'; + }; + 'profile': { + pathname: '/account/profile'; + pattern: '/account/profile'; + }; + 'security': { + pathname: '/account/security'; + pattern: '/account/security'; + }; + 'integrations': { + pathname: '/account/integrations'; + pattern: '/account/integrations'; + }; + 'tokens': { + pathname: '/account/tokens'; + pattern: '/account/tokens'; + }; + 'omnichannel': { + pathname: '/account/omnichannel'; + pattern: '/account/omnichannel'; + }; + } +} + export const registerAccountRoute = createRouteGroup( 'account', '/account', diff --git a/apps/meteor/client/views/account/sidebarItems.ts b/apps/meteor/client/views/account/sidebarItems.ts index 3f36d5a71361c..c2810bf48564d 100644 --- a/apps/meteor/client/views/account/sidebarItems.ts +++ b/apps/meteor/client/views/account/sidebarItems.ts @@ -9,36 +9,36 @@ export const { subscribeToSidebarItems: subscribeToAccountSidebarItems, } = createSidebarItems([ { - href: 'preferences', + href: '/account/preferences', i18nLabel: 'Preferences', icon: 'customize', }, { - href: 'profile', + href: '/account/profile', i18nLabel: 'Profile', icon: 'user', permissionGranted: (): boolean => settings.get('Accounts_AllowUserProfileChange'), }, { - href: 'security', + href: '/account/security', i18nLabel: 'Security', icon: 'lock', permissionGranted: (): boolean => settings.get('Accounts_TwoFactorAuthentication_Enabled') || settings.get('E2E_Enable'), }, { - href: 'integrations', + href: '/account/integrations', i18nLabel: 'Integrations', icon: 'code', permissionGranted: (): boolean => settings.get('Webdav_Integration_Enabled'), }, { - href: 'tokens', + href: '/account/tokens', i18nLabel: 'Personal_Access_Tokens', icon: 'key', permissionGranted: (): boolean => hasPermission('create-personal-access-tokens'), }, { - href: 'omnichannel', + href: '/account/omnichannel', i18nLabel: 'Omnichannel', icon: 'headset', permissionGranted: (): boolean => hasAtLeastOnePermission(['send-omnichannel-chat-transcript', 'request-pdf-transcript']), diff --git a/apps/meteor/client/views/admin/AdministrationRouter.tsx b/apps/meteor/client/views/admin/AdministrationRouter.tsx index 498585bfaaa53..93ea650d0d338 100644 --- a/apps/meteor/client/views/admin/AdministrationRouter.tsx +++ b/apps/meteor/client/views/admin/AdministrationRouter.tsx @@ -1,37 +1,65 @@ -import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts'; +import { useRouter } from '@rocket.chat/ui-contexts'; import type { ReactElement, ReactNode } from 'react'; import React, { Suspense, useEffect } from 'react'; import PageSkeleton from '../../components/PageSkeleton'; -import { useDefaultRoute } from '../../hooks/useDefaultRoute'; +import type { Item, SidebarDivider, SidebarItem } from '../../lib/createSidebarItems'; +import { isGoRocketChatLink } from '../../lib/createSidebarItems'; import SettingsProvider from '../../providers/SettingsProvider'; import { useUpgradeTabParams } from '../hooks/useUpgradeTabParams'; import AdministrationLayout from './AdministrationLayout'; import { getAdminSidebarItems } from './sidebarItems'; +const isSidebarDivider = (sidebarItem: SidebarItem): sidebarItem is SidebarDivider => { + return (sidebarItem as SidebarDivider).divider === true; +}; + +const firstSidebarPage = (sidebarItem: SidebarItem): sidebarItem is Item => { + if (isSidebarDivider(sidebarItem)) { + return false; + } + + return Boolean(sidebarItem.permissionGranted?.()); +}; + type AdministrationRouterProps = { children?: ReactNode; }; const AdministrationRouter = ({ children }: AdministrationRouterProps): ReactElement => { + const router = useRouter(); const { tabType, trialEndDate, isLoading } = useUpgradeTabParams(); - const [routeName] = useCurrentRoute(); - const defaultRoute = useDefaultRoute(getAdminSidebarItems, 'admin-info'); - const upgradeRoute = useRoute('upgrade'); + useEffect( + () => + router.subscribeToRouteChange(() => { + if (router.getRouteName() !== 'admin-index') { + return; + } + + if (tabType && !isLoading) { + router.navigate( + { + name: 'upgrade', + params: { type: tabType }, + search: trialEndDate ? { trialEndDate } : undefined, + }, + { replace: true }, + ); + return; + } - useEffect(() => { - if (routeName !== 'admin-index') { - return; - } + const defaultRoutePath = getAdminSidebarItems().find(firstSidebarPage)?.href ?? '/admin/info'; - if (tabType && !isLoading) { - upgradeRoute.replace({ type: tabType }, trialEndDate ? { trialEndDate } : undefined); - return; - } + if (isGoRocketChatLink(defaultRoutePath)) { + window.open(defaultRoutePath, '_blank'); + return; + } - defaultRoute.replace(); - }, [upgradeRoute, routeName, tabType, trialEndDate, defaultRoute, isLoading]); + router.navigate(defaultRoutePath, { replace: true }); + }), + [tabType, trialEndDate, isLoading, router], + ); return ( <AdministrationLayout> diff --git a/apps/meteor/client/views/admin/import/ImportHistoryPage.tsx b/apps/meteor/client/views/admin/import/ImportHistoryPage.tsx index b1d514a076c7e..8c4af4696ee5b 100644 --- a/apps/meteor/client/views/admin/import/ImportHistoryPage.tsx +++ b/apps/meteor/client/views/admin/import/ImportHistoryPage.tsx @@ -1,6 +1,6 @@ import { Button, ButtonGroup, Table, TableHead, TableCell, TableRow, TableBody } from '@rocket.chat/fuselage'; import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; -import { useToastMessageDispatch, useRoute, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useEndpoint, useTranslation, useRouter } from '@rocket.chat/ui-contexts'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import React, { useMemo } from 'react'; @@ -19,8 +19,7 @@ function ImportHistoryPage() { const getCurrentImportOperation = useEndpoint('GET', '/v1/getCurrentImportOperation'); const getLatestImportOperations = useEndpoint('GET', '/v1/getLatestImportOperations'); - const newImportRoute = useRoute('admin-import-new'); - const importProgressRoute = useRoute('admin-import-progress'); + const router = useRouter(); const currentOperation = useQuery( ['ImportHistoryPage', 'currentOperation'], @@ -51,7 +50,7 @@ function ImportHistoryPage() { }, [latestOperations.isSuccess, latestOperations.data]); const handleNewImportClick = () => { - newImportRoute.push(); + router.navigate('/admin/import/new'); }; const downloadPendingFilesResult = useMutation({ @@ -69,7 +68,7 @@ function ImportHistoryPage() { } dispatchToastMessage({ type: 'info', message: t('File_Downloads_Started') }); - importProgressRoute.push(); + router.navigate('/admin/import/progress'); }, }); @@ -88,7 +87,7 @@ function ImportHistoryPage() { } dispatchToastMessage({ type: 'info', message: t('File_Downloads_Started') }); - importProgressRoute.push(); + router.navigate('/admin/import/progress'); }, }); diff --git a/apps/meteor/client/views/admin/import/ImportOperationSummary.js b/apps/meteor/client/views/admin/import/ImportOperationSummary.js index 79e88ddbe358b..322f6901f1222 100644 --- a/apps/meteor/client/views/admin/import/ImportOperationSummary.js +++ b/apps/meteor/client/views/admin/import/ImportOperationSummary.js @@ -1,5 +1,5 @@ import { TableRow, TableCell } from '@rocket.chat/fuselage'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useMemo } from 'react'; import { @@ -55,17 +55,16 @@ function ImportOperationSummary({ const canCheckProgress = useMemo(() => valid && ImportingStartedStates.includes(status), [valid, status]); - const prepareImportRoute = useRoute('admin-import-prepare'); - const importProgressRoute = useRoute('admin-import-progress'); + const router = useRouter(); const handleClick = () => { if (canContinue) { - prepareImportRoute.push(); + router.navigate('/admin/import/prepare'); return; } if (canCheckProgress) { - importProgressRoute.push(); + router.navigate('/admin/import/progress'); } }; diff --git a/apps/meteor/client/views/admin/import/ImportProgressPage.tsx b/apps/meteor/client/views/admin/import/ImportProgressPage.tsx index 8cd1e78a4b895..d0274ab15111e 100644 --- a/apps/meteor/client/views/admin/import/ImportProgressPage.tsx +++ b/apps/meteor/client/views/admin/import/ImportProgressPage.tsx @@ -1,6 +1,6 @@ import { Box, Margins, Throbber } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useToastMessageDispatch, useRoute, useEndpoint, useTranslation, useStream } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useEndpoint, useTranslation, useStream, useRouter } from '@rocket.chat/ui-contexts'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import React, { useEffect } from 'react'; @@ -17,8 +17,7 @@ const ImportProgressPage = function ImportProgressPage() { const dispatchToastMessage = useToastMessageDispatch(); const handleError = useErrorHandler(); - const importHistoryRoute = useRoute('admin-import'); - const prepareImportRoute = useRoute('admin-import-prepare'); + const router = useRouter(); const getCurrentImportOperation = useEndpoint('GET', '/v1/getCurrentImportOperation'); const getImportProgress = useEndpoint('GET', '/v1/getImportProgress'); @@ -43,23 +42,23 @@ const ImportProgressPage = function ImportProgressPage() { refetchInterval: 1000, onSuccess: ({ valid, status }) => { if (!valid) { - importHistoryRoute.push(); + router.navigate('/admin/import'); return; } if (status === 'importer_done') { dispatchToastMessage({ type: 'success', message: t('Importer_done') }); - importHistoryRoute.push(); + router.navigate('/admin/import'); return; } if (!(ImportingStartedStates as string[]).includes(status)) { - prepareImportRoute.push(); + router.navigate('/admin/import/prepare'); } }, onError: (error) => { handleError(error, t('Failed_To_Load_Import_Data')); - importHistoryRoute.push(); + router.navigate('/admin/import'); }, }, ); @@ -82,13 +81,13 @@ const ImportProgressPage = function ImportProgressPage() { type: 'success', message: t(message), }); - importHistoryRoute.push(); + router.navigate('/admin/import'); return; case 'importer_import_failed': case 'importer_import_cancelled': t.has(message) && handleError(message); - importHistoryRoute.push(); + router.navigate('/admin/import'); return; default: @@ -114,7 +113,7 @@ const ImportProgressPage = function ImportProgressPage() { onSuccess: (progress) => { if (!progress) { dispatchToastMessage({ type: 'warning', message: t('Importer_not_in_progress') }); - prepareImportRoute.push(); + router.navigate('/admin/import/prepare'); return; } @@ -130,7 +129,7 @@ const ImportProgressPage = function ImportProgressPage() { }, onError: (error) => { handleError(error, t('Failed_To_Load_Import_Data')); - importHistoryRoute.push(); + router.navigate('/admin/import'); }, }, ); diff --git a/apps/meteor/client/views/admin/import/NewImportPage.js b/apps/meteor/client/views/admin/import/NewImportPage.js index 61e3ecaa8d095..b616579eb920e 100644 --- a/apps/meteor/client/views/admin/import/NewImportPage.js +++ b/apps/meteor/client/views/admin/import/NewImportPage.js @@ -14,7 +14,7 @@ import { UrlInput, } from '@rocket.chat/fuselage'; import { useUniqueId, useSafely } from '@rocket.chat/fuselage-hooks'; -import { useToastMessageDispatch, useRoute, useRouteParameter, useSetting, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useRouter, useRouteParameter, useSetting, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { useState, useMemo, useEffect } from 'react'; import { Importers } from '../../../../app/importer/client/index'; @@ -34,27 +34,31 @@ function NewImportPage() { const maxFileSize = useSetting('FileUpload_MaxFileSize'); - const importHistoryRoute = useRoute('admin-import'); - const newImportRoute = useRoute('admin-import-new'); - const prepareImportRoute = useRoute('admin-import-prepare'); + const router = useRouter(); const uploadImportFile = useEndpoint('POST', '/v1/uploadImportFile'); const downloadPublicImportFile = useEndpoint('POST', '/v1/downloadPublicImportFile'); useEffect(() => { if (importerKey && !importer) { - newImportRoute.replace(); + router.navigate('/admin/import/new', { replace: true }); } - }, [importer, importerKey, newImportRoute]); + }, [importer, importerKey, router]); const formatMemorySize = useFormatMemorySize(); const handleBackToImportsButtonClick = () => { - importHistoryRoute.push(); + router.navigate('/admin/import'); }; const handleImporterKeyChange = (importerKey) => { - newImportRoute.replace({ importerKey }); + router.navigate( + router.buildRoutePath({ + pattern: '/admin/import/new/:importerKey', + params: { importerKey }, + }), + { replace: true }, + ); }; const handleFileTypeChange = (fileType) => { @@ -106,7 +110,7 @@ function NewImportPage() { }), ), ); - prepareImportRoute.push(); + router.navigate('/admin/import/prepare'); } finally { setLoading(false); } @@ -124,7 +128,7 @@ function NewImportPage() { try { await downloadPublicImportFile({ importerKey, fileUrl }); dispatchToastMessage({ type: 'success', message: t('Import_requested_successfully') }); - prepareImportRoute.push(); + router.navigate('/admin/import/prepare'); } catch (error) { handleError(error, t('Failed_To_upload_Import_File')); } finally { @@ -144,7 +148,7 @@ function NewImportPage() { try { await downloadPublicImportFile({ importerKey, fileUrl: filePath }); dispatchToastMessage({ type: 'success', message: t('Import_requested_successfully') }); - prepareImportRoute.push(); + router.navigate('/admin/import/prepare'); } catch (error) { handleError(error, t('Failed_To_upload_Import_File')); } finally { diff --git a/apps/meteor/client/views/admin/import/PrepareImportPage.js b/apps/meteor/client/views/admin/import/PrepareImportPage.js index ebb58b43515ed..59e05b5f85d05 100644 --- a/apps/meteor/client/views/admin/import/PrepareImportPage.js +++ b/apps/meteor/client/views/admin/import/PrepareImportPage.js @@ -1,6 +1,6 @@ import { Badge, Box, Button, ButtonGroup, Icon, Margins, Throbber, Tabs } from '@rocket.chat/fuselage'; import { useDebouncedValue, useSafely } from '@rocket.chat/fuselage-hooks'; -import { useRoute, useEndpoint, useTranslation, useStream } from '@rocket.chat/ui-contexts'; +import { useEndpoint, useTranslation, useStream, useRouter } from '@rocket.chat/ui-contexts'; import React, { useEffect, useState, useMemo } from 'react'; import { @@ -48,9 +48,7 @@ function PrepareImportPage() { const usersCount = useMemo(() => users.filter(({ do_import }) => do_import).length, [users]); const channelsCount = useMemo(() => channels.filter(({ do_import }) => do_import).length, [channels]); - const importHistoryRoute = useRoute('admin-import'); - const newImportRoute = useRoute('admin-import-new'); - const importProgressRoute = useRoute('admin-import-progress'); + const router = useRouter(); const getImportFileData = useEndpoint('GET', '/v1/getImportFileData'); const getCurrentImportOperation = useEndpoint('GET', '/v1/getCurrentImportOperation'); @@ -73,13 +71,13 @@ function PrepareImportPage() { if (!data) { handleError(t('Importer_not_setup')); - importHistoryRoute.push(); + router.navigate('/admin/import'); return; } if (data.step) { handleError(t('Failed_To_Load_Import_Data')); - importHistoryRoute.push(); + router.navigate('/admin/import'); return; } @@ -90,7 +88,7 @@ function PrepareImportPage() { setProgressRate(null); } catch (error) { handleError(error, t('Failed_To_Load_Import_Data')); - importHistoryRoute.push(); + router.navigate('/admin/import'); } }; @@ -102,12 +100,12 @@ function PrepareImportPage() { ); if (!operation.valid) { - newImportRoute.push(); + router.navigate('/admin/import/new'); return; } if (ImportingStartedStates.includes(operation.status)) { - importProgressRoute.push(); + router.navigate('/admin/import/progress'); return; } @@ -123,42 +121,28 @@ function PrepareImportPage() { if (ImportingErrorStates.includes(operation.status)) { handleError(t('Import_Operation_Failed')); - importHistoryRoute.push(); + router.navigate('/admin/import'); return; } if (operation.status === ProgressStep.DONE) { - importHistoryRoute.push(); + router.navigate('/admin/import'); return; } handleError(t('Unknown_Import_State')); - importHistoryRoute.push(); + router.navigate('/admin/import'); } catch (error) { handleError(t('Failed_To_Load_Import_Data')); - importHistoryRoute.push(); + router.navigate('/admin/import'); } }; loadCurrentOperation(); - }, [ - getCurrentImportOperation, - getImportFileData, - handleError, - - importHistoryRoute, - importProgressRoute, - newImportRoute, - - setMessageCount, - setPreparing, - setProgressRate, - setStatus, - t, - ]); + }, [getCurrentImportOperation, getImportFileData, handleError, router, setMessageCount, setPreparing, setProgressRate, setStatus, t]); const handleBackToImportsButtonClick = () => { - importHistoryRoute.push(); + router.navigate('/admin/import'); }; const handleStartButtonClick = async () => { @@ -166,10 +150,10 @@ function PrepareImportPage() { try { await startImport({ input: { users, channels } }); - importProgressRoute.push(); + router.navigate('/admin/import/progress'); } catch (error) { handleError(error, t('Failed_To_Start_Import')); - importHistoryRoute.push(); + router.navigate('/admin/import'); } }; diff --git a/apps/meteor/client/views/admin/info/FederationCard/components/FederationModal/InviteUsers.tsx b/apps/meteor/client/views/admin/info/FederationCard/components/FederationModal/InviteUsers.tsx index ab25cf7fd69da..0e35f6e5bac49 100644 --- a/apps/meteor/client/views/admin/info/FederationCard/components/FederationModal/InviteUsers.tsx +++ b/apps/meteor/client/views/admin/info/FederationCard/components/FederationModal/InviteUsers.tsx @@ -1,15 +1,14 @@ import { Box, ButtonGroup, Button, Banner } from '@rocket.chat/fuselage'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { FC, ReactElement } from 'react'; import React from 'react'; const InviteUsers: FC<{ onClose: () => void }> = ({ onClose }): ReactElement => { const t = useTranslation(); - - const directoryRoute = useRoute('directory'); + const router = useRouter(); const handleDirectory = (): void => { onClose(); - directoryRoute.push({ tab: 'users' }); + router.navigate('/directory/users'); }; return ( diff --git a/apps/meteor/client/views/admin/moderation/MessageReportInfo.tsx b/apps/meteor/client/views/admin/moderation/MessageReportInfo.tsx index aaeae2d05beeb..72b69586efd82 100644 --- a/apps/meteor/client/views/admin/moderation/MessageReportInfo.tsx +++ b/apps/meteor/client/views/admin/moderation/MessageReportInfo.tsx @@ -9,6 +9,7 @@ import UserAvatar from '../../../components/avatar/UserAvatar'; import { useFormatDate } from '../../../hooks/useFormatDate'; import { useFormatDateAndTime } from '../../../hooks/useFormatDateAndTime'; import { useFormatTime } from '../../../hooks/useFormatTime'; +import { router } from '../../../providers/RouterProvider'; const MessageReportInfo = ({ msgId }: { msgId: string }): JSX.Element => { const t = useTranslation(); @@ -60,7 +61,7 @@ const MessageReportInfo = ({ msgId }: { msgId: string }): JSX.Element => { return ( <> <ContextualbarHeader> - <ContextualbarBack onClick={() => window.history.go(-1)} /> + <ContextualbarBack onClick={() => router.navigate(-1)} /> <ContextualbarTitle>{t('Report')}</ContextualbarTitle> <ContextualbarClose onClick={() => moderationRoute.push({})} /> </ContextualbarHeader> diff --git a/apps/meteor/client/views/admin/routes.tsx b/apps/meteor/client/views/admin/routes.tsx index 8ee81c9042925..354604c3bb119 100644 --- a/apps/meteor/client/views/admin/routes.tsx +++ b/apps/meteor/client/views/admin/routes.tsx @@ -1,7 +1,105 @@ import { lazy } from 'react'; +import type { UpgradeTabVariant } from '../../../lib/upgradeTab'; import { createRouteGroup } from '../../lib/createRouteGroup'; +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'admin-index': { + pathname: '/admin'; + pattern: '/admin'; + }; + 'custom-sounds': { + pathname: `/admin/custom-sounds${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/custom-sounds/:context?/:id?'; + }; + 'admin-info': { + pathname: '/admin/info'; + pattern: '/admin/info'; + }; + 'admin-import': { + pathname: '/admin/import'; + pattern: '/admin/import'; + }; + 'admin-import-new': { + pathname: `/admin/import/new${`/${string}` | ''}`; + pattern: '/admin/import/new/:importerKey?'; + }; + 'admin-import-prepare': { + pathname: '/admin/import/prepare'; + pattern: '/admin/import/prepare'; + }; + 'admin-import-progress': { + pathname: '/admin/import/progress'; + pattern: '/admin/import/progress'; + }; + 'admin-mailer': { + pathname: '/admin/mailer'; + pattern: '/admin/mailer'; + }; + 'admin-oauth-apps': { + pathname: `/admin/oauth-apps${`/${'new' | 'edit'}` | ''}${`/${string}` | ''}`; + pattern: '/admin/oauth-apps/:context?/:id?'; + }; + 'admin-integrations': { + pathname: `/admin/integrations${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/integrations/:context?/:type?/:id?'; + }; + 'user-status': { + pathname: `/admin/user-status${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/user-status/:context?/:id?'; + }; + 'emoji-custom': { + pathname: `/admin/emoji-custom${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/emoji-custom/:context?/:id?'; + }; + 'admin-users': { + pathname: `/admin/users${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/users/:context?/:id?'; + }; + 'admin-rooms': { + pathname: `/admin/rooms${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/rooms/:context?/:id?'; + }; + 'invites': { + pathname: '/admin/invites'; + pattern: '/admin/invites'; + }; + 'cloud': { + pathname: `/admin/cloud${`/${string}` | ''}`; + pattern: '/admin/cloud/:page?'; + }; + 'admin-view-logs': { + pathname: '/admin/view-logs'; + pattern: '/admin/view-logs'; + }; + 'federation-dashboard': { + pathname: '/admin/federation-dashboard'; + pattern: '/admin/federation-dashboard'; + }; + 'admin-permissions': { + pathname: `/admin/permissions${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/permissions/:context?/:_id?'; + }; + 'admin-email-inboxes': { + pathname: `/admin/email-inboxes${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/email-inboxes/:context?/:_id?'; + }; + 'admin-settings': { + pathname: `/admin/settings${`/${string}` | ''}`; + pattern: '/admin/settings/:group?'; + }; + 'upgrade': { + pathname: `/admin/upgrade${`/${UpgradeTabVariant}` | ''}`; + pattern: '/admin/upgrade/:type?'; + }; + 'moderation-console': { + pathname: `/admin/moderation-console${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/moderation-console/:context?/:id?'; + }; + } +} + export const registerAdminRoute = createRouteGroup( 'admin', '/admin', diff --git a/apps/meteor/client/views/admin/settings/SettingsGroupCard.tsx b/apps/meteor/client/views/admin/settings/SettingsGroupCard.tsx index 4ba1eba67b9d4..8bf75c3753d54 100644 --- a/apps/meteor/client/views/admin/settings/SettingsGroupCard.tsx +++ b/apps/meteor/client/views/admin/settings/SettingsGroupCard.tsx @@ -1,10 +1,9 @@ import type { ISetting } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import { Button, Box } from '@rocket.chat/fuselage'; -import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { Card } from '@rocket.chat/ui-client'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -25,26 +24,27 @@ type SettingsGroupCardProps = { const SettingsGroupCard = ({ id, title, description }: SettingsGroupCardProps): ReactElement => { const t = useTranslation(); - const router = useRoute('admin-settings'); - - const handleOpenGroup = useMutableCallback(() => { - if (id) { - router.push({ - group: id, - }); - } - }); + const router = useRouter(); return ( <Card data-qa-id={id}> <Card.Title>{t(title)}</Card.Title> - <Card.Body height='x88'> + <Card.Body height={88}> <Box className={clampStyle}> {description && t.has(description) && <MarkdownText variant='inlineWithoutBreaks' content={t(description)} />} </Box> </Card.Body> <Card.Footer> - <Button onClick={handleOpenGroup}>{t('Open')}</Button> + <Button + is='a' + href={router.buildRoutePath({ + pattern: '/admin/settings/:group?', + params: { group: id }, + })} + role='button' + > + {t('Open')} + </Button> </Card.Footer> </Card> ); diff --git a/apps/meteor/client/views/admin/sidebar/AdminSidebar.tsx b/apps/meteor/client/views/admin/sidebar/AdminSidebar.tsx index c44338356cebd..b288eaebe02de 100644 --- a/apps/meteor/client/views/admin/sidebar/AdminSidebar.tsx +++ b/apps/meteor/client/views/admin/sidebar/AdminSidebar.tsx @@ -1,4 +1,4 @@ -import { useRoutePath, useCurrentRoute, useTranslation, useLayout } from '@rocket.chat/ui-contexts'; +import { useTranslation, useLayout, useCurrentRoutePath } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { memo } from 'react'; @@ -12,9 +12,7 @@ const AdminSidebar: FC = () => { const { sidebar } = useLayout(); - const currentRoute = useCurrentRoute(); - const [currentRouteName, currentRouteParams, currentQueryStringParams] = currentRoute; - const currentPath = useRoutePath(currentRouteName || '', currentRouteParams, currentQueryStringParams); + const currentPath = useCurrentRoutePath(); // TODO: uplift this provider return ( diff --git a/apps/meteor/client/views/admin/sidebar/UpgradeTab.tsx b/apps/meteor/client/views/admin/sidebar/UpgradeTab.tsx index f5c67ede0eecc..37fa2598390ff 100644 --- a/apps/meteor/client/views/admin/sidebar/UpgradeTab.tsx +++ b/apps/meteor/client/views/admin/sidebar/UpgradeTab.tsx @@ -1,7 +1,7 @@ import { Box, Icon } from '@rocket.chat/fuselage'; -import { useRoutePath, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; -import React, { useMemo } from 'react'; +import React from 'react'; import type { UpgradeTabVariant } from '../../../../lib/upgradeTab'; import { getUpgradeTabLabel, isFullyFeature } from '../../../../lib/upgradeTab'; @@ -11,23 +11,21 @@ import Sidebar from '../../../components/Sidebar'; type UpgradeTabProps = { type: UpgradeTabVariant; currentPath: string; trialEndDate: string | undefined }; const UpgradeTab = ({ type, currentPath, trialEndDate }: UpgradeTabProps): ReactElement => { - const path = useRoutePath( - 'upgrade', - useMemo( - () => ({ - type, - }), - [type], - ), - useMemo(() => (trialEndDate ? { trialEndDate } : undefined), [trialEndDate]), - ); + const router = useRouter(); + + const path = router.buildRoutePath({ + name: 'upgrade', + params: { type }, + search: trialEndDate ? { trialEndDate } : undefined, + }); + const t = useTranslation(); const label = getUpgradeTabLabel(type); const displayEmoji = isFullyFeature(type); return ( - <Sidebar.GenericItem active={currentPath === path} href={String(path)} featured> + <Sidebar.GenericItem active={currentPath === path} href={path} featured> <Icon name='arrow-stack-up' size='x20' mi='x4' /> <Box withTruncatedText fontScale='p2' mi='x4'> {t(label)} {displayEmoji && <Emoji emojiHandle=':zap:' />} diff --git a/apps/meteor/client/views/admin/sidebarItems.ts b/apps/meteor/client/views/admin/sidebarItems.ts index 3cc27caa1732c..5cb699934d13a 100644 --- a/apps/meteor/client/views/admin/sidebarItems.ts +++ b/apps/meteor/client/views/admin/sidebarItems.ts @@ -8,82 +8,82 @@ export const { subscribeToSidebarItems: subscribeToAdminSidebarItems, } = createSidebarItems([ { - href: 'admin-info', + href: '/admin/info', i18nLabel: 'Info', icon: 'info-circled', permissionGranted: (): boolean => hasPermission('view-statistics'), }, { icon: 'shield-alt', - href: 'moderation-console', + href: '/admin/moderation-console', i18nLabel: 'Moderation console', tag: 'Beta', permissionGranted: (): boolean => hasPermission('view-moderation-console'), }, { - href: 'admin-import', + href: '/admin/import', i18nLabel: 'Import', icon: 'import', permissionGranted: (): boolean => hasPermission('run-import'), }, { - href: 'admin-users', + href: '/admin/users', i18nLabel: 'Users', icon: 'team', permissionGranted: (): boolean => hasPermission('view-user-administration'), }, { - href: 'admin-rooms', + href: '/admin/rooms', i18nLabel: 'Rooms', icon: 'hashtag', permissionGranted: (): boolean => hasPermission('view-room-administration'), }, { - href: 'invites', + href: '/admin/invites', i18nLabel: 'Invites', icon: 'user-plus', permissionGranted: (): boolean => hasPermission('create-invite-links'), }, { + href: '/admin/cloud', icon: 'cloud-plus', - href: 'cloud', i18nLabel: 'Registration', permissionGranted: (): boolean => hasPermission('manage-cloud'), }, { - href: 'admin-view-logs', + href: '/admin/view-logs', i18nLabel: 'View_Logs', icon: 'post', permissionGranted: (): boolean => hasPermission('view-logs'), }, { - href: 'custom-sounds', + href: '/admin/custom-sounds', i18nLabel: 'Custom_Sounds', icon: 'volume', permissionGranted: (): boolean => hasPermission('manage-sounds'), }, { + href: '/admin/federation-dashboard', icon: 'discover', - href: 'federation-dashboard', i18nLabel: 'Federation Dashboard', permissionGranted: (): boolean => hasPermission('view-federation-data'), }, { + href: '/admin/email-inboxes', icon: 'mail', - href: 'admin-email-inboxes', i18nLabel: 'Email_Inboxes', tag: 'Alpha', permissionGranted: (): boolean => hasPermission('manage-email-inbox'), }, { + href: '/admin/emoji-custom', icon: 'emoji', - href: 'emoji-custom', i18nLabel: 'Custom_Emoji', permissionGranted: (): boolean => hasPermission('manage-emoji'), }, { + href: '/admin/integrations', icon: 'code', - href: 'admin-integrations', i18nLabel: 'Integrations', permissionGranted: (): boolean => hasAtLeastOnePermission([ @@ -94,32 +94,32 @@ export const { ]), }, { + href: '/admin/oauth-apps', icon: 'discover', - href: 'admin-oauth-apps', i18nLabel: 'OAuth Apps', permissionGranted: (): boolean => hasAllPermission('manage-oauth-apps'), }, { + href: '/admin/mailer', icon: 'mail', - href: 'admin-mailer', i18nLabel: 'Mailer', permissionGranted: (): boolean => hasAllPermission('access-mailer'), }, { + href: '/admin/user-status', icon: 'user', - href: 'user-status', i18nLabel: 'User_Status', permissionGranted: (): boolean => hasAtLeastOnePermission(['manage-user-status']), }, { + href: '/admin/permissions', icon: 'lock', - href: 'admin-permissions', i18nLabel: 'Permissions', permissionGranted: (): boolean => hasAtLeastOnePermission(['access-permissions', 'access-setting-permissions']), }, { + href: '/admin/settings', icon: 'customize', - href: 'admin-settings', i18nLabel: 'Settings', permissionGranted: (): boolean => hasAtLeastOnePermission(['view-privileged-setting', 'edit-privileged-setting', 'manage-selected-settings']), diff --git a/apps/meteor/client/views/admin/upgrade/UpgradePage/UpgradePage.tsx b/apps/meteor/client/views/admin/upgrade/UpgradePage/UpgradePage.tsx index e7fbdcd49bbfe..57b4f2d3e80e8 100644 --- a/apps/meteor/client/views/admin/upgrade/UpgradePage/UpgradePage.tsx +++ b/apps/meteor/client/views/admin/upgrade/UpgradePage/UpgradePage.tsx @@ -1,5 +1,5 @@ import { Throbber, Box } from '@rocket.chat/fuselage'; -import { useLayout, useRouteParameter, useQueryStringParameter, useAbsoluteUrl, useLanguage } from '@rocket.chat/ui-contexts'; +import { useLayout, useRouteParameter, useSearchParameter, useAbsoluteUrl, useLanguage } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useEffect, useRef, useState } from 'react'; @@ -52,7 +52,7 @@ const UpgradePage = (): ReactElement => { const [isLoading, setIsLoading] = useState(true); const type = useRouteParameter('type') as UpgradeTabVariant; - const trialEndDate = useQueryStringParameter('trialEndDate'); + const trialEndDate = useSearchParameter('trialEndDate'); const language = useLanguage(); const pageUrl = getUrl(type, trialEndDate, language); diff --git a/apps/meteor/client/views/conference/ConferencePage.tsx b/apps/meteor/client/views/conference/ConferencePage.tsx index b95106efca39b..0b05e8b2b4d25 100644 --- a/apps/meteor/client/views/conference/ConferencePage.tsx +++ b/apps/meteor/client/views/conference/ConferencePage.tsx @@ -17,7 +17,7 @@ const getQueryParams = () => { const ConferencePage = (): ReactElement => { const user = useUser(); - const defaultRoute = useRoute('/'); + const defaultRoute = useRoute('home'); const setModal = useSetModal(); const handleOpenCall = useVideoConfOpenCall(); const userDisplayName = useUserDisplayName({ name: user?.name, username: user?.username }); diff --git a/apps/meteor/client/views/directory/DirectoryPage.tsx b/apps/meteor/client/views/directory/DirectoryPage.tsx index 8024d2ae66dbc..0015a63babfdf 100644 --- a/apps/meteor/client/views/directory/DirectoryPage.tsx +++ b/apps/meteor/client/views/directory/DirectoryPage.tsx @@ -1,5 +1,5 @@ import { Tabs } from '@rocket.chat/fuselage'; -import { useCurrentRoute, useRoute, useRouteParameter, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouter, useRouteParameter, useSetting, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useEffect, useCallback } from 'react'; @@ -8,26 +8,33 @@ import ChannelsTab from './tabs/channels/ChannelsTab'; import TeamsTab from './tabs/teams/TeamsTab'; import UsersTab from './tabs/users/UsersTab'; +type TabName = 'users' | 'channels' | 'teams' | 'external'; + const DirectoryPage = (): ReactElement => { const t = useTranslation(); - const defaultTab = String(useSetting('Accounts_Directory_DefaultView')); + const defaultTab = useSetting<TabName>('Accounts_Directory_DefaultView') ?? 'users'; const federationEnabled = useSetting('FEDERATION_Enabled'); - const [routeName] = useCurrentRoute(); - const tab = useRouteParameter('tab'); - const directoryRoute = useRoute('directory'); + const tab = useRouteParameter('tab') as TabName | undefined; + const router = useRouter(); + + useEffect( + () => + router.subscribeToRouteChange(() => { + if (router.getRouteName() !== 'directory') { + return; + } - useEffect(() => { - if (routeName !== 'directory') { - return; - } + const { tab } = router.getRouteParameters(); - if (!tab || (tab === 'external' && !federationEnabled)) { - return directoryRoute.replace({ tab: defaultTab }); - } - }, [routeName, directoryRoute, tab, federationEnabled, defaultTab]); + if (!tab || (tab === 'external' && !federationEnabled)) { + router.navigate(`/directory/${defaultTab}`, { replace: true }); + } + }), + [router, federationEnabled, defaultTab], + ); - const handleTabClick = useCallback((tab) => (): void => directoryRoute.push({ tab }), [directoryRoute]); + const handleTabClick = useCallback((tab: TabName) => () => router.navigate(`/directory/${tab}`), [router]); return ( <Page> diff --git a/apps/meteor/client/views/home/HomePage.tsx b/apps/meteor/client/views/home/HomePage.tsx index 667afa9286c56..b9647a0e0ec51 100644 --- a/apps/meteor/client/views/home/HomePage.tsx +++ b/apps/meteor/client/views/home/HomePage.tsx @@ -1,11 +1,41 @@ -import { useSetting } from '@rocket.chat/ui-contexts'; +import { useRouter, useSetting, useToastMessageDispatch } from '@rocket.chat/ui-contexts'; +import { Meteor } from 'meteor/meteor'; import type { ReactElement } from 'react'; -import React from 'react'; +import React, { useEffect } from 'react'; +import { KonchatNotification } from '../../../app/ui/client/lib/KonchatNotification'; import CustomHomePage from './CustomHomePage'; import DefaultHomePage from './DefaultHomePage'; const HomePage = (): ReactElement => { + useEffect(() => { + KonchatNotification.getDesktopPermission(); + }, []); + + const router = useRouter(); + const dispatchToastMessage = useToastMessageDispatch(); + + useEffect(() => { + const { saml_idp_credentialToken: token, ...search } = router.getSearchParameters(); + if (!token) { + return; + } + + Meteor.loginWithSamlToken(token, (error?: unknown) => { + if (error) { + dispatchToastMessage({ type: 'error', message: error }); + } + + router.navigate( + { + pathname: router.getLocationPathname(), + search, + }, + { replace: true }, + ); + }); + }, [dispatchToastMessage, router]); + const customOnly = useSetting('Layout_Custom_Body_Only'); if (customOnly) { diff --git a/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx b/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx index 49543c45f1a87..1a105ea8d58fe 100644 --- a/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx +++ b/apps/meteor/client/views/home/cards/JoinRoomsCard.tsx @@ -1,15 +1,15 @@ import { Button } from '@rocket.chat/fuselage'; import { Card } from '@rocket.chat/ui-client'; -import { useTranslation, useRoute } from '@rocket.chat/ui-contexts'; +import { useTranslation, useRouter } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; const JoinRoomsCard = (): ReactElement => { const t = useTranslation(); - const directoryRoute = useRoute('directory'); + const router = useRouter(); const handleDirectory = (): void => { - directoryRoute.push({}); + router.navigate('/directory'); }; return ( diff --git a/apps/meteor/client/views/invite/InvitePage.tsx b/apps/meteor/client/views/invite/InvitePage.tsx index be2075392e0fd..7fd180d85c80c 100644 --- a/apps/meteor/client/views/invite/InvitePage.tsx +++ b/apps/meteor/client/views/invite/InvitePage.tsx @@ -24,9 +24,9 @@ const InvitePage = (): ReactElement => { const registrationForm = useSetting('Accounts_RegistrationForm'); const setLoginDefaultState = useSessionDispatch('loginDefaultState'); const userId = useUserId(); - const homeRoute = useRoute('/'); - const groupRoute = useRoute('/group/:name/:tab?/:context?'); - const channelRoute = useRoute('/channel/:name/:tab?/:context?'); + const homeRoute = useRoute('home'); + const groupRoute = useRoute('group'); + const channelRoute = useRoute('channel'); const { isLoading, data } = useQuery( ['invite', token], diff --git a/apps/meteor/client/views/invite/SecretURLPage.tsx b/apps/meteor/client/views/invite/SecretURLPage.tsx index c5f1836c61e7a..20cca5787bc9a 100644 --- a/apps/meteor/client/views/invite/SecretURLPage.tsx +++ b/apps/meteor/client/views/invite/SecretURLPage.tsx @@ -1,17 +1,17 @@ -import { useUserId, useRoute } from '@rocket.chat/ui-contexts'; +import { useUserId, useRouter } from '@rocket.chat/ui-contexts'; import RegistrationPageRouter from '@rocket.chat/web-ui-registration'; import type { ReactElement } from 'react'; import React, { useEffect } from 'react'; const SecretURLPage = (): ReactElement | null => { const uid = useUserId(); - const homeRouter = useRoute('home'); + const router = useRouter(); useEffect(() => { if (uid) { - homeRouter.replace(); + router.navigate('/home'); } - }, [uid, homeRouter]); + }, [uid, router]); if (uid) { return null; diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx index 55d01c63a0c52..9be183c489dcb 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPage.tsx @@ -2,14 +2,7 @@ import type { ISetting } from '@rocket.chat/apps-engine/definition/settings'; import type { App } from '@rocket.chat/core-typings'; import { Button, ButtonGroup, Box, Throbber } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { - useTranslation, - useCurrentRoute, - useRoute, - useRouteParameter, - useToastMessageDispatch, - usePermission, -} from '@rocket.chat/ui-contexts'; +import { useTranslation, useRouteParameter, useToastMessageDispatch, usePermission, useRouter } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useState, useCallback, useRef } from 'react'; @@ -31,6 +24,7 @@ import AppSettings from './tabs/AppSettings'; const AppDetailsPage = ({ id }: { id: App['id'] }): ReactElement => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); + const router = useRouter(); const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false); const [isSaving, setIsSaving] = useState(false); @@ -38,19 +32,20 @@ const AppDetailsPage = ({ id }: { id: App['id'] }): ReactElement => { const settingsRef = useRef<Record<string, ISetting['value']>>({}); const isAdminUser = usePermission('manage-apps'); - const [currentRouteName] = useCurrentRoute(); - if (!currentRouteName) { - throw new Error('No current route name'); - } - const router = useRoute(currentRouteName); - const tab = useRouteParameter('tab'); const context = useRouteParameter('context'); const appData = useAppInfo(id, context || ''); const handleReturn = useMutableCallback((): void => { - context && router.push({ context, page: 'list' }); + if (!context) { + return; + } + + router.navigate({ + name: 'marketplace', + params: { context, page: 'list' }, + }); }); const { installed, settings, privacyPolicySummary, permissions, tosLink, privacyLink, name } = appData || {}; diff --git a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPageTabs.tsx b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPageTabs.tsx index d93af7b44d19b..e54613dc5cd86 100644 --- a/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPageTabs.tsx +++ b/apps/meteor/client/views/marketplace/AppDetailsPage/AppDetailsPageTabs.tsx @@ -1,5 +1,5 @@ import { Tabs } from '@rocket.chat/fuselage'; -import { useCurrentRoute, usePermission, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import { usePermission, useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -17,44 +17,45 @@ const AppDetailsPageTabs = ({ context, installed, isSecurityVisible, settings, t const t = useTranslation(); const isAdminUser = usePermission('manage-apps'); - const [currentRouteName] = useCurrentRoute(); - if (!currentRouteName) { - throw new Error('No current route name'); - } - const router = useRoute(currentRouteName); + const router = useRouter(); - const [, urlParams] = useCurrentRoute(); - const handleTabClick = (tab: 'details' | 'security' | 'releases' | 'settings' | 'logs' | 'requests'): void => { - router.replace({ ...urlParams, tab }); + const handleTabClick = (tab: 'details' | 'security' | 'releases' | 'settings' | 'logs' | 'requests') => { + router.navigate( + { + name: 'marketplace', + params: { ...router.getRouteParameters(), tab }, + }, + { replace: true }, + ); }; return ( <Tabs> - <Tabs.Item onClick={(): void => handleTabClick('details')} selected={!tab || tab === 'details'}> + <Tabs.Item onClick={() => handleTabClick('details')} selected={!tab || tab === 'details'}> {t('Details')} </Tabs.Item> {isAdminUser && context !== 'private' && ( - <Tabs.Item onClick={(): void => handleTabClick('requests')} selected={tab === 'requests'}> + <Tabs.Item onClick={() => handleTabClick('requests')} selected={tab === 'requests'}> {t('Requests')} </Tabs.Item> )} {isSecurityVisible && ( - <Tabs.Item onClick={(): void => handleTabClick('security')} selected={tab === 'security'}> + <Tabs.Item onClick={() => handleTabClick('security')} selected={tab === 'security'}> {t('Security')} </Tabs.Item> )} {context !== 'private' && ( - <Tabs.Item onClick={(): void => handleTabClick('releases')} selected={tab === 'releases'}> + <Tabs.Item onClick={() => handleTabClick('releases')} selected={tab === 'releases'}> {t('Releases')} </Tabs.Item> )} {Boolean(installed && settings && Object.values(settings).length) && isAdminUser && ( - <Tabs.Item onClick={(): void => handleTabClick('settings')} selected={tab === 'settings'}> + <Tabs.Item onClick={() => handleTabClick('settings')} selected={tab === 'settings'}> {t('Settings')} </Tabs.Item> )} {Boolean(installed) && isAdminUser && isAdminUser && ( - <Tabs.Item onClick={(): void => handleTabClick('logs')} selected={tab === 'logs'}> + <Tabs.Item onClick={() => handleTabClick('logs')} selected={tab === 'logs'}> {t('Logs')} </Tabs.Item> )} diff --git a/apps/meteor/client/views/marketplace/AppInstallPage.js b/apps/meteor/client/views/marketplace/AppInstallPage.js index 41c64ca0479c6..860a09d6ab75f 100644 --- a/apps/meteor/client/views/marketplace/AppInstallPage.js +++ b/apps/meteor/client/views/marketplace/AppInstallPage.js @@ -1,13 +1,12 @@ import { Button, ButtonGroup, Icon, Field, FieldGroup, TextInput, Throbber } from '@rocket.chat/fuselage'; import { useSetModal, - useRoute, - useQueryStringParameter, useEndpoint, useUpload, useTranslation, - useCurrentRoute, useRouteParameter, + useRouter, + useSearchParameter, } from '@rocket.chat/ui-contexts'; import React, { useCallback, useEffect, useState } from 'react'; @@ -30,20 +29,14 @@ function AppInstallPage() { const reload = useAppsReload(); - const [currentRouteName] = useCurrentRoute(); - if (!currentRouteName) { - throw new Error('No current route name'); - } - - const router = useRoute(currentRouteName); - const upgradeRoute = useRoute('upgrade'); + const router = useRouter(); const context = useRouteParameter('context'); const setModal = useSetModal(); - const appId = useQueryStringParameter('id'); - const queryUrl = useQueryStringParameter('url'); + const appId = useSearchParameter('id'); + const queryUrl = useSearchParameter('url'); const [installing, setInstalling] = useState(false); @@ -87,7 +80,14 @@ function AppInstallPage() { handleAPIError(e); } - router.push({ context: 'private', page: 'info', id: appId || app.app.id }); + router.navigate({ + name: 'marketplace', + params: { + context: 'private', + page: 'info', + id: appId || app.app.id, + }, + }); reload(); @@ -177,7 +177,12 @@ function AppInstallPage() { handleClose={cancelAction} handleConfirm={() => uploadFile(appFile, manifest)} handleEnableUnlimitedApps={() => { - upgradeRoute.push({ type: 'go-fully-featured-registered' }); + router.navigate({ + name: 'upgrade', + params: { + type: 'go-fully-featured-registered', + }, + }); setModal(null); }} />, @@ -185,7 +190,13 @@ function AppInstallPage() { }; const handleCancel = () => { - router.push({ context, page: 'list' }); + router.navigate({ + name: 'marketplace', + params: { + context, + page: 'list', + }, + }); }; return ( diff --git a/apps/meteor/client/views/marketplace/AppMenu.js b/apps/meteor/client/views/marketplace/AppMenu.js index 9754f099afa8f..b243dde200bd4 100644 --- a/apps/meteor/client/views/marketplace/AppMenu.js +++ b/apps/meteor/client/views/marketplace/AppMenu.js @@ -3,11 +3,10 @@ import { useSetModal, useEndpoint, useTranslation, - useRoute, useRouteParameter, useToastMessageDispatch, - useCurrentRoute, usePermission, + useRouter, } from '@rocket.chat/ui-contexts'; import React, { useMemo, useCallback, useState } from 'react'; import semver from 'semver'; @@ -26,12 +25,7 @@ function AppMenu({ app, isAppDetailsPage, ...props }) { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); const setModal = useSetModal(); - - const [currentRouteName, currentRouteParams] = useCurrentRoute(); - if (!currentRouteName) { - throw new Error('No current route name'); - } - const router = useRoute(currentRouteName); + const router = useRouter(); const context = useRouteParameter('context'); const currentTab = useRouteParameter('tab'); @@ -128,7 +122,16 @@ function AppMenu({ app, isAppDetailsPage, ...props }) { }, [app, isSubscribed, setModal, closeModal, openIncompatibleModal, buildExternalUrl, syncApp]); const handleViewLogs = useCallback(() => { - router.push({ context, page: 'info', id: app.id, version: app.version, tab: 'logs' }); + router.navigate({ + name: 'marketplace', + params: { + context, + page: 'info', + id: app.id, + version: app.version, + tab: 'logs', + }, + }); }, [app.id, app.version, context, router]); const handleDisable = useCallback(() => { @@ -163,7 +166,13 @@ function AppMenu({ app, isAppDetailsPage, ...props }) { if (success) { dispatchToastMessage({ type: 'success', message: `${app.name} uninstalled` }); if (context === 'details' && currentTab !== 'details') { - router.replace({ ...currentRouteParams, tab: 'details' }); + router.navigate( + { + name: 'marketplace', + params: { ...router.getRouteParameters(), tab: 'details' }, + }, + { replace: true }, + ); } } } catch (error) { @@ -209,10 +218,10 @@ function AppMenu({ app, isAppDetailsPage, ...props }) { <WarningModal close={closeModal} confirm={uninstall} text={t('Apps_Marketplace_Uninstall_App_Prompt')} confirmText={t('Yes')} />, ); }, [ + isSubscribed, appCountQuery.data, app.migrated, app.name, - isSubscribed, setModal, closeModal, t, @@ -221,7 +230,6 @@ function AppMenu({ app, isAppDetailsPage, ...props }) { context, currentTab, router, - currentRouteParams, handleSubscription, ]); diff --git a/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx b/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx index db3291b643a40..2b8986678537d 100644 --- a/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx +++ b/apps/meteor/client/views/marketplace/AppsList/AppRow.tsx @@ -2,7 +2,7 @@ import type { App } from '@rocket.chat/core-typings'; import { css } from '@rocket.chat/css-in-js'; import { Badge, Box, Palette } from '@rocket.chat/fuselage'; import { useBreakpoints } from '@rocket.chat/fuselage-hooks'; -import { useCurrentRoute, useRoute, useRouteParameter } from '@rocket.chat/ui-contexts'; +import { useRouteParameter, useRouter } from '@rocket.chat/ui-contexts'; import type { KeyboardEvent, MouseEvent, ReactElement } from 'react'; import React, { memo } from 'react'; import semver from 'semver'; @@ -16,27 +16,29 @@ import BundleChips from '../BundleChips'; const AppRow = (props: App): ReactElement => { const { name, id, shortDescription, iconFileData, marketplaceVersion, iconFileContent, installed, bundledIn, version } = props; const breakpoints = useBreakpoints(); - const [currentRouteName] = useCurrentRoute(); - if (!currentRouteName) { - throw new Error('No current route name'); - } - const router = useRoute(currentRouteName); + + const router = useRouter(); const context = useRouteParameter('context'); const isMobile = !breakpoints.includes('md'); - const handleNavigateToAppInfo = (): void => { - context && - router.push({ + const handleNavigateToAppInfo = () => { + if (!context) { + return; + } + router.navigate({ + name: 'marketplace', + params: { context, page: 'info', version: marketplaceVersion || version, id, tab: 'details', - }); + }, + }); }; - const handleKeyDown = (e: KeyboardEvent<HTMLOrSVGElement>): void => { + const handleKeyDown = (e: KeyboardEvent<HTMLOrSVGElement>) => { if (!['Enter', 'Space'].includes(e.nativeEvent.code)) { return; } @@ -44,7 +46,7 @@ const AppRow = (props: App): ReactElement => { handleNavigateToAppInfo(); }; - const preventClickPropagation = (e: MouseEvent<HTMLOrSVGElement>): void => { + const preventClickPropagation = (e: MouseEvent<HTMLOrSVGElement>) => { e.stopPropagation(); }; diff --git a/apps/meteor/client/views/marketplace/AppsPage/AppsPage.tsx b/apps/meteor/client/views/marketplace/AppsPage/AppsPage.tsx index e46c08805086e..4f2f28c8bd999 100644 --- a/apps/meteor/client/views/marketplace/AppsPage/AppsPage.tsx +++ b/apps/meteor/client/views/marketplace/AppsPage/AppsPage.tsx @@ -1,4 +1,4 @@ -import { useTranslation, useCurrentRoute, useRouteParameter } from '@rocket.chat/ui-contexts'; +import { useTranslation, useRouteParameter } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -11,15 +11,11 @@ type AppsContext = 'explore' | 'installed' | 'enterprise' | 'private'; const AppsPage = (): ReactElement => { const t = useTranslation(); - const [currentRouteName] = useCurrentRoute(); - if (!currentRouteName) { - throw new Error('No current route name'); - } - const context = useRouteParameter('context'); + const context = useRouteParameter('context') as AppsContext; return ( <Page background='tint'> - <MarketplaceHeader title={t(`Apps_context_${context as AppsContext}`)} /> + <MarketplaceHeader title={t(`Apps_context_${context}`)} /> <Page.Content paddingInline='0'> <AppsPageContent /> </Page.Content> diff --git a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx index 1e2c79fe40f1b..e9b91b663d8de 100644 --- a/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx +++ b/apps/meteor/client/views/marketplace/AppsPage/AppsPageContent.tsx @@ -1,5 +1,5 @@ import { useDebouncedState } from '@rocket.chat/fuselage-hooks'; -import { useCurrentRoute, useRoute, useRouteParameter, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouteParameter, useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useEffect, useMemo, useState, useCallback } from 'react'; @@ -27,11 +27,7 @@ const AppsPageContent = (): ReactElement => { const [text, setText] = useDebouncedState('', 500); const { current, itemsPerPage, setItemsPerPage: onSetItemsPerPage, setCurrent: onSetCurrent, ...paginationProps } = usePagination(); - const [currentRouteName] = useCurrentRoute(); - if (!currentRouteName) { - throw new Error('No current route name'); - } - const router = useRoute(currentRouteName); + const router = useRouter(); const context = useRouteParameter('context'); @@ -119,8 +115,14 @@ const AppsPageContent = (): ReactElement => { sortFilterStructure.items.find((item) => item.checked)?.id !== 'mru' || selectedCategories.length > 0; - const handleReturn = (): void => { - router.push({ context: 'explore', page: 'list' }); + const handleReturn = () => { + router.navigate({ + name: 'marketplace', + params: { + context: 'explore', + page: 'list', + }, + }); }; const toggleInitialSortOption = useCallback((isRequested: boolean) => { diff --git a/apps/meteor/client/views/marketplace/MarketplaceSidebar.tsx b/apps/meteor/client/views/marketplace/MarketplaceSidebar.tsx index 93c8bb66779fe..df851c0c1d3db 100644 --- a/apps/meteor/client/views/marketplace/MarketplaceSidebar.tsx +++ b/apps/meteor/client/views/marketplace/MarketplaceSidebar.tsx @@ -1,4 +1,4 @@ -import { useRoutePath, useCurrentRoute, useTranslation, useLayout } from '@rocket.chat/ui-contexts'; +import { useTranslation, useLayout, useCurrentRoutePath } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { memo } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; @@ -14,14 +14,12 @@ const MarketplaceSidebar = (): ReactElement => { const { sidebar } = useLayout(); - const currentRoute = useCurrentRoute(); - const [currentRouteName, currentRouteParams, currentQueryStringParams] = currentRoute; - const currentPath = useRoutePath(currentRouteName ?? '', currentRouteParams, currentQueryStringParams); + const currentPath = useCurrentRoutePath(); return ( <SettingsProvider privileged> <Sidebar> - <Sidebar.Header onClose={sidebar.close} title={<>{t('Marketplace')}</>} /> + <Sidebar.Header onClose={sidebar.close} title={t('Marketplace')} /> <Sidebar.Content> <SidebarItemsAssembler items={items} currentPath={currentPath} /> </Sidebar.Content> diff --git a/apps/meteor/client/views/marketplace/routes.tsx b/apps/meteor/client/views/marketplace/routes.tsx index b3c819939bcfb..881f955b10180 100644 --- a/apps/meteor/client/views/marketplace/routes.tsx +++ b/apps/meteor/client/views/marketplace/routes.tsx @@ -2,6 +2,19 @@ import { lazy } from 'react'; import { createRouteGroup } from '../../lib/createRouteGroup'; +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'marketplace-index': { + pattern: '/marketplace'; + pathname: '/marketplace'; + }; + 'marketplace': { + pattern: '/marketplace/:context?/:page?/:id?/:version?/:tab?'; + pathname: `/marketplace${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}`; + }; + } +} + export const registerMarketplaceRoute = createRouteGroup( 'marketplace', '/marketplace', diff --git a/apps/meteor/client/views/marketplace/sidebarItems.tsx b/apps/meteor/client/views/marketplace/sidebarItems.tsx index 1743dc9936554..bafcc4e62c581 100644 --- a/apps/meteor/client/views/marketplace/sidebarItems.tsx +++ b/apps/meteor/client/views/marketplace/sidebarItems.tsx @@ -11,32 +11,32 @@ export const { subscribeToSidebarItems: subscribeToMarketplaceSidebarItems, } = createSidebarItems([ { - href: 'marketplace/explore', + href: '/marketplace/explore', icon: 'compass', i18nLabel: 'Explore', permissionGranted: (): boolean => hasAtLeastOnePermission(['access-marketplace', 'manage-apps']), }, { - href: 'marketplace/enterprise', + href: '/marketplace/enterprise', icon: 'lightning', i18nLabel: 'Enterprise', permissionGranted: (): boolean => hasAtLeastOnePermission(['access-marketplace', 'manage-apps']), }, { - href: 'marketplace/installed', + href: '/marketplace/installed', icon: 'circle-arrow-down', i18nLabel: 'Installed', permissionGranted: (): boolean => hasAtLeastOnePermission(['access-marketplace', 'manage-apps']), }, { - href: 'marketplace/requested', + href: '/marketplace/requested', icon: 'cube', i18nLabel: 'Requested', badge: () => <MarketplaceRequestBadge />, permissionGranted: (): boolean => hasPermission('manage-apps'), }, { - href: 'marketplace/private', + href: '/marketplace/private', icon: 'lock', i18nLabel: 'Private_Apps', permissionGranted: (): boolean => hasAtLeastOnePermission(['access-marketplace', 'manage-apps']), diff --git a/apps/meteor/client/views/meet/MeetPage.tsx b/apps/meteor/client/views/meet/MeetPage.tsx index c9f2faf95356e..1f1f60d5295c7 100644 --- a/apps/meteor/client/views/meet/MeetPage.tsx +++ b/apps/meteor/client/views/meet/MeetPage.tsx @@ -1,7 +1,6 @@ import { Button, Box, Icon, Flex } from '@rocket.chat/fuselage'; -import { useRouteParameter, useQueryStringParameter } from '@rocket.chat/ui-contexts'; +import { useRouteParameter, useSearchParameter } from '@rocket.chat/ui-contexts'; import { Meteor } from 'meteor/meteor'; -import type { FC } from 'react'; import React, { useEffect, useState, useCallback } from 'react'; import { sdk } from '../../../app/utils/client/lib/SDKClient'; @@ -11,13 +10,13 @@ import PageLoading from '../root/PageLoading'; import CallPage from './CallPage'; import './styles.css'; -const MeetPage: FC = () => { +const MeetPage = () => { const [isRoomMember, setIsRoomMember] = useState(false); const [status, setStatus] = useState(null); const [visitorId, setVisitorId] = useState(null); const roomId = useRouteParameter('rid'); - const visitorToken = useQueryStringParameter('token'); - const layout = useQueryStringParameter('layout'); + const visitorToken = useSearchParameter('token'); + const layout = useSearchParameter('layout'); const [visitorName, setVisitorName] = useState(''); const [agentName, setAgentName] = useState(''); const [callStartTime, setCallStartTime] = useState(undefined); @@ -66,12 +65,15 @@ const MeetPage: FC = () => { } setupCallForAgent(); }, [setupCallForAgent, setupCallForVisitor, visitorToken]); + if (status === null) { - return <PageLoading></PageLoading>; + return <PageLoading />; } + if (!isRoomMember) { - return <NotFoundPage></NotFoundPage>; + return <NotFoundPage />; } + if (status === 'ended') { return ( <Flex.Container direction='column' justifyContent='center'> diff --git a/apps/meteor/client/views/meet/MeetRoute.tsx b/apps/meteor/client/views/meet/MeetRoute.tsx new file mode 100644 index 0000000000000..bbc20356a30b1 --- /dev/null +++ b/apps/meteor/client/views/meet/MeetRoute.tsx @@ -0,0 +1,63 @@ +import { useEndpoint, useRouter, useSearchParameter, useToastMessageDispatch, useUserId } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import { VisitorDoesNotExistError } from '../../lib/errors/VisitorDoesNotExistError'; +import PageLoading from '../root/PageLoading'; +import MeetPage from './MeetPage'; + +const MeetRoute = () => { + const router = useRouter(); + const dispatchToastMessage = useToastMessageDispatch(); + const { t } = useTranslation(); + + const uid = useUserId(); + const token = useSearchParameter('token') ?? ''; + const getVisitorByToken = useEndpoint('GET', '/v1/livechat/visitor/:token', { token }); + + const { data: hasVisitor } = useQuery( + ['meet', { token }], + async () => { + if (token) { + const result = await getVisitorByToken(); + if ('visitor' in result) { + return true; + } + + throw new VisitorDoesNotExistError(); + } + + if (!uid) { + return false; + } + + return true; + }, + { + onSuccess: (hasVisitor) => { + if (hasVisitor === false) { + router.navigate('/home'); + } + }, + onError: (error) => { + if (error instanceof VisitorDoesNotExistError) { + dispatchToastMessage({ type: 'error', message: t('core.Visitor_does_not_exist') }); + router.navigate('/home'); + return; + } + + dispatchToastMessage({ type: 'error', message: error }); + router.navigate('/home'); + }, + }, + ); + + if (!hasVisitor) { + return <PageLoading />; + } + + return <MeetPage />; +}; + +export default MeetRoute; diff --git a/apps/meteor/client/views/oauth/OAuthAuthorizationPage.tsx b/apps/meteor/client/views/oauth/OAuthAuthorizationPage.tsx index a0a4fb65d124e..bf537d72b1c8b 100644 --- a/apps/meteor/client/views/oauth/OAuthAuthorizationPage.tsx +++ b/apps/meteor/client/views/oauth/OAuthAuthorizationPage.tsx @@ -1,4 +1,4 @@ -import { useQueryStringParameter, useUser } from '@rocket.chat/ui-contexts'; +import { useSearchParameter, useUser } from '@rocket.chat/ui-contexts'; import RegistrationPageRouter from '@rocket.chat/web-ui-registration'; import React from 'react'; @@ -10,8 +10,8 @@ import { useOAuthAppQuery } from './hooks/useOAuthAppQuery'; const OAuthAuthorizationPage = () => { const user = useUser(); - const clientId = useQueryStringParameter('client_id'); - const redirectUri = useQueryStringParameter('redirect_uri'); + const clientId = useSearchParameter('client_id'); + const redirectUri = useSearchParameter('redirect_uri'); const oauthAppQuery = useOAuthAppQuery(clientId, { enabled: !!user, diff --git a/apps/meteor/client/views/oauth/components/AuthorizationFormPage.tsx b/apps/meteor/client/views/oauth/components/AuthorizationFormPage.tsx index c92bc42afe5e7..cb9eb160ef634 100644 --- a/apps/meteor/client/views/oauth/components/AuthorizationFormPage.tsx +++ b/apps/meteor/client/views/oauth/components/AuthorizationFormPage.tsx @@ -25,7 +25,7 @@ const AuthorizationFormPage = ({ oauthApp, redirectUri, user }: AuthorizationFor const { t } = useTranslation(); - const homeRoute = useRoute('/home'); + const homeRoute = useRoute('home'); const handleCancelButtonClick = () => { queueMicrotask(() => { diff --git a/apps/meteor/client/views/omnichannel/OmnichannelRouter.tsx b/apps/meteor/client/views/omnichannel/OmnichannelRouter.tsx index 42d61e57e8eed..eb699cea2a8b6 100644 --- a/apps/meteor/client/views/omnichannel/OmnichannelRouter.tsx +++ b/apps/meteor/client/views/omnichannel/OmnichannelRouter.tsx @@ -1,4 +1,4 @@ -import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts'; +import { useRouter } from '@rocket.chat/ui-contexts'; import type { ReactNode, ReactElement } from 'react'; import React, { Suspense, useEffect } from 'react'; @@ -11,24 +11,31 @@ type OmnichannelRouterProps = { }; const OmnichannelRouter = ({ children }: OmnichannelRouterProps): ReactElement => { - const [routeName] = useCurrentRoute(); - const defaultRoute = useRoute('omnichannel-current-chats'); + const router = useRouter(); - useEffect(() => { - if (routeName === 'omnichannel-index') { - defaultRoute.push(); - } - }, [defaultRoute, routeName]); + useEffect( + () => + router.subscribeToRouteChange(() => { + if (router.getRouteName() !== 'omnichannel-index') { + return; + } - return children ? ( + router.navigate({ name: 'omnichannel-current-chats' }, { replace: true }); + }), + [router], + ); + + if (!children) { + return <PageSkeleton />; + } + + return ( <> <Suspense fallback={<PageSkeleton />}>{children}</Suspense> <SidebarPortal> <OmnichannelSidebar /> </SidebarPortal> </> - ) : ( - <PageSkeleton /> ); }; diff --git a/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx b/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx index b330fd1bbee70..3f6bb41dbf67b 100644 --- a/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx +++ b/apps/meteor/client/views/omnichannel/directory/CallsContextualBarDirectory.tsx @@ -1,6 +1,6 @@ import type { IVoipRoom } from '@rocket.chat/core-typings'; import { Box } from '@rocket.chat/fuselage'; -import { useRoute, useRouteParameter, useQueryStringParameter, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRoute, useRouteParameter, useSearchParameter, useTranslation } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { useMemo } from 'react'; @@ -16,7 +16,7 @@ const CallsContextualBarDirectory: FC = () => { const bar = useRouteParameter('bar') || 'info'; const id = useRouteParameter('id'); - const token = useQueryStringParameter('token'); + const token = useSearchParameter('token'); const t = useTranslation(); diff --git a/apps/meteor/client/views/omnichannel/directory/OmnichannelDirectoryPage.tsx b/apps/meteor/client/views/omnichannel/directory/OmnichannelDirectoryPage.tsx index 3eb28d1ff8f5e..85a50e04f5a5a 100644 --- a/apps/meteor/client/views/omnichannel/directory/OmnichannelDirectoryPage.tsx +++ b/apps/meteor/client/views/omnichannel/directory/OmnichannelDirectoryPage.tsx @@ -1,5 +1,5 @@ import { Tabs } from '@rocket.chat/fuselage'; -import { useCurrentRoute, useRoute, useRouteParameter, usePermission, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouteParameter, usePermission, useTranslation, useRouter } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useEffect, useCallback } from 'react'; @@ -14,22 +14,26 @@ import ContactTab from './contacts/ContactTab'; const DEFAULT_TAB = 'contacts'; const OmnichannelDirectoryPage = (): ReactElement => { - const [routeName] = useCurrentRoute(); - const tab = useRouteParameter('page'); - const directoryRoute = useRoute('omnichannel-directory'); + const router = useRouter(); + const page = useRouteParameter('page'); const canViewDirectory = usePermission('view-omnichannel-contact-center'); - useEffect(() => { - if (routeName !== 'omnichannel-directory') { - return; - } + useEffect( + () => + router.subscribeToRouteChange(() => { + if (router.getRouteName() !== 'omnichannel-directory' || !!router.getRouteParameters().page) { + return; + } - if (!tab) { - return directoryRoute.replace({ page: DEFAULT_TAB }); - } - }, [routeName, directoryRoute, tab]); + router.navigate({ + name: 'omnichannel-directory', + params: { page: DEFAULT_TAB }, + }); + }), + [router], + ); - const handleTabClick = useCallback((tab) => (): void => directoryRoute.push({ tab }), [directoryRoute]); + const handleTabClick = useCallback((tab) => () => router.navigate({ name: 'omnichannel-directory', params: { tab } }), [router]); const chatReload = () => queryClient.invalidateQueries({ queryKey: ['current-chats'] }); @@ -44,18 +48,18 @@ const OmnichannelDirectoryPage = (): ReactElement => { <Page> <Page.Header title={t('Omnichannel_Contact_Center')} /> <Tabs flexShrink={0}> - <Tabs.Item selected={tab === 'contacts'} onClick={handleTabClick('contacts')}> + <Tabs.Item selected={page === 'contacts'} onClick={handleTabClick('contacts')}> {t('Contacts')} </Tabs.Item> - <Tabs.Item selected={tab === 'chats'} onClick={handleTabClick('chats')}> + <Tabs.Item selected={page === 'chats'} onClick={handleTabClick('chats')}> {t('Chats' as 'color')} </Tabs.Item> - <Tabs.Item selected={tab === 'calls'} onClick={handleTabClick('calls')}> + <Tabs.Item selected={page === 'calls'} onClick={handleTabClick('calls')}> {t('Calls' as 'color')} </Tabs.Item> </Tabs> <Page.Content> - {(tab === 'contacts' && <ContactTab />) || (tab === 'chats' && <ChatTab />) || (tab === 'calls' && <CallTab />)} + {(page === 'contacts' && <ContactTab />) || (page === 'chats' && <ChatTab />) || (page === 'calls' && <CallTab />)} </Page.Content> </Page> <ContextualBar chatReload={chatReload} /> diff --git a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.tsx b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.tsx index 0ee7df68b6c93..8c9019f08e100 100644 --- a/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.tsx +++ b/apps/meteor/client/views/omnichannel/directory/contacts/contextualBar/ContactInfo.tsx @@ -1,8 +1,10 @@ import { Box, Margins, ButtonGroup, Button, Icon, Divider } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useToastMessageDispatch, useCurrentRoute, useRoute, useTranslation, useEndpoint, usePermission } from '@rocket.chat/ui-contexts'; +import type { RouteName } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useRoute, useTranslation, useEndpoint, usePermission, useRouter } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -import React from 'react'; +import React, { useCallback } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { parseOutboundPhoneNumber } from '../../../../../../ee/client/lib/voip/parseOutboundPhoneNumber'; import ContactManagerInfo from '../../../../../../ee/client/omnichannel/ContactManagerInfo'; @@ -22,16 +24,20 @@ import { FormSkeleton } from '../../components/FormSkeleton'; type ContactInfoProps = { id: string; rid?: string; - route?: string; + route?: RouteName; }; const ContactInfo = ({ id: contactId, rid: roomId = '', route }: ContactInfoProps) => { const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); - const routePath = useRoute(route || 'omnichannel-directory'); + const router = useRouter(); const liveRoute = useRoute('live'); - const [currentRouteName] = useCurrentRoute(); + + const currentRouteName = useSyncExternalStore( + router.subscribeToRouteChange, + useCallback(() => router.getRouteName(), [router]), + ); const formatDate = useFormatDate(); const isCallReady = useIsCallReady(); @@ -56,19 +62,26 @@ const ContactInfo = ({ id: contactId, rid: roomId = '', route }: ContactInfoProp return dispatchToastMessage({ type: 'error', message: t('Not_authorized') }); } - routePath.push( - route - ? { - tab: 'contact-profile', - context: 'edit', - id: roomId, - } - : { - page: 'contacts', - id: contactId, - bar: 'edit', - }, - ); + if (route) { + router.navigate({ + name: route, + params: { + tab: 'contact-profile', + context: 'edit', + id: roomId, + }, + }); + return; + } + + router.navigate({ + name: 'omnichannel-directory', + params: { + page: 'contacts', + id: contactId, + bar: 'edit', + }, + }); }); if (isInitialLoading) { diff --git a/apps/meteor/client/views/omnichannel/routes.ts b/apps/meteor/client/views/omnichannel/routes.ts index ed15c9fb65126..9d2ed96e8b1fd 100644 --- a/apps/meteor/client/views/omnichannel/routes.ts +++ b/apps/meteor/client/views/omnichannel/routes.ts @@ -2,6 +2,79 @@ import { lazy } from 'react'; import { createRouteGroup } from '../../lib/createRouteGroup'; +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'omnichannel-index': { + pattern: '/omnichannel'; + pathname: '/omnichannel'; + }; + 'omnichannel-installation': { + pattern: '/omnichannel/installation'; + pathname: '/omnichannel/installation'; + }; + 'omnichannel-managers': { + pattern: '/omnichannel/managers'; + pathname: '/omnichannel/managers'; + }; + 'omnichannel-agents': { + pattern: '/omnichannel/agents/:context?/:id?'; + pathname: `/omnichannel/agents${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-webhooks': { + pattern: '/omnichannel/webhooks'; + pathname: '/omnichannel/webhooks'; + }; + 'omnichannel-customfields': { + pattern: '/omnichannel/customfields/:context?/:id?'; + pathname: `/omnichannel/customfields${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-appearance': { + pattern: '/omnichannel/appearance'; + pathname: '/omnichannel/appearance'; + }; + 'omnichannel-businessHours': { + pattern: '/omnichannel/businessHours/:context?/:type?/:id?'; + pathname: `/omnichannel/businessHours${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-units': { + pattern: '/omnichannel/units/:context?/:id?'; + pathname: `/omnichannel/units${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-tags': { + pattern: '/omnichannel/tags/:context?/:id?'; + pathname: `/omnichannel/tags${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-queue': { + pattern: '/omnichannel/queue/:context?/:id?'; + pathname: `/omnichannel/queue${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-rooms': { + pattern: '/omnichannel/rooms/:context?/:id?'; + pathname: `/omnichannel/rooms${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-triggers': { + pattern: '/omnichannel/triggers/:context?/:id?'; + pathname: `/omnichannel/triggers${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-current-chats': { + pattern: '/omnichannel/current/:id?/:tab?/:context?'; + pathname: `/omnichannel/current${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-departments': { + pattern: '/omnichannel/departments/:context?/:id?/:tab?'; + pathname: `/omnichannel/departments${`/${string}` | ''}${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-analytics': { + pattern: '/omnichannel/analytics'; + pathname: `/omnichannel/analytics`; + }; + 'omnichannel-realTime': { + pattern: '/omnichannel/realtime-monitoring'; + pathname: `/omnichannel/realtime-monitoring`; + }; + } +} + export const registerOmnichannelRoute = createRouteGroup( 'omnichannel', '/omnichannel', diff --git a/apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx b/apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx index 2501d5cba4d80..e64c8dd6df666 100644 --- a/apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx +++ b/apps/meteor/client/views/omnichannel/sidebar/OmnichannelSidebar.tsx @@ -1,4 +1,4 @@ -import { useRoutePath, useCurrentRoute, useTranslation, useLayout } from '@rocket.chat/ui-contexts'; +import { useTranslation, useLayout, useCurrentRoutePath } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; import React, { memo } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; @@ -14,14 +14,12 @@ const OmnichannelSidebar: FC = () => { const { sidebar } = useLayout(); - const currentRoute = useCurrentRoute(); - const [currentRouteName, currentRouteParams, currentQueryStringParams] = currentRoute; - const currentPath = useRoutePath(currentRouteName ?? '', currentRouteParams, currentQueryStringParams); + const currentPath = useCurrentRoutePath(); return ( <SettingsProvider privileged> <Sidebar> - <Sidebar.Header onClose={sidebar.close} title={<>{t('Omnichannel')}</>} /> + <Sidebar.Header onClose={sidebar.close} title={t('Omnichannel')} /> <Sidebar.Content> <SidebarItemsAssemblerProps items={items} currentPath={currentPath} /> </Sidebar.Content> diff --git a/apps/meteor/client/views/omnichannel/sidebarItems.ts b/apps/meteor/client/views/omnichannel/sidebarItems.ts index 450680c05d160..9d3395bcbe0a3 100644 --- a/apps/meteor/client/views/omnichannel/sidebarItems.ts +++ b/apps/meteor/client/views/omnichannel/sidebarItems.ts @@ -8,62 +8,62 @@ export const { subscribeToSidebarItems: subscribeToOmnichannelSidebarItems, } = createSidebarItems([ { - href: 'omnichannel/current', + href: '/omnichannel/current', i18nLabel: 'Current_Chats', permissionGranted: (): boolean => hasPermission('view-livechat-current-chats'), }, { - href: 'omnichannel-analytics', + href: '/omnichannel/analytics', i18nLabel: 'Analytics', permissionGranted: (): boolean => hasPermission('view-livechat-analytics'), }, { - href: 'omnichannel-realTime', + href: '/omnichannel/realtime-monitoring', i18nLabel: 'Real_Time_Monitoring', permissionGranted: (): boolean => hasPermission('view-livechat-real-time-monitoring'), }, { - href: 'omnichannel/managers', + href: '/omnichannel/managers', i18nLabel: 'Managers', permissionGranted: (): boolean => hasPermission('manage-livechat-managers'), }, { - href: 'omnichannel/agents', + href: '/omnichannel/agents', i18nLabel: 'Agents', permissionGranted: (): boolean => hasPermission('manage-livechat-agents'), }, { - href: 'omnichannel/departments', + href: '/omnichannel/departments', i18nLabel: 'Departments', permissionGranted: (): boolean => hasPermission('view-livechat-departments'), }, { - href: 'omnichannel-customfields', + href: '/omnichannel/customfields', i18nLabel: 'Custom_Fields', permissionGranted: (): boolean => hasPermission('view-livechat-customfields'), }, { - href: 'omnichannel-triggers', + href: '/omnichannel/triggers', i18nLabel: 'Livechat_Triggers', permissionGranted: (): boolean => hasPermission('view-livechat-triggers'), }, { - href: 'omnichannel-installation', + href: '/omnichannel/installation', i18nLabel: 'Livechat_Installation', permissionGranted: (): boolean => hasPermission('view-livechat-installation'), }, { - href: 'omnichannel-appearance', + href: '/omnichannel/appearance', i18nLabel: 'Livechat_Appearance', permissionGranted: (): boolean => hasPermission('view-livechat-appearance'), }, { - href: 'omnichannel-webhooks', + href: '/omnichannel/webhooks', i18nLabel: 'Webhooks', permissionGranted: (): boolean => hasPermission('view-livechat-webhooks'), }, { - href: 'omnichannel-businessHours', + href: '/omnichannel/businessHours', i18nLabel: 'Business_Hours', permissionGranted: (): boolean => hasPermission('view-livechat-business-hours'), }, diff --git a/apps/meteor/client/views/room/Header/Omnichannel/BackButton.tsx b/apps/meteor/client/views/room/Header/Omnichannel/BackButton.tsx index 854f2b05bac74..75869011b0833 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/BackButton.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/BackButton.tsx @@ -1,21 +1,27 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { HeaderToolboxAction } from '@rocket.chat/ui-client'; -import { useCurrentRoute, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRouter, useTranslation } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; export const BackButton = ({ routeName }: { routeName?: string }): ReactElement => { + const router = useRouter(); const t = useTranslation(); - const [route = '', params] = useCurrentRoute(); - const router = useRoute(route); const back = useMutableCallback(() => { switch (routeName) { case 'omnichannel-directory': - router.replace({ ...params, bar: 'info' }); + router.navigate({ + name: 'omnichannel-directory', + params: { + ...router.getRouteParameters(), + bar: 'info', + }, + }); break; + case 'omnichannel-current-chats': - router.push(); + router.navigate({ name: 'omnichannel-current-chats' }); break; } }); diff --git a/apps/meteor/client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx b/apps/meteor/client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx index b49896d19ecee..ca86752e7886c 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/OmnichannelRoomHeader.tsx @@ -1,7 +1,8 @@ import { HeaderToolbox } from '@rocket.chat/ui-client'; -import { useLayout, useCurrentRoute } from '@rocket.chat/ui-contexts'; +import { useLayout, useRouter } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import BurgerMenu from '../../../../components/BurgerMenu'; import { useOmnichannelRoom } from '../../contexts/RoomContext'; @@ -27,7 +28,13 @@ type OmnichannelRoomHeaderProps = { }; const OmnichannelRoomHeader: FC<OmnichannelRoomHeaderProps> = ({ slots: parentSlot }) => { - const [name] = useCurrentRoute(); + const router = useRouter(); + + const currentRouteName = useSyncExternalStore( + router.subscribeToRouteChange, + useCallback(() => router.getRouteName(), [router]), + ); + const { isMobile } = useLayout(); const room = useOmnichannelRoom(); const toolbox = useToolboxContext(); @@ -35,16 +42,17 @@ const OmnichannelRoomHeader: FC<OmnichannelRoomHeaderProps> = ({ slots: parentSl const slots = useMemo( () => ({ ...parentSlot, - start: (!!isMobile || name === 'omnichannel-directory' || name === 'omnichannel-current-chats') && ( + start: (!!isMobile || currentRouteName === 'omnichannel-directory' || currentRouteName === 'omnichannel-current-chats') && ( <HeaderToolbox> {isMobile && <BurgerMenu />} - {<BackButton routeName={name} />} + {<BackButton routeName={currentRouteName} />} </HeaderToolbox> ), posContent: <QuickActions room={room} />, }), - [isMobile, name, parentSlot, room], + [isMobile, currentRouteName, parentSlot, room], ); + return ( <ToolboxContext.Provider value={useMemo( diff --git a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx index 321f3522a5e5a..c8b642e56d960 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/QuickActions/hooks/useQuickActions.tsx @@ -10,7 +10,7 @@ import { useEndpoint, useMethod, useTranslation, - useRoute, + useRouter, } from '@rocket.chat/ui-contexts'; import React, { useCallback, useState, useEffect } from 'react'; @@ -38,7 +38,7 @@ export const useQuickActions = ( getAction: (id: string) => void; } => { const setModal = useSetModal(); - const homeRoute = useRoute('home'); + const router = useRouter(); const t = useTranslation(); const dispatchToastMessage = useToastMessageDispatch(); @@ -166,14 +166,14 @@ export const useQuickActions = ( try { await forwardChat(transferData); dispatchToastMessage({ type: 'success', message: t('Transferred') }); - homeRoute.push(); + router.navigate('/home'); LegacyRoomManager.close(room.t + rid); closeModal(); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } }, - [closeModal, dispatchToastMessage, forwardChat, room.t, rid, homeRoute, t], + [closeModal, dispatchToastMessage, forwardChat, room.t, rid, router, t], ); const closeChat = useEndpoint('POST', '/v1/livechat/room.closeByUser'); @@ -213,7 +213,7 @@ export const useQuickActions = ( const returnChatToQueueMutation = useReturnChatToQueueMutation({ onSuccess: () => { LegacyRoomManager.close(room.t + rid); - homeRoute.push(); + router.navigate('/home'); }, onError: (error) => { dispatchToastMessage({ type: 'error', message: error }); diff --git a/apps/meteor/client/views/room/Header/Omnichannel/VoipRoomHeader.tsx b/apps/meteor/client/views/room/Header/Omnichannel/VoipRoomHeader.tsx index 4660cb72af2ed..ec7beb37dda59 100644 --- a/apps/meteor/client/views/room/Header/Omnichannel/VoipRoomHeader.tsx +++ b/apps/meteor/client/views/room/Header/Omnichannel/VoipRoomHeader.tsx @@ -1,8 +1,9 @@ import type { IVoipRoom } from '@rocket.chat/core-typings'; import { HeaderToolbox } from '@rocket.chat/ui-client'; -import { useLayout, useCurrentRoute } from '@rocket.chat/ui-contexts'; +import { useLayout, useRouter } from '@rocket.chat/ui-contexts'; import type { FC } from 'react'; -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { parseOutboundPhoneNumber } from '../../../../../ee/client/lib/voip/parseOutboundPhoneNumber'; import BurgerMenu from '../../../../components/BurgerMenu'; @@ -17,21 +18,27 @@ type VoipRoomHeaderProps = { } & Omit<RoomHeaderProps, 'room'>; const VoipRoomHeader: FC<VoipRoomHeaderProps> = ({ slots: parentSlot, room }) => { - const [name] = useCurrentRoute(); + const router = useRouter(); + + const currentRouteName = useSyncExternalStore( + router.subscribeToRouteChange, + useCallback(() => router.getRouteName(), [router]), + ); + const { isMobile } = useLayout(); const toolbox = useToolboxContext(); const slots = useMemo( () => ({ ...parentSlot, - start: (!!isMobile || name === 'omnichannel-directory') && ( + start: (!!isMobile || currentRouteName === 'omnichannel-directory') && ( <HeaderToolbox> {isMobile && <BurgerMenu />} - {name === 'omnichannel-directory' && <BackButton />} + {currentRouteName === 'omnichannel-directory' && <BackButton />} </HeaderToolbox> ), }), - [isMobile, name, parentSlot], + [isMobile, currentRouteName, parentSlot], ); return ( <ToolboxContext.Provider diff --git a/apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts b/apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts index e8144477867c5..40712c262398d 100644 --- a/apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts +++ b/apps/meteor/client/views/room/MessageList/hooks/useJumpToMessage.ts @@ -1,7 +1,7 @@ import type { IMessage } from '@rocket.chat/core-typings'; -import { RouterContext } from '@rocket.chat/ui-contexts'; +import { useRouter } from '@rocket.chat/ui-contexts'; import type { RefObject } from 'react'; -import { useContext, useLayoutEffect } from 'react'; +import { useLayoutEffect } from 'react'; import { useMessageListJumpToMessageParam, useMessageListScroll } from '../../../../components/message/list/MessageListContext'; import { setHighlightMessage, clearHighlightMessage } from '../providers/messageHighlightSubscription'; @@ -12,7 +12,7 @@ const SCROLL_EXTRA_OFFSET = 60; export const useJumpToMessage = (messageId: IMessage['_id'], messageRef: RefObject<HTMLDivElement>): void => { const jumpToMessageParam = useMessageListJumpToMessageParam(); const scroll = useMessageListScroll(); - const router = useContext(RouterContext); + const router = useRouter(); useLayoutEffect(() => { if (jumpToMessageParam !== messageId || !messageRef.current || !scroll) { @@ -34,7 +34,15 @@ export const useJumpToMessage = (messageId: IMessage['_id'], messageRef: RefObje return { top: newScrollPosition, behavior: 'smooth' }; }); - router.setQueryString(({ msg: _, ...params }) => params); + const search = router.getSearchParameters(); + delete search.msg; + router.navigate( + { + pathname: router.getLocationPathname(), + search, + }, + { replace: true }, + ); setHighlightMessage(messageId); setTimeout(clearHighlightMessage, 2000); diff --git a/apps/meteor/client/views/room/MessageList/hooks/useMessages.ts b/apps/meteor/client/views/room/MessageList/hooks/useMessages.ts index a4a52fe84575d..72a85b45235ce 100644 --- a/apps/meteor/client/views/room/MessageList/hooks/useMessages.ts +++ b/apps/meteor/client/views/room/MessageList/hooks/useMessages.ts @@ -23,7 +23,7 @@ export const useMessages = ({ rid }: { rid: IRoom['_id'] }): IMessage[] => { const hideSysMessages = useStableArray(mergeHideSysMessages(hideSysMesSetting, hideRoomSysMes)); - const query: Mongo.Query<IMessage> = useMemo( + const query: Mongo.Selector<IMessage> = useMemo( () => ({ rid, _hidden: { $ne: true }, diff --git a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx index e23ffeea7bf66..8bc571c3afb6c 100644 --- a/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx +++ b/apps/meteor/client/views/room/MessageList/providers/MessageListProvider.tsx @@ -1,6 +1,6 @@ import type { IMessage } from '@rocket.chat/core-typings'; import { isMessageReactionsNormalized, isThreadMainMessage } from '@rocket.chat/core-typings'; -import { useLayout, useUser, useUserPreference, useSetting, useEndpoint, useQueryStringParameter } from '@rocket.chat/ui-contexts'; +import { useLayout, useUser, useUserPreference, useSetting, useEndpoint, useSearchParameter } from '@rocket.chat/ui-contexts'; import type { VFC, ReactNode } from 'react'; import React, { useMemo, memo } from 'react'; @@ -52,7 +52,7 @@ const MessageListProvider: VFC<MessageListProviderProps> = ({ children, scrollMe const { katexEnabled, katexDollarSyntaxEnabled, katexParenthesisSyntaxEnabled } = useKatex(); const hasSubscription = Boolean(subscription); - const msgParameter = useQueryStringParameter('msg'); + const msgParameter = useSearchParameter('msg'); useLoadSurroundingMessages(msgParameter); diff --git a/apps/meteor/client/views/room/RoomRoute.tsx b/apps/meteor/client/views/room/RoomRoute.tsx new file mode 100644 index 0000000000000..a0e67d8bf800e --- /dev/null +++ b/apps/meteor/client/views/room/RoomRoute.tsx @@ -0,0 +1,29 @@ +import type { RoomType } from '@rocket.chat/core-typings'; +import { useRouter } from '@rocket.chat/ui-contexts'; +import React, { useEffect, useState } from 'react'; + +import RoomOpener from './RoomOpener'; + +type RoomRouteProps = { + extractOpenRoomParams: (routeParams: Record<string, string | null | undefined>) => { + type: RoomType; + reference: string; + }; +}; + +const RoomRoute = ({ extractOpenRoomParams }: RoomRouteProps) => { + const router = useRouter(); + const [params, setParams] = useState(() => extractOpenRoomParams(router.getRouteParameters())); + + useEffect( + () => + router.subscribeToRouteChange(() => { + setParams(extractOpenRoomParams(router.getRouteParameters())); + }), + [extractOpenRoomParams, router], + ); + + return <RoomOpener {...params} />; +}; + +export default RoomRoute; diff --git a/apps/meteor/client/views/room/components/body/RoomBody.tsx b/apps/meteor/client/views/room/components/body/RoomBody.tsx index c2ea5d70d4bb8..aaa0d8a6318bb 100644 --- a/apps/meteor/client/views/room/components/body/RoomBody.tsx +++ b/apps/meteor/client/views/room/components/body/RoomBody.tsx @@ -1,10 +1,10 @@ import type { IMessage, IUser } from '@rocket.chat/core-typings'; import { isEditedMessage } from '@rocket.chat/core-typings'; import { - useCurrentRoute, usePermission, - useQueryStringParameter, useRole, + useRouter, + useSearchParameter, useSetting, useTranslation, useUser, @@ -258,7 +258,7 @@ const RoomBody = (): ReactElement => { }; }, [sendToBottomIfNecessary]); - const [routeName] = useCurrentRoute(); + const router = useRouter(); const roomRef = useRef(room); roomRef.current = room; @@ -273,16 +273,21 @@ const RoomBody = (): ReactElement => { [room._id], ); - useEffect(() => { - if (!routeName || !roomCoordinator.isRouteNameKnown(routeName)) { - return; - } + useEffect( + () => + router.subscribeToRouteChange(() => { + const routeName = router.getRouteName(); + if (!routeName || !roomCoordinator.isRouteNameKnown(routeName)) { + return; + } - debouncedReadMessageRead(); - if (subscribed && (subscription?.alert || subscription?.unread)) { - readMessage.refreshUnreadMark(room._id); - } - }, [debouncedReadMessageRead, room._id, routeName, subscribed, subscription?.alert, subscription?.unread]); + debouncedReadMessageRead(); + if (subscribed && (subscription?.alert || subscription?.unread)) { + readMessage.refreshUnreadMark(room._id); + } + }), + [debouncedReadMessageRead, room._id, router, subscribed, subscription?.alert, subscription?.unread], + ); useEffect(() => { if (!subscribed) { @@ -481,7 +486,7 @@ const RoomBody = (): ReactElement => { [chat], ); - const replyMID = useQueryStringParameter('reply'); + const replyMID = useSearchParameter('reply'); useEffect(() => { if (!replyMID) { diff --git a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannel.js b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannel.js index eb344108278bf..90234643f353e 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannel.js +++ b/apps/meteor/client/views/room/contextualBar/Info/EditRoomInfo/EditChannel.js @@ -24,7 +24,7 @@ import { useRole, useMethod, useTranslation, - useRoute, + useRouter, } from '@rocket.chat/ui-contexts'; import React, { useCallback, useMemo, useRef } from 'react'; @@ -134,7 +134,7 @@ function EditChannel({ room, onClickClose, onClickBack }) { const maxAgeDefault = useSetting(`RetentionPolicy_MaxAge_${typeMap[room.t]}`) || 30; const saveData = useRef({}); - const router = useRoute('home'); + const router = useRouter(); const onChange = useCallback(({ initialValue, value, key }) => { const { current } = saveData; @@ -277,7 +277,7 @@ function EditChannel({ room, onClickClose, onClickBack }) { const onConfirm = async () => { await deleteRoom(room._id); onCancel(); - router.push({}); + router.navigate('/home'); }; setModal( diff --git a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomDelete.tsx b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomDelete.tsx index 2d8b135b7d34a..b812e896bab9f 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomDelete.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomDelete.tsx @@ -1,7 +1,7 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { isRoomFederated } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useSetModal, useToastMessageDispatch, useRoute, useTranslation, useEndpoint, usePermission } from '@rocket.chat/ui-contexts'; +import { useSetModal, useToastMessageDispatch, useTranslation, useEndpoint, usePermission, useRouter } from '@rocket.chat/ui-contexts'; import React from 'react'; import GenericModal from '../../../../../../components/GenericModal'; @@ -11,7 +11,7 @@ export const useRoomDelete = (room: IRoom, resetState?: () => void) => { const t = useTranslation(); const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); - const router = useRoute('home'); + const router = useRouter(); const hasPermissionToDelete = usePermission(room.t === 'c' ? 'delete-c' : 'delete-p', room._id); const canDelete = isRoomFederated(room) ? false : hasPermissionToDelete; @@ -27,7 +27,7 @@ export const useRoomDelete = (room: IRoom, resetState?: () => void) => { return resetState(); } - router.push({}); + router.navigate('/home'); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } diff --git a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomHide.tsx b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomHide.tsx index 5047e785c8e36..fde85f426396f 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomHide.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomHide.tsx @@ -1,7 +1,7 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { useSetModal, useToastMessageDispatch, useRoute, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; +import { useSetModal, useToastMessageDispatch, useMethod, useTranslation, useRouter } from '@rocket.chat/ui-contexts'; import React from 'react'; import { UiTextContext } from '../../../../../../../definition/IRoomTypeConfig'; @@ -13,13 +13,13 @@ export const useRoomHide = (room: IRoom) => { const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); const hideRoom = useMethod('hideRoom'); - const router = useRoute('home'); + const router = useRouter(); const handleHide = useMutableCallback(async () => { const hide = async () => { try { await hideRoom(room._id); - router.push({}); + router.navigate('/home'); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } diff --git a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomLeave.tsx b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomLeave.tsx index 03a652cd58ee7..6b842c1fc173a 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomLeave.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/hooks/actions/useRoomLeave.tsx @@ -1,7 +1,7 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import type { TranslationKey } from '@rocket.chat/ui-contexts'; -import { useSetModal, useToastMessageDispatch, useRoute, useMethod, useTranslation, usePermission } from '@rocket.chat/ui-contexts'; +import { useRouter, useSetModal, useToastMessageDispatch, useMethod, useTranslation, usePermission } from '@rocket.chat/ui-contexts'; import React from 'react'; import { LegacyRoomManager } from '../../../../../../../app/ui-utils/client'; @@ -15,7 +15,7 @@ export const useRoomLeave = (room: IRoom, joined = true) => { const setModal = useSetModal(); const dispatchToastMessage = useToastMessageDispatch(); const leaveRoom = useMethod('leaveRoom'); - const router = useRoute('home'); + const router = useRouter(); const canLeave = usePermission(room.t === 'c' ? 'leave-c' : 'leave-p') && room.cl !== false && joined; @@ -23,7 +23,7 @@ export const useRoomLeave = (room: IRoom, joined = true) => { const leaveAction = async () => { try { await leaveRoom(room._id); - router.push({}); + router.navigate('/home'); LegacyRoomManager.close(room._id); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); diff --git a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useGetMessageByID.ts b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useGetMessageByID.ts index 64a6fcb50bc58..c9898a172495d 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useGetMessageByID.ts +++ b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useGetMessageByID.ts @@ -15,7 +15,7 @@ export const useGetMessageByID = () => { const { message: rawMessage } = await getMessage({ msgId: mid }); const mappedMessage = mapMessageFromApi(rawMessage); const message = (await onClientMessageReceived(mappedMessage)) || mappedMessage; - Messages.upsert({ _id: message._id }, { $set: message }); + Messages.upsert({ _id: message._id }, { $set: message as any }); return message; } catch (error) { if (typeof error === 'object' && error !== null && 'success' in error) { diff --git a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageJump.ts b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageJump.ts index 254afbf676914..551d08d5f1420 100644 --- a/apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageJump.ts +++ b/apps/meteor/client/views/room/contextualBar/Threads/hooks/useLegacyThreadMessageJump.ts @@ -1,22 +1,30 @@ -import { useCurrentRoute, useQueryStringParameter, useRoute } from '@rocket.chat/ui-contexts'; +import { useRouter, useSearchParameter } from '@rocket.chat/ui-contexts'; import { useRef, useEffect } from 'react'; import { waitForElement } from '../../../../../lib/utils/waitForElement'; import { clearHighlightMessage, setHighlightMessage } from '../../../MessageList/providers/messageHighlightSubscription'; export const useLegacyThreadMessageJump = ({ enabled = true }: { enabled?: boolean }) => { - const mid = useQueryStringParameter('msg'); - - const [currentRouteName, currentRouteParams, currentRouteQueryStringParams] = useCurrentRoute(); - if (!currentRouteName) { - throw new Error('No route name'); - } - const currentRoute = useRoute(currentRouteName); + const router = useRouter(); + const mid = useSearchParameter('msg'); const clearQueryStringParameter = () => { - const newQueryStringParams = { ...currentRouteQueryStringParams }; - delete newQueryStringParams.msg; - currentRoute.replace(currentRouteParams, newQueryStringParams); + const name = router.getRouteName(); + + if (!name) { + return; + } + + const { msg: _, ...search } = router.getSearchParameters(); + + router.navigate( + { + name, + params: router.getRouteParameters(), + search, + }, + { replace: true }, + ); }; const parentRef = useRef<HTMLElement>(null); diff --git a/apps/meteor/client/views/room/hooks/useAppsContextualBar.ts b/apps/meteor/client/views/room/hooks/useAppsContextualBar.ts index b1c8447229090..238ee807744f4 100644 --- a/apps/meteor/client/views/room/hooks/useAppsContextualBar.ts +++ b/apps/meteor/client/views/room/hooks/useAppsContextualBar.ts @@ -1,5 +1,5 @@ import type { IUIKitContextualBarInteraction } from '@rocket.chat/apps-engine/definition/uikit'; -import { useCurrentRoute } from '@rocket.chat/ui-contexts'; +import { useRouteParameter } from '@rocket.chat/ui-contexts'; import { useEffect, useState } from 'react'; import { getUserInteractionPayloadByViewId } from '../../../../app/ui-message/client/ActionManager'; @@ -13,13 +13,12 @@ type AppsContextualBarData = { }; export const useAppsContextualBar = (): AppsContextualBarData | undefined => { - const [, params] = useCurrentRoute(); const [payload, setPayload] = useState<IUIKitContextualBarInteraction>(); const [appId, setAppId] = useState<string>(); const { _id: roomId } = useRoom(); - const viewId = params?.context; + const viewId = useRouteParameter('context'); useEffect(() => { if (viewId) { diff --git a/apps/meteor/client/views/room/hooks/useGoToRoom.ts b/apps/meteor/client/views/room/hooks/useGoToRoom.ts index 1b4d8650a2327..330dc338fde4c 100644 --- a/apps/meteor/client/views/room/hooks/useGoToRoom.ts +++ b/apps/meteor/client/views/room/hooks/useGoToRoom.ts @@ -1,13 +1,13 @@ import type { IRoom, ISubscription } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useMethod } from '@rocket.chat/ui-contexts'; -import { FlowRouter } from 'meteor/kadira:flow-router'; +import { useMethod, useRouter } from '@rocket.chat/ui-contexts'; import { ChatSubscription } from '../../../../app/models/client'; import { roomCoordinator } from '../../../lib/rooms/roomCoordinator'; export const useGoToRoom = ({ replace = false }: { replace?: boolean } = {}): ((rid: IRoom['_id']) => void) => { const getRoomById = useMethod('getRoomById'); + const router = useRouter(); // TODO: remove params recycling return useMutableCallback(async (rid) => { @@ -15,16 +15,14 @@ export const useGoToRoom = ({ replace = false }: { replace?: boolean } = {}): (( return; } - const go = (fn: () => void) => (replace ? FlowRouter.withReplaceState(fn) : fn()); - const subscription: ISubscription | undefined = ChatSubscription.findOne({ rid }); if (subscription) { - go(() => roomCoordinator.openRouteLink(subscription.t, subscription, FlowRouter.current().queryParams)); + roomCoordinator.openRouteLink(subscription.t, subscription, router.getSearchParameters(), { replace }); return; } const room = await getRoomById(rid); - go(() => roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, FlowRouter.current().queryParams)); + roomCoordinator.openRouteLink(room.t, { rid: room._id, ...room }, router.getSearchParameters(), { replace }); }); }; diff --git a/apps/meteor/client/views/room/hooks/useGoToThread.ts b/apps/meteor/client/views/room/hooks/useGoToThread.ts index c37f7d6445c53..9a14ce2e28c21 100644 --- a/apps/meteor/client/views/room/hooks/useGoToThread.ts +++ b/apps/meteor/client/views/room/hooks/useGoToThread.ts @@ -1,23 +1,29 @@ import type { IMessage, IRoom } from '@rocket.chat/core-typings'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts'; +import { useRouter } from '@rocket.chat/ui-contexts'; export const useGoToThread = ({ replace = false }: { replace?: boolean } = {}): ((params: { rid: IRoom['_id']; tmid: IMessage['_id']; msg?: IMessage['_id']; }) => void) => { - const [routeName, params, queryParams] = useCurrentRoute(); - - if (!routeName) { - throw new Error('Route name is not defined'); - } - - const roomRoute = useRoute(routeName); - const go = replace ? roomRoute.replace : roomRoute.push; + const router = useRouter(); // TODO: remove params recycling return useMutableCallback(({ rid, tmid, msg }) => { - go({ rid, ...params, tab: 'thread', context: tmid }, { ...queryParams, ...(msg && { msg }) }); + const routeName = router.getRouteName(); + + if (!routeName) { + throw new Error('Route name is not defined'); + } + + router.navigate( + { + name: routeName, + params: { rid, ...router.getRouteParameters(), tab: 'thread', context: tmid }, + search: { ...router.getSearchParameters(), ...(msg && { msg }) }, + }, + { replace }, + ); }); }; diff --git a/apps/meteor/client/views/room/hooks/useGoToThreadList.ts b/apps/meteor/client/views/room/hooks/useGoToThreadList.ts index b0677118d035d..c44df790f7480 100644 --- a/apps/meteor/client/views/room/hooks/useGoToThreadList.ts +++ b/apps/meteor/client/views/room/hooks/useGoToThreadList.ts @@ -1,19 +1,28 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts'; +import { useRouter } from '@rocket.chat/ui-contexts'; import { useRoom } from '../contexts/RoomContext'; export const useGoToThreadList = ({ replace = false }: { replace?: boolean } = {}): (() => void) => { + const router = useRouter(); const room = useRoom(); - const [routeName, { context, ...params } = { context: '' }, queryParams] = useCurrentRoute(); - if (!routeName) { - throw new Error('Route name is not defined'); - } - - const roomRoute = useRoute(routeName); - const go = replace ? roomRoute.replace : roomRoute.push; return useMutableCallback(() => { - go({ rid: room._id, ...params, tab: 'thread' }, queryParams); + const routeName = router.getRouteName(); + + if (!routeName) { + throw new Error('Route name is not defined'); + } + + const { context, ...params } = router.getRouteParameters(); + + router.navigate( + { + name: routeName, + params: { rid: room._id, ...params, tab: 'thread' }, + search: router.getSearchParameters(), + }, + { replace }, + ); }); }; diff --git a/apps/meteor/client/views/room/hooks/useOpenRoom.ts b/apps/meteor/client/views/room/hooks/useOpenRoom.ts index 288767ccde532..4e109759593a1 100644 --- a/apps/meteor/client/views/room/hooks/useOpenRoom.ts +++ b/apps/meteor/client/views/room/hooks/useOpenRoom.ts @@ -42,8 +42,8 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st throw new RoomNotFoundError(undefined, { type, reference }); } - const $set: Record<string, unknown> = {}; - const $unset: Record<string, unknown> = {}; + const $set: any = {}; + const $unset: any = {}; for (const key of Object.keys(roomFields)) { if (key in roomData) { diff --git a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRedirectModerationConsole.ts b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRedirectModerationConsole.ts index 03db347ad9a08..00df2f1225acc 100644 --- a/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRedirectModerationConsole.ts +++ b/apps/meteor/client/views/room/hooks/useUserInfoActions/actions/useRedirectModerationConsole.ts @@ -6,7 +6,7 @@ import type { Action } from '../../../../hooks/useActionSpread'; export const useRedirectModerationConsole = (uid: IUser['_id']): Action | undefined => { const t = useTranslation(); const hasPermissionToView = usePermission('view-moderation-console'); - const router = useRoute('/admin/moderation-console/info/:uid'); + const router = useRoute('moderation-console'); // only rediret if user has permission else return undefined if (!hasPermissionToView) { diff --git a/apps/meteor/client/views/room/providers/RoomProvider.tsx b/apps/meteor/client/views/room/providers/RoomProvider.tsx index 3fba38e727bed..b2935f4702eac 100644 --- a/apps/meteor/client/views/room/providers/RoomProvider.tsx +++ b/apps/meteor/client/views/room/providers/RoomProvider.tsx @@ -1,6 +1,6 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { isOmnichannelRoom } from '@rocket.chat/core-typings'; -import { usePermission, useRoute, useStream, useUserId } from '@rocket.chat/ui-contexts'; +import { usePermission, useStream, useUserId, useRouter } from '@rocket.chat/ui-contexts'; import { useQueryClient } from '@tanstack/react-query'; import type { ReactNode, ContextType, ReactElement } from 'react'; import React, { useMemo, memo, useEffect, useCallback } from 'react'; @@ -49,12 +49,12 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { }, [subscribeToRoom, rid, queryClient, room]); // TODO: the following effect is a workaround while we don't have a general and definitive solution for it - const homeRoute = useRoute('home'); + const router = useRouter(); useEffect(() => { if (isSuccess && !room) { - homeRoute.push(); + router.navigate('/home'); } - }, [isSuccess, room, homeRoute]); + }, [isSuccess, room, router]); // TODO: Review the necessity of this effect when we move away from cached collections useEffect(() => { @@ -68,7 +68,7 @@ const RoomProvider = ({ rid, children }: RoomProviderProps): ReactElement => { queryClient.removeQueries(['rooms', { reference: room._id, type: 'l' }]); queryClient.removeQueries(['/v1/rooms.info', room._id]); } - }, [homeRoute, isLivechatAdmin, queryClient, userId, room]); + }, [isLivechatAdmin, queryClient, userId, room]); const subscriptionQuery = useReactiveQuery(['subscriptions', { rid }], () => ChatSubscription.findOne({ rid }) ?? null); diff --git a/apps/meteor/client/views/room/providers/ToolboxProvider.tsx b/apps/meteor/client/views/room/providers/ToolboxProvider.tsx index 810509f13f695..27fe6abbb1763 100644 --- a/apps/meteor/client/views/room/providers/ToolboxProvider.tsx +++ b/apps/meteor/client/views/room/providers/ToolboxProvider.tsx @@ -1,6 +1,6 @@ import type { IRoom } from '@rocket.chat/core-typings'; import { useDebouncedState, useMutableCallback, useSafely } from '@rocket.chat/fuselage-hooks'; -import { useCurrentRoute, useRoute, useUserId, useSetting } from '@rocket.chat/ui-contexts'; +import { useUserId, useSetting, useRouter, useRouteParameter } from '@rocket.chat/ui-contexts'; import type { ReactNode } from 'react'; import React, { useMemo } from 'react'; @@ -21,11 +21,10 @@ const ToolboxProvider = ({ children, room }: { children: ReactNode; room: IRoom }); const { listen, actions } = useToolboxActions(room); - const [routeName, params, queryStringParams] = useCurrentRoute(); - const router = useRoute(routeName || ''); + const router = useRouter(); - const tab = params?.tab; - const context = params?.context; + const tab = useRouteParameter('tab'); + const context = useRouteParameter('context'); const activeTabBar = useMemo( (): [ToolboxActionConfig | undefined, string?] => [tab ? (list.get(tab) as ToolboxActionConfig) : undefined, context], @@ -33,14 +32,21 @@ const ToolboxProvider = ({ children, room }: { children: ReactNode; room: IRoom ); const close = useMutableCallback(() => { - router.push( - { - ...params, + const routeName = router.getRouteName(); + + if (!routeName) { + throw new Error('Route name is not defined'); + } + + router.navigate({ + name: routeName, + params: { + ...router.getRouteParameters(), tab: '', context: '', }, - queryStringParams, - ); + search: router.getSearchParameters(), + }); }); const open = useMutableCallback((actionId: string, context?: string) => { @@ -48,16 +54,23 @@ const ToolboxProvider = ({ children, room }: { children: ReactNode; room: IRoom return close(); } - const { layout } = queryStringParams || {}; - const queryString = layout ? { layout } : undefined; - router.push( - { - ...params, + const routeName = router.getRouteName(); + + if (!routeName) { + throw new Error('Route name is not defined'); + } + + const { layout } = router.getSearchParameters(); + + router.navigate({ + name: routeName, + params: { + ...router.getRouteParameters(), tab: actionId, context: context ?? '', }, - queryString, - ); + search: layout ? { layout } : undefined, + }); }); const openRoomInfo = useMutableCallback((username?: string) => { diff --git a/apps/meteor/client/views/root/AppLayout.tsx b/apps/meteor/client/views/root/AppLayout.tsx index 4cfb9e7efbc59..5bdcd9d6a5b54 100644 --- a/apps/meteor/client/views/root/AppLayout.tsx +++ b/apps/meteor/client/views/root/AppLayout.tsx @@ -1,20 +1,20 @@ -import type { ReactElement } from 'react'; import React, { useEffect, Suspense } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { useAnalytics } from '../../../app/analytics/client/loadScript'; +import { useAnalyticsEventTracking } from '../../hooks/useAnalyticsEventTracking'; import { appLayout } from '../../lib/appLayout'; import PageLoading from './PageLoading'; import { useEscapeKeyStroke } from './hooks/useEscapeKeyStroke'; import { useGoogleTagManager } from './hooks/useGoogleTagManager'; import { useMessageLinkClicks } from './hooks/useMessageLinkClicks'; -const AppLayout = (): ReactElement => { +const AppLayout = () => { useEffect(() => { document.body.classList.add('color-primary-font-color', 'rcx-content--main'); return () => { - document.body.classList.add('color-primary-font-color', 'rcx-content--main'); + document.body.classList.remove('color-primary-font-color', 'rcx-content--main'); }; }, []); @@ -22,14 +22,11 @@ const AppLayout = (): ReactElement => { useGoogleTagManager(); useAnalytics(); useEscapeKeyStroke(); + useAnalyticsEventTracking(); const layout = useSyncExternalStore(appLayout.subscribe, appLayout.getSnapshot); - return ( - <> - <Suspense fallback={<PageLoading />}>{layout}</Suspense> - </> - ); + return <Suspense fallback={<PageLoading />}>{layout}</Suspense>; }; export default AppLayout; diff --git a/apps/meteor/client/views/root/IndexRoute.tsx b/apps/meteor/client/views/root/IndexRoute.tsx new file mode 100644 index 0000000000000..90134e75881c6 --- /dev/null +++ b/apps/meteor/client/views/root/IndexRoute.tsx @@ -0,0 +1,42 @@ +import type { RouteName } from '@rocket.chat/ui-contexts'; +import { useRouter, useUser, useUserId } from '@rocket.chat/ui-contexts'; +import React, { useEffect } from 'react'; + +import PageLoading from './PageLoading'; + +const IndexRoute = () => { + const router = useRouter(); + const uid = useUserId(); + const user = useUser(); + + useEffect(() => { + if (!uid) { + router.navigate('/home'); + return; + } + + const computation = Tracker.autorun((c) => { + setTimeout(async () => { + if (user?.defaultRoom) { + const room = user.defaultRoom.split('/') as [routeName: RouteName, routeParam: string]; + router.navigate({ + name: room[0], + params: { name: room[1] }, + search: router.getSearchParameters(), + }); + } else { + router.navigate('/home'); + } + }, 0); + c.stop(); + }); + + return () => { + computation.stop(); + }; + }, [router, uid, user?.defaultRoom]); + + return <PageLoading />; +}; + +export default IndexRoute; diff --git a/apps/meteor/client/views/root/LoginRoute.tsx b/apps/meteor/client/views/root/LoginRoute.tsx new file mode 100644 index 0000000000000..443e51d5cff06 --- /dev/null +++ b/apps/meteor/client/views/root/LoginRoute.tsx @@ -0,0 +1,14 @@ +import { useRouter } from '@rocket.chat/ui-contexts'; +import { useEffect } from 'react'; + +const LoginRoute = () => { + const router = useRouter(); + + useEffect(() => { + router.navigate('/home'); + }, [router]); + + return null; +}; + +export default LoginRoute; diff --git a/apps/meteor/client/views/root/LoginTokenRoute.tsx b/apps/meteor/client/views/root/LoginTokenRoute.tsx new file mode 100644 index 0000000000000..3bce5901da64d --- /dev/null +++ b/apps/meteor/client/views/root/LoginTokenRoute.tsx @@ -0,0 +1,25 @@ +import { useRouter } from '@rocket.chat/ui-contexts'; +import { Accounts } from 'meteor/accounts-base'; +import { useEffect } from 'react'; + +const LoginTokenRoute = () => { + const router = useRouter(); + + useEffect(() => { + Accounts.callLoginMethod({ + methodArguments: [ + { + loginToken: router.getRouteParameters().token, + }, + ], + userCallback(error) { + console.error(error); + router.navigate('/'); + }, + }); + }, [router]); + + return null; +}; + +export default LoginTokenRoute; diff --git a/apps/meteor/client/views/root/MainLayout/LayoutWithSidebar.tsx b/apps/meteor/client/views/root/MainLayout/LayoutWithSidebar.tsx index c4d73dc1e4e1e..ee50ad826785d 100644 --- a/apps/meteor/client/views/root/MainLayout/LayoutWithSidebar.tsx +++ b/apps/meteor/client/views/root/MainLayout/LayoutWithSidebar.tsx @@ -1,5 +1,5 @@ import { Box } from '@rocket.chat/fuselage'; -import { useLayout, useCurrentRoute, useRoutePath, useSetting, useCurrentModal, useRoute } from '@rocket.chat/ui-contexts'; +import { useLayout, useSetting, useCurrentModal, useRoute, useCurrentRoutePath } from '@rocket.chat/ui-contexts'; import { PaletteStyleTag } from '@rocket.chat/ui-theming/src/PaletteStyleTag'; import { SidebarPaletteStyleTag } from '@rocket.chat/ui-theming/src/SidebarPaletteStyleTag'; import type { ReactElement, ReactNode } from 'react'; @@ -9,10 +9,9 @@ import Sidebar from '../../../sidebar'; const LayoutWithSidebar = ({ children }: { children: ReactNode }): ReactElement => { const { isEmbedded: embeddedLayout } = useLayout(); - const [currentRouteName = '', currentParameters = {}] = useCurrentRoute(); const modal = useCurrentModal(); - const currentRoutePath = useRoutePath(currentRouteName, currentParameters); + const currentRoutePath = useCurrentRoutePath(); const channelRoute = useRoute('channel'); const removeSidenav = embeddedLayout && !currentRoutePath?.startsWith('/admin'); const readReceiptsEnabled = useSetting('Message_Read_Receipt_Store_Users'); diff --git a/apps/meteor/client/views/setupWizard/hooks/useRouteLock.ts b/apps/meteor/client/views/setupWizard/hooks/useRouteLock.ts index aa02b7231892f..e771dea6eeb58 100644 --- a/apps/meteor/client/views/setupWizard/hooks/useRouteLock.ts +++ b/apps/meteor/client/views/setupWizard/hooks/useRouteLock.ts @@ -1,5 +1,5 @@ import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; -import { useRoute, useUserId, useUser, useSetting, useRole } from '@rocket.chat/ui-contexts'; +import { useUserId, useUser, useSetting, useRole, useRouter } from '@rocket.chat/ui-contexts'; import { useEffect, useState } from 'react'; export const useRouteLock = (): boolean => { @@ -8,7 +8,7 @@ export const useRouteLock = (): boolean => { const userId = useUserId(); const user = useDebouncedValue(useUser(), 100); const hasAdminRole = useRole('admin'); - const homeRoute = useRoute('home'); + const router = useRouter(); useEffect(() => { if (!setupWizardState) { @@ -26,12 +26,12 @@ export const useRouteLock = (): boolean => { const mustRedirect = isComplete || noUserLoggedInAndIsNotPending || userIsLoggedInButIsNotAdmin; if (mustRedirect) { - homeRoute.replace(); + router.navigate('/home'); return; } setLocked(false); - }, [homeRoute, setupWizardState, userId, user, hasAdminRole, locked]); + }, [router, setupWizardState, userId, user, hasAdminRole, locked]); return locked; }; diff --git a/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.js b/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.js index 7c49b7dd3f34d..8044d15d93800 100644 --- a/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.js +++ b/apps/meteor/client/views/teams/contextualBar/info/TeamsInfoWithData.js @@ -2,12 +2,12 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useSetModal, useToastMessageDispatch, - useRoute, useUserId, useSetting, usePermission, useMethod, useTranslation, + useRouter, } from '@rocket.chat/ui-contexts'; import React, { useCallback } from 'react'; @@ -61,7 +61,7 @@ const TeamsInfoWithLogic = ({ room, openEditing }) => { const hideTeam = useMethod('hideRoom'); - const router = useRoute('home'); + const router = useRouter(); const canDelete = usePermission('delete-team', room._id); const canEdit = usePermission('edit-team-channel', room._id); @@ -75,7 +75,7 @@ const TeamsInfoWithLogic = ({ room, openEditing }) => { try { await deleteTeam({ teamId: room.teamId, ...(roomsToRemove.length && { roomsToRemove }) }); dispatchToastMessage({ type: 'success', message: t('Team_has_been_deleted') }); - router.push({}); + router.navigate('/home'); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } finally { @@ -97,7 +97,7 @@ const TeamsInfoWithLogic = ({ room, openEditing }) => { ...(roomsToLeave.length && { rooms: roomsToLeave }), }); dispatchToastMessage({ type: 'success', message: t('Teams_left_team_successfully') }); - router.push({}); + router.navigate('/home'); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } finally { @@ -112,7 +112,7 @@ const TeamsInfoWithLogic = ({ room, openEditing }) => { const hide = async () => { try { await hideTeam(room._id); - router.push({}); + router.navigate('/home'); } catch (error) { dispatchToastMessage({ type: 'error', message: error }); } finally { diff --git a/apps/meteor/definition/IRoomTypeConfig.ts b/apps/meteor/definition/IRoomTypeConfig.ts index 5ff2986ec3974..1cf746ad1fe1d 100644 --- a/apps/meteor/definition/IRoomTypeConfig.ts +++ b/apps/meteor/definition/IRoomTypeConfig.ts @@ -11,12 +11,13 @@ import type { } from '@rocket.chat/core-typings'; import type { ComponentProps } from 'react'; import type { Icon } from '@rocket.chat/fuselage'; +import type { IRouterPaths, RouteName } from '@rocket.chat/ui-contexts'; export type RoomIdentification = { rid?: IRoom['_id']; name?: string; tab?: string }; -export interface IRoomTypeRouteConfig { - name: string; - path?: string; +export interface IRoomTypeRouteConfig<TRouteName extends RouteName> { + name: TRouteName; + path?: IRouterPaths[TRouteName]['pattern']; link?: (data: RoomIdentification) => Record<string, string>; } @@ -56,7 +57,7 @@ export const UiTextContext = { export interface IRoomTypeConfig { identifier: string; - route?: IRoomTypeRouteConfig; + route?: IRoomTypeRouteConfig<RouteName>; } export interface IRoomTypeClientConfig extends IRoomTypeConfig { @@ -81,7 +82,7 @@ export interface IRoomTypeClientDirectives { room: AtLeast<IRoom, '_id' | 'name' | 'fname' | 'prid' | 'avatarETag' | 'uids' | 'usernames'> & { username?: IRoom['_id'] }, ) => string; getIcon?: (room: Partial<IRoom>) => ComponentProps<typeof Icon>['name']; - extractOpenRoomParams?: (routeParams: Record<string, string | null | undefined>) => { type: RoomType; ref: string }; + extractOpenRoomParams?: (routeParams: Record<string, string | null | undefined>) => { type: RoomType; reference: string }; findRoom: (identifier: string) => IRoom | undefined; showJoinLink: (roomId: string) => boolean; isLivechatRoom: () => boolean; diff --git a/apps/meteor/definition/externals/meteor/http.d.ts b/apps/meteor/definition/externals/meteor/http.d.ts new file mode 100644 index 0000000000000..5d89f1954837d --- /dev/null +++ b/apps/meteor/definition/externals/meteor/http.d.ts @@ -0,0 +1,53 @@ +import 'meteor/http'; + +declare module 'meteor/http' { + namespace HTTP { + interface HTTPRequest { + content?: string | undefined; + data?: any; + query?: string | undefined; + params?: { [id: string]: string } | undefined; + auth?: string | undefined; + headers?: { [id: string]: string } | undefined; + timeout?: number | undefined; + followRedirects?: boolean | undefined; + } + + interface HTTPResponse { + statusCode?: number | undefined; + headers?: { [id: string]: string } | undefined; + content?: string | undefined; + data?: any; + } + + type AsyncCallback = (error: Meteor.Error | null, result?: HTTPResponse) => void; + + function del(url: string, callOptions?: HTTP.HTTPRequest, asyncCallback?: AsyncCallback): HTTP.HTTPResponse; + + function get(url: string, callOptions?: HTTP.HTTPRequest, asyncCallback?: AsyncCallback): HTTP.HTTPResponse; + + function post(url: string, callOptions?: HTTP.HTTPRequest, asyncCallback?: AsyncCallback): HTTP.HTTPResponse; + + function put(url: string, callOptions?: HTTP.HTTPRequest, asyncCallback?: AsyncCallback): HTTP.HTTPResponse; + + function call(method: string, url: string, options?: HTTP.HTTPRequest, asyncCallback?: AsyncCallback): HTTP.HTTPResponse; + + function call( + method: string, + url: string, + options?: { + content?: string | undefined; + data?: Object | undefined; + query?: string | undefined; + params?: Object | undefined; + auth?: string | undefined; + headers?: Object | undefined; + timeout?: number | undefined; + followRedirects?: boolean | undefined; + npmRequestOptions?: Object | undefined; + beforeSend?: Function | undefined; + }, + asyncCallback?: AsyncCallback, + ): HTTP.HTTPResponse; + } +} diff --git a/apps/meteor/definition/externals/meteor/kadira-flow-router.d.ts b/apps/meteor/definition/externals/meteor/kadira-flow-router.d.ts index 4c72b82d202fd..8dfc0de5a01be 100644 --- a/apps/meteor/definition/externals/meteor/kadira-flow-router.d.ts +++ b/apps/meteor/definition/externals/meteor/kadira-flow-router.d.ts @@ -5,30 +5,30 @@ declare module 'meteor/kadira:flow-router' { params: Record<string, string>; queryParams: Record<string, string>; pathname: string; - oldRoute?: Route; - route: Route; + oldRoute?: Route<string, any>; + route: Route<string, any>; }; - type RouteOptions = { - name?: string; - action?: (this: Route, params?: Record<string, string>, queryParams?: Record<string, string>) => void; - subscriptions?: (this: Route, params?: Record<string, string>, queryParams?: Record<string, string>) => void; - triggersEnter?: ((context: Context, redirect: (pathDef: string) => void, stop: () => void) => void)[]; + type RouteOptions<TRouteName extends string> = { + name?: TRouteName; + action?: (this: Route, params?: Record<string, string>, queryParams?: Record<string, string | string[]>) => void; + subscriptions?: (this: Route, params?: Record<string, string>, queryParams?: Record<string, string | string[]>) => void; + triggersEnter?: ((context: Context, redirect: (path: string) => void, stop: () => void) => void)[]; triggersExit?: ((context: Context) => void)[]; }; - class Route { - constructor(router: Router, pathDef: string, options?: RouteOptions, group?: Group); + class Route<TRouteName extends string, TGroup extends Group<string> | undefined = any> { + constructor(router: Router, pathDef: string, options?: RouteOptions<TRouteName>, group?: TGroup); - options: RouteOptions; + options: RouteOptions<TRouteName>; pathDef: string; path: string; - name?: string; + name?: TRouteName; - group?: Group; + group?: TGroup; clearSubscriptions(): void; @@ -55,29 +55,38 @@ declare module 'meteor/kadira:flow-router' { registerRouteChange(currentContext: Context, routeChanging?: boolean): void; } - type GroupOptions = { - name: string; + type GroupOptions<TGroupName extends string> = { + name: TGroupName; prefix?: string; triggersEnter?: unknown[]; triggersExit?: unknown[]; - subscriptions?: (this: Route, params?: Record<string, string>, queryParams?: Record<string, string>) => void; + subscriptions?: (this: Route, params?: Record<string, string>, queryParams?: Record<string, string | string[]>) => void; }; - class Group { - constructor(router: Router, options?: GroupOptions, parent?: Group); + /** @deprecated */ + class Group<TGroupName extends string, TParentGroup extends Group<string> | undefined = any> { + /** @deprecated */ + constructor(router: Router, options?: GroupOptions<TGroupName>, parent?: TParentGroup); - name: string; + /** @deprecated */ + name: TGroupName; + /** @deprecated */ prefix: string; - options: GroupOptions; + /** @deprecated */ + options: GroupOptions<TGroupName>; - parent: Group | undefined; + /** @deprecated */ + parent: TParentGroup; - route(pathDef: string, options: RouteOptions, group?: Group): Route; + /** @deprecated */ + route<TRouteName extends string>(pathDef: string, options: RouteOptions<TRouteName>, group?: TParentGroup): Route<TRouteName, TGroup>; - group(options?: GroupOptions): Group; + /** @deprecated */ + group(options?: GroupOptions<TGroupName>): Group; + /** @deprecated */ callSubscriptions(current: Current): void; } @@ -85,9 +94,9 @@ declare module 'meteor/kadira:flow-router' { path: string; context: Context; params: Record<string, string>; - queryParams: Record<string, string>; - route?: Route | undefined; - oldRoute?: Route | undefined; + queryParams?: Record<string, string>; + route?: Route<string> | undefined; + oldRoute?: Route<string> | undefined; }; type RouterOptions = { @@ -97,9 +106,16 @@ declare module 'meteor/kadira:flow-router' { class Router { constructor(); - route(pathDef: string, options: RouteOptions, group?: Group): Route; + route<TRouteName extends string>(pathDef: string, options: RouteOptions<TRouteName>): Route<TRouteName, undefined>; + + route<TRouteName extends string, TGroup extends Group<string>>( + pathDef: string, + options: RouteOptions<TRouteName>, + group: TGroup, + ): Route<TRouteName, TGroup>; - group(options: GroupOptions): Group; + /** @deprecated */ + group<TGroupName extends string>(options: GroupOptions<TGroupName>): Group<TGroupName>; path(pathDef: string, fields?: Record<string, string>, queryParams?: Record<string, string>): string; @@ -113,7 +129,7 @@ declare module 'meteor/kadira:flow-router' { setParams(newParams: Record<string, string>): boolean; - setQueryParams(newParams: Record<string, string | null>): boolean; + setQueryParams(newParams: Record<string, string | undefined | null>): boolean; current(): Current; @@ -127,7 +143,7 @@ declare module 'meteor/kadira:flow-router' { wait(): void; - notFound: Omit<RouteOptions, 'name'>; + notFound: Omit<RouteOptions<any>, 'name'>; getRouteName(): string; @@ -144,10 +160,18 @@ declare module 'meteor/kadira:flow-router' { _routesMap: Record<string, Route>; _updateCallbacks(): void; + + _current: Current; } const FlowRouter: Router & { Route: typeof Route; Router: typeof Router; + _page: { + start(): void; + stop(): void; + show(path: string): void; + dispatch(ctx: { path: string; params: any }): void; + }; }; } diff --git a/apps/meteor/definition/externals/meteor/meteor.d.ts b/apps/meteor/definition/externals/meteor/meteor.d.ts index 1bf3bf0ebc649..9d7028675669f 100644 --- a/apps/meteor/definition/externals/meteor/meteor.d.ts +++ b/apps/meteor/definition/externals/meteor/meteor.d.ts @@ -122,6 +122,8 @@ declare module 'meteor/meteor' { cb: (error?: Error | Meteor.Error | Meteor.TypedError) => void, ): void; + function loginWithSamlToken(token: string, cb: (error?: Error | Meteor.Error | Meteor.TypedError) => void): void; + function methods<TServerMethods extends ServerMethods>(methods: { [TMethodName in keyof TServerMethods]?: ( this: MethodThisType, diff --git a/apps/meteor/ee/app/livechat-enterprise/client/views/livechatSideNavItems.js b/apps/meteor/ee/app/livechat-enterprise/client/views/livechatSideNavItems.js index 7fe3c468f323c..a669834d103ae 100644 --- a/apps/meteor/ee/app/livechat-enterprise/client/views/livechatSideNavItems.js +++ b/apps/meteor/ee/app/livechat-enterprise/client/views/livechatSideNavItems.js @@ -2,37 +2,37 @@ import { registerOmnichannelSidebarItem } from '../../../../../client/views/omni import { hasPermission, hasAtLeastOnePermission } from '../../../../../app/authorization/client'; registerOmnichannelSidebarItem({ - href: 'omnichannel-monitors', + href: '/omnichannel/monitors', i18nLabel: 'Livechat_Monitors', permissionGranted: () => hasPermission('manage-livechat-monitors'), }); registerOmnichannelSidebarItem({ - href: 'omnichannel/units', + href: '/omnichannel/units', i18nLabel: 'Units', permissionGranted: () => hasPermission('manage-livechat-units'), }); registerOmnichannelSidebarItem({ - href: 'omnichannel-canned-responses', + href: '/omnichannel/canned-responses', i18nLabel: 'Canned_Responses', permissionGranted: () => hasPermission('manage-livechat-canned-responses'), }); registerOmnichannelSidebarItem({ - href: 'omnichannel/tags', + href: '/omnichannel/tags', i18nLabel: 'Tags', permissionGranted: () => hasPermission('manage-livechat-tags'), }); registerOmnichannelSidebarItem({ - href: 'omnichannel/sla-policies', + href: '/omnichannel/sla-policies', i18nLabel: 'SLA_Policies', permissionGranted: () => hasAtLeastOnePermission('manage-livechat-sla'), }); registerOmnichannelSidebarItem({ - href: 'omnichannel/priorities', + href: '/omnichannel/priorities', i18nLabel: 'Priorities', permissionGranted: () => hasAtLeastOnePermission('manage-livechat-priorities'), }); diff --git a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/index.tsx b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/index.tsx index 5d6eed0e8859d..f862a63fdf696 100644 --- a/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/index.tsx +++ b/apps/meteor/ee/client/omnichannel/components/contextualBar/CannedResponse/index.tsx @@ -1,5 +1,5 @@ import { useDebouncedValue, useLocalStorage, useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useSetModal, useCurrentRoute, useRoute } from '@rocket.chat/ui-contexts'; +import { useSetModal, useRouter } from '@rocket.chat/ui-contexts'; import type { FC, MouseEvent } from 'react'; import React, { memo, useCallback, useMemo, useState } from 'react'; @@ -14,8 +14,7 @@ import CannedResponseList from './CannedResponseList'; export const WrapCannedResponseList: FC<{ tabBar: any }> = ({ tabBar }) => { const room = useRoom(); - const [name] = useCurrentRoute(); - const channelRoute = useRoute(name || ''); + const router = useRouter(); const setModal = useSetModal(); const options = useCannedResponseFilterOptions() as [string, string][]; @@ -37,10 +36,13 @@ export const WrapCannedResponseList: FC<{ tabBar: any }> = ({ tabBar }) => { const onClickItem = useMutableCallback((data) => { const { _id: context } = data; - channelRoute?.push({ - id: room._id, - tab: 'canned-responses', - context, + router.navigate({ + name: router.getRouteName() ?? 'live', + params: { + id: room._id, + tab: 'canned-responses', + context, + }, }); }); diff --git a/apps/meteor/ee/client/omnichannel/routes.ts b/apps/meteor/ee/client/omnichannel/routes.ts index a3d80739a86fa..3efe8d6aab221 100644 --- a/apps/meteor/ee/client/omnichannel/routes.ts +++ b/apps/meteor/ee/client/omnichannel/routes.ts @@ -2,6 +2,27 @@ import { lazy } from 'react'; import { registerOmnichannelRoute } from '../../../client/views/omnichannel/routes'; +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'omnichannel-monitors': { + pattern: '/omnichannel/monitors'; + pathname: '/omnichannel/monitors'; + }; + 'omnichannel-sla-policies': { + pattern: '/omnichannel/sla-policies/:context?/:id?'; + pathname: `/omnichannel/sla-policies${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-priorities': { + pattern: '/omnichannel/priorities/:context?/:id?'; + pathname: `/omnichannel/priorities${`/${string}` | ''}${`/${string}` | ''}`; + }; + 'omnichannel-canned-responses': { + pattern: '/omnichannel/canned-responses/:context?/:id?'; + pathname: `/omnichannel/canned-responses${`/${string}` | ''}${`/${string}` | ''}`; + }; + } +} + registerOmnichannelRoute('/monitors', { name: 'omnichannel-monitors', component: lazy(() => import('./monitors/MonitorsPageContainer')), diff --git a/apps/meteor/ee/client/startup/audit.tsx b/apps/meteor/ee/client/startup/audit.tsx index e3d687fc846cb..0f1e8b59ec255 100644 --- a/apps/meteor/ee/client/startup/audit.tsx +++ b/apps/meteor/ee/client/startup/audit.tsx @@ -1,9 +1,8 @@ -import { FlowRouter } from 'meteor/kadira:flow-router'; -import { Tracker } from 'meteor/tracker'; import React, { lazy } from 'react'; import { hasAllPermission } from '../../../app/authorization/client'; import { appLayout } from '../../../client/lib/appLayout'; +import { router } from '../../../client/providers/RouterProvider'; import NotAuthorizedPage from '../../../client/views/notAuthorized/NotAuthorizedPage'; import MainLayout from '../../../client/views/root/MainLayout'; import { onToggledFeature } from '../lib/onToggledFeature'; @@ -11,66 +10,55 @@ import { onToggledFeature } from '../lib/onToggledFeature'; const AuditPage = lazy(() => import('../views/audit/AuditPage')); const AuditLogPage = lazy(() => import('../views/audit/AuditLogPage')); -let auditRoute = FlowRouter.route('/audit', { name: 'audit-home' }); -let auditLogRoute = FlowRouter.route('/audit-log', { name: 'audit-log' }); - -const registerRoutes = () => { - FlowRouter._routes = FlowRouter._routes.filter((r) => r !== auditRoute && r !== auditLogRoute); - if (auditRoute.name) { - delete FlowRouter._routesMap[auditRoute.name]; - } - if (auditLogRoute.name) { - delete FlowRouter._routesMap[auditLogRoute.name]; +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'audit-home': { + pathname: '/audit'; + pattern: '/audit/:tab?'; + }; + 'audit-log': { + pathname: '/audit-log'; + pattern: '/audit-log'; + }; } +} - auditRoute = FlowRouter.route('/audit/:tab?', { - name: 'audit-home', - action() { - Tracker.autorun(() => { - const canAudit = hasAllPermission('can-audit'); - - appLayout.render(<MainLayout>{canAudit ? <AuditPage /> : <NotAuthorizedPage />}</MainLayout>); - }); - }, - }); +const PermissionGuard = ({ children, permission }: { children: React.ReactNode; permission: string }) => { + const canView = hasAllPermission(permission); - auditLogRoute = FlowRouter.route('/audit-log', { - name: 'audit-log', - action() { - Tracker.autorun(() => { - const canAuditLog = hasAllPermission('can-audit-log'); - - appLayout.render(<MainLayout>{canAuditLog ? <AuditLogPage /> : <NotAuthorizedPage />}</MainLayout>); - }); - }, - }); - - if (FlowRouter._initialized) { - FlowRouter._updateCallbacks(); - FlowRouter.reload(); - } + return <>{canView ? children : <NotAuthorizedPage />}</>; }; -const unregisterRoutes = () => { - FlowRouter._routes = FlowRouter._routes.filter((r) => r !== auditRoute && r !== auditLogRoute); - if (auditRoute.name) { - delete FlowRouter._routesMap[auditRoute.name]; - } - if (auditLogRoute.name) { - delete FlowRouter._routesMap[auditLogRoute.name]; - } - - if (FlowRouter._initialized) { - FlowRouter._updateCallbacks(); - FlowRouter.reload(); - } -}; +let unregisterAuditRoutes: () => void; onToggledFeature('auditing', { up: () => { - registerRoutes(); + unregisterAuditRoutes = router.defineRoutes([ + { + path: '/audit/:tab?', + id: 'audit-home', + element: appLayout.wrap( + <MainLayout> + <PermissionGuard permission='can-audit'> + <AuditPage /> + </PermissionGuard> + </MainLayout>, + ), + }, + { + path: '/audit-log', + id: 'audit-log', + element: appLayout.wrap( + <MainLayout> + <PermissionGuard permission='can-audit-log'> + <AuditLogPage /> + </PermissionGuard> + </MainLayout>, + ), + }, + ]); }, down: () => { - unregisterRoutes(); + unregisterAuditRoutes(); }, }); diff --git a/apps/meteor/ee/client/startup/deviceManagement.ts b/apps/meteor/ee/client/startup/deviceManagement.ts index bac54f23408e2..33f1a99dd767f 100644 --- a/apps/meteor/ee/client/startup/deviceManagement.ts +++ b/apps/meteor/ee/client/startup/deviceManagement.ts @@ -5,13 +5,26 @@ import { registerAccountRoute, registerAccountSidebarItem, unregisterSidebarItem import { registerAdminRoute, registerAdminSidebarItem, unregisterAdminSidebarItem } from '../../../client/views/admin'; import { onToggledFeature } from '../lib/onToggledFeature'; +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'device-management': { + pathname: `/admin/device-management${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/admin/device-management/:context?/:id?'; + }; + 'manage-devices': { + pathname: '/account/manage-devices'; + pattern: '/account/manage-devices'; + }; + } +} + const [registerAdminRouter, unregisterAdminRouter] = registerAdminRoute('/device-management/:context?/:id?', { name: 'device-management', component: lazy(() => import('../views/admin/deviceManagement/DeviceManagementAdminRoute')), ready: false, }); -const [registerAccountRouter, unregisterAccountRouter] = registerAccountRoute('/manage-devices/', { +const [registerAccountRouter, unregisterAccountRouter] = registerAccountRoute('/manage-devices', { name: 'manage-devices', component: lazy(() => import('../views/account/deviceManagement/DeviceManagementAccountPage')), }); diff --git a/apps/meteor/ee/client/startup/engagementDashboard.ts b/apps/meteor/ee/client/startup/engagementDashboard.ts index b6391495bee1c..0bd1d49951691 100644 --- a/apps/meteor/ee/client/startup/engagementDashboard.ts +++ b/apps/meteor/ee/client/startup/engagementDashboard.ts @@ -5,6 +5,15 @@ import { hasAllPermission } from '../../../app/authorization/client'; import { registerAdminRoute, registerAdminSidebarItem, unregisterAdminSidebarItem } from '../../../client/views/admin'; import { onToggledFeature } from '../lib/onToggledFeature'; +declare module '@rocket.chat/ui-contexts' { + interface IRouterPaths { + 'engagement-dashboard': { + pattern: '/admin/engagement-dashboard/:tab?'; + pathname: `/admin/engagement-dashboard${`/${string}` | ''}`; + }; + } +} + const [registerRoute, unregisterRoute] = registerAdminRoute('/engagement-dashboard/:tab?', { name: 'engagement-dashboard', component: lazy(() => import('../views/admin/engagementDashboard/EngagementDashboardRoute')), diff --git a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx index 29afa2b4a0f86..5423a7aa10ab6 100644 --- a/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx +++ b/apps/meteor/ee/client/views/admin/engagementDashboard/EngagementDashboardRoute.tsx @@ -1,4 +1,4 @@ -import { useCurrentRoute, useRoute, usePermission } from '@rocket.chat/ui-contexts'; +import { usePermission, useRouter, useRouteParameter } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React, { useEffect } from 'react'; @@ -11,19 +11,30 @@ const isValidTab = (tab: string | undefined): tab is 'users' | 'messages' | 'cha const EngagementDashboardRoute = (): ReactElement | null => { const canViewEngagementDashboard = usePermission('view-engagement-dashboard'); - const engagementDashboardRoute = useRoute('engagement-dashboard'); - const [routeName, routeParams] = useCurrentRoute(); - const { tab } = routeParams ?? {}; + const router = useRouter(); + const tab = useRouteParameter('tab'); - useEffect(() => { - if (routeName !== 'engagement-dashboard') { - return; - } + useEffect( + () => + router.subscribeToRouteChange(() => { + if (router.getRouteName() !== 'engagement-dashboard') { + return; + } - if (!isValidTab(tab)) { - engagementDashboardRoute.replace({ tab: 'users' }); - } - }, [routeName, engagementDashboardRoute, tab]); + const { tab } = router.getRouteParameters(); + + if (!isValidTab(tab)) { + router.navigate( + { + pattern: '/admin/engagement-dashboard/:tab?', + params: { tab: 'users' }, + }, + { replace: true }, + ); + } + }), + [router], + ); const eventStats = useEndpointAction('POST', '/v1/statistics.telemetry'); @@ -38,7 +49,17 @@ const EngagementDashboardRoute = (): ReactElement | null => { eventStats({ params: [{ eventName: 'updateCounter', settingsId: 'Engagement_Dashboard_Load_Count' }], }); - return <EngagementDashboardPage tab={tab} onSelectTab={(tab): void => engagementDashboardRoute.push({ tab })} />; + return ( + <EngagementDashboardPage + tab={tab} + onSelectTab={(tab) => + router.navigate({ + pattern: '/admin/engagement-dashboard/:tab?', + params: { tab }, + }) + } + /> + ); }; export default EngagementDashboardRoute; diff --git a/apps/meteor/ee/client/views/audit/hooks/useAuditTab.ts b/apps/meteor/ee/client/views/audit/hooks/useAuditTab.ts index 12bb9ae8654c7..1045e20ea5929 100644 --- a/apps/meteor/ee/client/views/audit/hooks/useAuditTab.ts +++ b/apps/meteor/ee/client/views/audit/hooks/useAuditTab.ts @@ -17,7 +17,7 @@ export const useAuditTab = () => { const tab = useRouteParameter('tab'); const type = useMemo(() => tabToTabMap.get(tab ?? 'rooms') ?? '', [tab]); - const auditRoute = useRoute('/audit/:tab?'); + const auditRoute = useRoute('audit-home'); const setType = useMutableCallback((newType: SetStateAction<IAuditLog['fields']['type']>) => { auditRoute.replace({ tab: typeToTabMap[typeof newType === 'function' ? newType(type) : newType] ?? 'rooms' }); diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index f84b927c89e0f..69913b79e3c56 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -204,6 +204,13 @@ type ChainedCallbackSignatures = { room: IOmnichannelRoom; options: { forwardingToDepartment?: { oldDepartmentId: string; transferData: any }; clientAction?: boolean }; }) => (IOmnichannelRoom & { chatQueued: boolean }) | void; + 'roomNameChanged': (room: IRoom) => void; + 'roomTopicChanged': (room: IRoom) => void; + 'roomAnnouncementChanged': (room: IRoom) => void; + 'roomTypeChanged': (room: IRoom) => void; + 'archiveRoom': (room: IRoom) => void; + 'unarchiveRoom': (room: IRoom) => void; + 'roomAvatarChanged': (room: IRoom) => void; }; export type Hook = @@ -217,7 +224,6 @@ export type Hook = | 'afterRoomTopicChange' | 'afterSaveUser' | 'afterValidateNewOAuthUser' - | 'archiveRoom' | 'beforeActivateUser' | 'beforeCreateUser' | 'beforeGetMentions' @@ -242,15 +248,9 @@ export type Hook = | 'onValidateLogin' | 'openBroadcast' | 'renderNotification' - | 'roomAnnouncementChanged' - | 'roomAvatarChanged' - | 'roomNameChanged' - | 'roomTopicChanged' - | 'roomTypeChanged' | 'setReaction' | 'streamMessage' | 'streamNewMessage' - | 'unarchiveRoom' | 'unsetReaction' | 'userAvatarSet' | 'userConfirmationEmailRequested' diff --git a/apps/meteor/lib/rooms/roomTypes/direct.ts b/apps/meteor/lib/rooms/roomTypes/direct.ts index 66b677b0a754c..8d87643801969 100644 --- a/apps/meteor/lib/rooms/roomTypes/direct.ts +++ b/apps/meteor/lib/rooms/roomTypes/direct.ts @@ -1,6 +1,15 @@ import type { IRoomTypeConfig } from '../../../definition/IRoomTypeConfig'; import type { RoomCoordinator } from '../coordinator'; +declare module '@rocket.chat/ui-contexts' { + export interface IRouterPaths { + direct: { + pathname: `/direct/:rid${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/direct/:rid/:tab?/:context?'; + }; + } +} + export function getDirectMessageRoomType(_coordinator: RoomCoordinator): IRoomTypeConfig { return { identifier: 'd', diff --git a/apps/meteor/lib/rooms/roomTypes/livechat.ts b/apps/meteor/lib/rooms/roomTypes/livechat.ts index 7c7051cc8e505..b05cdefc07a0b 100644 --- a/apps/meteor/lib/rooms/roomTypes/livechat.ts +++ b/apps/meteor/lib/rooms/roomTypes/livechat.ts @@ -1,6 +1,15 @@ import type { IRoomTypeConfig } from '../../../definition/IRoomTypeConfig'; import type { RoomCoordinator } from '../coordinator'; +declare module '@rocket.chat/ui-contexts' { + export interface IRouterPaths { + live: { + pathname: `/live/${string}${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/live/:id/:tab?/:context?'; + }; + } +} + export function getLivechatRoomType(_coordinator: RoomCoordinator): IRoomTypeConfig { return { identifier: 'l', diff --git a/apps/meteor/lib/rooms/roomTypes/private.ts b/apps/meteor/lib/rooms/roomTypes/private.ts index e72aec56bd30e..959463e94ae8f 100644 --- a/apps/meteor/lib/rooms/roomTypes/private.ts +++ b/apps/meteor/lib/rooms/roomTypes/private.ts @@ -1,6 +1,15 @@ import type { IRoomTypeConfig } from '../../../definition/IRoomTypeConfig'; import type { RoomCoordinator } from '../coordinator'; +declare module '@rocket.chat/ui-contexts' { + export interface IRouterPaths { + group: { + pathname: `/group/${string}${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/group/:name/:tab?/:context?'; + }; + } +} + export function getPrivateRoomType(_coordinator: RoomCoordinator): IRoomTypeConfig { return { identifier: 'p', diff --git a/apps/meteor/lib/rooms/roomTypes/public.ts b/apps/meteor/lib/rooms/roomTypes/public.ts index d09b8aef99716..2f90488d88350 100644 --- a/apps/meteor/lib/rooms/roomTypes/public.ts +++ b/apps/meteor/lib/rooms/roomTypes/public.ts @@ -1,6 +1,15 @@ import type { IRoomTypeConfig } from '../../../definition/IRoomTypeConfig'; import type { RoomCoordinator } from '../coordinator'; +declare module '@rocket.chat/ui-contexts' { + export interface IRouterPaths { + channel: { + pathname: `/channel/${string}${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/channel/:name/:tab?/:context?'; + }; + } +} + export function getPublicRoomType(_coordinator: RoomCoordinator): IRoomTypeConfig { return { identifier: 'c', diff --git a/apps/meteor/lib/rooms/roomTypes/voip.ts b/apps/meteor/lib/rooms/roomTypes/voip.ts index 0c692baa5f458..04ae151ef5a4e 100644 --- a/apps/meteor/lib/rooms/roomTypes/voip.ts +++ b/apps/meteor/lib/rooms/roomTypes/voip.ts @@ -1,6 +1,15 @@ import type { IRoomTypeConfig } from '../../../definition/IRoomTypeConfig'; import type { RoomCoordinator } from '../coordinator'; +declare module '@rocket.chat/ui-contexts' { + export interface IRouterPaths { + voip: { + pathname: `/voip/${string}${`/${string}` | ''}${`/${string}` | ''}`; + pattern: '/voip/:id/:tab?/:context?'; + }; + } +} + export function getVoipRoomType(_coordinator: RoomCoordinator): IRoomTypeConfig { return { identifier: 'v', diff --git a/apps/meteor/lib/utils/generatePath.ts b/apps/meteor/lib/utils/generatePath.ts new file mode 100644 index 0000000000000..40c4bab1043ca --- /dev/null +++ b/apps/meteor/lib/utils/generatePath.ts @@ -0,0 +1,13 @@ +import { compile } from 'path-to-regexp'; + +type PathParams<TPath extends string> = TPath extends `${string}:${infer TParam}/${infer TRest}` + ? (TParam extends `${infer U}?` ? U : TParam) | PathParams<TRest> + : TPath extends `${string}:${infer TParam}` + ? TParam extends `${infer U}?` + ? U + : TParam + : never; + +export function generatePath<TPath extends string>(path: TPath, params?: Partial<Record<PathParams<TPath>, string>>): string { + return compile(path, { encode: encodeURIComponent })(params); +} diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 726ddd56e1181..4f2422cb3fedc 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -39,7 +39,7 @@ "testapi": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha --config ./.mocharc.api.js", "testunit": "npm run .testunit:definition && npm run .testunit:client && npm run .testunit:server", ".testunit:server": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha --config ./.mocharc.js", - ".testunit:client": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha --config ./.mocharc.client.js", + ".testunit:client": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha --config ./.mocharc.client.js --exit", ".testunit:definition": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha --config ./.mocharc.definition.js", "testunit-watch": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha --watch --config ./.mocharc.js", "test": "npm run testapi && npm run testui", diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center.spec.ts index b9ac92d67a9a8..0f5f1e9605b26 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-contact-center.spec.ts @@ -9,7 +9,7 @@ import { test, expect } from '../utils/test'; const createContact = (generateToken = false) => ({ id: null, - name: `${faker.person.firstName()} ${faker.name.lastName()}`, + name: `${faker.person.firstName()} ${faker.person.lastName()}`, email: faker.internet.email().toLowerCase(), phone: faker.phone.number('+############'), token: generateToken ? createToken() : null, diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-departaments.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-departaments.spec.ts index 36e2b54601f96..6641baae2e664 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-departaments.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-departaments.spec.ts @@ -78,12 +78,12 @@ test.describe.serial('omnichannel-departments', () => { await poOmnichannelDepartments.btnSave.click(); await poOmnichannelDepartments.btnCloseToastSuccess.click(); - await poOmnichannelDepartments.inputSearch.fill(departmentName); + await poOmnichannelDepartments.search(departmentName); await expect(poOmnichannelDepartments.firstRowInTable).toBeVisible(); }); await test.step('expect update department name', async () => { - await poOmnichannelDepartments.inputSearch.fill(departmentName); + await poOmnichannelDepartments.search(departmentName); await poOmnichannelDepartments.firstRowInTableMenu.click(); await poOmnichannelDepartments.menuEditOption.click(); @@ -92,14 +92,14 @@ test.describe.serial('omnichannel-departments', () => { await poOmnichannelDepartments.btnSave.click(); await poOmnichannelDepartments.btnCloseToastSuccess.click(); - await poOmnichannelDepartments.inputSearch.fill(`edited-${departmentName}`); + await poOmnichannelDepartments.search(`edited-${departmentName}`); await expect(poOmnichannelDepartments.firstRowInTable).toBeVisible(); }); await test.step('expect archive department', async () => { await expect(poOmnichannelDepartments.firstRowInTable).toBeVisible(); - await poOmnichannelDepartments.inputSearch.fill(`edited-${departmentName}`); + await poOmnichannelDepartments.search(`edited-${departmentName}`); await poOmnichannelDepartments.firstRowInTableMenu.click(); @@ -121,7 +121,7 @@ test.describe.serial('omnichannel-departments', () => { await test.step('expect unarchive department', async () => { await poOmnichannelDepartments.archivedDepartmentsTab.click(); - await poOmnichannelDepartments.inputSearch.fill(`edited-${departmentName}`); + await poOmnichannelDepartments.search(`edited-${departmentName}`); await poOmnichannelDepartments.firstRowInTableMenu.click(); @@ -133,7 +133,7 @@ test.describe.serial('omnichannel-departments', () => { await test.step('expect delete department', async () => { await poOmnichannelDepartments.allDepartmentsTab.click(); - await poOmnichannelDepartments.inputSearch.fill(`edited-${departmentName}`); + await poOmnichannelDepartments.search(`edited-${departmentName}`); await poOmnichannelDepartments.selectedDepartmentMenu(`edited-${departmentName}`).click(); @@ -143,7 +143,7 @@ test.describe.serial('omnichannel-departments', () => { await poOmnichannelDepartments.btnModalConfirmDelete.click(); - await poOmnichannelDepartments.inputSearch.fill(`edited-${departmentName}`); + await poOmnichannelDepartments.search(`edited-${departmentName}`); await expect(poOmnichannelDepartments.firstRowInTable).toHaveCount(0); }); @@ -160,12 +160,12 @@ test.describe.serial('omnichannel-departments', () => { await poOmnichannelDepartments.btnSave.click(); await poOmnichannelDepartments.btnCloseToastSuccess.click(); - await poOmnichannelDepartments.inputSearch.fill(tagsDepartmentName); + await poOmnichannelDepartments.search(tagsDepartmentName); await expect(poOmnichannelDepartments.firstRowInTable).toBeVisible(); }); await test.step('expect save form button be disabled', async () => { - await poOmnichannelDepartments.inputSearch.fill(tagsDepartmentName); + await poOmnichannelDepartments.search(tagsDepartmentName); await poOmnichannelDepartments.firstRowInTableMenu.click(); await poOmnichannelDepartments.menuEditOption.click(); await expect(poOmnichannelDepartments.btnSave).toBeDisabled(); @@ -173,7 +173,7 @@ test.describe.serial('omnichannel-departments', () => { }); await test.step('Disabled tags state', async () => { - await poOmnichannelDepartments.inputSearch.fill(tagsDepartmentName); + await poOmnichannelDepartments.search(tagsDepartmentName); await poOmnichannelDepartments.firstRowInTableMenu.click(); await poOmnichannelDepartments.menuEditOption.click(); @@ -188,9 +188,9 @@ test.describe.serial('omnichannel-departments', () => { }); await test.step('Enabled tags state', async () => { - const tagName = faker.datatype.string(5); + const tagName = faker.string.sample(5); - await poOmnichannelDepartments.inputSearch.fill(tagsDepartmentName); + await poOmnichannelDepartments.search(tagsDepartmentName); await poOmnichannelDepartments.firstRowInTableMenu.click(); await poOmnichannelDepartments.menuEditOption.click(); @@ -224,7 +224,7 @@ test.describe.serial('omnichannel-departments', () => { }); await test.step('expect to not be possible adding same tag twice', async () => { - const tagName = faker.datatype.string(5); + const tagName = faker.string.sample(5); await poOmnichannelDepartments.inputTags.fill(tagName); await poOmnichannelDepartments.btnTagsAdd.click(); await poOmnichannelDepartments.inputTags.fill(tagName); diff --git a/apps/meteor/tests/e2e/page-objects/fragments/omnichannel-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/omnichannel-sidenav.ts index ebca2dc9450fc..6b10bcd47b2e0 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/omnichannel-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/omnichannel-sidenav.ts @@ -8,15 +8,15 @@ export class OmnichannelSidenav { } get linkDepartments(): Locator { - return this.page.locator('a[href="omnichannel/departments"]'); + return this.page.locator('a[href="/omnichannel/departments"]'); } get linkAgents(): Locator { - return this.page.locator('a[href="omnichannel/agents"]'); + return this.page.locator('a[href="/omnichannel/agents"]'); } get linkManagers(): Locator { - return this.page.locator('a[href="omnichannel/managers"]'); + return this.page.locator('a[href="/omnichannel/managers"]'); } get linkCustomFields(): Locator { @@ -24,7 +24,7 @@ export class OmnichannelSidenav { } get linkCurrentChats(): Locator { - return this.page.locator('a[href="omnichannel/current"]'); + return this.page.locator('a[href="/omnichannel/current"]'); } get linkTriggers(): Locator { @@ -32,10 +32,10 @@ export class OmnichannelSidenav { } get linkSlaPolicies(): Locator { - return this.page.locator('a[href="omnichannel/sla-policies"]'); + return this.page.locator('a[href="/omnichannel/sla-policies"]'); } get linkPriorities(): Locator { - return this.page.locator('a[href="omnichannel/priorities"]'); + return this.page.locator('a[href="/omnichannel/priorities"]'); } } diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-departments.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-departments.ts index 59611bc77f89e..347708f312ef2 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-departments.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-departments.ts @@ -16,6 +16,11 @@ export class OmnichannelDepartments { return this.page.locator('[placeholder="Search"]'); } + async search(text: string) { + await this.inputSearch.fill(text); + await this.page.waitForTimeout(500); + } + get btnNew() { return this.page.locator('button.rcx-button >> text="New"'); } diff --git a/apps/meteor/tests/mocks/client/RouterContextMock.tsx b/apps/meteor/tests/mocks/client/RouterContextMock.tsx index 9a403065ffea0..c21c05492e3e5 100644 --- a/apps/meteor/tests/mocks/client/RouterContextMock.tsx +++ b/apps/meteor/tests/mocks/client/RouterContextMock.tsx @@ -1,72 +1,119 @@ -import type { ReactElement, ReactNode } from 'react'; -import React, { useCallback, useRef, useState, useMemo } from 'react'; +import type { MutableRefObject, ReactElement, ReactNode } from 'react'; +import React, { useRef, useMemo } from 'react'; +import type { To, SearchParameters, LocationPathname, LocationSearch } from '@rocket.chat/ui-contexts'; import { RouterContext } from '@rocket.chat/ui-contexts'; -import { Emitter } from '@rocket.chat/emitter'; +import { compile } from 'path-to-regexp'; -const useSubscribableState = <T,>(initialState: T | (() => T)) => { - const [value, setValue] = useState<T>(initialState); +import type { UpgradeTabVariant } from '../../../lib/upgradeTab'; - const emitterRef = useRef(new Emitter<{ update: void }>()); +const encodeSearchParameters = (searchParameters: SearchParameters) => { + const search = new URLSearchParams(); - const get = useCallback(() => value, [value]); + for (const [key, value] of Object.entries(searchParameters)) { + search.append(key, value); + } - const set = useCallback((newValue: T | ((prev: T) => T)) => { - setValue(newValue); - emitterRef.current.emit('update'); - }, []); + const searchString = search.toString(); - const subscribe = useMemo(() => emitterRef.current.on.bind(emitterRef.current, 'update'), []); - - return { get, set, subscribe }; + return searchString ? (`?${searchString}` as `?${LocationSearch}`) : ''; }; -type RouteTuple = [name: string, parameters?: Record<string, string>, queryStringParameters?: Record<string, string>]; +const buildRoutePath = (to: To): LocationPathname | `${LocationPathname}?${LocationSearch}` => { + if (typeof to === 'string') { + return to; + } + + if ('pathname' in to) { + const { pathname, search = {} } = to; + return (pathname + encodeSearchParameters(search)) as LocationPathname | `${LocationPathname}?${LocationSearch}`; + } + + if ('pattern' in to) { + const { pattern, params = {}, search = {} } = to; + return (compile(pattern, { encode: encodeURIComponent })(params) + encodeSearchParameters(search)) as + | LocationPathname + | `${LocationPathname}?${LocationSearch}`; + } + + if ('name' in to) { + const { name, params = {}, search = {} } = to; + + switch (name) { + case 'audit-home': + return `/audit${encodeSearchParameters(search)}`; + + case 'audit-log': + return `/audit-log${encodeSearchParameters(search)}`; + + case 'marketplace': + return `/marketplace/${params.context}/${params.page}${encodeSearchParameters(search)}`; + + case 'upgrade': + return `/admin/upgrade/${params.type as UpgradeTabVariant}${encodeSearchParameters(search)}`; + } + + return (compile(name, { encode: encodeURIComponent })(params) + encodeSearchParameters(search)) as + | LocationPathname + | `${LocationPathname}?${LocationSearch}`; + } + + throw new Error('Invalid route'); +}; type RouterContextMockProps = { children?: ReactNode; - initialRoute?: RouteTuple; - pushRoute?: (...args: RouteTuple) => void; - replaceRoute?: (...args: RouteTuple) => void; + navigate?: (toOrDelta: number | To) => void; + currentPath?: MutableRefObject<string | undefined>; }; -const RouterContextMock = ({ children, initialRoute, pushRoute, replaceRoute }: RouterContextMockProps): ReactElement => { - const currentRoute = useSubscribableState(initialRoute ?? (['home'] as RouteTuple)); +const RouterContextMock = ({ children, navigate, currentPath }: RouterContextMockProps): ReactElement => { + const history = useRef<{ stack: To[]; index: number }>({ stack: ['/'], index: 0 }); + + if (currentPath) { + currentPath.current = buildRoutePath(history.current.stack[history.current.index]); + } return ( <RouterContext.Provider value={useMemo(() => { - const subscribeToCurrentRoute = currentRoute.subscribe; - const getCurrentRoute = currentRoute.get; - const setCurrentRoute = currentRoute.set; - return { - queryRoutePath: () => [() => (): void => undefined, (): undefined => undefined], - queryRouteUrl: () => [() => (): void => undefined, (): undefined => undefined], - pushRoute: ( - name: string, - parameters?: Record<string, string>, - queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>, - ) => { - const queryParams = typeof queryStringParameters === 'function' ? queryStringParameters({}) : queryStringParameters; - setCurrentRoute([name, parameters, queryParams]); - pushRoute?.(name, parameters, queryParams); - }, - replaceRoute: ( - name: string, - parameters?: Record<string, string>, - queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>, - ) => { - const queryParams = typeof queryStringParameters === 'function' ? queryStringParameters({}) : queryStringParameters; - setCurrentRoute([name, parameters, queryParams]); - replaceRoute?.(name, parameters, queryParams); - }, - queryRouteParameter: () => [() => (): void => undefined, (): undefined => undefined], - queryQueryStringParameter: () => [() => (): void => undefined, (): undefined => undefined], - queryCurrentRoute: () => [subscribeToCurrentRoute, getCurrentRoute], - setQueryString: () => undefined, - getRoutePath: () => '/', + subscribeToRouteChange: () => () => undefined, + getLocationPathname: () => '/', + getLocationSearch: () => '', + getRouteParameters: () => ({}), + getSearchParameters: () => ({}), + getRouteName: () => 'home', + buildRoutePath, + navigate: + navigate ?? + ((toOrDelta: number | To) => { + if (typeof toOrDelta === 'number') { + history.current.index += toOrDelta; + + if (history.current.index < 0) { + history.current.index = 0; + } + + if (history.current.index >= history.current.stack.length) { + history.current.index = history.current.stack.length - 1; + } + + return; + } + + history.current.stack = history.current.stack.slice(0, history.current.index + 1); + history.current.stack.push(toOrDelta); + history.current.index = history.current.stack.length - 1; + + if (currentPath) { + currentPath.current = buildRoutePath(history.current.stack[history.current.index]); + } + }), + defineRoutes: () => () => undefined, + getRoutes: () => [], + subscribeToRoutesChange: () => () => undefined, }; - }, [currentRoute.get, currentRoute.set, currentRoute.subscribe, pushRoute, replaceRoute])} + }, [currentPath, navigate])} > {children} </RouterContext.Provider> diff --git a/apps/meteor/tests/unit/client/views/notFound/NotFoundPage.spec.tsx b/apps/meteor/tests/unit/client/views/notFound/NotFoundPage.spec.tsx index 8e3c97f0a1645..cf08d8562bbb6 100644 --- a/apps/meteor/tests/unit/client/views/notFound/NotFoundPage.spec.tsx +++ b/apps/meteor/tests/unit/client/views/notFound/NotFoundPage.spec.tsx @@ -1,6 +1,7 @@ import { render, waitFor, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { expect, spy } from 'chai'; +import { expect } from 'chai'; +import type { MutableRefObject } from 'react'; import React from 'react'; import RouterContextMock from '../../../../mocks/client/RouterContextMock'; @@ -28,16 +29,18 @@ describe('views/notFound/NotFoundPage', () => { context('"Return to home" button', () => { context('when clicked', () => { it('should go back on history', async () => { - const pushRoute = spy(); + const currentPath: MutableRefObject<string | undefined> = { current: undefined }; + render( - <RouterContextMock pushRoute={pushRoute}> + <RouterContextMock currentPath={currentPath}> <NotFoundPage /> </RouterContextMock>, ); const button = screen.getByRole('button', { name: 'Homepage' }); userEvent.click(button); - await waitFor(() => expect(pushRoute).to.have.been.called.with('home')); + + await waitFor(() => expect(currentPath.current).to.be.equal('/home')); }); }); }); diff --git a/apps/meteor/tsconfig.json b/apps/meteor/tsconfig.json index 1adc6cac5adf4..e07772b269a9f 100644 --- a/apps/meteor/tsconfig.json +++ b/apps/meteor/tsconfig.json @@ -23,7 +23,7 @@ /* Support absolute /imports/* with a leading '/' */ "/*": ["*"], "meteor/*": [ - "node_modules/@types/meteor/*", + "../../node_modules/@types/meteor/*", ".meteor/local/types/packages.d.ts" ] }, diff --git a/packages/ui-contexts/.eslintrc.json b/packages/ui-contexts/.eslintrc.json index a83aeda48e66d..5b4e017cb8209 100644 --- a/packages/ui-contexts/.eslintrc.json +++ b/packages/ui-contexts/.eslintrc.json @@ -1,4 +1,4 @@ { - "extends": ["@rocket.chat/eslint-config"], + "extends": ["@rocket.chat/eslint-config", "plugin:react-hooks/recommended"], "ignorePatterns": ["**/dist"] } diff --git a/packages/ui-contexts/package.json b/packages/ui-contexts/package.json index ab5c03750ab45..2fac15a348580 100644 --- a/packages/ui-contexts/package.json +++ b/packages/ui-contexts/package.json @@ -12,6 +12,7 @@ "@types/react-dom": "~17.0.20", "@types/use-sync-external-store": "^0.0.3", "eslint": "~8.43.0", + "eslint-plugin-react-hooks": "^4.6.0", "jest": "~29.5.0", "mongodb": "^4.12.1", "react": "~17.0.2", diff --git a/packages/ui-contexts/src/RouterContext.ts b/packages/ui-contexts/src/RouterContext.ts index 697c41ef69954..3fbd4bd98cf21 100644 --- a/packages/ui-contexts/src/RouterContext.ts +++ b/packages/ui-contexts/src/RouterContext.ts @@ -1,60 +1,96 @@ +import type { ReactNode } from 'react'; import { createContext } from 'react'; -export type RouteName = string; +export interface IRouterPaths { + index: { + pattern: '/'; + pathname: '/'; + }; + home: { + pattern: '/home'; + pathname: '/home'; + }; +} + +export type LocationPathname = IRouterPaths[keyof IRouterPaths]['pathname']; +export type LocationSearch = string; export type RouteParameters = Record<string, string>; +export type SearchParameters = Record<string, string>; + +export type RouteName = keyof IRouterPaths; +export type RouterPathPattern = IRouterPaths[keyof IRouterPaths]['pattern']; -export type QueryStringParameters = Record<string, string>; +export type To = + | LocationPathname + | { + name: RouteName; + params?: RouteParameters; + search?: SearchParameters; + } + | { + pattern: RouterPathPattern; + params?: RouteParameters; + search?: SearchParameters; + } + | { + pathname: LocationPathname; + search?: SearchParameters; + }; -export type RouteGroupName = string; +type RelativeRoutingType = 'route' | 'path'; + +export type RouteObject = + | { + path: RouterPathPattern; + id: RouteName; + element: ReactNode; + } + | { + path: '*'; + id: 'not-found'; + element: ReactNode; + }; export type RouterContextValue = { - queryRoutePath: ( - name: RouteName, - parameters: RouteParameters | undefined, - queryStringParameters: QueryStringParameters | undefined, - ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined]; - queryRouteUrl: ( - name: RouteName, - parameters: RouteParameters | undefined, - queryStringParameters: QueryStringParameters | undefined, - ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined]; - pushRoute: ( - name: RouteName, - parameters: RouteParameters | undefined, - queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>, - ) => void; - replaceRoute: ( - name: RouteName, - parameters: RouteParameters | undefined, - queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>, - ) => void; - queryRouteParameter: (name: string) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined]; - queryQueryStringParameter: ( - name: string, - ) => [subscribe: (onStoreChange: () => void) => () => void, getSnapshot: () => string | undefined]; - queryCurrentRoute: () => [ - subscribe: (onStoreChange: () => void) => () => void, - getSnapshot: () => [RouteName?, RouteParameters?, QueryStringParameters?, RouteGroupName?], - ]; - setQueryString(parameters: Record<string, string | null>): void; - setQueryString(fn: (parameters: Record<string, string>) => Record<string, string>): void; - getRoutePath(nameOrPathDef: string, parameters?: Record<string, string>, queryStringParameters?: Record<string, string>): string; + subscribeToRouteChange(onRouteChange: () => void): () => void; + getLocationPathname(): LocationPathname; + getLocationSearch(): LocationSearch; + getRouteParameters(): RouteParameters; + getSearchParameters(): SearchParameters; + getRouteName(): RouteName | undefined; + buildRoutePath(to: To): LocationPathname | `${LocationPathname}?${LocationSearch}`; + navigate(to: To, options?: { replace?: boolean; state?: any; relative?: RelativeRoutingType }): void; + navigate(delta: number): void; + defineRoutes(routes: RouteObject[]): () => void; + getRoutes(): RouteObject[]; + subscribeToRoutesChange(onRoutesChange: () => void): () => void; }; export const RouterContext = createContext<RouterContextValue>({ - queryRoutePath: () => [() => (): void => undefined, (): undefined => undefined], - queryRouteUrl: () => [() => (): void => undefined, (): undefined => undefined], - pushRoute: () => undefined, - replaceRoute: () => undefined, - queryRouteParameter: () => [() => (): void => undefined, (): undefined => undefined], - queryQueryStringParameter: () => [() => (): void => undefined, (): undefined => undefined], - queryCurrentRoute: () => [ - () => (): void => undefined, - (): [undefined, RouteParameters, QueryStringParameters, undefined] => [undefined, {}, {}, undefined], - ], - setQueryString: () => undefined, - getRoutePath: () => { + subscribeToRouteChange: () => () => undefined, + getLocationPathname: () => { + throw new Error('not implemented'); + }, + getRouteParameters: () => { + throw new Error('not implemented'); + }, + getLocationSearch: () => { + throw new Error('not implemented'); + }, + getSearchParameters: () => { + throw new Error('not implemented'); + }, + getRouteName: () => { + throw new Error('not implemented'); + }, + buildRoutePath: () => { + throw new Error('not implemented'); + }, + navigate: () => undefined, + defineRoutes: () => () => undefined, + getRoutes: () => { throw new Error('not implemented'); }, + subscribeToRoutesChange: () => () => undefined, }); diff --git a/packages/ui-contexts/src/hooks/useCurrentRoute.ts b/packages/ui-contexts/src/hooks/useCurrentRoute.ts deleted file mode 100644 index 99ea13c66b912..0000000000000 --- a/packages/ui-contexts/src/hooks/useCurrentRoute.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext, useMemo } from 'react'; -import { useSyncExternalStore } from 'use-sync-external-store/shim'; - -import type { QueryStringParameters, RouteGroupName, RouteName, RouteParameters } from '../RouterContext'; -import { RouterContext } from '../RouterContext'; - -export const useCurrentRoute = (): [RouteName?, RouteParameters?, QueryStringParameters?, RouteGroupName?] => { - const { queryCurrentRoute } = useContext(RouterContext); - - const [subscribe, getSnapshot] = useMemo(() => queryCurrentRoute(), [queryCurrentRoute]); - return useSyncExternalStore(subscribe, getSnapshot); -}; diff --git a/packages/ui-contexts/src/hooks/useCurrentRoutePath.ts b/packages/ui-contexts/src/hooks/useCurrentRoutePath.ts new file mode 100644 index 0000000000000..dd7ba683bea14 --- /dev/null +++ b/packages/ui-contexts/src/hooks/useCurrentRoutePath.ts @@ -0,0 +1,21 @@ +import { useCallback, useContext } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; + +import { RouterContext } from '../RouterContext'; + +export const useCurrentRoutePath = () => { + const router = useContext(RouterContext); + + const getSnapshot = useCallback(() => { + const name = router.getRouteName(); + return name + ? router.buildRoutePath({ + name, + params: router.getRouteParameters(), + search: router.getSearchParameters(), + }) + : undefined; + }, [router]); + + return useSyncExternalStore(router.subscribeToRouteChange, getSnapshot); +}; diff --git a/packages/ui-contexts/src/hooks/useLogout.ts b/packages/ui-contexts/src/hooks/useLogout.ts index 9e67bbfc3a863..fbbe758d48c45 100644 --- a/packages/ui-contexts/src/hooks/useLogout.ts +++ b/packages/ui-contexts/src/hooks/useLogout.ts @@ -1,16 +1,16 @@ import { useContext } from 'react'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; -import { useRoute } from './useRoute'; import { UserContext } from '../UserContext'; +import { useRouter } from './useRouter'; export const useLogout = (): (() => void) => { - const router = useRoute('home'); + const router = useRouter(); const { logout } = useContext(UserContext); const handleLogout = useMutableCallback(() => { logout(); - router.push({}); + router.navigate('/'); }); return handleLogout; diff --git a/packages/ui-contexts/src/hooks/useQueryStringParameter.ts b/packages/ui-contexts/src/hooks/useQueryStringParameter.ts deleted file mode 100644 index cc7a50dfad704..0000000000000 --- a/packages/ui-contexts/src/hooks/useQueryStringParameter.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext, useMemo } from 'react'; -import { useSyncExternalStore } from 'use-sync-external-store/shim'; - -import { RouterContext } from '../RouterContext'; - -export const useQueryStringParameter = (name: string): string | undefined => { - const { queryQueryStringParameter } = useContext(RouterContext); - - const [subscribe, getSnapshot] = useMemo(() => queryQueryStringParameter(name), [queryQueryStringParameter, name]); - - return useSyncExternalStore(subscribe, getSnapshot); -}; diff --git a/packages/ui-contexts/src/hooks/useRoute.ts b/packages/ui-contexts/src/hooks/useRoute.ts index 9123bd01f5c05..50fbbc3ca769d 100644 --- a/packages/ui-contexts/src/hooks/useRoute.ts +++ b/packages/ui-contexts/src/hooks/useRoute.ts @@ -1,11 +1,9 @@ import { useContext, useMemo } from 'react'; -import type { QueryStringParameters, RouteParameters } from '../RouterContext'; +import type { RouteName, RouteParameters } from '../RouterContext'; import { RouterContext } from '../RouterContext'; type Route = { - getPath: (parameters?: RouteParameters, queryStringParameters?: QueryStringParameters) => string | undefined; - getUrl: (parameters?: RouteParameters, queryStringParameters?: QueryStringParameters) => string | undefined; push: ( parameters?: RouteParameters, queryStringParameters?: ((prev: Record<string, string>) => Record<string, string>) | Record<string, string>, @@ -16,16 +14,23 @@ type Route = { ) => void; }; -export const useRoute = (name: string): Route => { - const { queryRoutePath, queryRouteUrl, pushRoute, replaceRoute } = useContext(RouterContext); +/** @deprecated prefer `useRouter` */ +export const useRoute = (name: RouteName): Route => { + const router = useContext(RouterContext); return useMemo<Route>( () => ({ - getPath: (parameters, queryStringParameters) => queryRoutePath(name, parameters, queryStringParameters)[1](), - getUrl: (parameters, queryStringParameters) => queryRouteUrl(name, parameters, queryStringParameters)[1](), - push: (parameters, queryStringParameters) => pushRoute(name, parameters, queryStringParameters), - replace: (parameters, queryStringParameters) => replaceRoute(name, parameters, queryStringParameters), + push: (params, queryStringParameters) => { + const search = + typeof queryStringParameters === 'function' ? queryStringParameters(router.getSearchParameters()) : queryStringParameters; + router.navigate({ name, params, search }, { replace: false }); + }, + replace: (params, queryStringParameters) => { + const search = + typeof queryStringParameters === 'function' ? queryStringParameters(router.getSearchParameters()) : queryStringParameters; + router.navigate({ name, params, search }, { replace: true }); + }, }), - [queryRoutePath, queryRouteUrl, name, pushRoute, replaceRoute], + [name, router], ); }; diff --git a/packages/ui-contexts/src/hooks/useRouteParameter.ts b/packages/ui-contexts/src/hooks/useRouteParameter.ts index eaae7e45fd060..3990c516246a0 100644 --- a/packages/ui-contexts/src/hooks/useRouteParameter.ts +++ b/packages/ui-contexts/src/hooks/useRouteParameter.ts @@ -1,12 +1,14 @@ -import { useContext, useMemo } from 'react'; +import { useCallback, useContext } from 'react'; import { useSyncExternalStore } from 'use-sync-external-store/shim'; import { RouterContext } from '../RouterContext'; export const useRouteParameter = (name: string): string | undefined => { - const { queryRouteParameter } = useContext(RouterContext); + const router = useContext(RouterContext); - const [subscribe, getSnapshot] = useMemo(() => queryRouteParameter(name), [queryRouteParameter, name]); + const getSnapshot = useCallback(() => { + return router.getRouteParameters()[name]; + }, [router, name]); - return useSyncExternalStore(subscribe, getSnapshot); + return useSyncExternalStore(router.subscribeToRouteChange, getSnapshot); }; diff --git a/packages/ui-contexts/src/hooks/useRoutePath.ts b/packages/ui-contexts/src/hooks/useRoutePath.ts deleted file mode 100644 index 2bbe1ce45d225..0000000000000 --- a/packages/ui-contexts/src/hooks/useRoutePath.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useContext, useMemo } from 'react'; -import { useSyncExternalStore } from 'use-sync-external-store/shim'; - -import type { QueryStringParameters, RouteParameters } from '../RouterContext'; -import { RouterContext } from '../RouterContext'; - -export const useRoutePath = ( - name: string, - parameters?: RouteParameters, - queryStringParameters?: QueryStringParameters, -): string | undefined => { - const { queryRoutePath } = useContext(RouterContext); - - const [subscribe, getSnapshot] = useMemo( - () => queryRoutePath(name, parameters, queryStringParameters), - [queryRoutePath, name, parameters, queryStringParameters], - ); - - return useSyncExternalStore(subscribe, getSnapshot); -}; diff --git a/packages/ui-contexts/src/hooks/useRouteUrl.ts b/packages/ui-contexts/src/hooks/useRouteUrl.ts deleted file mode 100644 index f067f776ddf6b..0000000000000 --- a/packages/ui-contexts/src/hooks/useRouteUrl.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { useContext, useMemo } from 'react'; -import { useSyncExternalStore } from 'use-sync-external-store/shim'; - -import type { QueryStringParameters, RouteParameters } from '../RouterContext'; -import { RouterContext } from '../RouterContext'; - -export const useRouteUrl = ( - name: string, - parameters?: RouteParameters, - queryStringParameters?: QueryStringParameters, -): string | undefined => { - const { queryRouteUrl } = useContext(RouterContext); - - const [subscribe, getSnapshot] = useMemo( - () => queryRouteUrl(name, parameters, queryStringParameters), - [queryRouteUrl, name, parameters, queryStringParameters], - ); - - return useSyncExternalStore(subscribe, getSnapshot); -}; diff --git a/packages/ui-contexts/src/hooks/useRouter.ts b/packages/ui-contexts/src/hooks/useRouter.ts new file mode 100644 index 0000000000000..314e7682bbf89 --- /dev/null +++ b/packages/ui-contexts/src/hooks/useRouter.ts @@ -0,0 +1,5 @@ +import { useContext } from 'react'; + +import { RouterContext } from '../RouterContext'; + +export const useRouter = () => useContext(RouterContext); diff --git a/packages/ui-contexts/src/hooks/useSearchParameter.ts b/packages/ui-contexts/src/hooks/useSearchParameter.ts new file mode 100644 index 0000000000000..98740b2491753 --- /dev/null +++ b/packages/ui-contexts/src/hooks/useSearchParameter.ts @@ -0,0 +1,15 @@ +import { useCallback, useContext } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; + +import { RouterContext } from '../RouterContext'; + +export const useSearchParameter = (name: string): string | undefined => { + const { getSearchParameters, subscribeToRouteChange } = useContext(RouterContext); + + const getSnapshot = useCallback(() => { + const searchParameters = getSearchParameters(); + return searchParameters[name]; + }, [getSearchParameters]); + + return useSyncExternalStore(subscribeToRouteChange, getSnapshot); +}; diff --git a/packages/ui-contexts/src/hooks/useSearchParameters.ts b/packages/ui-contexts/src/hooks/useSearchParameters.ts new file mode 100644 index 0000000000000..385ddce2529af --- /dev/null +++ b/packages/ui-contexts/src/hooks/useSearchParameters.ts @@ -0,0 +1,9 @@ +import { useContext } from 'react'; +import { useSyncExternalStore } from 'use-sync-external-store/shim'; + +import { RouterContext } from '../RouterContext'; + +export const useSearchParameters = () => { + const { getSearchParameters, subscribeToRouteChange } = useContext(RouterContext); + return useSyncExternalStore(subscribeToRouteChange, getSearchParameters); +}; diff --git a/packages/ui-contexts/src/index.ts b/packages/ui-contexts/src/index.ts index 7780e6fa7fa16..ec981344ed292 100644 --- a/packages/ui-contexts/src/index.ts +++ b/packages/ui-contexts/src/index.ts @@ -5,7 +5,7 @@ export { ConnectionStatusContext, ConnectionStatusContextValue } from './Connect export { CustomSoundContext, CustomSoundContextValue } from './CustomSoundContext'; export { LayoutContext, LayoutContextValue } from './LayoutContext'; export { ModalContext, ModalContextValue } from './ModalContext'; -export { RouterContext, RouterContextValue } from './RouterContext'; +export * from './RouterContext'; export { ServerContext, ServerContextValue } from './ServerContext'; export { SessionContext, SessionContextValue } from './SessionContext'; export { SettingsContext, SettingsContextValue, SettingsContextQuery } from './SettingsContext'; @@ -25,7 +25,7 @@ export { useAttachmentDimensions } from './hooks/useAttachmentDimensions'; export { useAttachmentIsCollapsedByDefault } from './hooks/useAttachmentIsCollapsedByDefault'; export { useConnectionStatus } from './hooks/useConnectionStatus'; export { useCurrentModal } from './hooks/useCurrentModal'; -export { useCurrentRoute } from './hooks/useCurrentRoute'; +export { useCurrentRoutePath } from './hooks/useCurrentRoutePath'; export { useCustomSound } from './hooks/useCustomSound'; export { useEndpoint } from './hooks/useEndpoint'; export type { EndpointFunction } from './hooks/useEndpoint'; @@ -48,14 +48,14 @@ export { useMethod } from './hooks/useMethod'; export { useModal } from './hooks/useModal'; export { usePermission } from './hooks/usePermission'; export { usePermissionWithScopedRoles } from './hooks/usePermissionWithScopedRoles'; -export { useQueryStringParameter } from './hooks/useQueryStringParameter'; export { useRole } from './hooks/useRole'; export { useRolesDescription } from './hooks/useRolesDescription'; export { useRoomAvatarPath } from './hooks/useRoomAvatarPath'; +export { useRouter } from './hooks/useRouter'; export { useRoute } from './hooks/useRoute'; export { useRouteParameter } from './hooks/useRouteParameter'; -export { useRoutePath } from './hooks/useRoutePath'; -export { useRouteUrl } from './hooks/useRouteUrl'; +export { useSearchParameter } from './hooks/useSearchParameter'; +export { useSearchParameters } from './hooks/useSearchParameters'; export { useServerInformation } from './hooks/useServerInformation'; export { useSession } from './hooks/useSession'; export { useSessionDispatch } from './hooks/useSessionDispatch'; diff --git a/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx b/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx index c392f1376ac8b..4cbe78cb3f769 100644 --- a/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx +++ b/packages/web-ui-registration/src/ResetPassword/ResetPasswordPage.tsx @@ -3,8 +3,8 @@ import type { TranslationKey } from '@rocket.chat/ui-contexts'; import { useSetting, useVerifyPassword, + useRouter, useRouteParameter, - useRoute, useUser, useMethod, useTranslation, @@ -31,7 +31,7 @@ const ResetPasswordPage = (): ReactElement => { const requiresPasswordConfirmation = useSetting('Accounts_RequirePasswordConfirmation'); - const homeRouter = useRoute('home'); + const router = useRouter(); const changePasswordReason = getChangePasswordReason(user || {}); @@ -58,7 +58,7 @@ const ResetPasswordPage = (): ReactElement => { if (token) { const result = await resetPassword(token, data.password); await loginWithToken(result.token); - homeRouter.push({}); + router.navigate('/home'); } else { await setUserPassword(data.password); } diff --git a/yarn.lock b/yarn.lock index 8cc8eada135cb..3ffbb18d891f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10886,6 +10886,7 @@ __metadata: "@types/react-dom": ~17.0.20 "@types/use-sync-external-store": ^0.0.3 eslint: ~8.43.0 + eslint-plugin-react-hooks: ^4.6.0 jest: ~29.5.0 mongodb: ^4.12.1 react: ~17.0.2 From b837cb9f2a00979934861818e3f07fe357dc9b70 Mon Sep 17 00:00:00 2001 From: Kevin Aleman <kaleman960@gmail.com> Date: Wed, 5 Jul 2023 13:52:08 -0600 Subject: [PATCH 51/79] fix: Remove the association between BH and disabled/archived departments (#29543) Co-authored-by: Murtaza Patrawala <34130764+murtaza98@users.noreply.github.com> Co-authored-by: Guilherme Gazzo <5263975+ggazzo@users.noreply.github.com> --- .changeset/funny-coins-trade.md | 6 + .changeset/rotten-spoons-teach.md | 6 + .../imports/server/rest/departments.ts | 6 +- .../business-hour/AbstractBusinessHour.ts | 4 +- .../business-hour/BusinessHourManager.ts | 60 ++- .../livechat/server/business-hour/Single.ts | 8 + .../app/livechat/server/lib/Livechat.js | 8 +- .../AutoCompleteDepartmentMultiple.tsx | 6 +- .../server/business-hour/Helper.ts | 18 +- .../server/business-hour/Multiple.ts | 104 +++- .../server/business-hour/lib/business-hour.ts | 19 +- .../server/lib/LivechatEnterprise.ts | 8 +- .../app/livechat-enterprise/server/startup.ts | 2 +- .../additionalForms/BusinessHoursMultiple.js | 2 +- apps/meteor/lib/callbacks.ts | 2 + .../models/raw/LivechatBusinessHours.ts | 4 + .../server/models/raw/LivechatDepartment.ts | 78 ++- .../models/raw/LivechatDepartmentAgents.ts | 10 +- .../tests/data/livechat/business-hours.ts | 12 - .../tests/data/livechat/businessHours.ts | 76 ++- apps/meteor/tests/data/livechat/department.ts | 68 ++- apps/meteor/tests/data/livechat/rooms.ts | 16 - .../end-to-end/api/livechat/10-departments.ts | 3 +- .../api/livechat/19-business-hours.ts | 466 +++++++++++++++++- .../src/models/ILivechatBusinessHoursModel.ts | 2 + .../models/ILivechatDepartmentAgentsModel.ts | 2 + .../src/models/ILivechatDepartmentModel.ts | 9 + 27 files changed, 908 insertions(+), 97 deletions(-) create mode 100644 .changeset/funny-coins-trade.md create mode 100644 .changeset/rotten-spoons-teach.md delete mode 100644 apps/meteor/tests/data/livechat/business-hours.ts diff --git a/.changeset/funny-coins-trade.md b/.changeset/funny-coins-trade.md new file mode 100644 index 0000000000000..e7fffad5d960e --- /dev/null +++ b/.changeset/funny-coins-trade.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +Fixed a problem where disabled department agent's where still being activated when applicable business hours met. diff --git a/.changeset/rotten-spoons-teach.md b/.changeset/rotten-spoons-teach.md new file mode 100644 index 0000000000000..bd046064eb130 --- /dev/null +++ b/.changeset/rotten-spoons-teach.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +Fixed logic around Default Business Hours where agents from disabled/archived departments where being omitted from processing at closing time diff --git a/apps/meteor/app/livechat/imports/server/rest/departments.ts b/apps/meteor/app/livechat/imports/server/rest/departments.ts index f05b8026bf530..4f7886f837929 100644 --- a/apps/meteor/app/livechat/imports/server/rest/departments.ts +++ b/apps/meteor/app/livechat/imports/server/rest/departments.ts @@ -192,11 +192,9 @@ API.v1.addRoute( }, { async post() { - if (await Livechat.archiveDepartment(this.urlParams._id)) { - return API.v1.success(); - } + await Livechat.archiveDepartment(this.urlParams._id); - return API.v1.failure(); + return API.v1.success(); }, }, ); diff --git a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts index 80cdc5aea2961..aeaf6bc1f2879 100644 --- a/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts +++ b/apps/meteor/app/livechat/server/business-hour/AbstractBusinessHour.ts @@ -16,7 +16,9 @@ export interface IBusinessHourBehavior { onDisableBusinessHours(): Promise<void>; onAddAgentToDepartment(options?: { departmentId: string; agentsId: string[] }): Promise<any>; onRemoveAgentFromDepartment(options?: Record<string, any>): Promise<any>; - onRemoveDepartment(department?: ILivechatDepartment): Promise<any>; + onRemoveDepartment(options: { department: ILivechatDepartment; agentsIds: string[] }): Promise<any>; + onDepartmentDisabled(department?: ILivechatDepartment): Promise<any>; + onDepartmentArchived(department: Pick<ILivechatDepartment, '_id'>): Promise<void>; onStartBusinessHours(): Promise<void>; afterSaveBusinessHours(businessHourData: ILivechatBusinessHour): Promise<void>; allowAgentChangeServiceStatus(agentId: string): Promise<boolean>; diff --git a/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts b/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts index 7e72a17563586..6b14f82856a02 100644 --- a/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts +++ b/apps/meteor/app/livechat/server/business-hour/BusinessHourManager.ts @@ -2,11 +2,12 @@ import moment from 'moment'; import { LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; import type { ILivechatBusinessHour } from '@rocket.chat/core-typings'; import type { AgendaCronJobs } from '@rocket.chat/cron'; -import { Users } from '@rocket.chat/models'; +import { LivechatDepartment, Users } from '@rocket.chat/models'; import type { IBusinessHourBehavior, IBusinessHourType } from './AbstractBusinessHour'; import { settings } from '../../../settings/server'; import { callbacks } from '../../../../lib/callbacks'; +import { closeBusinessHour } from '../../../../ee/app/livechat-enterprise/server/business-hour/Helper'; import { businessHourLogger } from '../lib/logger'; export class BusinessHourManager { @@ -28,6 +29,7 @@ export class BusinessHourManager { await this.createCronJobsForWorkHours(); businessHourLogger.debug('Cron jobs created, setting up callbacks'); this.setupCallbacks(); + await this.cleanupDisabledDepartmentReferences(); await this.behavior.onStartBusinessHours(); } @@ -38,6 +40,40 @@ export class BusinessHourManager { await this.behavior.onDisableBusinessHours(); } + async restartManager(): Promise<void> { + await this.stopManager(); + await this.startManager(); + } + + async cleanupDisabledDepartmentReferences(): Promise<void> { + // Get business hours with departments enabled and disabled + const bhWithDepartments = await LivechatDepartment.getBusinessHoursWithDepartmentStatuses(); + + if (!bhWithDepartments.length) { + // If there are no bh, skip + return; + } + + for await (const { _id: businessHourId, validDepartments, invalidDepartments } of bhWithDepartments) { + if (!invalidDepartments.length) { + continue; + } + + // If there are no enabled departments, close the business hour + const allDepsAreDisabled = validDepartments.length === 0 && invalidDepartments.length > 0; + if (allDepsAreDisabled) { + const businessHour = await this.getBusinessHour(businessHourId, LivechatBusinessHourTypes.CUSTOM); + if (!businessHour) { + continue; + } + await closeBusinessHour(businessHour); + } + + // Remove business hour from disabled departments + await LivechatDepartment.removeBusinessHourFromDepartmentsByIdsAndBusinessHourId(invalidDepartments, businessHourId); + } + } + async allowAgentChangeServiceStatus(agentId: string): Promise<boolean> { if (!settings.get('Livechat_enable_business_hours')) { return true; @@ -88,6 +124,14 @@ export class BusinessHourManager { return Users.setLivechatStatusActiveBasedOnBusinessHours(agentId); } + async restartCronJobsIfNecessary(): Promise<void> { + if (!settings.get('Livechat_enable_business_hours')) { + return; + } + + await this.createCronJobsForWorkHours(); + } + private setupCallbacks(): void { callbacks.add( 'livechat.removeAgentDepartment', @@ -107,6 +151,18 @@ export class BusinessHourManager { callbacks.priority.HIGH, 'business-hour-livechat-on-save-agent-department', ); + callbacks.add( + 'livechat.afterDepartmentDisabled', + this.behavior.onDepartmentDisabled.bind(this), + callbacks.priority.HIGH, + 'business-hour-livechat-on-department-disabled', + ); + callbacks.add( + 'livechat.afterDepartmentArchived', + this.behavior.onDepartmentArchived.bind(this), + callbacks.priority.HIGH, + 'business-hour-livechat-on-department-archived', + ); callbacks.add( 'livechat.onNewAgentCreated', this.behavior.onNewAgentCreated.bind(this), @@ -119,6 +175,8 @@ export class BusinessHourManager { callbacks.remove('livechat.removeAgentDepartment', 'business-hour-livechat-on-remove-agent-department'); callbacks.remove('livechat.afterRemoveDepartment', 'business-hour-livechat-after-remove-department'); callbacks.remove('livechat.saveAgentDepartment', 'business-hour-livechat-on-save-agent-department'); + callbacks.remove('livechat.afterDepartmentDisabled', 'business-hour-livechat-on-department-disabled'); + callbacks.remove('livechat.afterDepartmentArchived', 'business-hour-livechat-on-department-archived'); callbacks.remove('livechat.onNewAgentCreated', 'business-hour-livechat-on-agent-created'); } diff --git a/apps/meteor/app/livechat/server/business-hour/Single.ts b/apps/meteor/app/livechat/server/business-hour/Single.ts index 2996d5f1c2c74..b351c480ab28b 100644 --- a/apps/meteor/app/livechat/server/business-hour/Single.ts +++ b/apps/meteor/app/livechat/server/business-hour/Single.ts @@ -49,4 +49,12 @@ export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior imp onRemoveDepartment(): Promise<void> { return Promise.resolve(); } + + onDepartmentDisabled(): Promise<void> { + return Promise.resolve(); + } + + onDepartmentArchived(): Promise<void> { + return Promise.resolve(); + } } diff --git a/apps/meteor/app/livechat/server/lib/Livechat.js b/apps/meteor/app/livechat/server/lib/Livechat.js index 1fcf39c30a319..950e8450df4b6 100644 --- a/apps/meteor/app/livechat/server/lib/Livechat.js +++ b/apps/meteor/app/livechat/server/lib/Livechat.js @@ -740,6 +740,8 @@ export const Livechat = { }); } + // TODO: these kind of actions should be on events instead of here + await LivechatDepartmentAgents.enableAgentsByDepartmentId(_id); return LivechatDepartmentRaw.unarchiveDepartment(_id); }, @@ -754,7 +756,11 @@ export const Livechat = { }); } - return LivechatDepartmentRaw.archiveDepartment(_id); + await LivechatDepartmentAgents.disableAgentsByDepartmentId(_id); + await LivechatDepartmentRaw.archiveDepartment(_id); + + this.logger.debug({ msg: 'Running livechat.afterDepartmentArchived callback for department:', departmentId: _id }); + await callbacks.run('livechat.afterDepartmentArchived', department); }, showConnecting() { diff --git a/apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx b/apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx index ea08b18ca6097..50d53da351bc2 100644 --- a/apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx +++ b/apps/meteor/client/components/AutoCompleteDepartmentMultiple.tsx @@ -13,12 +13,14 @@ type AutoCompleteDepartmentMultipleProps = { onChange: (value: PaginatedMultiSelectOption[]) => void; onlyMyDepartments?: boolean; showArchived?: boolean; + enabled?: boolean; }; const AutoCompleteDepartmentMultiple = ({ value, onlyMyDepartments = false, showArchived = false, + enabled = false, onChange = () => undefined, }: AutoCompleteDepartmentMultipleProps) => { const t = useTranslation(); @@ -28,8 +30,8 @@ const AutoCompleteDepartmentMultiple = ({ const { itemsList: departmentsList, loadMoreItems: loadMoreDepartments } = useDepartmentsList( useMemo( - () => ({ filter: debouncedDepartmentsFilter, onlyMyDepartments, ...(showArchived && { showArchived: true }) }), - [debouncedDepartmentsFilter, onlyMyDepartments, showArchived], + () => ({ filter: debouncedDepartmentsFilter, onlyMyDepartments, ...(showArchived && { showArchived: true }), enabled }), + [debouncedDepartmentsFilter, enabled, onlyMyDepartments, showArchived], ), ); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts index 6575f13bbde2d..b51294287d55a 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts @@ -7,9 +7,12 @@ import { isEnterprise } from '../../../license/server/license'; import { businessHourLogger } from '../../../../../app/livechat/server/lib/logger'; const getAllAgentIdsWithoutDepartment = async (): Promise<string[]> => { - const agentIdsWithDepartment = ( - await LivechatDepartmentAgents.find({ departmentEnabled: true }, { projection: { agentId: 1 } }).toArray() - ).map((dept) => dept.agentId); + // Fetch departments with agents excluding archived ones (disabled ones still can be tied to business hours) + // Then find the agents that are not in any of those departments + + const departmentIds = (await LivechatDepartment.findNotArchived({ projection: { _id: 1 } }).toArray()).map(({ _id }) => _id); + + const agentIdsWithDepartment = await LivechatDepartmentAgents.findAllAgentsConnectedToListOfDepartments(departmentIds); const agentIdsWithoutDepartment = ( await Users.findUsersInRolesWithQuery( @@ -62,7 +65,10 @@ const getAgentIdsToHandle = async (businessHour: Pick<ILivechatBusinessHour, '_i ).map((dept) => dept.agentId); }; -export const openBusinessHour = async (businessHour: Pick<ILivechatBusinessHour, '_id' | 'type'>): Promise<void> => { +export const openBusinessHour = async ( + businessHour: Pick<ILivechatBusinessHour, '_id' | 'type'>, + updateLivechatStatus = true, +): Promise<void> => { const agentIds = await getAgentIdsToHandle(businessHour); businessHourLogger.debug({ msg: 'Opening business hour', @@ -72,7 +78,9 @@ export const openBusinessHour = async (businessHour: Pick<ILivechatBusinessHour, }); await Users.addBusinessHourByAgentIds(agentIds, businessHour._id); - await Users.updateLivechatStatusBasedOnBusinessHours(); + if (updateLivechatStatus) { + await Users.updateLivechatStatusBasedOnBusinessHours(); + } }; export const closeBusinessHour = async (businessHour: Pick<ILivechatBusinessHour, '_id' | 'type'>): Promise<void> => { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts index 0fa768453e3ad..3e1eb7aea652c 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Multiple.ts @@ -1,12 +1,14 @@ import moment from 'moment'; import type { ILivechatDepartment, ILivechatBusinessHour } from '@rocket.chat/core-typings'; -import { LivechatDepartment, LivechatDepartmentAgents } from '@rocket.chat/models'; +import { LivechatDepartment, LivechatDepartmentAgents, Users } from '@rocket.chat/models'; import type { IBusinessHourBehavior } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; import { AbstractBusinessHourBehavior } from '../../../../../app/livechat/server/business-hour/AbstractBusinessHour'; import { filterBusinessHoursThatMustBeOpened } from '../../../../../app/livechat/server/business-hour/Helper'; import { closeBusinessHour, openBusinessHour, removeBusinessHourByAgentIds } from './Helper'; -import { businessHourLogger } from '../../../../../app/livechat/server/lib/logger'; +import { bhLogger } from '../lib/logger'; +import { settings } from '../../../../../app/settings/server'; +import { businessHourManager } from '../../../../../app/livechat/server/business-hour'; interface IBusinessHoursExtraProperties extends ILivechatBusinessHour { timezoneName: string; @@ -19,6 +21,8 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior this.onAddAgentToDepartment = this.onAddAgentToDepartment.bind(this); this.onRemoveAgentFromDepartment = this.onRemoveAgentFromDepartment.bind(this); this.onRemoveDepartment = this.onRemoveDepartment.bind(this); + this.onDepartmentArchived = this.onDepartmentArchived.bind(this); + this.onDepartmentDisabled = this.onDepartmentDisabled.bind(this); this.onNewAgentCreated = this.onNewAgentCreated.bind(this); } @@ -36,7 +40,7 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior }, }); const businessHoursToOpen = await filterBusinessHoursThatMustBeOpened(activeBusinessHours); - businessHourLogger.debug({ + bhLogger.debug({ msg: 'Starting Multiple Business Hours', totalBusinessHoursToOpen: businessHoursToOpen.length, top10BusinessHoursToOpen: businessHoursToOpen.slice(0, 10), @@ -125,13 +129,100 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior return this.handleRemoveAgentsFromDepartments(department, agentsId, options); } - async onRemoveDepartment(options: Record<string, any> = {}): Promise<any> { + async onRemoveDepartment(options: { department: ILivechatDepartment; agentsIds: string[] }): Promise<any> { + bhLogger.debug(`onRemoveDepartment: department ${options.department._id} removed`); const { department, agentsIds } = options; if (!department || !agentsIds?.length) { return options; } - const deletedDepartment = LivechatDepartment.trashFindOneById(department._id); - return this.handleRemoveAgentsFromDepartments(deletedDepartment, agentsIds, options); + return this.onDepartmentDisabled(department); + } + + async onDepartmentDisabled(department: ILivechatDepartment): Promise<void> { + if (!department.businessHourId) { + bhLogger.debug({ + msg: 'onDepartmentDisabled: department has no business hour', + departmentId: department._id, + }); + return; + } + + // Get business hour + let businessHour = await this.BusinessHourRepository.findOneById(department.businessHourId); + if (!businessHour) { + bhLogger.error({ + msg: 'onDepartmentDisabled: business hour not found', + businessHourId: department.businessHourId, + }); + return; + } + + // Unlink business hour from department + await LivechatDepartment.removeBusinessHourFromDepartmentsByIdsAndBusinessHourId([department._id], businessHour._id); + + // cleanup user's cache for default business hour and this business hour + const defaultBH = await this.BusinessHourRepository.findOneDefaultBusinessHour(); + if (!defaultBH) { + bhLogger.error('onDepartmentDisabled: default business hour not found'); + throw new Error('Default business hour not found'); + } + await this.UsersRepository.closeAgentsBusinessHoursByBusinessHourIds([businessHour._id, defaultBH._id]); + + // If i'm the only one, disable the business hour + const imTheOnlyOne = !(await LivechatDepartment.countByBusinessHourIdExcludingDepartmentId(businessHour._id, department._id)); + if (imTheOnlyOne) { + bhLogger.warn({ + msg: 'onDepartmentDisabled: department is the only one on business hour, disabling it', + departmentId: department._id, + businessHourId: businessHour._id, + }); + await this.BusinessHourRepository.disableBusinessHour(businessHour._id); + + businessHour = await this.BusinessHourRepository.findOneById(department.businessHourId); + if (!businessHour) { + bhLogger.error({ + msg: 'onDepartmentDisabled: business hour not found', + businessHourId: department.businessHourId, + }); + + throw new Error(`Business hour ${department.businessHourId} not found`); + } + } + + // start default business hour and this BH if needed + if (!settings.get('Livechat_enable_business_hours')) { + bhLogger.debug(`onDepartmentDisabled: business hours are disabled. skipping`); + return; + } + const businessHourToOpen = await filterBusinessHoursThatMustBeOpened([businessHour, defaultBH]); + for await (const bh of businessHourToOpen) { + bhLogger.debug({ + msg: 'onDepartmentDisabled: opening business hour', + businessHourId: bh._id, + }); + await openBusinessHour(bh, false); + } + + await Users.updateLivechatStatusBasedOnBusinessHours(); + + await businessHourManager.restartCronJobsIfNecessary(); + + bhLogger.debug({ + msg: 'onDepartmentDisabled: successfully processed department disabled event', + departmentId: department._id, + }); + } + + async onDepartmentArchived(department: Pick<ILivechatDepartment, '_id'>): Promise<void> { + bhLogger.debug('Processing department archived event on multiple business hours', department); + const dbDepartment = await LivechatDepartment.findOneById(department._id, { projection: { businessHourId: 1, _id: 1 } }); + + if (!dbDepartment) { + bhLogger.error(`No department found with id: ${department._id} when archiving it`); + return; + } + + return this.onDepartmentDisabled(dbDepartment); } allowAgentChangeServiceStatus(agentId: string): Promise<boolean> { @@ -147,7 +238,6 @@ export class MultipleBusinessHoursBehavior extends AbstractBusinessHourBehavior } // TODO: We're doing a full fledged aggregation with lookups and getting the whole array just for getting the length? :( if (!(await LivechatDepartmentAgents.findAgentsByAgentIdAndBusinessHourId(agentId, department.businessHourId)).length) { - // eslint-disable-line no-await-in-loop agentIdsToRemoveCurrentBusinessHour.push(agentId); } } diff --git a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/lib/business-hour.ts b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/lib/business-hour.ts index 216a345a7ee3f..f82eb65f66244 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/lib/business-hour.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/lib/business-hour.ts @@ -1,6 +1,6 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import type { ILivechatBusinessHour } from '@rocket.chat/core-typings'; -import { LivechatBusinessHours } from '@rocket.chat/models'; +import { LivechatBusinessHours, LivechatDepartment } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../../../../app/authorization/server/functions/hasPermission'; import type { IPaginatedResponse, IPagination } from '../../api/lib/definition'; @@ -26,8 +26,23 @@ export async function findBusinessHours(userId: string, { offset, count, sort }: const [businessHours, total] = await Promise.all([cursor.toArray(), totalCount]); + // add departments to businessHours + const businessHoursWithDepartments = await Promise.all( + businessHours.map(async (businessHour) => { + const currentDepartments = await LivechatDepartment.findByBusinessHourId(businessHour._id, { + projection: { _id: 1 }, + }).toArray(); + + if (currentDepartments.length) { + businessHour.departments = currentDepartments; + } + + return businessHour; + }), + ); + return { - businessHours, + businessHours: businessHoursWithDepartments, count: businessHours.length, offset, total, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts index 995ac4f8cabad..653dfbda74e45 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts @@ -26,6 +26,7 @@ import { RoutingManager } from '../../../../../app/livechat/server/lib/RoutingMa import { settings } from '../../../../../app/settings/server'; import { queueLogger } from './logger'; import { getInquirySortMechanismSetting } from '../../../../../app/livechat/server/lib/settings'; +import { callbacks } from '../../../../../lib/callbacks'; export const LivechatEnterprise = { async addMonitor(username: string) { @@ -201,7 +202,7 @@ export const LivechatEnterprise = { ) { check(_id, Match.Maybe(String)); - const department = _id ? await LivechatDepartmentRaw.findOneById(_id, { projection: { _id: 1, archived: 1 } }) : null; + const department = _id ? await LivechatDepartmentRaw.findOneById(_id, { projection: { _id: 1, archived: 1, enabled: 1 } }) : null; if (!hasLicense('livechat-enterprise')) { const totalDepartments = await LivechatDepartmentRaw.countTotal(); @@ -278,6 +279,11 @@ export const LivechatEnterprise = { await updateDepartmentAgents(departmentDB._id, departmentAgents, departmentDB.enabled); } + // Disable event + if (department?.enabled && !departmentDB?.enabled) { + void callbacks.run('livechat.afterDepartmentDisabled', departmentDB); + } + return departmentDB; }, diff --git a/apps/meteor/ee/app/livechat-enterprise/server/startup.ts b/apps/meteor/ee/app/livechat-enterprise/server/startup.ts index 7ba2479dd2fb5..0b63ae2c587a7 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/startup.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/startup.ts @@ -39,7 +39,7 @@ Meteor.startup(async function () { } businessHourManager.registerBusinessHourBehavior(businessHours[value as keyof typeof businessHours]); if (settings.get('Livechat_enable_business_hours')) { - await businessHourManager.startManager(); + await businessHourManager.restartManager(); logger.debug(`Business hour manager started`); } }); diff --git a/apps/meteor/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.js b/apps/meteor/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.js index 4606e3f790d0f..ba168826b435f 100644 --- a/apps/meteor/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.js +++ b/apps/meteor/ee/client/omnichannel/additionalForms/BusinessHoursMultiple.js @@ -30,7 +30,7 @@ const BusinessHoursMultiple = ({ values = {}, handlers = {}, className }) => { <Field className={className}> <Field.Label>{t('Departments')}</Field.Label> <Field.Row> - <AutoCompleteDepartmentMultiple value={departments} onChange={handleDepartments} /> + <AutoCompleteDepartmentMultiple value={departments} onChange={handleDepartments} enabled={true} /> </Field.Row> </Field> </> diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index 69913b79e3c56..2fad995dfd1f2 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -92,6 +92,8 @@ interface EventLikeCallbackSignatures { 'afterValidateLogin': (login: { user: IUser }) => void; 'afterJoinRoom': (user: IUser, room: IRoom) => void; 'beforeCreateRoom': (data: { type: IRoom['t']; extraData: { encrypted: boolean } }) => void; + 'livechat.afterDepartmentDisabled': (department: ILivechatDepartmentRecord) => void; + 'livechat.afterDepartmentArchived': (department: Pick<ILivechatDepartmentRecord, '_id'>) => void; 'afterSaveUser': ({ user, oldUser }: { user: IUser; oldUser: IUser | null }) => void; } diff --git a/apps/meteor/server/models/raw/LivechatBusinessHours.ts b/apps/meteor/server/models/raw/LivechatBusinessHours.ts index 1659740a55efe..e17cf27d60069 100644 --- a/apps/meteor/server/models/raw/LivechatBusinessHours.ts +++ b/apps/meteor/server/models/raw/LivechatBusinessHours.ts @@ -171,4 +171,8 @@ export class LivechatBusinessHoursRaw extends BaseRaw<ILivechatBusinessHour> imp } return this.col.find(query, options).toArray(); } + + disableBusinessHour(businessHourId: string): Promise<any> { + return this.updateOne({ _id: businessHourId }, { $set: { active: false } }); + } } diff --git a/apps/meteor/server/models/raw/LivechatDepartment.ts b/apps/meteor/server/models/raw/LivechatDepartment.ts index 5b1b4010dd381..2c9997b4b048c 100644 --- a/apps/meteor/server/models/raw/LivechatDepartment.ts +++ b/apps/meteor/server/models/raw/LivechatDepartment.ts @@ -83,6 +83,12 @@ export class LivechatDepartmentRaw extends BaseRaw<ILivechatDepartment> implemen }, sparse: true, }, + { + key: { + archived: 1, + }, + sparse: true, + }, ]; } @@ -123,6 +129,11 @@ export class LivechatDepartmentRaw extends BaseRaw<ILivechatDepartment> implemen return this.find(query, options); } + countByBusinessHourIdExcludingDepartmentId(businessHourId: string, departmentId: string): Promise<number> { + const query = { businessHourId, _id: { $ne: departmentId } }; + return this.col.countDocuments(query); + } + findEnabledByBusinessHourId(businessHourId: string, options: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment> { const query = { businessHourId, enabled: true }; return this.find(query, options); @@ -236,7 +247,12 @@ export class LivechatDepartmentRaw extends BaseRaw<ILivechatDepartment> implemen await LivechatDepartmentAgents.setDepartmentEnabledByDepartmentId(_id, data.enabled); } - return Object.assign(record, { _id }); + const latestDept = await this.findOneById(_id); + if (!latestDept) { + throw new Error(`Department ${_id} not found`); + } + + return latestDept; } unsetFallbackDepartmentByDepartmentId(departmentId: string): Promise<Document | UpdateResult> { @@ -354,6 +370,66 @@ export class LivechatDepartmentRaw extends BaseRaw<ILivechatDepartment> implemen return this.find(query, options); } + findNotArchived(options: FindOptions<ILivechatDepartment> = {}): FindCursor<ILivechatDepartment> { + const query = { archived: { $ne: false } }; + + return this.find(query, options); + } + + getBusinessHoursWithDepartmentStatuses(): Promise< + { + _id: string; + validDepartments: string[]; + invalidDepartments: string[]; + }[] + > { + return this.col + .aggregate<{ _id: string; validDepartments: string[]; invalidDepartments: string[] }>([ + { + $match: { + businessHourId: { + $exists: true, + }, + }, + }, + { + $group: { + _id: '$businessHourId', + validDepartments: { + $push: { + $cond: { + if: { + $or: [ + { + $eq: ['$enabled', true], + }, + { + $ne: ['$archived', true], + }, + ], + }, + then: '$_id', + else: '$$REMOVE', + }, + }, + }, + invalidDepartments: { + $push: { + $cond: { + if: { + $or: [{ $eq: ['$enabled', false] }, { $eq: ['$archived', true] }], + }, + then: '$_id', + else: '$$REMOVE', + }, + }, + }, + }, + }, + ]) + .toArray(); + } + checkIfMonitorIsMonitoringDepartmentById(monitorId: string, departmentId: string): Promise<boolean> { const aggregation = [ { diff --git a/apps/meteor/server/models/raw/LivechatDepartmentAgents.ts b/apps/meteor/server/models/raw/LivechatDepartmentAgents.ts index da709ff1136e4..52f946f34835c 100644 --- a/apps/meteor/server/models/raw/LivechatDepartmentAgents.ts +++ b/apps/meteor/server/models/raw/LivechatDepartmentAgents.ts @@ -357,8 +357,16 @@ export class LivechatDepartmentAgentsRaw extends BaseRaw<ILivechatDepartmentAgen return this.col.countDocuments({ departmentId }); } + disableAgentsByDepartmentId(departmentId: string): Promise<UpdateResult | Document> { + return this.updateMany({ departmentId }, { $set: { departmentEnabled: false } }); + } + + enableAgentsByDepartmentId(departmentId: string): Promise<UpdateResult | Document> { + return this.updateMany({ departmentId }, { $set: { departmentEnabled: true } }); + } + findAllAgentsConnectedToListOfDepartments(departmentIds: string[]): Promise<string[]> { - return this.col.distinct('agentId', { departmentId: { $in: departmentIds } }); + return this.col.distinct('agentId', { departmentId: { $in: departmentIds }, departmentEnabled: true }); } } diff --git a/apps/meteor/tests/data/livechat/business-hours.ts b/apps/meteor/tests/data/livechat/business-hours.ts deleted file mode 100644 index f3335047cca3f..0000000000000 --- a/apps/meteor/tests/data/livechat/business-hours.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { ILivechatBusinessHour } from '@rocket.chat/core-typings'; -import { credentials, methodCall, request } from '../api-data'; - -export const saveBusinessHour = async (businessHour: ILivechatBusinessHour) => { - const { body } = await request - .post(methodCall('livechat:saveBusinessHour')) - .set(credentials) - .send({ message: JSON.stringify({ params: [businessHour], msg: 'method', method: 'livechat:saveBusinessHour', id: '101' }) }) - .expect(200); - - return JSON.parse(body.message); -}; diff --git a/apps/meteor/tests/data/livechat/businessHours.ts b/apps/meteor/tests/data/livechat/businessHours.ts index 73ccdf75d0961..8ce1d91a79b90 100644 --- a/apps/meteor/tests/data/livechat/businessHours.ts +++ b/apps/meteor/tests/data/livechat/businessHours.ts @@ -1,11 +1,49 @@ import { ILivechatBusinessHour, LivechatBusinessHourTypes } from "@rocket.chat/core-typings"; import { api, credentials, methodCall, request } from "../api-data"; import { updateEESetting, updateSetting } from "../permissions.helper" -import { saveBusinessHour } from "./business-hours"; import moment from "moment"; type ISaveBhApiWorkHour = Omit<ILivechatBusinessHour, '_id' | 'ts' | 'timezone'> & { workHours: { day: string, start: string, finish: string, open: boolean }[] } & { departmentsToApplyBusinessHour?: string } & { timezoneName: string }; +// TODO: Migrate to an API call and return the business hour updated/created +export const saveBusinessHour = async (businessHour: ISaveBhApiWorkHour) => { + const { body } = await request + .post(methodCall('livechat:saveBusinessHour')) + .set(credentials) + .send({ message: JSON.stringify({ params: [businessHour], msg: 'method', method: 'livechat:saveBusinessHour', id: '101' }) }) + .expect(200); + + return JSON.parse(body.message); +}; + +export const createCustomBusinessHour = async (departments: string[]): Promise<ILivechatBusinessHour> => { + const name = `business-hour-${Date.now()}`; + const businessHour: ISaveBhApiWorkHour = { + name, + active: true, + type: LivechatBusinessHourTypes.CUSTOM, + workHours: getWorkHours(), + timezoneName: 'Asia/Calcutta', + departmentsToApplyBusinessHour: '', + }; + + if (departments.length) { + businessHour.departmentsToApplyBusinessHour = departments.join(','); + } + + await saveBusinessHour(businessHour); + + + const existingBusinessHours: ILivechatBusinessHour[] = await getAllCustomBusinessHours(); + const createdBusinessHour = existingBusinessHours.find((bh) => bh.name === name); + if (!createdBusinessHour) { + throw new Error('Could not create business hour'); + } + + return createdBusinessHour; +}; + + export const makeDefaultBusinessHourActiveAndClosed = async () => { // enable settings await updateSetting('Livechat_enable_business_hours', true); @@ -17,6 +55,7 @@ export const makeDefaultBusinessHourActiveAndClosed = async () => { .set(credentials) .send(); + // TODO: Refactor this to use openOrCloseBusinessHour() instead const workHours = businessHour.workHours as { start: string; finish: string; day: string, open: boolean }[]; const allEnabledWorkHours = workHours.map((workHour) => { workHour.open = true; @@ -53,6 +92,7 @@ export const disableDefaultBusinessHour = async () => { .set(credentials) .send(); + // TODO: Refactor this to use openOrCloseBusinessHour() instead const workHours = businessHour.workHours as { start: string; finish: string; day: string, open: boolean }[]; const allDisabledWorkHours = workHours.map((workHour) => { workHour.open = false; @@ -78,16 +118,47 @@ export const disableDefaultBusinessHour = async () => { }); } +export const removeCustomBusinessHour = async (businessHourId: string) => { + await request + .post(methodCall('livechat:removeBusinessHour')) + .set(credentials) + .send({ message: JSON.stringify({ params: [businessHourId, LivechatBusinessHourTypes.CUSTOM], msg: 'method', method: 'livechat:removeBusinessHour', id: '101' }) }) + .expect(200); +}; + +const getAllCustomBusinessHours = async (): Promise<ILivechatBusinessHour[]> => { + const response = await request.get(api('livechat/business-hours')).set(credentials).expect(200); + return (response.body.businessHours || []).filter((businessHour: ILivechatBusinessHour) => businessHour.type === LivechatBusinessHourTypes.CUSTOM); +}; + + +export const removeAllCustomBusinessHours = async () => { + const existingBusinessHours: ILivechatBusinessHour[] = await getAllCustomBusinessHours(); + + const promises = existingBusinessHours.map((businessHour) => removeCustomBusinessHour(businessHour._id)); + await Promise.all(promises); +}; + export const getDefaultBusinessHour = async (): Promise<ILivechatBusinessHour> => { const response = await request.get(api('livechat/business-hour')).set(credentials).query({ type: LivechatBusinessHourTypes.DEFAULT }).expect(200); return response.body.businessHour; }; +export const getCustomBusinessHourById = async (businessHourId: string): Promise<ILivechatBusinessHour> => { + const response = await request.get(api('livechat/business-hour')).set(credentials).query({ type: LivechatBusinessHourTypes.CUSTOM, _id: businessHourId }).expect(200); + return response.body.businessHour; +}; + export const openOrCloseBusinessHour = async (businessHour: ILivechatBusinessHour, open: boolean) => { const enabledBusinessHour = { ...businessHour, timezoneName: businessHour.timezone.name, - workHours: getWorkHours(open), + workHours: getWorkHours().map((workHour) => { + return { + ...workHour, + open, + } + }), departmentsToApplyBusinessHour: businessHour.departments?.map((department) => department._id).join(',') || '', } @@ -102,6 +173,7 @@ export const getWorkHours = (open = true): ISaveBhApiWorkHour['workHours'] => { day: moment().day(i).format('dddd'), start: '00:00', finish: '23:59', + open, }); } diff --git a/apps/meteor/tests/data/livechat/department.ts b/apps/meteor/tests/data/livechat/department.ts index 884ffaeade644..1780a83c8c076 100644 --- a/apps/meteor/tests/data/livechat/department.ts +++ b/apps/meteor/tests/data/livechat/department.ts @@ -1,33 +1,33 @@ import { faker } from '@faker-js/faker'; -import type { ILivechatDepartment, IUser } from '@rocket.chat/core-typings'; +import { expect } from 'chai'; +import type { ILivechatDepartment, IUser, LivechatDepartmentDTO } from '@rocket.chat/core-typings'; import { api, credentials, methodCall, request } from '../api-data'; import { IUserCredentialsHeader, password } from '../user'; import { createUser, login } from '../users.helper'; import { createAgent, makeAgentAvailable } from './rooms'; -import type { DummyResponse } from './utils'; -export const createDepartment = (): Promise<ILivechatDepartment> => - new Promise((resolve, reject) => { - request - .post(api('livechat/department')) - .send({ - department: { - enabled: false, - email: 'email@email.com', - showOnRegistration: true, - showOnOfflineForm: true, - name: `new department ${Date.now()}`, - description: 'created from api', - }, - }) - .set(credentials) - .end((err: Error, res: DummyResponse<ILivechatDepartment>) => { - if (err) { - return reject(err); - } - resolve(res.body.department); - }); - }); +export const NewDepartmentData = ((): Partial<ILivechatDepartment> => ({ + enabled: true, + name: `new department ${Date.now()}`, + description: 'created from api', + showOnRegistration: true, + email: faker.internet.email(), + showOnOfflineForm: true, +}))(); + +export const createDepartment = async (departmentData: Partial<ILivechatDepartment> = NewDepartmentData): Promise<ILivechatDepartment> => { + const response = await request.post(api('livechat/department')).set(credentials).send({ + department: departmentData, + }).expect(200); + return response.body.department; +}; + +export const updateDepartment = async (departmentId: string, departmentData: Partial<LivechatDepartmentDTO>): Promise<ILivechatDepartment> => { + const response = await request.put(api(`livechat/department/${ departmentId }`)).set(credentials).send({ + department: departmentData, + }).expect(200); + return response.body.department; +}; export const createDepartmentWithMethod = (initialAgents: { agentId: string, username: string }[] = []) => new Promise((resolve, reject) => { @@ -88,3 +88,23 @@ export const addOrRemoveAgentFromDepartment = async (departmentId: string, agent throw new Error('Failed to add or remove agent from department. Status code: ' + response.status + '\n' + response.body); } } + +export const archiveDepartment = async (departmentId: string): Promise<void> => { + await request.post(api(`livechat/department/${ departmentId }/archive`)).set(credentials).expect(200); +} + +export const disableDepartment = async (department: ILivechatDepartment): Promise<void> => { + department.enabled = false; + delete department._updatedAt; + const updatedDepartment = await updateDepartment(department._id, department); + expect(updatedDepartment.enabled).to.be.false; +} + +export const deleteDepartment = async (departmentId: string): Promise<void> => { + await request.delete(api(`livechat/department/${ departmentId }`)).set(credentials).expect(200); +} + +export const getDepartmentById = async (departmentId: string): Promise<ILivechatDepartment> => { + const response = await request.get(api(`livechat/department/${ departmentId }`)).set(credentials).expect(200); + return response.body.department; +}; diff --git a/apps/meteor/tests/data/livechat/rooms.ts b/apps/meteor/tests/data/livechat/rooms.ts index bceedec83b1c1..92d4c9a6a0458 100644 --- a/apps/meteor/tests/data/livechat/rooms.ts +++ b/apps/meteor/tests/data/livechat/rooms.ts @@ -106,22 +106,6 @@ export const createDepartment = (agents?: { agentId: string }[], name?: string): }); }; -export const deleteDepartment = (departmentId: string): Promise<unknown> => { - return new Promise((resolve, reject) => { - request - .delete(api(`livechat/department/${departmentId}`)) - .set(credentials) - .send() - .expect(200) - .end((err: Error, res: DummyResponse<ILivechatAgent>) => { - if (err) { - return reject(err); - } - resolve(res.body); - }); - }); -}; - export const createAgent = (overrideUsername?: string): Promise<ILivechatAgent> => new Promise((resolve, reject) => { request diff --git a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts index 052193fb55fea..1423b58856829 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/10-departments.ts @@ -12,9 +12,8 @@ import { createVisitor, createLivechatRoom, getLivechatRoomInfo, - deleteDepartment, } from '../../../data/livechat/rooms'; -import { createDepartmentWithAnOnlineAgent } from '../../../data/livechat/department'; +import { createDepartmentWithAnOnlineAgent, deleteDepartment } from '../../../data/livechat/department'; import { IS_EE } from '../../../e2e/config/constants'; import { createUser } from '../../../data/users.helper'; import { createMonitor, createUnit } from '../../../data/livechat/units'; diff --git a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts index d2e41fa11beca..e03306d18df07 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/19-business-hours.ts @@ -1,22 +1,37 @@ /* eslint-env mocha */ -import type { ILivechatAgent, ILivechatBusinessHour } from '@rocket.chat/core-typings'; -import { ILivechatAgentStatus, LivechatBusinessHourBehaviors, LivechatBusinessHourTypes } from '@rocket.chat/core-typings'; +import type { ILivechatAgent, ILivechatBusinessHour, ILivechatDepartment } from '@rocket.chat/core-typings'; +import { LivechatBusinessHourBehaviors, LivechatBusinessHourTypes, ILivechatAgentStatus } from '@rocket.chat/core-typings'; import { expect } from 'chai'; import { getCredentials, api, request, credentials } from '../../../data/api-data'; -import { saveBusinessHour } from '../../../data/livechat/business-hours'; -import { updateEESetting, updatePermission, updateSetting } from '../../../data/permissions.helper'; +import { + getDefaultBusinessHour, + removeAllCustomBusinessHours, + saveBusinessHour, + openOrCloseBusinessHour, + createCustomBusinessHour, + getCustomBusinessHourById, + getWorkHours, +} from '../../../data/livechat/businessHours'; +import { removePermissionFromAllRoles, restorePermissionToRoles, updateSetting, updateEESetting } from '../../../data/permissions.helper'; import { IS_EE } from '../../../e2e/config/constants'; +import { + addOrRemoveAgentFromDepartment, + archiveDepartment, + createDepartmentWithAnOnlineAgent, + disableDepartment, + getDepartmentById, + deleteDepartment, +} from '../../../data/livechat/department'; +import { sleep } from '../../../../lib/utils/sleep'; import { createUser, deleteUser, getMe, login } from '../../../data/users.helper'; import { createAgent, makeAgentAvailable } from '../../../data/livechat/rooms'; -import { sleep } from '../../../../lib/utils/sleep'; -import { getDefaultBusinessHour, openOrCloseBusinessHour, getWorkHours } from '../../../data/livechat/businessHours'; import type { IUserCredentialsHeader } from '../../../data/user'; import { password } from '../../../data/user'; import { removeAgent } from '../../../data/livechat/users'; -describe('[CE] LIVECHAT - business hours', function () { +describe('LIVECHAT - business hours', function () { this.retries(0); before((done) => getCredentials(done)); @@ -28,14 +43,15 @@ describe('[CE] LIVECHAT - business hours', function () { }); let defaultBhId: any; - describe('livechat/business-hour', () => { + describe('[CE] livechat/business-hour', () => { it('should fail when user doesnt have view-livechat-business-hours permission', async () => { - await updatePermission('view-livechat-business-hours', []); + await removePermissionFromAllRoles('view-livechat-business-hours'); const response = await request.get(api('livechat/business-hour')).set(credentials).expect(403); expect(response.body.success).to.be.false; + + await restorePermissionToRoles('view-livechat-business-hours'); }); it('should fail when business hour type is not a valid BH type', async () => { - await updatePermission('view-livechat-business-hours', ['admin', 'livechat-manager']); const response = await request.get(api('livechat/business-hour')).set(credentials).query({ type: 'invalid' }).expect(200); expect(response.body.success).to.be.true; expect(response.body.businessHour).to.be.null; @@ -87,14 +103,15 @@ describe('[CE] LIVECHAT - business hours', function () { }); }); - (IS_EE ? describe : describe.skip)('[EE] LIVECHAT - business hours', () => { + (IS_EE ? describe : describe.skip)('[EE] livechat/business-hour', () => { it('should fail if user doesnt have view-livechat-business-hours permission', async () => { - await updatePermission('view-livechat-business-hours', []); + await removePermissionFromAllRoles('view-livechat-business-hours'); const response = await request.get(api('livechat/business-hours')).set(credentials).expect(403); expect(response.body.success).to.be.false; + + await restorePermissionToRoles('view-livechat-business-hours'); }); it('should return a list of business hours', async () => { - await updatePermission('view-livechat-business-hours', ['admin', 'livechat-manager']); const response = await request.get(api('livechat/business-hours')).set(credentials).expect(200); expect(response.body.success).to.be.true; expect(response.body.businessHours).to.be.an('array').with.lengthOf.greaterThan(0); @@ -233,6 +250,429 @@ describe('[CE] LIVECHAT - business hours', function () { }); }); + // Scenario: Assume we have a BH linked to a department, and we archive the department + // Expected result: + // 1) If BH is open and only linked to that department, it should be closed + // 2) If BH is open and linked to other departments, it should remain open + // 3) Agents within the archived department should be assigned to default BH + // 3.1) We'll also need to handle the case where if an agent is assigned to "dep1" + // and "dep2" and both these depts are connected to same BH, then in this case after + // archiving "dep1", we'd still need to BH within this user's cache since he's part of + // "dep2" which is linked to BH + (IS_EE ? describe : describe.skip)('[EE] BH operations post department archiving', () => { + let defaultBusinessHour: ILivechatBusinessHour; + let customBusinessHour: ILivechatBusinessHour; + let deptLinkedToCustomBH: ILivechatDepartment; + let agentLinkedToDept: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>['agent']; + + before(async () => { + await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE); + // wait for the callbacks to be registered + await sleep(1000); + }); + + beforeEach(async () => { + // cleanup any existing business hours + await removeAllCustomBusinessHours(); + + // get default business hour + defaultBusinessHour = await getDefaultBusinessHour(); + + // close default business hour + await openOrCloseBusinessHour(defaultBusinessHour, false); + + // create custom business hour and link it to a department + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + customBusinessHour = await createCustomBusinessHour([department._id]); + agentLinkedToDept = agent; + deptLinkedToCustomBH = department; + + // open custom business hour + await openOrCloseBusinessHour(customBusinessHour, true); + }); + + it('upon archiving a department, if BH is open and only linked to that department, it should be closed', async () => { + // archive department + await archiveDepartment(deptLinkedToCustomBH._id); + + // verify if department is archived and BH link is removed + const department = await getDepartmentById(deptLinkedToCustomBH._id); + expect(department).to.be.an('object'); + expect(department).to.have.property('archived', true); + expect(department.businessHourId).to.be.undefined; + + // verify if BH is closed + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH).to.have.property('active', false); + expect(latestCustomBH.departments).to.be.an('array').that.is.empty; + }); + + it('upon archiving a department, if BH is open and linked to other departments, it should remain open', async () => { + // create another department and link it to the same BH + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + await removeAllCustomBusinessHours(); + customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]); + + // archive department + await archiveDepartment(deptLinkedToCustomBH._id); + + // verify if department is archived and BH link is removed + const archivedDepartment = await getDepartmentById(deptLinkedToCustomBH._id); + expect(archivedDepartment).to.be.an('object'); + expect(archivedDepartment).to.have.property('archived', true); + expect(archivedDepartment.businessHourId).to.be.undefined; + // verify if other department is not archived and BH link is not removed + const otherDepartment = await getDepartmentById(department._id); + expect(otherDepartment).to.be.an('object'); + expect(otherDepartment.businessHourId).to.be.equal(customBusinessHour._id); + + // verify if BH is still open + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH).to.have.property('active', true); + expect(latestCustomBH.departments).to.be.an('array').of.length(1); + expect(latestCustomBH?.departments?.[0]._id).to.be.equal(department._id); + + // cleanup + await deleteDepartment(department._id); + await deleteUser(agent.user); + }); + + it('upon archiving a department, agents within the archived department should be assigned to default BH', async () => { + await openOrCloseBusinessHour(defaultBusinessHour, true); + + // archive department + await archiveDepartment(deptLinkedToCustomBH._id); + + const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBusinessHour._id); + }); + + it('upon archiving a department, overlapping agents should still have BH within their cache', async () => { + // create another department and link it to the same BH + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + await removeAllCustomBusinessHours(); + customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]); + + // create overlapping agent by adding previous agent to newly created department + await addOrRemoveAgentFromDepartment( + department._id, + { + agentId: agentLinkedToDept.user._id, + username: agentLinkedToDept.user.username || '', + }, + true, + ); + + // archive department + await archiveDepartment(deptLinkedToCustomBH._id); + + // verify if department is archived and BH link is removed + const archivedDepartment = await getDepartmentById(deptLinkedToCustomBH._id); + expect(archivedDepartment).to.be.an('object'); + expect(archivedDepartment).to.have.property('archived', true); + expect(archivedDepartment.businessHourId).to.be.undefined; + + // verify if BH is still open + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH).to.have.property('active', true); + expect(latestCustomBH.departments).to.be.an('array').of.length(1); + expect(latestCustomBH?.departments?.[0]?._id).to.be.equal(department._id); + + // verify if overlapping agent still has BH within his cache + const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id); + + // verify if other agent still has BH within his cache + const otherAgent: ILivechatAgent = await getMe(agent.credentials as any); + expect(otherAgent).to.be.an('object'); + expect(otherAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(otherAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id); + + // cleanup + await deleteDepartment(department._id); + await deleteUser(agent.user); + }); + + afterEach(async () => { + await deleteDepartment(deptLinkedToCustomBH._id); + await deleteUser(agentLinkedToDept.user); + }); + }); + (IS_EE ? describe : describe.skip)('[EE] BH operations post department disablement', () => { + let defaultBusinessHour: ILivechatBusinessHour; + let customBusinessHour: ILivechatBusinessHour; + let deptLinkedToCustomBH: ILivechatDepartment; + let agentLinkedToDept: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>['agent']; + + before(async () => { + await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE); + // wait for the callbacks to be registered + await sleep(1000); + }); + + beforeEach(async () => { + // cleanup any existing business hours + await removeAllCustomBusinessHours(); + + // get default business hour + defaultBusinessHour = await getDefaultBusinessHour(); + + // close default business hour + await openOrCloseBusinessHour(defaultBusinessHour, false); + + // create custom business hour and link it to a department + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + customBusinessHour = await createCustomBusinessHour([department._id]); + agentLinkedToDept = agent; + deptLinkedToCustomBH = department; + + // open custom business hour + await openOrCloseBusinessHour(customBusinessHour, true); + }); + + it('upon disabling a department, if BH is open and only linked to that department, it should be closed', async () => { + // disable department + await disableDepartment(deptLinkedToCustomBH); + + // verify if BH link is removed + const department = await getDepartmentById(deptLinkedToCustomBH._id); + expect(department).to.be.an('object'); + expect(department.businessHourId).to.be.undefined; + + // verify if BH is closed + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH.active).to.be.false; + expect(latestCustomBH.departments).to.be.an('array').that.is.empty; + }); + + it('upon disabling a department, if BH is open and linked to other departments, it should remain open', async () => { + // create another department and link it to the same BH + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + await removeAllCustomBusinessHours(); + customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]); + + // disable department + await disableDepartment(deptLinkedToCustomBH); + + // verify if BH link is removed + const disabledDepartment = await getDepartmentById(deptLinkedToCustomBH._id); + expect(disabledDepartment).to.be.an('object'); + expect(disabledDepartment.businessHourId).to.be.undefined; + + // verify if other department BH link is not removed + const otherDepartment = await getDepartmentById(department._id); + expect(otherDepartment).to.be.an('object'); + expect(otherDepartment.businessHourId).to.be.equal(customBusinessHour._id); + + // verify if BH is still open + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH).to.have.property('active', true); + expect(latestCustomBH.departments).to.be.an('array').of.length(1); + expect(latestCustomBH?.departments?.[0]._id).to.be.equal(department._id); + + // cleanup + await deleteDepartment(department._id); + await deleteUser(agent.user); + }); + + it('upon disabling a department, agents within the disabled department should be assigned to default BH', async () => { + await openOrCloseBusinessHour(defaultBusinessHour, true); + + // disable department + await disableDepartment(deptLinkedToCustomBH); + + const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBusinessHour._id); + }); + + it('upon disabling a department, overlapping agents should still have BH within their cache', async () => { + // create another department and link it to the same BH + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + await removeAllCustomBusinessHours(); + customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]); + + // create overlapping agent by adding previous agent to newly created department + await addOrRemoveAgentFromDepartment( + department._id, + { + agentId: agentLinkedToDept.user._id, + username: agentLinkedToDept.user.username || '', + }, + true, + ); + + // disable department + await disableDepartment(deptLinkedToCustomBH); + + // verify if BH link is removed + const disabledDepartment = await getDepartmentById(deptLinkedToCustomBH._id); + expect(disabledDepartment).to.be.an('object'); + expect(disabledDepartment.businessHourId).to.be.undefined; + + // verify if BH is still open + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH).to.have.property('active', true); + expect(latestCustomBH.departments).to.be.an('array').of.length(1); + expect(latestCustomBH?.departments?.[0]?._id).to.be.equal(department._id); + + // verify if overlapping agent still has BH within his cache + const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id); + + // verify if other agent still has BH within his cache + const otherAgent: ILivechatAgent = await getMe(agent.credentials as any); + expect(otherAgent).to.be.an('object'); + expect(otherAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(otherAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id); + + // cleanup + await deleteDepartment(department._id); + await deleteUser(agent.user); + }); + + afterEach(async () => { + await deleteDepartment(deptLinkedToCustomBH._id); + await deleteUser(agentLinkedToDept.user); + }); + }); + (IS_EE ? describe : describe.skip)('[EE] BH operations post department removal', () => { + let defaultBusinessHour: ILivechatBusinessHour; + let customBusinessHour: ILivechatBusinessHour; + let deptLinkedToCustomBH: ILivechatDepartment; + let agentLinkedToDept: Awaited<ReturnType<typeof createDepartmentWithAnOnlineAgent>>['agent']; + + before(async () => { + await updateSetting('Livechat_business_hour_type', LivechatBusinessHourBehaviors.MULTIPLE); + // wait for the callbacks to be registered + await sleep(1000); + }); + + beforeEach(async () => { + // cleanup any existing business hours + await removeAllCustomBusinessHours(); + + // get default business hour + defaultBusinessHour = await getDefaultBusinessHour(); + + // close default business hour + await openOrCloseBusinessHour(defaultBusinessHour, false); + + // create custom business hour and link it to a department + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + customBusinessHour = await createCustomBusinessHour([department._id]); + agentLinkedToDept = agent; + deptLinkedToCustomBH = department; + + // open custom business hour + await openOrCloseBusinessHour(customBusinessHour, true); + }); + + it('upon deleting a department, if BH is open and only linked to that department, it should be closed', async () => { + await deleteDepartment(deptLinkedToCustomBH._id); + + // verify if BH is closed + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH.active).to.be.false; + expect(latestCustomBH.departments).to.be.an('array').that.is.empty; + }); + + it('upon deleting a department, if BH is open and linked to other departments, it should remain open', async () => { + // create another department and link it to the same BH + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + await removeAllCustomBusinessHours(); + customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]); + + await deleteDepartment(deptLinkedToCustomBH._id); + + // verify if other department BH link is not removed + const otherDepartment = await getDepartmentById(department._id); + expect(otherDepartment).to.be.an('object'); + expect(otherDepartment.businessHourId).to.be.equal(customBusinessHour._id); + + // verify if BH is still open + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH).to.have.property('active', true); + expect(latestCustomBH.departments).to.be.an('array').of.length(1); + expect(latestCustomBH?.departments?.[0]._id).to.be.equal(department._id); + + // cleanup + await deleteDepartment(department._id); + await deleteUser(agent.user); + }); + + it('upon deleting a department, agents within the disabled department should be assigned to default BH', async () => { + await openOrCloseBusinessHour(defaultBusinessHour, true); + + await deleteDepartment(deptLinkedToCustomBH._id); + + const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(defaultBusinessHour._id); + }); + + it('upon deleting a department, overlapping agents should still have BH within their cache', async () => { + // create another department and link it to the same BH + const { department, agent } = await createDepartmentWithAnOnlineAgent(); + await removeAllCustomBusinessHours(); + customBusinessHour = await createCustomBusinessHour([deptLinkedToCustomBH._id, department._id]); + + // create overlapping agent by adding previous agent to newly created department + await addOrRemoveAgentFromDepartment( + department._id, + { + agentId: agentLinkedToDept.user._id, + username: agentLinkedToDept.user.username || '', + }, + true, + ); + + await deleteDepartment(deptLinkedToCustomBH._id); + + // verify if BH is still open + const latestCustomBH = await getCustomBusinessHourById(customBusinessHour._id); + expect(latestCustomBH).to.be.an('object'); + expect(latestCustomBH).to.have.property('active', true); + expect(latestCustomBH.departments).to.be.an('array').of.length(1); + expect(latestCustomBH?.departments?.[0]?._id).to.be.equal(department._id); + + // verify if overlapping agent still has BH within his cache + const latestAgent: ILivechatAgent = await getMe(agentLinkedToDept.credentials as any); + expect(latestAgent).to.be.an('object'); + expect(latestAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(latestAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id); + + // verify if other agent still has BH within his cache + const otherAgent: ILivechatAgent = await getMe(agent.credentials as any); + expect(otherAgent).to.be.an('object'); + expect(otherAgent.openBusinessHours).to.be.an('array').of.length(1); + expect(otherAgent?.openBusinessHours?.[0]).to.be.equal(customBusinessHour._id); + + // cleanup + await deleteDepartment(department._id); + await deleteUser(agent.user); + }); + + afterEach(async () => { + await deleteUser(agentLinkedToDept.user); + }); + }); describe('BH behavior upon new agent creation/deletion', () => { let defaultBH: ILivechatBusinessHour; let agent: ILivechatAgent; diff --git a/packages/model-typings/src/models/ILivechatBusinessHoursModel.ts b/packages/model-typings/src/models/ILivechatBusinessHoursModel.ts index 6ba6e003ce421..0bbb8f381b7f1 100644 --- a/packages/model-typings/src/models/ILivechatBusinessHoursModel.ts +++ b/packages/model-typings/src/models/ILivechatBusinessHoursModel.ts @@ -39,4 +39,6 @@ export interface ILivechatBusinessHoursModel extends IBaseModel<ILivechatBusines type?: LivechatBusinessHourTypes, options?: any, ): Promise<ILivechatBusinessHour[]>; + + disableBusinessHour(businessHourId: string): Promise<any>; } diff --git a/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts b/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts index 94c824e14f07a..2d0c8c5ec69dd 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentAgentsModel.ts @@ -87,5 +87,7 @@ export interface ILivechatDepartmentAgentsModel extends IBaseModel<ILivechatDepa getNextBotForDepartment(departmentId: string, ignoreAgentId?: string): Promise<{ agentId: string; username: string } | undefined>; replaceUsernameOfAgentByUserId(userId: string, username: string): Promise<UpdateResult | Document>; countByDepartmentId(departmentId: string): Promise<number>; + disableAgentsByDepartmentId(departmentId: string): Promise<UpdateResult | Document>; + enableAgentsByDepartmentId(departmentId: string): Promise<UpdateResult | Document>; findAllAgentsConnectedToListOfDepartments(departmentIds: string[]): Promise<string[]>; } diff --git a/packages/model-typings/src/models/ILivechatDepartmentModel.ts b/packages/model-typings/src/models/ILivechatDepartmentModel.ts index 8008008780880..271e433f39d62 100644 --- a/packages/model-typings/src/models/ILivechatDepartmentModel.ts +++ b/packages/model-typings/src/models/ILivechatDepartmentModel.ts @@ -14,6 +14,7 @@ export interface ILivechatDepartmentModel extends IBaseModel<ILivechatDepartment ): FindCursor<ILivechatDepartment>; findByBusinessHourId(businessHourId: string, options: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>; + countByBusinessHourIdExcludingDepartmentId(businessHourId: string, departmentId: string): Promise<number>; findEnabledByBusinessHourId(businessHourId: string, options: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>; @@ -59,5 +60,13 @@ export interface ILivechatDepartmentModel extends IBaseModel<ILivechatDepartment findOneByIdOrName(_idOrName: string, options?: FindOptions<ILivechatDepartment>): Promise<ILivechatDepartment | null>; findByUnitIds(unitIds: string[], options?: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>; findActiveByUnitIds(unitIds: string[], options?: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>; + findNotArchived(options?: FindOptions<ILivechatDepartment>): FindCursor<ILivechatDepartment>; + getBusinessHoursWithDepartmentStatuses(): Promise< + { + _id: string; + validDepartments: string[]; + invalidDepartments: string[]; + }[] + >; checkIfMonitorIsMonitoringDepartmentById(monitorId: string, departmentId: string): Promise<boolean>; } From 7765526938c2a4b15cddfc2d1aed8d21370855e8 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Thu, 6 Jul 2023 01:05:03 -0300 Subject: [PATCH 52/79] chore: configure packages as public (#29638) --- .../notifications/client/lib/Notifications.ts | 2 +- apps/meteor/app/utils/client/lib/SDKClient.ts | 2 +- .../packages}/api-client/.eslintrc.json | 0 .../packages}/api-client/CHANGELOG.md | 0 ee/packages/api-client/LICENSE | 43 +++++++++++++++++++ .../api-client/__tests__/2fahandling.spec.ts | 0 .../packages}/api-client/jest.config.ts | 0 .../packages}/api-client/package.json | 1 - .../api-client/src/RestClientInterface.ts | 0 .../packages}/api-client/src/errors.ts | 0 .../packages}/api-client/src/index.ts | 0 .../packages}/api-client/tsconfig.json | 2 +- .../packages}/ddp-client/.eslintrc.json | 0 ee/packages/ddp-client/LICENSE | 43 +++++++++++++++++++ .../ddp-client/__examples__/simple.ts | 0 .../packages}/ddp-client/__mocks__/ws.ts | 0 .../ddp-client/__tests__/ClientStream.spec.ts | 0 .../ddp-client/__tests__/Connection.spec.ts | 0 .../__tests__/DDPDispatcher.spec.ts | 0 .../ddp-client/__tests__/DDPSDK.spec.ts | 0 .../__tests__/MinimalDDPClient.spec.ts | 0 .../ddp-client/__tests__/Timeout.spec.ts | 0 .../ddp-client/__tests__/helpers/index.ts | 0 .../wrapOnceEventIntoPromise.spec.ts | 0 .../packages}/ddp-client/jest.config.ts | 0 .../packages}/ddp-client/package.json | 4 +- .../packages}/ddp-client/src/ClientStream.ts | 0 .../packages}/ddp-client/src/Connection.ts | 0 .../packages}/ddp-client/src/DDPDispatcher.ts | 0 .../packages}/ddp-client/src/DDPSDK.ts | 0 .../ddp-client/src/MinimalDDPClient.ts | 0 .../packages}/ddp-client/src/README.md | 0 .../ddp-client/src/TimeoutControl.ts | 0 .../packages}/ddp-client/src/index.ts | 0 .../src/legacy/RocketchatSDKLegacy.ts | 5 ++- .../ddp-client/src/legacy/types/SDKLegacy.ts | 3 +- .../src/livechat/LivechatClientImpl.ts | 4 +- .../src/livechat/types/LivechatSDK.ts | 3 +- .../packages}/ddp-client/src/types/Account.ts | 0 .../ddp-client/src/types/ClientStream.ts | 0 .../ddp-client/src/types/DDPClient.ts | 0 .../ddp-client/src/types/IncomingPayload.ts | 0 .../ddp-client/src/types/OutgoingPayload.ts | 0 .../ddp-client/src/types/RemoveListener.ts | 0 .../packages}/ddp-client/src/types/SDK.ts | 0 .../ddp-client/src/types/Subscription.ts | 0 .../src/types/connectionPayloads.ts | 0 .../src/types/heartbeatsPayloads.ts | 0 .../packages/ddp-client/src/types}/methods.ts | 0 .../ddp-client/src/types/methodsPayloads.ts | 0 .../src/types/publicationPayloads.ts | 0 .../packages/ddp-client/src/types}/streams.ts | 0 .../src/wrapOnceEventIntoPromise.ts | 0 .../packages}/ddp-client/tsconfig.json | 2 +- packages/core-typings/package.json | 1 - packages/rest-typings/package.json | 1 - packages/ui-contexts/package.json | 1 + .../src/ServerContext/ServerContext.ts | 5 +-- packages/ui-contexts/src/hooks/useMethod.ts | 2 +- packages/ui-contexts/src/hooks/useStream.ts | 2 +- packages/ui-contexts/src/index.ts | 17 +++++++- yarn.lock | 10 ++--- 62 files changed, 125 insertions(+), 28 deletions(-) rename {packages => ee/packages}/api-client/.eslintrc.json (100%) rename {packages => ee/packages}/api-client/CHANGELOG.md (100%) create mode 100644 ee/packages/api-client/LICENSE rename {packages => ee/packages}/api-client/__tests__/2fahandling.spec.ts (100%) rename {packages => ee/packages}/api-client/jest.config.ts (100%) rename {packages => ee/packages}/api-client/package.json (98%) rename {packages => ee/packages}/api-client/src/RestClientInterface.ts (100%) rename {packages => ee/packages}/api-client/src/errors.ts (100%) rename {packages => ee/packages}/api-client/src/index.ts (100%) rename {packages => ee/packages}/api-client/tsconfig.json (71%) rename {packages => ee/packages}/ddp-client/.eslintrc.json (100%) create mode 100644 ee/packages/ddp-client/LICENSE rename {packages => ee/packages}/ddp-client/__examples__/simple.ts (100%) rename {packages => ee/packages}/ddp-client/__mocks__/ws.ts (100%) rename {packages => ee/packages}/ddp-client/__tests__/ClientStream.spec.ts (100%) rename {packages => ee/packages}/ddp-client/__tests__/Connection.spec.ts (100%) rename {packages => ee/packages}/ddp-client/__tests__/DDPDispatcher.spec.ts (100%) rename {packages => ee/packages}/ddp-client/__tests__/DDPSDK.spec.ts (100%) rename {packages => ee/packages}/ddp-client/__tests__/MinimalDDPClient.spec.ts (100%) rename {packages => ee/packages}/ddp-client/__tests__/Timeout.spec.ts (100%) rename {packages => ee/packages}/ddp-client/__tests__/helpers/index.ts (100%) rename {packages => ee/packages}/ddp-client/__tests__/wrapOnceEventIntoPromise.spec.ts (100%) rename {packages => ee/packages}/ddp-client/jest.config.ts (100%) rename {packages => ee/packages}/ddp-client/package.json (89%) rename {packages => ee/packages}/ddp-client/src/ClientStream.ts (100%) rename {packages => ee/packages}/ddp-client/src/Connection.ts (100%) rename {packages => ee/packages}/ddp-client/src/DDPDispatcher.ts (100%) rename {packages => ee/packages}/ddp-client/src/DDPSDK.ts (100%) rename {packages => ee/packages}/ddp-client/src/MinimalDDPClient.ts (100%) rename {packages => ee/packages}/ddp-client/src/README.md (100%) rename {packages => ee/packages}/ddp-client/src/TimeoutControl.ts (100%) rename {packages => ee/packages}/ddp-client/src/index.ts (100%) rename {packages => ee/packages}/ddp-client/src/legacy/RocketchatSDKLegacy.ts (98%) rename {packages => ee/packages}/ddp-client/src/legacy/types/SDKLegacy.ts (98%) rename {packages => ee/packages}/ddp-client/src/livechat/LivechatClientImpl.ts (98%) rename {packages => ee/packages}/ddp-client/src/livechat/types/LivechatSDK.ts (98%) rename {packages => ee/packages}/ddp-client/src/types/Account.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/ClientStream.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/DDPClient.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/IncomingPayload.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/OutgoingPayload.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/RemoveListener.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/SDK.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/Subscription.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/connectionPayloads.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/heartbeatsPayloads.ts (100%) rename {packages/ui-contexts/src/ServerContext => ee/packages/ddp-client/src/types}/methods.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/methodsPayloads.ts (100%) rename {packages => ee/packages}/ddp-client/src/types/publicationPayloads.ts (100%) rename {packages/ui-contexts/src/ServerContext => ee/packages/ddp-client/src/types}/streams.ts (100%) rename {packages => ee/packages}/ddp-client/src/wrapOnceEventIntoPromise.ts (100%) rename {packages => ee/packages}/ddp-client/tsconfig.json (81%) diff --git a/apps/meteor/app/notifications/client/lib/Notifications.ts b/apps/meteor/app/notifications/client/lib/Notifications.ts index 65c33c3da1bfc..1b548c4ada104 100644 --- a/apps/meteor/app/notifications/client/lib/Notifications.ts +++ b/apps/meteor/app/notifications/client/lib/Notifications.ts @@ -1,6 +1,6 @@ import { Meteor } from 'meteor/meteor'; import { Tracker } from 'meteor/tracker'; -import type { StreamKeys, StreamerCallback } from '@rocket.chat/ui-contexts/src/ServerContext/streams'; +import type { StreamKeys, StreamerCallback } from '@rocket.chat/ddp-client/src/types/streams'; import './Presence'; import { sdk } from '../../../utils/client/lib/SDKClient'; diff --git a/apps/meteor/app/utils/client/lib/SDKClient.ts b/apps/meteor/app/utils/client/lib/SDKClient.ts index 6eefccb3fdacd..baf06c9a53893 100644 --- a/apps/meteor/app/utils/client/lib/SDKClient.ts +++ b/apps/meteor/app/utils/client/lib/SDKClient.ts @@ -2,7 +2,7 @@ import type { RestClientInterface } from '@rocket.chat/api-client'; import type { SDK } from '@rocket.chat/ddp-client/src/DDPSDK'; import type { ClientStream } from '@rocket.chat/ddp-client/src/types/ClientStream'; import { Emitter } from '@rocket.chat/emitter'; -import type { StreamKeys, StreamNames, StreamerCallbackArgs } from '@rocket.chat/ui-contexts/src/ServerContext/streams'; +import type { StreamKeys, StreamNames, StreamerCallbackArgs } from '@rocket.chat/ddp-client/src/types/streams'; import { DDPCommon } from 'meteor/ddp-common'; import { Meteor } from 'meteor/meteor'; import type { ServerMethods } from '@rocket.chat/ui-contexts'; diff --git a/packages/api-client/.eslintrc.json b/ee/packages/api-client/.eslintrc.json similarity index 100% rename from packages/api-client/.eslintrc.json rename to ee/packages/api-client/.eslintrc.json diff --git a/packages/api-client/CHANGELOG.md b/ee/packages/api-client/CHANGELOG.md similarity index 100% rename from packages/api-client/CHANGELOG.md rename to ee/packages/api-client/CHANGELOG.md diff --git a/ee/packages/api-client/LICENSE b/ee/packages/api-client/LICENSE new file mode 100644 index 0000000000000..db5cba161765b --- /dev/null +++ b/ee/packages/api-client/LICENSE @@ -0,0 +1,43 @@ +The Rocket.Chat Enterprise Edition (EE) license (the "EE License") +Copyright (c) 2015-2023 Rocket.Chat Technologies Corp. + +With regard to the Rocket.Chat Software: + +This software and associated documentation files (the "Software") may only be +used in production, if you (and any entity that you represent) have agreed to, +and are in compliance with, the Rocket.Chat Subscription Terms of Service, +available at https://rocket.chat/terms (the "EE Terms"), or other agreement +governing the use of the Software, as agreed by you and Rocket.Chat, and otherwise +have a valid Rocket.Chat Enterprise Edition subscription for the correct number +of user seats. Subject to the foregoing sentence, you are free to modify this +Software and publish patches to the Software. You agree that Rocket.Chat and/or +its licensors (as applicable) retain all right, title and interest in and to all +such modifications and/or patches, and all such modifications and/or patches may +only be used, copied, modified, displayed, distributed, or otherwise exploited +with a valid Rocket.Chat Enterprise Edition subscription for the correct number +of user seats. Notwithstanding the foregoing, you may copy and modify the Software +for development and testing purposes, without requiring a Subscription. You agree +that Rocket.Chat and/or its licensors (as applicable) retain all right, title and +interest in and to all such modifications. You are not granted any other rights +beyond what is expressly stated herein. Subject to the foregoing, it is forbidden +to copy, merge, publish, distribute, sublicense, and/or sell the Software. + +This EE License applies only to the part of this Software that is not distributed +as part of Rocket.Chat Community Edition (CE). Any part of this Software distributed +as part of Rocket.Chat CE or is served client-side as an image, font, cascading +stylesheet (CSS), file which produces or is compiled, arranged, augmented, or combined +into client-side JavaScript, in whole or in part, is copyrighted under the MIT Expat +license. The full text of this EE License shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +For all third-party components incorporated into the Rocket.Chat Software, +those components are licensed under the original license provided by the owner +of the applicable component. diff --git a/packages/api-client/__tests__/2fahandling.spec.ts b/ee/packages/api-client/__tests__/2fahandling.spec.ts similarity index 100% rename from packages/api-client/__tests__/2fahandling.spec.ts rename to ee/packages/api-client/__tests__/2fahandling.spec.ts diff --git a/packages/api-client/jest.config.ts b/ee/packages/api-client/jest.config.ts similarity index 100% rename from packages/api-client/jest.config.ts rename to ee/packages/api-client/jest.config.ts diff --git a/packages/api-client/package.json b/ee/packages/api-client/package.json similarity index 98% rename from packages/api-client/package.json rename to ee/packages/api-client/package.json index 0d0e86d573c44..62923ece2f2cb 100644 --- a/packages/api-client/package.json +++ b/ee/packages/api-client/package.json @@ -1,7 +1,6 @@ { "name": "@rocket.chat/api-client", "version": "0.0.2", - "private": true, "devDependencies": { "@swc/core": "^1.3.66", "@swc/jest": "^0.2.26", diff --git a/packages/api-client/src/RestClientInterface.ts b/ee/packages/api-client/src/RestClientInterface.ts similarity index 100% rename from packages/api-client/src/RestClientInterface.ts rename to ee/packages/api-client/src/RestClientInterface.ts diff --git a/packages/api-client/src/errors.ts b/ee/packages/api-client/src/errors.ts similarity index 100% rename from packages/api-client/src/errors.ts rename to ee/packages/api-client/src/errors.ts diff --git a/packages/api-client/src/index.ts b/ee/packages/api-client/src/index.ts similarity index 100% rename from packages/api-client/src/index.ts rename to ee/packages/api-client/src/index.ts diff --git a/packages/api-client/tsconfig.json b/ee/packages/api-client/tsconfig.json similarity index 71% rename from packages/api-client/tsconfig.json rename to ee/packages/api-client/tsconfig.json index 9d8ef0c3a3736..b397e2c4421fb 100644 --- a/packages/api-client/tsconfig.json +++ b/ee/packages/api-client/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.client.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "module": "commonjs", "rootDir": "./src", diff --git a/packages/ddp-client/.eslintrc.json b/ee/packages/ddp-client/.eslintrc.json similarity index 100% rename from packages/ddp-client/.eslintrc.json rename to ee/packages/ddp-client/.eslintrc.json diff --git a/ee/packages/ddp-client/LICENSE b/ee/packages/ddp-client/LICENSE new file mode 100644 index 0000000000000..db5cba161765b --- /dev/null +++ b/ee/packages/ddp-client/LICENSE @@ -0,0 +1,43 @@ +The Rocket.Chat Enterprise Edition (EE) license (the "EE License") +Copyright (c) 2015-2023 Rocket.Chat Technologies Corp. + +With regard to the Rocket.Chat Software: + +This software and associated documentation files (the "Software") may only be +used in production, if you (and any entity that you represent) have agreed to, +and are in compliance with, the Rocket.Chat Subscription Terms of Service, +available at https://rocket.chat/terms (the "EE Terms"), or other agreement +governing the use of the Software, as agreed by you and Rocket.Chat, and otherwise +have a valid Rocket.Chat Enterprise Edition subscription for the correct number +of user seats. Subject to the foregoing sentence, you are free to modify this +Software and publish patches to the Software. You agree that Rocket.Chat and/or +its licensors (as applicable) retain all right, title and interest in and to all +such modifications and/or patches, and all such modifications and/or patches may +only be used, copied, modified, displayed, distributed, or otherwise exploited +with a valid Rocket.Chat Enterprise Edition subscription for the correct number +of user seats. Notwithstanding the foregoing, you may copy and modify the Software +for development and testing purposes, without requiring a Subscription. You agree +that Rocket.Chat and/or its licensors (as applicable) retain all right, title and +interest in and to all such modifications. You are not granted any other rights +beyond what is expressly stated herein. Subject to the foregoing, it is forbidden +to copy, merge, publish, distribute, sublicense, and/or sell the Software. + +This EE License applies only to the part of this Software that is not distributed +as part of Rocket.Chat Community Edition (CE). Any part of this Software distributed +as part of Rocket.Chat CE or is served client-side as an image, font, cascading +stylesheet (CSS), file which produces or is compiled, arranged, augmented, or combined +into client-side JavaScript, in whole or in part, is copyrighted under the MIT Expat +license. The full text of this EE License shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +For all third-party components incorporated into the Rocket.Chat Software, +those components are licensed under the original license provided by the owner +of the applicable component. diff --git a/packages/ddp-client/__examples__/simple.ts b/ee/packages/ddp-client/__examples__/simple.ts similarity index 100% rename from packages/ddp-client/__examples__/simple.ts rename to ee/packages/ddp-client/__examples__/simple.ts diff --git a/packages/ddp-client/__mocks__/ws.ts b/ee/packages/ddp-client/__mocks__/ws.ts similarity index 100% rename from packages/ddp-client/__mocks__/ws.ts rename to ee/packages/ddp-client/__mocks__/ws.ts diff --git a/packages/ddp-client/__tests__/ClientStream.spec.ts b/ee/packages/ddp-client/__tests__/ClientStream.spec.ts similarity index 100% rename from packages/ddp-client/__tests__/ClientStream.spec.ts rename to ee/packages/ddp-client/__tests__/ClientStream.spec.ts diff --git a/packages/ddp-client/__tests__/Connection.spec.ts b/ee/packages/ddp-client/__tests__/Connection.spec.ts similarity index 100% rename from packages/ddp-client/__tests__/Connection.spec.ts rename to ee/packages/ddp-client/__tests__/Connection.spec.ts diff --git a/packages/ddp-client/__tests__/DDPDispatcher.spec.ts b/ee/packages/ddp-client/__tests__/DDPDispatcher.spec.ts similarity index 100% rename from packages/ddp-client/__tests__/DDPDispatcher.spec.ts rename to ee/packages/ddp-client/__tests__/DDPDispatcher.spec.ts diff --git a/packages/ddp-client/__tests__/DDPSDK.spec.ts b/ee/packages/ddp-client/__tests__/DDPSDK.spec.ts similarity index 100% rename from packages/ddp-client/__tests__/DDPSDK.spec.ts rename to ee/packages/ddp-client/__tests__/DDPSDK.spec.ts diff --git a/packages/ddp-client/__tests__/MinimalDDPClient.spec.ts b/ee/packages/ddp-client/__tests__/MinimalDDPClient.spec.ts similarity index 100% rename from packages/ddp-client/__tests__/MinimalDDPClient.spec.ts rename to ee/packages/ddp-client/__tests__/MinimalDDPClient.spec.ts diff --git a/packages/ddp-client/__tests__/Timeout.spec.ts b/ee/packages/ddp-client/__tests__/Timeout.spec.ts similarity index 100% rename from packages/ddp-client/__tests__/Timeout.spec.ts rename to ee/packages/ddp-client/__tests__/Timeout.spec.ts diff --git a/packages/ddp-client/__tests__/helpers/index.ts b/ee/packages/ddp-client/__tests__/helpers/index.ts similarity index 100% rename from packages/ddp-client/__tests__/helpers/index.ts rename to ee/packages/ddp-client/__tests__/helpers/index.ts diff --git a/packages/ddp-client/__tests__/wrapOnceEventIntoPromise.spec.ts b/ee/packages/ddp-client/__tests__/wrapOnceEventIntoPromise.spec.ts similarity index 100% rename from packages/ddp-client/__tests__/wrapOnceEventIntoPromise.spec.ts rename to ee/packages/ddp-client/__tests__/wrapOnceEventIntoPromise.spec.ts diff --git a/packages/ddp-client/jest.config.ts b/ee/packages/ddp-client/jest.config.ts similarity index 100% rename from packages/ddp-client/jest.config.ts rename to ee/packages/ddp-client/jest.config.ts diff --git a/packages/ddp-client/package.json b/ee/packages/ddp-client/package.json similarity index 89% rename from packages/ddp-client/package.json rename to ee/packages/ddp-client/package.json index 83c45259e5e6c..8a8c73c844364 100644 --- a/packages/ddp-client/package.json +++ b/ee/packages/ddp-client/package.json @@ -1,7 +1,6 @@ { "name": "@rocket.chat/ddp-client", "version": "0.0.1", - "private": true, "devDependencies": { "@swc/core": "^1.3.66", "@swc/jest": "^0.2.26", @@ -35,7 +34,6 @@ }, "dependencies": { "@rocket.chat/api-client": "workspace:^", - "@rocket.chat/rest-typings": "workspace:^", - "@rocket.chat/ui-contexts": "workspace:^" + "@rocket.chat/rest-typings": "workspace:^" } } diff --git a/packages/ddp-client/src/ClientStream.ts b/ee/packages/ddp-client/src/ClientStream.ts similarity index 100% rename from packages/ddp-client/src/ClientStream.ts rename to ee/packages/ddp-client/src/ClientStream.ts diff --git a/packages/ddp-client/src/Connection.ts b/ee/packages/ddp-client/src/Connection.ts similarity index 100% rename from packages/ddp-client/src/Connection.ts rename to ee/packages/ddp-client/src/Connection.ts diff --git a/packages/ddp-client/src/DDPDispatcher.ts b/ee/packages/ddp-client/src/DDPDispatcher.ts similarity index 100% rename from packages/ddp-client/src/DDPDispatcher.ts rename to ee/packages/ddp-client/src/DDPDispatcher.ts diff --git a/packages/ddp-client/src/DDPSDK.ts b/ee/packages/ddp-client/src/DDPSDK.ts similarity index 100% rename from packages/ddp-client/src/DDPSDK.ts rename to ee/packages/ddp-client/src/DDPSDK.ts diff --git a/packages/ddp-client/src/MinimalDDPClient.ts b/ee/packages/ddp-client/src/MinimalDDPClient.ts similarity index 100% rename from packages/ddp-client/src/MinimalDDPClient.ts rename to ee/packages/ddp-client/src/MinimalDDPClient.ts diff --git a/packages/ddp-client/src/README.md b/ee/packages/ddp-client/src/README.md similarity index 100% rename from packages/ddp-client/src/README.md rename to ee/packages/ddp-client/src/README.md diff --git a/packages/ddp-client/src/TimeoutControl.ts b/ee/packages/ddp-client/src/TimeoutControl.ts similarity index 100% rename from packages/ddp-client/src/TimeoutControl.ts rename to ee/packages/ddp-client/src/TimeoutControl.ts diff --git a/packages/ddp-client/src/index.ts b/ee/packages/ddp-client/src/index.ts similarity index 100% rename from packages/ddp-client/src/index.ts rename to ee/packages/ddp-client/src/index.ts diff --git a/packages/ddp-client/src/legacy/RocketchatSDKLegacy.ts b/ee/packages/ddp-client/src/legacy/RocketchatSDKLegacy.ts similarity index 98% rename from packages/ddp-client/src/legacy/RocketchatSDKLegacy.ts rename to ee/packages/ddp-client/src/legacy/RocketchatSDKLegacy.ts index fb307b2a83f70..aba6ffe56e313 100644 --- a/packages/ddp-client/src/legacy/RocketchatSDKLegacy.ts +++ b/ee/packages/ddp-client/src/legacy/RocketchatSDKLegacy.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-this-alias */ -import type { StreamNames, StreamKeys, StreamerCallbackArgs } from '@rocket.chat/ui-contexts/src/ServerContext/streams'; -import type { ServerMethods, ServerMethodReturn } from '@rocket.chat/ui-contexts'; + import type { IMessage, Serialized } from '@rocket.chat/core-typings'; import type { OperationParams, OperationResult } from '@rocket.chat/rest-typings'; import { Emitter } from '@rocket.chat/emitter'; @@ -21,6 +20,8 @@ import { ClientStreamImpl } from '../ClientStream'; import { AccountImpl } from '../types/Account'; import { TimeoutControl } from '../TimeoutControl'; import type { ClientStream } from '../types/ClientStream'; +import type { ServerMethodReturn, ServerMethods } from '../types/methods'; +import type { StreamNames, StreamKeys, StreamerCallbackArgs } from '../types/streams'; declare module '../ClientStream' { interface ClientStream { diff --git a/packages/ddp-client/src/legacy/types/SDKLegacy.ts b/ee/packages/ddp-client/src/legacy/types/SDKLegacy.ts similarity index 98% rename from packages/ddp-client/src/legacy/types/SDKLegacy.ts rename to ee/packages/ddp-client/src/legacy/types/SDKLegacy.ts index 5e422d3addd63..740ea96b49291 100644 --- a/packages/ddp-client/src/legacy/types/SDKLegacy.ts +++ b/ee/packages/ddp-client/src/legacy/types/SDKLegacy.ts @@ -1,7 +1,8 @@ -import type { StreamerCallbackArgs } from '@rocket.chat/ui-contexts/src/ServerContext/streams'; import type { IMessage, Serialized } from '@rocket.chat/core-typings'; import type { OperationParams, OperationResult } from '@rocket.chat/rest-typings'; +import type { StreamerCallbackArgs } from '../../types/streams'; + export interface APILegacy { users: { all(fields?: { name: 1; username: 1; status: 1; type: 1 }): Promise<Serialized<OperationResult<'GET', '/v1/users.list'>>>; diff --git a/packages/ddp-client/src/livechat/LivechatClientImpl.ts b/ee/packages/ddp-client/src/livechat/LivechatClientImpl.ts similarity index 98% rename from packages/ddp-client/src/livechat/LivechatClientImpl.ts rename to ee/packages/ddp-client/src/livechat/LivechatClientImpl.ts index 8005528b35c8f..996e1c47eb91c 100644 --- a/packages/ddp-client/src/livechat/LivechatClientImpl.ts +++ b/ee/packages/ddp-client/src/livechat/LivechatClientImpl.ts @@ -1,5 +1,3 @@ -import type { StreamNames, StreamKeys, StreamerCallbackArgs } from '@rocket.chat/ui-contexts/src/ServerContext/streams'; -import type { ServerMethods, ServerMethodReturn } from '@rocket.chat/ui-contexts'; import { Emitter } from '@rocket.chat/emitter'; import { RestClient } from '@rocket.chat/api-client'; import type { IOmnichannelRoom, Serialized } from '@rocket.chat/core-typings'; @@ -14,6 +12,8 @@ import { ConnectionImpl } from '../Connection'; import { AccountImpl } from '../types/Account'; import { TimeoutControl } from '../TimeoutControl'; import type { ClientStream } from '../types/ClientStream'; +import type { ServerMethodReturn, ServerMethods } from '../types/methods'; +import type { StreamKeys, StreamNames, StreamerCallbackArgs } from '../types/streams'; declare module '../ClientStream' { interface ClientStream { diff --git a/packages/ddp-client/src/livechat/types/LivechatSDK.ts b/ee/packages/ddp-client/src/livechat/types/LivechatSDK.ts similarity index 98% rename from packages/ddp-client/src/livechat/types/LivechatSDK.ts rename to ee/packages/ddp-client/src/livechat/types/LivechatSDK.ts index 171d6dfc24100..c0b36abadb15c 100644 --- a/packages/ddp-client/src/livechat/types/LivechatSDK.ts +++ b/ee/packages/ddp-client/src/livechat/types/LivechatSDK.ts @@ -1,6 +1,7 @@ import type { Serialized } from '@rocket.chat/core-typings'; import type { OperationParams, OperationResult } from '@rocket.chat/rest-typings'; -import type { StreamerCallbackArgs } from '@rocket.chat/ui-contexts/src/ServerContext/streams'; + +import type { StreamerCallbackArgs } from '../../types/streams'; export type LivechatRoomEvents<T> = StreamerCallbackArgs<'livechat-room', `${string}`> extends [infer A] ? A extends { type: T; data: unknown } diff --git a/packages/ddp-client/src/types/Account.ts b/ee/packages/ddp-client/src/types/Account.ts similarity index 100% rename from packages/ddp-client/src/types/Account.ts rename to ee/packages/ddp-client/src/types/Account.ts diff --git a/packages/ddp-client/src/types/ClientStream.ts b/ee/packages/ddp-client/src/types/ClientStream.ts similarity index 100% rename from packages/ddp-client/src/types/ClientStream.ts rename to ee/packages/ddp-client/src/types/ClientStream.ts diff --git a/packages/ddp-client/src/types/DDPClient.ts b/ee/packages/ddp-client/src/types/DDPClient.ts similarity index 100% rename from packages/ddp-client/src/types/DDPClient.ts rename to ee/packages/ddp-client/src/types/DDPClient.ts diff --git a/packages/ddp-client/src/types/IncomingPayload.ts b/ee/packages/ddp-client/src/types/IncomingPayload.ts similarity index 100% rename from packages/ddp-client/src/types/IncomingPayload.ts rename to ee/packages/ddp-client/src/types/IncomingPayload.ts diff --git a/packages/ddp-client/src/types/OutgoingPayload.ts b/ee/packages/ddp-client/src/types/OutgoingPayload.ts similarity index 100% rename from packages/ddp-client/src/types/OutgoingPayload.ts rename to ee/packages/ddp-client/src/types/OutgoingPayload.ts diff --git a/packages/ddp-client/src/types/RemoveListener.ts b/ee/packages/ddp-client/src/types/RemoveListener.ts similarity index 100% rename from packages/ddp-client/src/types/RemoveListener.ts rename to ee/packages/ddp-client/src/types/RemoveListener.ts diff --git a/packages/ddp-client/src/types/SDK.ts b/ee/packages/ddp-client/src/types/SDK.ts similarity index 100% rename from packages/ddp-client/src/types/SDK.ts rename to ee/packages/ddp-client/src/types/SDK.ts diff --git a/packages/ddp-client/src/types/Subscription.ts b/ee/packages/ddp-client/src/types/Subscription.ts similarity index 100% rename from packages/ddp-client/src/types/Subscription.ts rename to ee/packages/ddp-client/src/types/Subscription.ts diff --git a/packages/ddp-client/src/types/connectionPayloads.ts b/ee/packages/ddp-client/src/types/connectionPayloads.ts similarity index 100% rename from packages/ddp-client/src/types/connectionPayloads.ts rename to ee/packages/ddp-client/src/types/connectionPayloads.ts diff --git a/packages/ddp-client/src/types/heartbeatsPayloads.ts b/ee/packages/ddp-client/src/types/heartbeatsPayloads.ts similarity index 100% rename from packages/ddp-client/src/types/heartbeatsPayloads.ts rename to ee/packages/ddp-client/src/types/heartbeatsPayloads.ts diff --git a/packages/ui-contexts/src/ServerContext/methods.ts b/ee/packages/ddp-client/src/types/methods.ts similarity index 100% rename from packages/ui-contexts/src/ServerContext/methods.ts rename to ee/packages/ddp-client/src/types/methods.ts diff --git a/packages/ddp-client/src/types/methodsPayloads.ts b/ee/packages/ddp-client/src/types/methodsPayloads.ts similarity index 100% rename from packages/ddp-client/src/types/methodsPayloads.ts rename to ee/packages/ddp-client/src/types/methodsPayloads.ts diff --git a/packages/ddp-client/src/types/publicationPayloads.ts b/ee/packages/ddp-client/src/types/publicationPayloads.ts similarity index 100% rename from packages/ddp-client/src/types/publicationPayloads.ts rename to ee/packages/ddp-client/src/types/publicationPayloads.ts diff --git a/packages/ui-contexts/src/ServerContext/streams.ts b/ee/packages/ddp-client/src/types/streams.ts similarity index 100% rename from packages/ui-contexts/src/ServerContext/streams.ts rename to ee/packages/ddp-client/src/types/streams.ts diff --git a/packages/ddp-client/src/wrapOnceEventIntoPromise.ts b/ee/packages/ddp-client/src/wrapOnceEventIntoPromise.ts similarity index 100% rename from packages/ddp-client/src/wrapOnceEventIntoPromise.ts rename to ee/packages/ddp-client/src/wrapOnceEventIntoPromise.ts diff --git a/packages/ddp-client/tsconfig.json b/ee/packages/ddp-client/tsconfig.json similarity index 81% rename from packages/ddp-client/tsconfig.json rename to ee/packages/ddp-client/tsconfig.json index b98ff74ba385e..29b8cb051fe34 100644 --- a/packages/ddp-client/tsconfig.json +++ b/ee/packages/ddp-client/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "../../tsconfig.base.client.json", + "extends": "../../../tsconfig.base.client.json", "compilerOptions": { "rootDir": "./src", "outDir": "./dist", diff --git a/packages/core-typings/package.json b/packages/core-typings/package.json index 1e049be63ca11..2cea7c47872ca 100644 --- a/packages/core-typings/package.json +++ b/packages/core-typings/package.json @@ -1,7 +1,6 @@ { "name": "@rocket.chat/core-typings", "version": "6.2.6", - "private": true, "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", "eslint": "~8.43.0", diff --git a/packages/rest-typings/package.json b/packages/rest-typings/package.json index 98cfd1bf67f67..08ecb8b1e1bdc 100644 --- a/packages/rest-typings/package.json +++ b/packages/rest-typings/package.json @@ -1,7 +1,6 @@ { "name": "@rocket.chat/rest-typings", "version": "6.2.6", - "private": true, "devDependencies": { "@rocket.chat/eslint-config": "workspace:^", "@types/jest": "~29.5.2", diff --git a/packages/ui-contexts/package.json b/packages/ui-contexts/package.json index 2fac15a348580..cdac72d2176bf 100644 --- a/packages/ui-contexts/package.json +++ b/packages/ui-contexts/package.json @@ -22,6 +22,7 @@ }, "peerDependencies": { "@rocket.chat/core-typings": "workspace:^", + "@rocket.chat/ddp-client": "workspace:^", "@rocket.chat/emitter": "*", "@rocket.chat/fuselage-hooks": "*", "@rocket.chat/rest-typings": "workspace:^", diff --git a/packages/ui-contexts/src/ServerContext/ServerContext.ts b/packages/ui-contexts/src/ServerContext/ServerContext.ts index 7c8ad7ff486e7..7e4b8e9e28fa6 100644 --- a/packages/ui-contexts/src/ServerContext/ServerContext.ts +++ b/packages/ui-contexts/src/ServerContext/ServerContext.ts @@ -1,9 +1,8 @@ import type { IServerInfo, Serialized } from '@rocket.chat/core-typings'; import type { Method, OperationParams, OperationResult, PathFor, PathPattern, UrlParams } from '@rocket.chat/rest-typings'; import { createContext } from 'react'; - -import type { StreamKeys, StreamNames, StreamerCallbackArgs } from './streams'; -import type { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from './methods'; +import type { StreamKeys, StreamNames, StreamerCallbackArgs } from '@rocket.chat/ddp-client/src/types/streams'; +import type { ServerMethodName, ServerMethodParameters, ServerMethodReturn } from '@rocket.chat/ddp-client/src/types/methods'; export type UploadResult = { success: boolean; diff --git a/packages/ui-contexts/src/hooks/useMethod.ts b/packages/ui-contexts/src/hooks/useMethod.ts index 4381189039f84..9d59d16e6a733 100644 --- a/packages/ui-contexts/src/hooks/useMethod.ts +++ b/packages/ui-contexts/src/hooks/useMethod.ts @@ -1,7 +1,7 @@ import { useCallback, useContext } from 'react'; +import type { ServerMethodFunction, ServerMethodParameters, ServerMethods } from '@rocket.chat/ddp-client/src/types/methods'; import { ServerContext } from '../ServerContext'; -import type { ServerMethodFunction, ServerMethodParameters, ServerMethods } from '../ServerContext/methods'; export const useMethod = <MethodName extends keyof ServerMethods>(methodName: MethodName): ServerMethodFunction<MethodName> => { const { callMethod } = useContext(ServerContext); diff --git a/packages/ui-contexts/src/hooks/useStream.ts b/packages/ui-contexts/src/hooks/useStream.ts index 6aef7d9a0191f..34c49c5a778ca 100644 --- a/packages/ui-contexts/src/hooks/useStream.ts +++ b/packages/ui-contexts/src/hooks/useStream.ts @@ -1,4 +1,4 @@ -import type { StreamNames, StreamerEvents, StreamKeys, StreamerCallbackArgs } from '@rocket.chat/ui-contexts/src/ServerContext/streams'; +import type { StreamNames, StreamerEvents, StreamKeys, StreamerCallbackArgs } from '@rocket.chat/ddp-client/src/types/streams'; import { useContext, useMemo } from 'react'; import { ServerContext } from '../ServerContext'; diff --git a/packages/ui-contexts/src/index.ts b/packages/ui-contexts/src/index.ts index ec981344ed292..941eeeec61ec0 100644 --- a/packages/ui-contexts/src/index.ts +++ b/packages/ui-contexts/src/index.ts @@ -89,8 +89,21 @@ export { useSetOutputMediaDevice } from './hooks/useSetOutputMediaDevice'; export { useSetInputMediaDevice } from './hooks/useSetInputMediaDevice'; export { useAccountsCustomFields } from './hooks/useAccountsCustomFields'; -export { ServerMethods, ServerMethodName, ServerMethodParameters, ServerMethodReturn, ServerMethodFunction } from './ServerContext/methods'; -export { StreamerEvents, StreamNames, StreamKeys, StreamerConfigs, StreamerConfig, StreamerCallbackArgs } from './ServerContext/streams'; +export { + ServerMethods, + ServerMethodName, + ServerMethodParameters, + ServerMethodReturn, + ServerMethodFunction, +} from '@rocket.chat/ddp-client/src/types/methods'; +export { + StreamerEvents, + StreamNames, + StreamKeys, + StreamerConfigs, + StreamerConfig, + StreamerCallbackArgs, +} from '@rocket.chat/ddp-client/src/types/streams'; export { UploadResult } from './ServerContext'; export { TranslationKey, TranslationLanguage } from './TranslationContext'; export { Fields } from './UserContext'; diff --git a/yarn.lock b/yarn.lock index 3ffbb18d891f8..76c68d5fd8ddc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9213,9 +9213,9 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/api-client@workspace:^, @rocket.chat/api-client@workspace:packages/api-client": +"@rocket.chat/api-client@workspace:^, @rocket.chat/api-client@workspace:ee/packages/api-client": version: 0.0.0-use.local - resolution: "@rocket.chat/api-client@workspace:packages/api-client" + resolution: "@rocket.chat/api-client@workspace:ee/packages/api-client" dependencies: "@rocket.chat/core-typings": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" @@ -9427,13 +9427,12 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/ddp-client@workspace:^, @rocket.chat/ddp-client@workspace:packages/ddp-client": +"@rocket.chat/ddp-client@workspace:^, @rocket.chat/ddp-client@workspace:ee/packages/ddp-client": version: 0.0.0-use.local - resolution: "@rocket.chat/ddp-client@workspace:packages/ddp-client" + resolution: "@rocket.chat/ddp-client@workspace:ee/packages/ddp-client" dependencies: "@rocket.chat/api-client": "workspace:^" "@rocket.chat/rest-typings": "workspace:^" - "@rocket.chat/ui-contexts": "workspace:^" "@swc/core": ^1.3.66 "@swc/jest": ^0.2.26 "@types/jest": ^29.5.2 @@ -10895,6 +10894,7 @@ __metadata: use-sync-external-store: ^1.2.0 peerDependencies: "@rocket.chat/core-typings": "workspace:^" + "@rocket.chat/ddp-client": "workspace:^" "@rocket.chat/emitter": "*" "@rocket.chat/fuselage-hooks": "*" "@rocket.chat/rest-typings": "workspace:^" From 94477bd9f83ceb11bc81e8b43a260471a36c11f0 Mon Sep 17 00:00:00 2001 From: Kevin Aleman <kaleman960@gmail.com> Date: Thu, 6 Jul 2023 08:21:39 -0600 Subject: [PATCH 53/79] fix: Only update online & unavailable agents when opening business hours (#29540) --- .changeset/thirty-lobsters-vanish.md | 6 +++++ .../livechat/server/business-hour/Helper.ts | 3 +++ .../livechat/server/business-hour/Single.ts | 12 +++------ .../server/business-hour/Helper.ts | 1 + apps/meteor/server/models/raw/Users.js | 27 ++++++++++++++----- .../model-typings/src/models/IUsersModel.ts | 4 ++- 6 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 .changeset/thirty-lobsters-vanish.md diff --git a/.changeset/thirty-lobsters-vanish.md b/.changeset/thirty-lobsters-vanish.md new file mode 100644 index 0000000000000..56fb91e4cbc6d --- /dev/null +++ b/.changeset/thirty-lobsters-vanish.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/model-typings": patch +--- + +Update database query to only update online & unavailable agents when opening & closing business hours diff --git a/apps/meteor/app/livechat/server/business-hour/Helper.ts b/apps/meteor/app/livechat/server/business-hour/Helper.ts index ccf0381f0c0ca..2106a962e4f7a 100644 --- a/apps/meteor/app/livechat/server/business-hour/Helper.ts +++ b/apps/meteor/app/livechat/server/business-hour/Helper.ts @@ -55,6 +55,9 @@ export const openBusinessHourDefault = async (): Promise<void> => { const businessHoursToOpenIds = (await filterBusinessHoursThatMustBeOpened(activeBusinessHours)).map((businessHour) => businessHour._id); businessHourLogger.debug({ msg: 'Opening default business hours', businessHoursToOpenIds }); await Users.openAgentsBusinessHoursByBusinessHourId(businessHoursToOpenIds); + if (businessHoursToOpenIds.length) { + await Users.makeAgentsWithinBusinessHourAvailable(); + } await Users.updateLivechatStatusBasedOnBusinessHours(); businessHourLogger.debug('Done opening default business hours'); }; diff --git a/apps/meteor/app/livechat/server/business-hour/Single.ts b/apps/meteor/app/livechat/server/business-hour/Single.ts index b351c480ab28b..df9b3bce2a2e5 100644 --- a/apps/meteor/app/livechat/server/business-hour/Single.ts +++ b/apps/meteor/app/livechat/server/business-hour/Single.ts @@ -6,13 +6,9 @@ import { openBusinessHourDefault } from './Helper'; import { businessHourLogger } from '../lib/logger'; export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior implements IBusinessHourBehavior { - async openBusinessHoursByDayAndHour(day: string, hour: string): Promise<void> { - const businessHoursIds = ( - await this.BusinessHourRepository.findActiveBusinessHoursToOpen(day, hour, LivechatBusinessHourTypes.DEFAULT, { - projection: { _id: 1 }, - }) - ).map((businessHour) => businessHour._id); - this.UsersRepository.openAgentsBusinessHoursByBusinessHourId(businessHoursIds); + async openBusinessHoursByDayAndHour(): Promise<void> { + businessHourLogger.debug('opening single business hour'); + return openBusinessHourDefault(); } async closeBusinessHoursByDayAndHour(day: string, hour: string): Promise<void> { @@ -22,7 +18,7 @@ export class SingleBusinessHourBehavior extends AbstractBusinessHourBehavior imp }) ).map((businessHour) => businessHour._id); await this.UsersRepository.closeAgentsBusinessHoursByBusinessHourIds(businessHoursIds); - this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); + await this.UsersRepository.updateLivechatStatusBasedOnBusinessHours(); } async onStartBusinessHours(): Promise<void> { diff --git a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts index b51294287d55a..5a612195eb930 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/business-hour/Helper.ts @@ -78,6 +78,7 @@ export const openBusinessHour = async ( }); await Users.addBusinessHourByAgentIds(agentIds, businessHour._id); + await Users.makeAgentsWithinBusinessHourAvailable(agentIds); if (updateLivechatStatus) { await Users.updateLivechatStatusBasedOnBusinessHours(); } diff --git a/apps/meteor/server/models/raw/Users.js b/apps/meteor/server/models/raw/Users.js index 996c3abaebcd1..d9195da6a4370 100644 --- a/apps/meteor/server/models/raw/Users.js +++ b/apps/meteor/server/models/raw/Users.js @@ -857,9 +857,6 @@ export class UsersRaw extends BaseRaw { }; const update = { - $set: { - statusLivechat: 'available', - }, $addToSet: { openBusinessHours: { $each: businessHourIds }, }, @@ -883,6 +880,25 @@ export class UsersRaw extends BaseRaw { return this.updateMany(query, update); } + makeAgentsWithinBusinessHourAvailable(agentIds) { + const query = { + ...(agentIds && { _id: { $in: agentIds } }), + roles: 'livechat-agent', + // Exclude away users + status: 'online', + // Exclude users that are already available, maybe due to other business hour + statusLivechat: 'not-available', + }; + + const update = { + $set: { + statusLivechat: 'available', + }, + }; + + return this.updateMany(query, update); + } + removeBusinessHourByAgentIds(agentIds = [], businessHourId) { const query = { _id: { $in: agentIds }, @@ -904,9 +920,6 @@ export class UsersRaw extends BaseRaw { }; const update = { - $set: { - statusLivechat: 'available', - }, $addToSet: { openBusinessHours: businessHourId, }, @@ -947,6 +960,8 @@ export class UsersRaw extends BaseRaw { const query = { $or: [{ openBusinessHours: { $exists: false } }, { openBusinessHours: { $size: 0 } }], roles: 'livechat-agent', + // Avoid unnecessary updates + statusLivechat: 'available', ...(Array.isArray(userIds) && userIds.length > 0 && { _id: { $in: userIds } }), }; diff --git a/packages/model-typings/src/models/IUsersModel.ts b/packages/model-typings/src/models/IUsersModel.ts index 70b161acb6f3a..fecc7386f4017 100644 --- a/packages/model-typings/src/models/IUsersModel.ts +++ b/packages/model-typings/src/models/IUsersModel.ts @@ -128,10 +128,12 @@ export interface IUsersModel extends IBaseModel<IUser> { openAgentsBusinessHoursByBusinessHourId(businessHourIds: any): any; - openAgentBusinessHoursByBusinessHourIdsAndAgentId(businessHourIds: any, agentId: any): any; + openAgentBusinessHoursByBusinessHourIdsAndAgentId(businessHourIds: string[], agentId: string): Promise<UpdateResult | Document>; addBusinessHourByAgentIds(agentIds: string[], businessHourId: string): any; + makeAgentsWithinBusinessHourAvailable(agentIds?: string[]): Promise<UpdateResult | Document>; + removeBusinessHourByAgentIds(agentIds: any, businessHourId: any): any; openBusinessHourToAgentsWithoutDepartment(agentIdsWithDepartment: any, businessHourId: any): any; From e19832ff4c20d7c7a03b68f5dcd43845eb2894d5 Mon Sep 17 00:00:00 2001 From: Douglas Fabris <devfabris@gmail.com> Date: Thu, 6 Jul 2023 12:04:20 -0300 Subject: [PATCH 54/79] chore: Most used emojis as `quickReactions` (#29735) Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com> --- apps/meteor/app/emoji/client/helpers.ts | 12 +++++++ .../components/message/toolbox/Toolbox.tsx | 11 +++---- .../client/contexts/EmojiPickerContext.ts | 2 ++ .../client/providers/EmojiPickerProvider.tsx | 32 +++++++++++++++++-- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/apps/meteor/app/emoji/client/helpers.ts b/apps/meteor/app/emoji/client/helpers.ts index ac49033fe1929..d9fba8720d1c1 100644 --- a/apps/meteor/app/emoji/client/helpers.ts +++ b/apps/meteor/app/emoji/client/helpers.ts @@ -155,3 +155,15 @@ export const updateRecent = (recentList: string[]) => { !recentPkgList.includes(_emoji) && recentPkgList.push(_emoji); }); }; + +const getEmojiRender = (emojiName: string) => { + const emojiPackageName = emoji.list[emojiName]?.emojiPackage; + const emojiPackage = emoji.packages[emojiPackageName]; + return emojiPackage.render(emojiName); +}; + +export const getFrequentEmoji = (frequentEmoji: string[]) => { + return frequentEmoji?.map((frequentEmoji) => { + return { emoji: frequentEmoji, image: getEmojiRender(`:${frequentEmoji}:`) }; + }); +}; diff --git a/apps/meteor/client/components/message/toolbox/Toolbox.tsx b/apps/meteor/client/components/message/toolbox/Toolbox.tsx index 731502428a560..7ab5895ef6f15 100644 --- a/apps/meteor/client/components/message/toolbox/Toolbox.tsx +++ b/apps/meteor/client/components/message/toolbox/Toolbox.tsx @@ -55,7 +55,7 @@ const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): const mapSettings = useMemo(() => Object.fromEntries(settings.map((setting) => [setting._id, setting.value])), [settings]); const chat = useChat(); - const { addRecentEmoji, emojiListByCategory } = useEmojiPickerData(); + const { quickReactions, addRecentEmoji } = useEmojiPickerData(); const actionsQueryResult = useQuery(['rooms', room._id, 'messages', message._id, 'actions'] as const, async () => { const messageActions = await MessageAction.getButtons( @@ -87,14 +87,11 @@ const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): addRecentEmoji(emoji); }; - const recentList = emojiListByCategory.filter(({ key }) => key === 'recent')[0].emojis.list; - return ( <MessageToolbox> - {recentList.length > 0 && - recentList.slice(0, 3).map(({ emoji, image }) => { - return <EmojiElement small key={emoji} title={emoji} emoji={emoji} image={image} onClick={() => handleSetReaction(emoji)} />; - })} + {quickReactions.slice(0, 3).map(({ emoji, image }) => { + return <EmojiElement small key={emoji} title={emoji} emoji={emoji} image={image} onClick={() => handleSetReaction(emoji)} />; + })} {actionsQueryResult.data?.message.map((action) => ( <MessageToolboxItem onClick={(e): void => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions })} diff --git a/apps/meteor/client/contexts/EmojiPickerContext.ts b/apps/meteor/client/contexts/EmojiPickerContext.ts index 594ae24e3e691..50490182d519e 100644 --- a/apps/meteor/client/contexts/EmojiPickerContext.ts +++ b/apps/meteor/client/contexts/EmojiPickerContext.ts @@ -19,6 +19,7 @@ type EmojiPickerContextValue = { customItemsLimit: number; setCustomItemsLimit: (limit: number) => void; setActualTone: (tone: number) => void; + quickReactions: { emoji: string; image: string }[]; }; export const EmojiPickerContext = createContext<EmojiPickerContextValue | undefined>(undefined); @@ -54,4 +55,5 @@ export const useEmojiPickerData = () => ({ customItemsLimit: useEmojiPickerContext().customItemsLimit, setCustomItemsLimit: useEmojiPickerContext().setCustomItemsLimit, setActualTone: useEmojiPickerContext().setActualTone, + quickReactions: useEmojiPickerContext().quickReactions, }); diff --git a/apps/meteor/client/providers/EmojiPickerProvider.tsx b/apps/meteor/client/providers/EmojiPickerProvider.tsx index d112ccf205a73..5c9f3a8e99464 100644 --- a/apps/meteor/client/providers/EmojiPickerProvider.tsx +++ b/apps/meteor/client/providers/EmojiPickerProvider.tsx @@ -3,7 +3,7 @@ import type { ReactNode, ReactElement } from 'react'; import React, { useState, useCallback, useMemo, useEffect } from 'react'; import type { EmojiByCategory } from '../../app/emoji/client'; -import { emoji, updateRecent, createEmojiList, createPickerEmojis, CUSTOM_CATEGORY } from '../../app/emoji/client'; +import { emoji, getFrequentEmoji, updateRecent, createEmojiList, createPickerEmojis, CUSTOM_CATEGORY } from '../../app/emoji/client'; import { EmojiPickerContext } from '../contexts/EmojiPickerContext'; import EmojiPicker from '../views/composer/EmojiPicker/EmojiPicker'; @@ -18,6 +18,28 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen const [currentCategory, setCurrentCategory] = useState('recent'); const [customItemsLimit, setCustomItemsLimit] = useState(DEFAULT_ITEMS_LIMIT); + const [frequentEmojis, setFrequentEmojis] = useLocalStorage<[string, number][]>('emoji.frequent', []); + + const [quickReactions, setQuickReactions] = useState<{ emoji: string; image: string }[]>(() => + getFrequentEmoji(frequentEmojis.map(([emoji]) => emoji)), + ); + + const addFrequentEmojis = useCallback( + (emoji: string) => { + const empty: [string, number][] = frequentEmojis.some(([emojiName]) => emojiName === emoji) ? [] : [[emoji, 0]]; + + const sortedFrequent = [...empty, ...frequentEmojis] + .map(([emojiName, count]) => { + return (emojiName === emoji ? [emojiName, Math.min(count + 5, 100)] : [emojiName, Math.max(count - 1, 0)]) as [string, number]; + }) + .sort(([, frequentA], [, frequentB]) => frequentB - frequentA); + + setFrequentEmojis(sortedFrequent); + setQuickReactions(getFrequentEmoji(sortedFrequent.map(([emoji]) => emoji))); + }, + [frequentEmojis, setFrequentEmojis], + ); + // TODO: improve this update const updateEmojiListByCategory = useCallback( (categoryKey: string, limit: number = DEFAULT_ITEMS_LIMIT) => { @@ -40,6 +62,8 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen const addRecentEmoji = useCallback( (_emoji: string) => { + addFrequentEmojis(_emoji); + const recent = recentEmojis || []; const pos = recent.indexOf(_emoji as never); @@ -56,7 +80,7 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen emoji.packages.base.emojisByCategory.recent = recent; updateEmojiListByCategory('recent'); }, - [recentEmojis, setRecentEmojis, updateEmojiListByCategory], + [recentEmojis, setRecentEmojis, updateEmojiListByCategory, addFrequentEmojis], ); useEffect(() => { @@ -66,7 +90,7 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen const emojis = createPickerEmojis(customItemsLimit, actualTone, recentEmojis, setRecentEmojis); setEmojiListByCategory(emojis); - }, [actualTone, recentEmojis, customItemsLimit, currentCategory, setRecentEmojis]); + }, [actualTone, recentEmojis, customItemsLimit, currentCategory, setRecentEmojis, frequentEmojis]); const open = useCallback((ref: Element, callback: (emoji: string) => void) => { return setEmojiPicker(<EmojiPicker reference={ref} onClose={() => setEmojiPicker(null)} onPickEmoji={(emoji) => callback(emoji)} />); @@ -90,6 +114,7 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen customItemsLimit, setCustomItemsLimit, setActualTone, + quickReactions, }), [ emojiPicker, @@ -105,6 +130,7 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen setCurrentCategory, customItemsLimit, setActualTone, + quickReactions, ], ); From b62dde15f3a1799142a6cb1a93c71fd8410a3336 Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Thu, 6 Jul 2023 21:12:26 +0530 Subject: [PATCH 55/79] fix: Clear MessageBox popup on message send (#29709) --- .changeset/rare-dingos-boil.md | 5 +++++ .../client/popup/hooks/useComposerBoxPopup.ts | 15 +++++++++++++++ .../body/composer/messageBox/MessageBox.tsx | 2 ++ 3 files changed, 22 insertions(+) create mode 100644 .changeset/rare-dingos-boil.md diff --git a/.changeset/rare-dingos-boil.md b/.changeset/rare-dingos-boil.md new file mode 100644 index 0000000000000..563dd5b48dbe0 --- /dev/null +++ b/.changeset/rare-dingos-boil.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': minor +--- + +Close message composer popup on sending message diff --git a/apps/meteor/app/ui-message/client/popup/hooks/useComposerBoxPopup.ts b/apps/meteor/app/ui-message/client/popup/hooks/useComposerBoxPopup.ts index 01376eb4d5925..81a20f3bef126 100644 --- a/apps/meteor/app/ui-message/client/popup/hooks/useComposerBoxPopup.ts +++ b/apps/meteor/app/ui-message/client/popup/hooks/useComposerBoxPopup.ts @@ -28,6 +28,7 @@ type ComposerBoxPopupResult<T extends { _id: string; sort?: number }> = commandsRef: ComposerBoxPopupImperativeCommands<T>; suspended: boolean; filter: unknown; + clearPopup: () => void; } | { popup: undefined; @@ -39,6 +40,7 @@ type ComposerBoxPopupResult<T extends { _id: string; sort?: number }> = commandsRef: ComposerBoxPopupImperativeCommands<T>; suspended: boolean; filter: unknown; + clearPopup: () => void; }; const keys = { @@ -140,6 +142,7 @@ export const useComposerBoxPopup = <T extends { _id: string; sort?: number }>({ setFocused(undefined); setFilter(''); } + if (configuration) { const selector = configuration.matchSelectorRegex ?? @@ -232,6 +235,16 @@ export const useComposerBoxPopup = <T extends { _id: string; sort?: number }>({ } }); + const clearPopup = useMutableCallback(() => { + if (!popup) { + return; + } + + setPopup(undefined); + setFocused(undefined); + setFilter(''); + }); + const callbackRef = useCallback( (node: HTMLElement | null) => { if (!node) { @@ -256,6 +269,7 @@ export const useComposerBoxPopup = <T extends { _id: string; sort?: number }>({ suspended: true, commandsRef, filter: undefined, + clearPopup, }; } @@ -269,5 +283,6 @@ export const useComposerBoxPopup = <T extends { _id: string; sort?: number }>({ suspended, commandsRef, callbackRef, + clearPopup, }; }; diff --git a/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBox.tsx b/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBox.tsx index 5819254fd842f..153be6f85a5b3 100644 --- a/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBox.tsx +++ b/apps/meteor/client/views/room/components/body/composer/messageBox/MessageBox.tsx @@ -159,6 +159,7 @@ const MessageBox = ({ const handleSendMessage = useMutableCallback(() => { const text = chat.composer?.text ?? ''; chat.composer?.clear(); + clearPopup(); onSend?.({ value: text, @@ -334,6 +335,7 @@ const MessageBox = ({ commandsRef, callbackRef: c, filter, + clearPopup, } = useComposerBoxPopup<{ _id: string; sort?: number }>({ configurations: composerPopupConfig, }); From 01e39b5c4e3a5a1f9a7359a205de0f07559bb47d Mon Sep 17 00:00:00 2001 From: Heitor Tanoue <68477006+heitortanoue@users.noreply.github.com> Date: Thu, 6 Jul 2023 15:26:05 -0300 Subject: [PATCH 56/79] fix: Last message appears in extended view after deletion (#29624) Co-authored-by: Matheus Barbosa Silva <36537004+matheusbsilva137@users.noreply.github.com> --- .changeset/modern-geese-laugh.md | 5 +++++ .../app/lib/server/functions/deleteMessage.ts | 20 ++++++++++--------- 2 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 .changeset/modern-geese-laugh.md diff --git a/.changeset/modern-geese-laugh.md b/.changeset/modern-geese-laugh.md new file mode 100644 index 0000000000000..acae2927ab497 --- /dev/null +++ b/.changeset/modern-geese-laugh.md @@ -0,0 +1,5 @@ +--- +"@rocket.chat/meteor": patch +--- + +fix: Last message appears in extended view after deletion diff --git a/apps/meteor/app/lib/server/functions/deleteMessage.ts b/apps/meteor/app/lib/server/functions/deleteMessage.ts index d4e2f8c4750b1..2c08cac6d024f 100644 --- a/apps/meteor/app/lib/server/functions/deleteMessage.ts +++ b/apps/meteor/app/lib/server/functions/deleteMessage.ts @@ -68,26 +68,28 @@ export async function deleteMessage(message: IMessage, user: IUser): Promise<voi } } + if (showDeletedStatus) { + // TODO is there a better way to tell TS "IUser[username]" is not undefined? + await Messages.setAsDeletedByIdAndUser(message._id, user as Required<Pick<IUser, '_id' | 'username' | 'name'>>); + } else { + void api.broadcast('notify.deleteMessage', message.rid, { _id: message._id }); + } + const room = await Rooms.findOneById(message.rid, { projection: { lastMessage: 1, prid: 1, mid: 1, federated: 1 } }); - await callbacks.run('afterDeleteMessage', deletedMsg, room); // update last message if (settings.get('Store_Last_Message')) { if (!room?.lastMessage || room.lastMessage._id === message._id) { - await Rooms.resetLastMessageById(message.rid, deletedMsg); + const lastMessageNotDeleted = await Messages.getLastVisibleMessageSentWithNoTypeByRoomId(message.rid); + await Rooms.resetLastMessageById(message.rid, lastMessageNotDeleted); } } + await callbacks.run('afterDeleteMessage', deletedMsg, room); + // decrease message count await Rooms.decreaseMessageCountById(message.rid, 1); - if (showDeletedStatus) { - // TODO is there a better way to tell TS "IUser[username]" is not undefined? - await Messages.setAsDeletedByIdAndUser(message._id, user as Required<Pick<IUser, '_id' | 'username' | 'name'>>); - } else { - void api.broadcast('notify.deleteMessage', message.rid, { _id: message._id }); - } - if (bridges) { void bridges.getListenerBridge().messageEvent('IPostMessageDeleted', deletedMsg, user); } From 230bbc73a17d6edc7f3e505244c56d7ec1cb43c1 Mon Sep 17 00:00:00 2001 From: Yash Rajpal <58601732+yash-rajpal@users.noreply.github.com> Date: Fri, 7 Jul 2023 01:24:23 +0530 Subject: [PATCH 57/79] chore: console warning on sending messages (#29750) --- apps/meteor/app/lib/client/methods/sendMessage.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/lib/client/methods/sendMessage.ts b/apps/meteor/app/lib/client/methods/sendMessage.ts index 58bfbd1a0ee35..cd4a91b273331 100644 --- a/apps/meteor/app/lib/client/methods/sendMessage.ts +++ b/apps/meteor/app/lib/client/methods/sendMessage.ts @@ -36,15 +36,15 @@ Meteor.methods<ServerMethods>({ } // If the room is federated, send the message to matrix only - const federated = ChatRoom.findOne({ _id: message.rid }, { fields: { federated: 1 } })?.federated; - if (federated) { + const room = ChatRoom.findOne({ _id: message.rid }, { fields: { federated: 1, name: 1 } }); + if (room?.federated) { return; } message = await callbacks.run('beforeSaveMessage', message); await onClientMessageReceived(message as IMessage).then(function (message) { ChatMessage.insert(message); - return callbacks.run('afterSaveMessage', message); + return callbacks.run('afterSaveMessage', message, room); }); }, }); From b4d6b9fbdfc4be4a457d6be77ab2ef4dc9d07ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Thu, 6 Jul 2023 17:16:22 -0300 Subject: [PATCH 58/79] fix: normalize `ButtonGroup` items as buttons (#29678) --- .../attachments/default/ActionAttachtment.tsx | 47 ++++++++++--------- apps/meteor/client/hooks/useExternalLink.ts | 13 +++++ .../components/RegisterWorkspaceMenu.tsx | 5 +- .../admin/settings/groups/LDAPGroupPage.tsx | 5 +- .../client/views/home/HomePageHeader.tsx | 6 ++- .../views/home/cards/CustomContentCard.tsx | 6 ++- .../views/room/components/body/LeaderBar.tsx | 5 +- 7 files changed, 59 insertions(+), 28 deletions(-) create mode 100644 apps/meteor/client/hooks/useExternalLink.ts diff --git a/apps/meteor/client/components/message/content/attachments/default/ActionAttachtment.tsx b/apps/meteor/client/components/message/content/attachments/default/ActionAttachtment.tsx index 6e81c7c152960..8c72a84f67215 100644 --- a/apps/meteor/client/components/message/content/attachments/default/ActionAttachtment.tsx +++ b/apps/meteor/client/components/message/content/attachments/default/ActionAttachtment.tsx @@ -3,29 +3,34 @@ import { Box, Button, ButtonGroup } from '@rocket.chat/fuselage'; import type { FC } from 'react'; import React from 'react'; +import { useExternalLink } from '../../../../../hooks/useExternalLink'; import ActionAttachmentButton from './ActionAttachmentButton'; -export const ActionAttachment: FC<MessageAttachmentAction> = ({ actions }) => ( - <ButtonGroup mb='x4' small> - {actions - .filter( - ({ type, msg_in_chat_window: msgInChatWindow, url, image_url: image, text }) => - type === 'button' && (image || text) && (url || msgInChatWindow), - ) - .map(({ text, url, msgId, msg, msg_processing_type: processingType = 'sendMessage', image_url: image }, index) => { - const content = image ? <Box is='img' src={image} maxHeight={200} /> : text; - if (url) { +export const ActionAttachment: FC<MessageAttachmentAction> = ({ actions }) => { + const handleLinkClick = useExternalLink(); + + return ( + <ButtonGroup mb='x4' small> + {actions + .filter( + ({ type, msg_in_chat_window: msgInChatWindow, url, image_url: image, text }) => + type === 'button' && (image || text) && (url || msgInChatWindow), + ) + .map(({ text, url, msgId, msg, msg_processing_type: processingType = 'sendMessage', image_url: image }, index) => { + const content = image ? <Box is='img' src={image} maxHeight={200} /> : text; + if (url) { + return ( + <Button role='link' onClick={() => handleLinkClick(url)} key={index} small> + {content} + </Button> + ); + } return ( - <Button is='a' href={url} target='_blank' rel='noopener noreferrer' key={index} small> + <ActionAttachmentButton key={index} processingType={processingType} msg={msg} mid={msgId}> {content} - </Button> + </ActionAttachmentButton> ); - } - return ( - <ActionAttachmentButton key={index} processingType={processingType} msg={msg} mid={msgId}> - {content} - </ActionAttachmentButton> - ); - })} - </ButtonGroup> -); + })} + </ButtonGroup> + ); +}; diff --git a/apps/meteor/client/hooks/useExternalLink.ts b/apps/meteor/client/hooks/useExternalLink.ts new file mode 100644 index 0000000000000..bdff1d847ded8 --- /dev/null +++ b/apps/meteor/client/hooks/useExternalLink.ts @@ -0,0 +1,13 @@ +import { useToastMessageDispatch } from '@rocket.chat/ui-contexts'; + +export const useExternalLink = () => { + const dispatchToastMessage = useToastMessageDispatch(); + + return (url: string | undefined) => { + if (!url) { + dispatchToastMessage({ message: 'Invalid url', type: 'error' }); + return; + } + window.open(url, '_blank', 'noopener noreferrer'); + }; +}; diff --git a/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx b/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx index 050ea60e210f1..b24cd12bbab26 100644 --- a/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx +++ b/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx @@ -2,6 +2,7 @@ import { Button, ButtonGroup, Icon } from '@rocket.chat/fuselage'; import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; +import { useExternalLink } from '../../../../hooks/useExternalLink'; import { cloudConsoleUrl } from '../constants'; import RegisteredWorkspaceModal from '../modals/RegisteredWorkspaceModal'; @@ -28,11 +29,13 @@ const RegisterWorkspaceMenu = ({ setModal(<RegisteredWorkspaceModal onClose={handleModalClose} onStatusChange={onStatusChange} />); }; + const handleLinkClick = useExternalLink(); + return ( <ButtonGroup> {isWorkspaceRegistered && isConnectedToCloud && ( <> - <Button is='a' href={cloudConsoleUrl} target='_blank' rel='noopener noreferrer'> + <Button role='link' onClick={() => handleLinkClick(cloudConsoleUrl)}> <Icon name='new-window' size='x20' pie={4} /> {t('Cloud')} </Button> diff --git a/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx b/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx index df60251ee9dde..900351877027a 100644 --- a/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx +++ b/apps/meteor/client/views/admin/settings/groups/LDAPGroupPage.tsx @@ -6,6 +6,7 @@ import type { FormEvent } from 'react'; import React, { memo, useMemo } from 'react'; import GenericModal from '../../../../components/GenericModal'; +import { useExternalLink } from '../../../../hooks/useExternalLink'; import { useEditableSettings } from '../../EditableSettingsContext'; import TabbedGroupPage from './TabbedGroupPage'; @@ -19,6 +20,8 @@ function LDAPGroupPage({ _id, ...group }: ISetting): JSX.Element { const setModal = useSetModal(); const closeModal = useMutableCallback(() => setModal()); + const handleLinkClick = useExternalLink(); + const editableSettings = useEditableSettings( useMemo( () => ({ @@ -130,7 +133,7 @@ function LDAPGroupPage({ _id, ...group }: ISetting): JSX.Element { <Button children={t('Test_Connection')} disabled={!ldapEnabled || changed} onClick={handleTestConnectionButtonClick} /> <Button children={t('Test_LDAP_Search')} disabled={!ldapEnabled || changed} onClick={handleSearchTestButtonClick} /> <Button children={t('LDAP_Sync_Now')} disabled={!ldapEnabled || changed} onClick={handleSyncNowButtonClick} /> - <Button is='a' href='https://go.rocket.chat/i/ldap-docs' target='_blank'> + <Button role='link' onClick={() => handleLinkClick('https://go.rocket.chat/i/ldap-docs')}> {t('LDAP_Documentation')} </Button> </> diff --git a/apps/meteor/client/views/home/HomePageHeader.tsx b/apps/meteor/client/views/home/HomePageHeader.tsx index 6be03a41e8637..498e3839b032b 100644 --- a/apps/meteor/client/views/home/HomePageHeader.tsx +++ b/apps/meteor/client/views/home/HomePageHeader.tsx @@ -1,5 +1,5 @@ import { Button, Icon } from '@rocket.chat/fuselage'; -import { useSetting, useTranslation, useAllPermissions } from '@rocket.chat/ui-contexts'; +import { useSetting, useTranslation, useAllPermissions, useRoute } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -11,10 +11,12 @@ const HomepageHeader = (): ReactElement => { const t = useTranslation(); const title = useSetting('Layout_Home_Title') as string; const canEditLayout = useAllPermissions(EDIT_LAYOUT_PERMISSIONS); + const settingsRoute = useRoute('admin-settings'); + return ( <PageHeader title={title} data-qa-id='home-header' role='heading'> {canEditLayout && ( - <Button is='a' href='/admin/settings/Layout' role='button'> + <Button onClick={() => settingsRoute.push({ group: 'Layout' })}> <Icon name='pencil' size='x16' /> {t('Customize')} </Button> )} diff --git a/apps/meteor/client/views/home/cards/CustomContentCard.tsx b/apps/meteor/client/views/home/cards/CustomContentCard.tsx index 43750f6ace93c..8f59c00486b1d 100644 --- a/apps/meteor/client/views/home/cards/CustomContentCard.tsx +++ b/apps/meteor/client/views/home/cards/CustomContentCard.tsx @@ -1,6 +1,6 @@ import { Box, Button, Icon, Tag } from '@rocket.chat/fuselage'; import { Card } from '@rocket.chat/ui-client'; -import { useRole, useSettingSetValue, useSetting, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; +import { useRole, useSettingSetValue, useSetting, useToastMessageDispatch, useTranslation, useRoute } from '@rocket.chat/ui-contexts'; import type { ReactElement } from 'react'; import React from 'react'; @@ -18,6 +18,8 @@ const CustomContentCard = (): ReactElement | null => { const isCustomContentVisible = Boolean(useSetting('Layout_Home_Custom_Block_Visible')); const isCustomContentOnly = Boolean(useSetting('Layout_Custom_Body_Only')); + const settingsRoute = useRoute('admin-settings'); + const setCustomContentVisible = useSettingSetValue('Layout_Home_Custom_Block_Visible'); const setCustomContentOnly = useSettingSetValue('Layout_Custom_Body_Only'); @@ -63,7 +65,7 @@ const CustomContentCard = (): ReactElement | null => { </Box> <Card.FooterWrapper> <Card.Footer> - <Button role='link' is='a' href='/admin/settings/Layout' title={t('Layout_Home_Page_Content')}> + <Button onClick={() => settingsRoute.push({ group: 'Layout' })} title={t('Layout_Home_Page_Content')}> {t('Customize_Content')} </Button> <Button diff --git a/apps/meteor/client/views/room/components/body/LeaderBar.tsx b/apps/meteor/client/views/room/components/body/LeaderBar.tsx index ce1fc8e631cc1..afc28bc632201 100644 --- a/apps/meteor/client/views/room/components/body/LeaderBar.tsx +++ b/apps/meteor/client/views/room/components/body/LeaderBar.tsx @@ -8,6 +8,7 @@ import React, { memo, useCallback, useMemo } from 'react'; import { isTruthy } from '../../../../../lib/isTruthy'; import { ReactiveUserStatus } from '../../../../components/UserStatus'; import UserAvatar from '../../../../components/avatar/UserAvatar'; +import { useExternalLink } from '../../../../hooks/useExternalLink'; import { roomCoordinator } from '../../../../lib/rooms/roomCoordinator'; type LeaderBarProps = { @@ -21,6 +22,8 @@ type LeaderBarProps = { const LeaderBar = ({ _id, name, username, visible, onAvatarClick }: LeaderBarProps): ReactElement => { const t = useTranslation(); + const handleLinkClick = useExternalLink(); + const chatNowLink = useMemo(() => roomCoordinator.getRouteLink('d', { name: username }) || undefined, [username]); const handleAvatarClick = useCallback( @@ -74,7 +77,7 @@ const LeaderBar = ({ _id, name, username, visible, onAvatarClick }: LeaderBarPro </Box> </Box> </Box> - <Button is='a' href={chatNowLink}> + <Button role='link' onClick={() => handleLinkClick(chatNowLink)}> {t('Chat_Now')} </Button> </Box> From 0113c62a5cdd53b2effc0d89061355d1378b5dab Mon Sep 17 00:00:00 2001 From: Douglas Fabris <devfabris@gmail.com> Date: Thu, 6 Jul 2023 18:46:31 -0300 Subject: [PATCH 59/79] regression: Omit `quickReactions` based on `MessageContext` (#29753) --- .../client/components/message/toolbox/Toolbox.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/meteor/client/components/message/toolbox/Toolbox.tsx b/apps/meteor/client/components/message/toolbox/Toolbox.tsx index 7ab5895ef6f15..4f14daf9ac67c 100644 --- a/apps/meteor/client/components/message/toolbox/Toolbox.tsx +++ b/apps/meteor/client/components/message/toolbox/Toolbox.tsx @@ -46,9 +46,9 @@ type ToolboxProps = { const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): ReactElement | null => { const t = useTranslation(); + const user = useUser(); const settings = useSettings(); - const user = useUser(); const context = getMessageContext(message, room, messageContext); @@ -82,6 +82,8 @@ const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): return null; } + const isReactionAllowed = actionsQueryResult.data?.message.find(({ id }) => id === 'reaction-message'); + const handleSetReaction = (emoji: string) => { sdk.call('setReaction', `:${emoji}:`, message._id); addRecentEmoji(emoji); @@ -89,9 +91,10 @@ const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): return ( <MessageToolbox> - {quickReactions.slice(0, 3).map(({ emoji, image }) => { - return <EmojiElement small key={emoji} title={emoji} emoji={emoji} image={image} onClick={() => handleSetReaction(emoji)} />; - })} + {isReactionAllowed && + quickReactions.slice(0, 3).map(({ emoji, image }) => { + return <EmojiElement small key={emoji} title={emoji} emoji={emoji} image={image} onClick={() => handleSetReaction(emoji)} />; + })} {actionsQueryResult.data?.message.map((action) => ( <MessageToolboxItem onClick={(e): void => action.action(e, { message, tabbar: toolbox, room, chat, autoTranslateOptions })} From e846d873b77f6f2885fa66c621b118a1644c4cf3 Mon Sep 17 00:00:00 2001 From: Douglas Fabris <devfabris@gmail.com> Date: Thu, 6 Jul 2023 18:57:16 -0300 Subject: [PATCH 60/79] feat: Introduce Feature Preview page (#29698) Co-authored-by: Guilherme Gazzo <guilhermegazzo@gmail.com> --- .changeset/fair-rivers-occur.md | 6 + apps/meteor/app/reactions/client/init.js | 2 +- .../client/lib/messageActionDefault.ts | 2 +- .../components/message/toolbox/Toolbox.tsx | 7 +- apps/meteor/client/hooks/useFeaturePreview.ts | 21 +++ .../client/hooks/useFeaturePreviewList.ts | 44 ++++++ .../sidebar/header/hooks/useAccountItems.tsx | 25 ++++ apps/meteor/client/sidebar/header/index.tsx | 9 +- .../AccountFeaturePreviewBadge.tsx | 22 +++ .../AccountFeaturePreviewPage.tsx | 136 ++++++++++++++++++ apps/meteor/client/views/account/routes.tsx | 9 ++ .../{sidebarItems.ts => sidebarItems.tsx} | 11 ++ .../rocketchat-i18n/i18n/en.i18n.json | 8 ++ .../images/featurePreview/quick-reactions.png | Bin 0 -> 5613 bytes apps/meteor/server/settings/accounts.ts | 4 + .../v1/users/UsersSetPreferenceParamsPOST.ts | 12 ++ yarn.lock | 40 +++--- 17 files changed, 327 insertions(+), 31 deletions(-) create mode 100644 .changeset/fair-rivers-occur.md create mode 100644 apps/meteor/client/hooks/useFeaturePreview.ts create mode 100644 apps/meteor/client/hooks/useFeaturePreviewList.ts create mode 100644 apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewBadge.tsx create mode 100644 apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx rename apps/meteor/client/views/account/{sidebarItems.ts => sidebarItems.tsx} (76%) create mode 100644 apps/meteor/public/images/featurePreview/quick-reactions.png diff --git a/.changeset/fair-rivers-occur.md b/.changeset/fair-rivers-occur.md new file mode 100644 index 0000000000000..2d7a78a23155c --- /dev/null +++ b/.changeset/fair-rivers-occur.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/rest-typings': minor +'@rocket.chat/meteor': minor +--- + +feat: Introduce Feature Preview page diff --git a/apps/meteor/app/reactions/client/init.js b/apps/meteor/app/reactions/client/init.js index 4c77531050b24..05ac899de1d9a 100644 --- a/apps/meteor/app/reactions/client/init.js +++ b/apps/meteor/app/reactions/client/init.js @@ -39,7 +39,7 @@ Meteor.startup(function () { return true; }, - order: -2, + order: -3, group: ['message', 'menu'], }); }); diff --git a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts index 6d3f856e2b9c9..d00e6bf9675d7 100644 --- a/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts +++ b/apps/meteor/app/ui-utils/client/lib/messageActionDefault.ts @@ -110,7 +110,7 @@ Meteor.startup(async function () { return true; }, - order: -3, + order: -2, group: ['message', 'menu'], }); diff --git a/apps/meteor/client/components/message/toolbox/Toolbox.tsx b/apps/meteor/client/components/message/toolbox/Toolbox.tsx index 4f14daf9ac67c..2c9a3ccae95b9 100644 --- a/apps/meteor/client/components/message/toolbox/Toolbox.tsx +++ b/apps/meteor/client/components/message/toolbox/Toolbox.tsx @@ -10,6 +10,7 @@ import type { MessageActionContext } from '../../../../app/ui-utils/client/lib/M import { MessageAction } from '../../../../app/ui-utils/client/lib/MessageAction'; import { sdk } from '../../../../app/utils/client/lib/SDKClient'; import { useEmojiPickerData } from '../../../contexts/EmojiPickerContext'; +import { useFeaturePreview } from '../../../hooks/useFeaturePreview'; import EmojiElement from '../../../views/composer/EmojiPicker/EmojiElement'; import { useIsSelecting } from '../../../views/room/MessageList/contexts/SelectedMessagesContext'; import { useAutoTranslate } from '../../../views/room/MessageList/hooks/useAutoTranslate'; @@ -47,9 +48,10 @@ type ToolboxProps = { const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): ReactElement | null => { const t = useTranslation(); const user = useUser(); - const settings = useSettings(); + const quickReactionsEnabled = useFeaturePreview('quickReactions'); + const context = getMessageContext(message, room, messageContext); const mapSettings = useMemo(() => Object.fromEntries(settings.map((setting) => [setting._id, setting.value])), [settings]); @@ -91,7 +93,8 @@ const Toolbox = ({ message, messageContext, room, subscription }: ToolboxProps): return ( <MessageToolbox> - {isReactionAllowed && + {quickReactionsEnabled && + isReactionAllowed && quickReactions.slice(0, 3).map(({ emoji, image }) => { return <EmojiElement small key={emoji} title={emoji} emoji={emoji} image={image} onClick={() => handleSetReaction(emoji)} />; })} diff --git a/apps/meteor/client/hooks/useFeaturePreview.ts b/apps/meteor/client/hooks/useFeaturePreview.ts new file mode 100644 index 0000000000000..fcfc4ef85c826 --- /dev/null +++ b/apps/meteor/client/hooks/useFeaturePreview.ts @@ -0,0 +1,21 @@ +import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts'; + +import type { FeaturesAvailable, FeaturePreviewProps } from './useFeaturePreviewList'; + +export const useFeaturePreview = (featureName: FeaturesAvailable) => { + const featurePreviewEnabled = useSetting('Accounts_AllowFeaturePreview'); + const features = useUserPreference<FeaturePreviewProps[]>('featuresPreview'); + + const currentFeature = features?.find((feature) => feature.name === featureName); + + if (!featurePreviewEnabled) { + return false; + } + + if (!currentFeature) { + console.error(`Feature ${featureName} not found`); + return false; + } + + return currentFeature.value; +}; diff --git a/apps/meteor/client/hooks/useFeaturePreviewList.ts b/apps/meteor/client/hooks/useFeaturePreviewList.ts new file mode 100644 index 0000000000000..b4e9a1dee6d3d --- /dev/null +++ b/apps/meteor/client/hooks/useFeaturePreviewList.ts @@ -0,0 +1,44 @@ +import type { TranslationKey } from '@rocket.chat/ui-contexts'; +import { useUserPreference, useSetting } from '@rocket.chat/ui-contexts'; + +export type FeaturesAvailable = 'quickReactions'; + +export type FeaturePreviewProps = { + name: FeaturesAvailable; + i18n: TranslationKey; + description: TranslationKey; + group: 'Message' | 'Navigation'; + imageUrl?: string; + value: boolean; +}; + +export const defaultFeaturesPreview: FeaturePreviewProps[] = [ + { + name: 'quickReactions', + i18n: 'Quick_reactions', + description: 'Quick_reactions_description', + group: 'Message', + imageUrl: 'images/featurePreview/quick-reactions.png', + value: false, + }, +]; + +export const useFeaturePreviewList = () => { + const featurePreviewEnabled = useSetting<boolean>('Accounts_AllowFeaturePreview'); + const userFeaturesPreview = useUserPreference<FeaturePreviewProps[]>('featuresPreview'); + + if (!featurePreviewEnabled) { + return { unseenFeatures: 0, features: [] as FeaturePreviewProps[], featurePreviewEnabled }; + } + + const unseenFeatures = defaultFeaturesPreview.filter( + (feature) => !userFeaturesPreview?.find((userFeature) => userFeature.name === feature.name), + ).length; + + const mergedFeatures = defaultFeaturesPreview.map((feature) => { + const userFeature = userFeaturesPreview?.find((userFeature) => userFeature.name === feature.name); + return { ...feature, ...userFeature }; + }); + + return { unseenFeatures, features: mergedFeatures, featurePreviewEnabled }; +}; diff --git a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx index b46923a562e80..6265a380fe674 100644 --- a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx +++ b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx @@ -1,21 +1,45 @@ +import { Badge } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useLogout, useRoute, useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; import type { GenericMenuItemProps } from '../../../components/GenericMenu/GenericMenuItem'; +import { useFeaturePreviewList, defaultFeaturesPreview } from '../../../hooks/useFeaturePreviewList'; export const useAccountItems = (): GenericMenuItemProps[] => { const t = useTranslation(); const accountRoute = useRoute('account-index'); + const featurePreviewRoute = useRoute('feature-preview'); + const { unseenFeatures, featurePreviewEnabled } = useFeaturePreviewList(); + const logout = useLogout(); const handleMyAccount = useMutableCallback(() => { accountRoute.push({}); }); + const handleFeaturePreview = useMutableCallback(() => { + featurePreviewRoute.push(); + }); + const handleLogout = useMutableCallback(() => { logout(); }); + const featurePreviewItem = { + id: 'feature-preview', + icon: 'flask' as const, + content: t('Feature_preview'), + onClick: handleFeaturePreview, + ...(unseenFeatures > 0 && { + addon: () => ( + <Badge variant='primary' aria-label={t('Unseen_features')}> + {unseenFeatures} + </Badge> + ), + }), + }; + return [ { id: 'my-account', @@ -23,6 +47,7 @@ export const useAccountItems = (): GenericMenuItemProps[] => { content: t('My_Account'), onClick: handleMyAccount, }, + ...(featurePreviewEnabled && defaultFeaturesPreview.length > 0 ? [featurePreviewItem] : []), { id: 'logout', icon: 'sign-out', diff --git a/apps/meteor/client/sidebar/header/index.tsx b/apps/meteor/client/sidebar/header/index.tsx index 1027474aaec22..95b225c0a242c 100644 --- a/apps/meteor/client/sidebar/header/index.tsx +++ b/apps/meteor/client/sidebar/header/index.tsx @@ -13,18 +13,13 @@ import Login from './actions/Login'; import Search from './actions/Search'; import Sort from './actions/Sort'; -// TODO: Remove styles from here const HeaderWithData = (): ReactElement => { - const user = useUser(); const t = useTranslation(); + const user = useUser(); return ( <> - <Sidebar.TopBar.Section - {...{ - style: { flexShrink: 0 }, - }} - > + <Sidebar.TopBar.Section> {user ? <UserMenu user={user} /> : <UserAvatarWithStatus />} <Sidebar.TopBar.Actions> <Home title={t('Home')} /> diff --git a/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewBadge.tsx b/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewBadge.tsx new file mode 100644 index 0000000000000..631a2b76c625e --- /dev/null +++ b/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewBadge.tsx @@ -0,0 +1,22 @@ +import { Badge } from '@rocket.chat/fuselage'; +import { useTranslation } from '@rocket.chat/ui-contexts'; +import React from 'react'; + +import { useFeaturePreviewList } from '../../../hooks/useFeaturePreviewList'; + +const AccountFeaturePreviewBadge = () => { + const t = useTranslation(); + const { unseenFeatures } = useFeaturePreviewList(); + + if (!unseenFeatures) { + return null; + } + + return ( + <Badge variant='primary' aria-label={t('Unseen_features')}> + {unseenFeatures} + </Badge> + ); +}; + +export default AccountFeaturePreviewBadge; diff --git a/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx b/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx new file mode 100644 index 0000000000000..414aa23774908 --- /dev/null +++ b/apps/meteor/client/views/account/featurePreview/AccountFeaturePreviewPage.tsx @@ -0,0 +1,136 @@ +import { css } from '@rocket.chat/css-in-js'; +import { + ButtonGroup, + Button, + Box, + Field, + ToggleSwitch, + FieldGroup, + States, + StatesIcon, + StatesTitle, + Accordion, +} from '@rocket.chat/fuselage'; +import type { TranslationKey } from '@rocket.chat/ui-contexts'; +import { useToastMessageDispatch, useTranslation, useEndpoint } from '@rocket.chat/ui-contexts'; +import type { ChangeEvent } from 'react'; +import React, { useEffect, Fragment } from 'react'; +import { useForm } from 'react-hook-form'; + +import Page from '../../../components/Page'; +import type { FeaturePreviewProps } from '../../../hooks/useFeaturePreviewList'; +import { useFeaturePreviewList } from '../../../hooks/useFeaturePreviewList'; + +const AccountFeaturePreviewPage = () => { + const t = useTranslation(); + const dispatchToastMessage = useToastMessageDispatch(); + const { features, unseenFeatures } = useFeaturePreviewList(); + + const setUserPreferences = useEndpoint('POST', '/v1/users.setPreferences'); + + useEffect(() => { + if (unseenFeatures) { + const featuresPreview = features.map((feature) => ({ + name: feature.name, + value: feature.value, + })); + + void setUserPreferences({ data: { featuresPreview } }); + } + }, [setUserPreferences, features, unseenFeatures]); + + const { + watch, + formState: { isDirty }, + setValue, + handleSubmit, + reset, + } = useForm({ + defaultValues: { featuresPreview: features }, + }); + + const { featuresPreview } = watch(); + + const handleSave = async () => { + try { + await setUserPreferences({ data: { featuresPreview } }); + dispatchToastMessage({ type: 'success', message: t('Preferences_saved') }); + } catch (error) { + dispatchToastMessage({ type: 'error', message: error }); + } finally { + reset({ featuresPreview }); + } + }; + + const handleFeatures = (e: ChangeEvent<HTMLInputElement>) => { + const updated = featuresPreview.map((item) => (item.name === e.target.name ? { ...item, value: e.target.checked } : item)); + setValue('featuresPreview', updated, { shouldDirty: true }); + }; + + const grouppedFeaturesPreview = Object.entries( + featuresPreview.reduce((result, currentValue) => { + (result[currentValue.group] = result[currentValue.group] || []).push(currentValue); + return result; + }, {} as Record<FeaturePreviewProps['group'], FeaturePreviewProps[]>), + ); + + return ( + <Page> + <Page.Header title={t('Feature_preview')}> + <ButtonGroup> + <Button primary disabled={!isDirty} onClick={handleSubmit(handleSave)}> + {t('Save_changes')} + </Button> + </ButtonGroup> + </Page.Header> + <Page.ScrollableContentWithShadow> + <Box maxWidth='x600' w='full' alignSelf='center'> + {featuresPreview.length === 0 && ( + <States> + <StatesIcon name='magnifier' /> + <StatesTitle>{t('No_feature_to_preview')}</StatesTitle> + </States> + )} + {featuresPreview.length > 0 && ( + <> + <Box + className={css` + white-space: break-spaces; + `} + pbe='x24' + fontScale='p1' + > + {t('Feature_preview_page_description')} + </Box> + <Accordion> + {grouppedFeaturesPreview?.map(([group, features], index) => ( + <Accordion.Item defaultExpanded={index === 0} key={group} title={t(group as TranslationKey)}> + <FieldGroup> + {features.map((feature) => ( + <Fragment key={feature.name}> + <Field> + <Box display='flex' flexDirection='row' justifyContent='spaceBetween' flexGrow={1}> + <Field.Label>{t(feature.i18n)}</Field.Label> + <Field.Row> + <Box mie='x12'>{t('Enabled')}</Box> + <ToggleSwitch checked={feature.value} name={feature.name} onChange={handleFeatures} /> + </Field.Row> + </Box> + {feature.description && <Field.Hint mbs='x12'>{t(feature.description)}</Field.Hint>} + </Field> + {feature.imageUrl && <Box is='img' width='100%' height='auto' mbs='x16' src={feature.imageUrl} />} + </Fragment> + ))} + </FieldGroup> + </Accordion.Item> + ))} + </Accordion> + </> + )} + </Box> + </Page.ScrollableContentWithShadow> + </Page> + ); +}; + +export default AccountFeaturePreviewPage; diff --git a/apps/meteor/client/views/account/routes.tsx b/apps/meteor/client/views/account/routes.tsx index d31287fa7a570..f6a49eb268de2 100644 --- a/apps/meteor/client/views/account/routes.tsx +++ b/apps/meteor/client/views/account/routes.tsx @@ -32,6 +32,10 @@ declare module '@rocket.chat/ui-contexts' { pathname: '/account/omnichannel'; pattern: '/account/omnichannel'; }; + 'feature-preview': { + pathname: '/account/feature-preview'; + pattern: '/account/feature-preview'; + }; } } @@ -70,3 +74,8 @@ registerAccountRoute('/omnichannel', { name: 'omnichannel', component: lazy(() => import('./omnichannel/OmnichannelPreferencesPage')), }); + +registerAccountRoute('/feature-preview', { + name: 'feature-preview', + component: lazy(() => import('./featurePreview/AccountFeaturePreviewPage')), +}); diff --git a/apps/meteor/client/views/account/sidebarItems.ts b/apps/meteor/client/views/account/sidebarItems.tsx similarity index 76% rename from apps/meteor/client/views/account/sidebarItems.ts rename to apps/meteor/client/views/account/sidebarItems.tsx index c2810bf48564d..4087774d98cce 100644 --- a/apps/meteor/client/views/account/sidebarItems.ts +++ b/apps/meteor/client/views/account/sidebarItems.tsx @@ -1,6 +1,10 @@ +import React from 'react'; + import { hasPermission, hasAtLeastOnePermission } from '../../../app/authorization/client'; import { settings } from '../../../app/settings/client'; +import { defaultFeaturesPreview } from '../../hooks/useFeaturePreviewList'; import { createSidebarItems } from '../../lib/createSidebarItems'; +import AccountFeaturePreviewBadge from './featurePreview/AccountFeaturePreviewBadge'; export const { registerSidebarItem: registerAccountSidebarItem, @@ -43,4 +47,11 @@ export const { icon: 'headset', permissionGranted: (): boolean => hasAtLeastOnePermission(['send-omnichannel-chat-transcript', 'request-pdf-transcript']), }, + { + href: '/account/feature-preview', + i18nLabel: 'Feature_preview', + icon: 'flask', + badge: () => <AccountFeaturePreviewBadge />, + permissionGranted: () => settings.get('Accounts_AllowFeaturePreview') && defaultFeaturesPreview?.length > 0, + }, ]); diff --git a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json index ec6c27d3799bb..6fe3950137972 100644 --- a/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json +++ b/apps/meteor/packages/rocketchat-i18n/i18n/en.i18n.json @@ -65,6 +65,7 @@ "Accounts_AllowInvisibleStatusOption": "Allow Invisible status option", "Accounts_AllowEmailChange": "Allow Email Change", "Accounts_AllowEmailNotifications": "Allow Email Notifications", + "Accounts_AllowFeaturePreview": "Allow Feature Preview", "Accounts_AllowPasswordChange": "Allow Password Change", "Accounts_AllowPasswordChangeForOAuthUsers": "Allow Password Change for OAuth Users", "Accounts_AllowRealNameChange": "Allow Name Change", @@ -1572,6 +1573,7 @@ "Desktop_Notifications_Enabled": "Desktop Notifications are Enabled", "Desktop_Notifications_Not_Enabled": "Desktop Notifications are Not Enabled", "Unselected_by_default": "Unselected by default", + "Unseen_features": "Unseen features", "Details": "Details", "Device_Changes_Not_Available": "Device changes not available in this browser. For guaranteed availability, please use Rocket.Chat's official desktop app.", "Device_Changes_Not_Available_Insecure_Context": "Device changes are only available on secure contexts (e.g. https://)", @@ -2141,6 +2143,8 @@ "Favorite": "Favorite", "Favorite_Rooms": "Enable Favorite Rooms", "Favorites": "Favorites", + "Feature_preview": "Feature preview", + "Feature_preview_page_description": "Welcome to the features preview page! Here, you can enable the latest cutting-edge features that are currently under development and not yet officially released.\n\nPlease note that these configurations are still in the testing phase and may not be stable or fully functional.", "featured": "featured", "Featured": "Featured", "Feature_depends_on_selected_call_provider_to_be_enabled_from_administration_settings": "This feature depends on the above selected call provider to be enabled from the administration settings (Admin -> Video Conference).", @@ -3563,6 +3567,7 @@ "Name_of_agent": "Name of agent", "Name_optional": "Name (optional)", "Name_Placeholder": "Please enter your name...", + "Navigation": "Navigation", "Navigation_History": "Navigation History", "Next": "Next", "Never": "Never", @@ -4055,6 +4060,8 @@ "Queue_delay_timeout": "Queue processing delay timeout", "Queue_Time": "Queue Time", "Queue_management": "Queue Management", + "Quick_reactions": "Quick reactions", + "Quick_reactions_description": "The three most used reactions get an easy access while your mouse is over the message", "quote": "quote", "Quote": "Quote", "Random": "Random", @@ -4268,6 +4275,7 @@ "Required_action": "Required action", "Default_Referrer_Policy": "Default Referrer Policy", "Default_Referrer_Policy_Description": "This controls the 'referrer' header that's sent when requesting embedded media from other servers. For more information, refer to [this link from MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy). Remember, a full page refresh is required for this to take effect", + "No_feature_to_preview": "No feature to preview", "No_Referrer": "No Referrer", "No_Referrer_When_Downgrade": "No referrer when downgrade", "Notes": "Notes", diff --git a/apps/meteor/public/images/featurePreview/quick-reactions.png b/apps/meteor/public/images/featurePreview/quick-reactions.png new file mode 100644 index 0000000000000000000000000000000000000000..600636789c96bfa685d02e5f8bd63505b8f5b2cc GIT binary patch literal 5613 zcmchbXH=6-l*dDn9-0tBq)3sj36LOCq<0WOiuC>}Ql%F|?^0Ey!%LTr^b$zuAPPt? z5{h&PL3(F#ch8>vxS#gJGtd0z%(-{w&dfbCzv$=BRDcu^3IG5AR8v*d1po;4ZtJFG zB)4VKo%O!kAGxclkvjlDdH3%}07%aS-!>As>#E2DDu-A$Zw2uyIW0K=;A;ZqrPUn( zfH6c(QBKc?05^|JWbsTR-E%oPMC}gkIE&3B(#jL6%U1{O(Hodc#Rfr?d|N~5k94ug zpI`D7e=^?Scup!1u8;D0y-JfXdJr#9PtOoW#_@{-8oke%NGvZe=3={Gv5st+Ti>+$ zK+bJzRs8Ce$b$AnO|7VcmWy@+8ohpY)zXKNEEqd;j{`i#OYAD`|7M}3qx)F^qe5*E zLOaQ^;5f+Kv46Qom*@PXW5XU7(=OsZCud$dr(A$PoCWn07RzxpL+J`dxu5ZxGVz8+ zMMb^&V6d|EeD618xd^+YkEL??+rP*tYiKR7pfS!vLYh>0QR~)=4$?I)Ap=GvLr6zQ zHvyb-g^B<hhN7dQwqgk0VO3wbJ-Rjar84!ezm_flFDGZI0+s8nkm&yZ6`-hh%T14J z(u7$0f(09+qb{wDK=Mnpnq9K_mO&q(aMV}{JTD2BcN+|n^L);{tx-abk3TpH`!ZkF zS;=QU^w?K=l%XU9krM)0`P@yaIt2e_q}(qwLo0Uxcr6$x#5zZZZYKxA9dCXz`)krg z^JDU3I{gHL<+wU*1i>>!MZHMyqN1XrXViZQ9|-dV4=?F@#R9=G7={yt<=4W}GUZ8> z|G~84l#4KRoT(m_Tpt5iLhm93NW>=!E-Oi`fI^XUv0b}JvSXAa-RtboHv{q1_rcMa znag{rHf2GUk7K(v$-e_&qd?w!!K`R-@Qk6)dQR_bnH0v=aXP_*ygOcTu{<ZZ_m!<J zakL_#VkFV6Y3|2~K@LooVfkdlDv1F&14Zq&dlQMjP@r&WhefI?`9m(sV4JEBfE;K7 zvFD&ACPHVLq!v%3Dud*J9m#Wl%uyWM8Pj=qY!aw!xjW-38tO{25HxelF_JqUus&}d zwcxe=@CymD;?-0|-%wWT%JCN}w&j<um+w5Xzmh55?Ew_0+t%k0`uo%WqZt5Gpf9S* zGyV{2mU{fN%Ifq1(@!qR!Yyizo>rAS$uBAIK5+i}>?9HtPx(9*LHXmLQW5ViSPEI( z9kB?efd(hBhu0pc1rhA>-&u<QX1I2o<?MC3q5D)+o&#H0(Jkwbu_*!z@fp{@c~cSF zs-_1&To!9I9(CG|2KN|NwLoZOng~B?oupB_c`S^a!Z0N~X-g%pA+uW^o`jHw7g_G# zB3sQdn25-cuN(4k_F7^algUokP}PKjk=j+e6?b12PhH+TEe?i)xOtDmGx`mV9c&#5 ze=D>5(3!~P8h5MRjjEswT>m=6#B#c92{Z~T=esN-eF^;6d-U7>IeWlDD-!1&=!&^H zxfR1x$45Bt>vb$9({m-szswXEF_9p>JCAL4Xgty0x@M&DnX9q9zKkJ_h*)N{Fx|p^ zv_m%?1Wg=Y2^#p#UF^7Tzn#-|>9rm07^+{m=qPZG+^wx0mR@MRYFy7*h4+cSy+bv@ z&D;FKc*3{Y4l}^3L-sv5holi5n+qIF!oFjVH`-@x<|QiAD%&0`)W0I%VP@&A45o9$ z-;+DRz+z|K)8z#6e=jP*x-*%cB3=%pjG(U#!2wRd%ay~Y<h8%`ni)rusAD(TgU1dS zO0Bwj<IGQ!=b9Z-h}Wy$H?GQ-C!`p*_|3IycP`LCMhs1x$0=P{6{YIE5Ny|1ZsbGO zDz-;94yeiI0Q;9t(@g`d6+}tE46)o%I28hgzry=d&k9U^Ghq)rZuT@!)NZ&)k%5)W z>99=)TuDA*`AS%vzOrLPASiMNvS=(4UX)EHbJTVz^ExY#-n!T<O4eQTh3@W;ffM0^ z2B~<Nn`;hJ-yh`tE2ia_tLon~S(R*u`nVNVo0Z9e8@#wBpvo;S_i9=v^X;(Kr(NOp z8jLgJXUD@NvE6@QUQz-yS;Z-N@`ztv+wE_(6gFZ6r*J2yM>40^*lz|3$fF##NDJqd zvtsQ1<t5S-_UiP>YN7hlsw=Fg`JLS<mL`MA7MX10i(&<Xq?QWfqkyAr!{34$Xn2W! zaeL0}lb^wThM&(Dl>B&+^UF`)*PaKkgBSJODX!>AkZ4ll`0&QpLbPE|&Y8q#0{F$} zmLh2a*ZO=8Ck0&($>~@Qeu`QucUHSA$a+!kWHY%A-O-AIC*0>Iy?HSlqpm6wBX1f4 zcVi(m5ZyT>!@jI#tCE_JgD!jp-A$f5zfaf&#ncHkiG%|b+RN|9s^0y+S=^72TU*uP zd-?<%IrEfh_gHczP*X$FY;D8rh4Ia3TmL_njoI-d!Z|%lszfi!RHRQ+Xu#@?^%8~? z6yXzr#-W3c+Fqlbe7)wcLo+CcF1oR}pA^WBKYjja6-3AjQl=BA#i|znr{lOr*7qRa zQ1U8&+Jr&8t8i(Y`vRVwDDzE>^dZHs$JeYK-I<N}BtRoQ*=$Bqz}8rG(Zh)3)Q7n| zI8RZ|ij`qZto|B9rBT|euNTqW@o95$jw2oYi~kZYgFC$_48DaVH5S-A-FMzXhJt+* z*l60K6iq0<XU0|D1Tw3%=JORqSoRnT({Y)wD>d2hSgUw)(tc`odU361SIJ7y%sS#e zf<WPrHwuH%y3x?Y`Q*r@mBm@#=`^d@wrf7O40hSRCCK8S;Vx6^ENIHB{Sob0)q|YY zaY~(vrh1C0>T!W#C?rt=-*&R@x!vZ4d@>(#$Bu}?Y_Z6FDS))8q5nvO6xhNhH%0zw z<GR&xvIh$Z&Tzga2MLK^n^6L;dX*giMuSH`U~qU<Mt76M#EN*%@(>GMLhAi_=djw+ zpWxY*BpcKFfUklr&22Yw%Gg_^zs;4se2hJaIsIk$DuY{;gum<e9dS40Ja~(_dzM#M zwT>8O7|#od8fa(GysO%Vh+##Nn9qGy;t>t&$#GP$R%hX5VUo2SSGKD2?<BzbtpUM4 zo4nPeEhAD$HldR4Hm1i%A3q=drg!f_2I&r(8WSmR4l0@p{8=&^){x6q?wgr-V)sVm zwI%lAhyU^Q&O=>=x8~Qfb^16aAUiO3-%`kBfjyR*Pg(3-5^rL>=#6*5`wqXD_87@W zM>ZUJ#<v?=eN%E|*X$zp;btM$AD*5T&mO-aE7wVDMHC=1DOIEXDNN69hoTUlcIeU! zvGHG6R#?a%j7>IJd^btk1t}+P%}0b~C0z*YkwOXVji@6ooTR==L^go#y03?g)Xyoa zo9Ed~E6IvEZD#{^P+V5*7}*4=lLWPQJD0GK2;kh?LP^*kZG{@^v5|nSBe2iWI#}IV z>GF#xZA`tYDQVl)YK6?inKP0f=!AdZR3)0kh7HaA;b0ja-y16$C={Cgt>T$>_xlUq zp-7O=g6GedzJ&%7d!MO$A?5-4pZ!J*SA$C?sv`XCGvHzHM3B&QW1KPWO>+AVlXvci zZziLwb6;?O+|27uKwA<UsR#$2%RI^xI40$4zcO9=<}&O3G<flM7eyw|aQ}fwaVvh& zVfX5EuaRhEpi>Q}JUsbyg(6h7E}ed*O=O)#mM2YR!GymKdMRO?&zj0NM$r-Hai1R2 z{X(1CQo~ym#6zN!7~-u{JQNs78_!4zdM`5{v-6s*wM7K{!}jZ$7G;OZ@#6QpHL7dC zu-RAn^n|$}O%os1h{j83#N&koes>A09=pIa$ntryNhyZaiOvUi(5cEq^7v`=#^JEd zHxx8)6i!wjGuCVll*<~Pcu`arSAm+CLbvr%ssVv*7A7?imH<O{h~D}6Du=$GmYHI4 zZl#QaXFxRtl9QT2Vpe_%`E}ty%=yDr!?b^UM6$Dq?_Arc$IbS|zj%GM5;L<|nidxi z|3m390u@A=OV91@p}IXc(P19Weg03&r{o%IZK57%!j0&-ix6D2l7y8%xjF1@wjvAL zvi&p#4qP-&wae`)%F|wTv6_VUWZhXwxKQ~nj&ld^Zg<9K&#R&<Q?#}Ge3{7*T;t<0 z|CFT1i~EZ02VQYKMO;nwQ%k67oAD*|J%2)C9DhZq+d-RIX@QYG*8?vx*U*V1IBb|U zZEHPfivQlXz7R4t7tVix4t?EcwD;Zs)k3B|S5dgNpI(Okdh!<d+>EL)H+RC$Uf-*y zQGrmI=Gadl6+7u9@%(f{s=4y%$y&#10B~HFH}?blo%)>4mIO;_c;zs1{oni#C?$`Y z8}{oZ3TU0@&BmezUa%Oiz4^<dVth~%G|}0NKS>~X_Oa`k#@>c6cCA$#IlAX%7eDpP z54RyZB7g8HO6rFa-wKAx;q?;{Xtol^3{k!d5vS;Iw_<(v{)58`)A-G3DeHN3*YPXe z)VIG>gad#2rb^g=gvfQWJ`NXr0B_94v1VlVH5B6&9Nr+x?Tx5nC0=iuQpkjHj@i2Y z1bTaf?xa5=ciqO7kT~Tq#az@R2B+<YGunsdIHSv&_%uS*OjaSWtE3B~XWi#bB+Vpk z7NoB#SOSkM(0@ubkgU>ubK7L5Zb?(0C#y#(8ZS#<LCnu<*$)vOl}6}<;t%7M!J~VJ zze^qOiM1PK+NA86a~{kZdv0&v-UZ80$YPmo|1TGcOn|Kb{;Kh^==alD8LkT61~I@; zu%V&F4Q){aT1DR!OlXdVV2^q<a=sMiRO$=0wUz2JHLmuZ3y;o+RvIeP+PhhW=a0%! ze>1JyHWNg?S@%wSd1|H?7Iv`hogK<^U>9bb@3WI8%s_ExWktK&ix5U+<r_oIB0{F_ zMG`8)#adH+ddM^LizOnmXldHz>W)F({)E}C%j3?SGem5>K?$Y{`>w5GJ!tUX-gg&o zJZrwXDi=a>{x}i8x}m)MSI!;hU3DO!?1bLx(RIGqY*%tC>`^VGct+D7ptta#$9*Ja zw^3Z9QAfcHdOx3|ka9{rK|fy!wbH54=1D%d+wNdmr8CO|oK?3Nt*hHNwKtRvZGC5{ z<A*+cI<Fl7bfhl)@P7JmdhFM)FlEebmQnfFNM5DXmHU0#L&kzK_L&mFih6C_!|$V8 zlChTDVKbEUQN3Mc4`vI^*v2;YAF;oA9IhigN}h^56G1iau#eF9s|!j8BZjHCXg_~} zd$=Y&lGb^wC9r>g$c!N}W609dn?w|f9!uFXaK^j4+O^NKvIO0OC4H9bc{%zg1Z7kN zwjND}6$lbApNL-#1D@V#GWR>I(TG_ma}c~rFRPlx|FI5l#IslfsbuV74lw&d3yRgy z`U=k>^RAsN)=>4aj&7~TH(-sY-sF$OZ=|4L8ZY(F23Zo*YEqyS+Bw`@apC6uvbtwW zvN8H+D`AbxoDDP)jN)g@iHuEtxP{w?Qe?((ZsOp51bjn(+KO2^!|xBMtf|zguJK;D zhkA%q)XO<I6oYWgGNotU_JHT;3~2Ma3)gJE-D$y7zIGnSD?x;0g_5*jzj3Assk*jC z@ph&PKL~0b)3Q|-0q+jv<9?aAc3b24^scwMpS9d;)-N7N<%!IUb?T)c>D>pV$6ti# zEio5(ONBKh%W3pStU~z0iaGqQB{r+4Ft0uvs-2xJ^c<hH2OU2$udz<{?qD)QSX2nc zF+xRNphPmf=Z|V=r*?xHqcjDyk>yRsHyVuWFMS*sR>%UY_bLl%MEQ5$M?<Y03MeuO zp0$IugCvP=B3H&Vk|1o!RV*a_rc_56;<=)+YsX>4O<_&UvdYw6_im(uRBHv7*ggwL z1LnMh?XHVqxJ(nvP2b<i+)n$B_<tPR)+vbHtJGFqI9P!Fi<~R<<p)OgWv{p;R90Jk z8U8W9bmxx0@R`|kNlwQ35tmHd#Kv9@@jnj2(DCN|F3+Ib7XRV@&SMzjakC0aMONhx z3=FXNko}=F;DCE_K-BE%Ca(>x1+z0Ml3Topcz(kL=Gn5GN6yYhJ=@>QF`iTg^*&kn zW*x*H0bZOyR8pA6IyNy$PDvA%U2)e9Ni#pJl?ll;WXEi1$SA<a1@#;G-G{^+OC(H+ z8aWtT4g_N4#el`X)!{iFtd~oEa@dnjbt<$g)NOt^AJ)=3Jx|lfA#wn(%l08J5<=-o z5c*Z`;oXDJr>->HokKasNO8sQh@2>|^+|*;Zu}>G&#vPJM^2_%mty0V`ww<yw^4&5 zQ79UrnYy?5VXA@NPJj%RFs94_MGycx8>jU%zvODMxD5S%<;4YJQPjB9f@`Z`-kNep zx~Y0#<4-RKm!K!O9?!kCUuJy~B`IPPnB%qWs~cJLuYRvsoDcCs_H*8}gg`tpU3<58 zFC(Gfa-+67ohKkbN~g{0zk~7<Ad`sF!!%!4VEOfyj(w}Gud^R1DIP_ZjYPkB4tqLw zaf@``9NzqeJ<I<?Ioe@IQclGjpABhs;zqXNaC#E)v(Ag|YDbWw;>3p^YqS)Ip%A-A z)@AouFIOq=;4f@riM7ULM{2M~GxtfC{c=c`PaCp+s)F-v_-8;wba+C(-_v}Yc{H*K zHk+EDO%IS$#oLvAF$jS+R~hY(4%7DMcOPa}$I8+qr})ko%&mhOxh^<)^?h<jz=4{I z@F#V$)dky3F7av{HtHwPgxj=*l8;IDc8U%6Fp=*#32?v(eAnKV^5Gs8pZd*~paA6F zUcz)a2+&TD+~$7OhYzQhmxVd=x^A!Aw-con#{30cwEtIul1oF0`A%%sZ_%7(LIN#% z4<u=IJoj`<fdULQmUGX#ke687pALuw-pe;<NM<}<R&k;O;9?mJxCg6`!c)@4ktQ(w zFM#wvasFRuschulr|iNNajCd+<CtR0H1TnJY_g9<>6!6e9q62qU~en>%Uc-co?h<r zARG);HUMW03&CIU8kU?CrdJ{8j%u>%t)(vBLr6_FfH9=qTkgGIhXs$?^$zNJ^2{4* z4a9#j=_wPvyZ3jco%^5fDvfzoQ``<p?=fKnag!BTQ10&tyWWO8PXGDZwL)*nJLtCS zo9wz_e3WvW6PB~V5$+SpapmzB9{vz1@MHs$y6icp?6P>wPr6D3hFUF~{3o1T*2mIg zMUJEkWYF6m0Gl3u@k9p^K~|aE+E1BpUBHbR)Ie1trZ$Dod{bohym{f9Kd7XhncdP^ OfSS@X#Y%b0kpBSo`qq^I literal 0 HcmV?d00001 diff --git a/apps/meteor/server/settings/accounts.ts b/apps/meteor/server/settings/accounts.ts index 586795cef6387..c3273c282aa8d 100644 --- a/apps/meteor/server/settings/accounts.ts +++ b/apps/meteor/server/settings/accounts.ts @@ -205,6 +205,10 @@ export const createAccountSettings = () => type: 'boolean', public: true, }); + await this.add('Accounts_AllowFeaturePreview', false, { + type: 'boolean', + public: true, + }); await this.add('Accounts_CustomFieldsToShowInUserInfo', '', { type: 'string', public: true, diff --git a/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts b/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts index d820dfe5cb2d7..ce1e33e02dc6e 100644 --- a/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts +++ b/packages/rest-typings/src/v1/users/UsersSetPreferenceParamsPOST.ts @@ -39,6 +39,7 @@ export type UsersSetPreferencesParamsPOST = { sidebarGroupByType?: boolean; muteFocusedConversations?: boolean; dontAskAgainList?: Array<{ action: string; label: string }>; + featuresPreview?: { name: string; value: boolean }[]; themeAppearence?: 'auto' | 'light' | 'dark'; receiveLoginDetectionEmail?: boolean; notifyCalendarEvents?: boolean; @@ -197,6 +198,17 @@ const UsersSetPreferencesParamsPostSchema = { }, nullable: true, }, + featuresPreview: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + value: { type: 'boolean' }, + }, + }, + nullable: true, + }, themeAppearence: { type: 'string', nullable: true, diff --git a/yarn.lock b/yarn.lock index 76c68d5fd8ddc..7bc670d2dd2af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9392,7 +9392,7 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/css-in-js@npm:^0.31.12, @rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.151": +"@rocket.chat/css-in-js@npm:^0.31.12, @rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.154": version: 0.31.23 resolution: "@rocket.chat/css-in-js@npm:0.31.23" dependencies: @@ -9418,7 +9418,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.151": +"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.154": version: 0.31.23 resolution: "@rocket.chat/css-supports@npm:0.31.23" dependencies: @@ -9646,10 +9646,10 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.327": - version: 0.32.0-dev.327 - resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.327" - checksum: 93597b59a76829d5074834295c0b13ecf1256433bdedab6ce5f92b50799893ca8f5eceabcdeaba0fbd24df7abd64f4c62fa28f008d8fa90a7ecd869bbd8b0d6a +"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.330": + version: 0.32.0-dev.330 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.330" + checksum: 9733eeb0c8f1d8220e13d20c85e9ecf219b6d22e3c339352a080a19cbdc7aae7828ae591884b070c48047fa984383122d8a816e7102a4c38ba6a6ce73f364452 languageName: node linkType: hard @@ -9709,14 +9709,14 @@ __metadata: linkType: soft "@rocket.chat/fuselage@npm:next": - version: 0.32.0-dev.377 - resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.377" - dependencies: - "@rocket.chat/css-in-js": ~0.31.23-dev.151 - "@rocket.chat/css-supports": ~0.31.23-dev.151 - "@rocket.chat/fuselage-tokens": ~0.32.0-dev.327 - "@rocket.chat/memo": ~0.31.23-dev.151 - "@rocket.chat/styled": ~0.31.23-dev.151 + version: 0.32.0-dev.380 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.380" + dependencies: + "@rocket.chat/css-in-js": ~0.31.23-dev.154 + "@rocket.chat/css-supports": ~0.31.23-dev.154 + "@rocket.chat/fuselage-tokens": ~0.32.0-dev.330 + "@rocket.chat/memo": ~0.31.23-dev.154 + "@rocket.chat/styled": ~0.31.23-dev.154 invariant: ^2.2.4 react-aria: ~3.23.1 react-keyed-flatten-children: ^1.3.0 @@ -9728,7 +9728,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: e61ddd6ce6dd7ea4ba8d5b5c5f464a82d5600934c7ae05a38f20227308f39b55dbd5b91f9190d494b814efac6e60eb70810ab7d171ff1b482e54a2132573bdda + checksum: 07be707ad2fab881852d1b4ac49044915b24d5bf82352cc6e3e57526dfbcd13ec3b2d73b362bbe52d122aa5b45d90371838f1e463a443934d573022ac70a57e6 languageName: node linkType: hard @@ -9816,9 +9816,9 @@ __metadata: linkType: soft "@rocket.chat/icons@npm:next": - version: 0.32.0-dev.349 - resolution: "@rocket.chat/icons@npm:0.32.0-dev.349" - checksum: 45553695c95ef0aa908b133e7347aff9b3845fe03486e145ed2e717efa1b2fc5044f8d7bd390031cc3e401ecfb89723ef4c158a136f164bf73b4a9653775963d + version: 0.32.0-dev.362 + resolution: "@rocket.chat/icons@npm:0.32.0-dev.362" + checksum: 1c2096913e154d0db68b8ccc933294486ff06e250983809ce165f1497df05deec8b5165b47dbefdd013013b4cdf4a3a20f2ac191155b629e5ec4a7bcab5d8870 languageName: node linkType: hard @@ -9956,7 +9956,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.151": +"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.154": version: 0.31.23 resolution: "@rocket.chat/memo@npm:0.31.23" checksum: 070debb940749a2e4463cf767dd65c6967cea664a5bd67c22a812d611f6c3c46d6fe4bb0bf329e43dcd927493413add37c45ae3b05ec08f0b24e9d7385caebdd @@ -10761,7 +10761,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.151": +"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.154": version: 0.31.23 resolution: "@rocket.chat/styled@npm:0.31.23" dependencies: From 7a829e05113278bf3495ed154f05cb47ad5273de Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Thu, 6 Jul 2023 18:57:37 -0300 Subject: [PATCH 61/79] ci: create reporter (#29662) --- .github/workflows/ci-test-e2e.yml | 9 ++++++ .github/workflows/ci.yml | 4 +++ apps/meteor/playwright.config.ts | 12 ++++++-- apps/meteor/reporters/rocketchat.ts | 47 +++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 apps/meteor/reporters/rocketchat.ts diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index a27915154277e..28cbdf0ce4e77 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -56,6 +56,10 @@ on: required: true QASE_API_TOKEN: required: false + REPORTER_ROCKETCHAT_URL: + required: false + REPORTER_ROCKETCHAT_API_KEY: + required: false env: MONGO_URL: mongodb://localhost:27017/rocketchat?replicaSet=rs0&directConnection=true @@ -243,6 +247,11 @@ jobs: env: E2E_COVERAGE: ${{ inputs.release == 'ee' && 'true' || '' }} IS_EE: ${{ inputs.release == 'ee' && 'true' || '' }} + REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} + REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} + REPORTER_ROCKETCHAT_REPORT: ${{ github.ref == 'refs/heads/develop' && 'true' || '' }} + REPORTER_ROCKETCHAT_BRANCH: ${{ github.ref }} + REPORTER_ROCKETCHAT_DRAFT: ${{ github.event.pull_request.draft }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} QASE_REPORT: ${{ github.ref == 'refs/heads/develop' && 'true' || '' }} working-directory: ./apps/meteor diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c71965aa907c..1452e5b122d62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -259,6 +259,8 @@ jobs: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} + REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} + REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} test-api-ee: name: 🔨 Test API (EE) @@ -307,6 +309,8 @@ jobs: CR_USER: ${{ secrets.CR_USER }} CR_PAT: ${{ secrets.CR_PAT }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} + REPORTER_ROCKETCHAT_API_KEY: ${{ secrets.REPORTER_ROCKETCHAT_API_KEY }} + REPORTER_ROCKETCHAT_URL: ${{ secrets.REPORTER_ROCKETCHAT_URL }} tests-done: name: ✅ Tests Done diff --git a/apps/meteor/playwright.config.ts b/apps/meteor/playwright.config.ts index cc846051a5a7a..aaedd42e78d6a 100644 --- a/apps/meteor/playwright.config.ts +++ b/apps/meteor/playwright.config.ts @@ -22,7 +22,15 @@ export default { outputDir: 'tests/e2e/.playwright', reporter: [ ['list'], - // process.env.CI ? ['github'] : ['list'], + process.env.REPORTER_ROCKETCHAT_REPORT === 'true' && [ + './reporters/rocketchat.ts', + { + url: process.env.REPORTER_ROCKETCHAT_URL, + apiKey: process.env.REPORTER_ROCKETCHAT_API_KEY, + branch: process.env.REPORTER_ROCKETCHAT_BRANCH, + draft: process.env.REPORTER_ROCKETCHAT_DRAFT === 'true', + }, + ], [ 'playwright-qase-reporter', { @@ -36,7 +44,7 @@ export default { environmentId: '1', }, ], - ], + ].filter(Boolean), testDir: 'tests/e2e', testIgnore: 'tests/e2e/federation/**', workers: 1, diff --git a/apps/meteor/reporters/rocketchat.ts b/apps/meteor/reporters/rocketchat.ts new file mode 100644 index 0000000000000..c4ef26acf049d --- /dev/null +++ b/apps/meteor/reporters/rocketchat.ts @@ -0,0 +1,47 @@ +import fetch from 'node-fetch'; +import type { Reporter, TestCase, TestResult } from '@playwright/test/reporter'; + +class RocketChatReporter implements Reporter { + private url: string; + + private apiKey: string; + + private branch: string; + + private draft: boolean; + + constructor(options: { url: string; apiKey: string; branch: string; draft: boolean }) { + this.url = options.url; + this.apiKey = options.apiKey; + this.branch = options.branch; + this.draft = options.draft; + } + + onTestEnd(test: TestCase, result: TestResult) { + if (process.env.REPORTER_ROCKETCHAT_REPORT !== 'true') { + console.log('REPORTER_ROCKETCHAT_REPORT is not true, skipping', { + draft: this.draft, + branch: this.branch, + }); + return; + } + const payload = { + name: test.title, + status: result.status, + duration: result.duration, + branch: this.branch, + draft: this.draft, + }; + console.log(`Sending test result to Rocket.Chat: ${JSON.stringify(payload)}`); + return fetch(this.url, { + method: 'POST', + body: JSON.stringify(payload), + headers: { + 'Content-Type': 'application/json', + 'X-Api-Key': this.apiKey, + }, + }); + } +} + +export default RocketChatReporter; From b6a2c7763a0e86def9b79d4921fadc267512c0e7 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Thu, 6 Jul 2023 18:57:48 -0300 Subject: [PATCH 62/79] chore: improve server error handling (#29745) --- .../server/lib/RocketChat.ErrorHandler.ts | 118 +++++++++++------- apps/meteor/app/lib/server/lib/meteorFixes.js | 45 +------ 2 files changed, 74 insertions(+), 89 deletions(-) diff --git a/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts b/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts index 0aa8c3a53747e..e4a549a1f02d3 100644 --- a/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts +++ b/apps/meteor/app/error-handler/server/lib/RocketChat.ErrorHandler.ts @@ -20,55 +20,15 @@ class ErrorHandler { this.reporting = false; this.rid = null; this.lastError = null; - - Meteor.startup(async () => { - await this.registerHandlers(); - - settings.watch<string>('Log_Exceptions_to_Channel', async (value) => { - this.rid = null; - const roomName = value.trim(); - if (roomName) { - const rid = await this.getRoomId(roomName); - if (rid) { - this.rid = rid; - } - } - - if (this.rid) { - this.reporting = true; - } else { - this.reporting = false; - } - }); - }); - } - - async registerHandlers() { - process.on('uncaughtException', async (error) => { - incException(); - if (!this.reporting) { - return; - } - await this.trackError(error.message, error.stack); - }); - - // eslint-disable-next-line @typescript-eslint/no-this-alias - const self = this; - const originalMeteorDebug = Meteor._debug; - Meteor._debug = function (message, stack, ...args) { - if (!self.reporting) { - return originalMeteorDebug.call(this, message, stack); - } - void self.trackError(message, stack); - return originalMeteorDebug.apply(this, [message, stack, ...args]); - }; } - async getRoomId(roomName: string): Promise<string | undefined> { - roomName = roomName.replace('#', ''); - const room = await Rooms.findOneByName(roomName, { projection: { _id: 1, t: 1 } }); + async getRoomId(roomName: string): Promise<string | null> { + if (!roomName) { + return null; + } + const room = await Rooms.findOneByName(roomName.replace('#', ''), { projection: { _id: 1, t: 1 } }); if (!room || (room.t !== 'c' && room.t !== 'p')) { - return; + return null; } return room._id; } @@ -88,4 +48,68 @@ class ErrorHandler { } } -export default new ErrorHandler(); +const errorHandler = new ErrorHandler(); + +Meteor.startup(async () => { + settings.watch<string>('Log_Exceptions_to_Channel', async (value) => { + errorHandler.rid = null; + const roomName = value.trim(); + + const rid = await errorHandler.getRoomId(roomName); + + errorHandler.reporting = Boolean(rid); + errorHandler.rid = rid; + }); +}); + +// eslint-disable-next-line @typescript-eslint/no-this-alias +const originalMeteorDebug = Meteor._debug; + +Meteor._debug = function (message, stack, ...args) { + if (!errorHandler.reporting) { + return originalMeteorDebug.call(this, message, stack); + } + void errorHandler.trackError(message, stack); + return originalMeteorDebug.apply(this, [message, stack, ...args]); +}; + +/** + * If some promise is rejected and doesn't have a catch (unhandledRejection) it may cause this finally + * here https://github.com/meteor/meteor/blob/be6e529a739f47446950e045f4547ee60e5de7ae/packages/mongo/oplog_tailing.js#L348 + * to not be executed never ending the oplog worker and freezing the entire process. + * + * The only way to release the process is executing the following code via inspect: + * MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle._workerActive = false + * + * Since unhandled rejections are deprecated in NodeJS: + * (node:83382) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections + * that are not handled will terminate the Node.js process with a non-zero exit code. + * we will start respecting this and exit the process to prevent these kind of problems. + */ + +process.on('unhandledRejection', (error) => { + incException(); + + if (error instanceof Error) { + void errorHandler.trackError(error.message, error.stack); + } + + console.error('=== UnHandledPromiseRejection ==='); + console.error(error); + console.error('---------------------------------'); + console.error('Errors like this can cause oplog processing errors.'); + console.error( + 'Setting EXIT_UNHANDLEDPROMISEREJECTION will cause the process to exit allowing your service to automatically restart the process', + ); + console.error('Future node.js versions will automatically exit the process'); + console.error('================================='); + + if (process.env.NODE_ENV === 'development' || process.env.EXIT_UNHANDLEDPROMISEREJECTION) { + process.exit(1); + } +}); + +process.on('uncaughtException', async (error) => { + incException(); + void errorHandler.trackError(error.message, error.stack); +}); diff --git a/apps/meteor/app/lib/server/lib/meteorFixes.js b/apps/meteor/app/lib/server/lib/meteorFixes.js index 39616cac4042b..dd0764c468263 100644 --- a/apps/meteor/app/lib/server/lib/meteorFixes.js +++ b/apps/meteor/app/lib/server/lib/meteorFixes.js @@ -1,7 +1,4 @@ import { MongoInternals } from 'meteor/mongo'; -import { Settings } from '@rocket.chat/models'; - -import { throttledCounter } from '../../../../lib/utils/throttledCounter'; const timeoutQuery = parseInt(process.env.OBSERVERS_CHECK_TIMEOUT) || 2 * 60 * 1000; const interval = parseInt(process.env.OBSERVERS_CHECK_INTERVAL) || 60 * 1000; @@ -19,16 +16,16 @@ const debug = Boolean(process.env.OBSERVERS_CHECK_DEBUG); * A good way to freeze a observer is running the instance with --inspect and execute in inspector the following code: * multiplexer = Object.values(MongoInternals.defaultRemoteCollectionDriver().mongo._observeMultiplexers)[0] * multiplexer._observeDriver._needToPollQuery() - * Whis will raise an error of bindEnvironment and block the observer + * This will raise an error of bindEnvironment and block the observer * here https://github.com/meteor/meteor/blob/be6e529a739f47446950e045f4547ee60e5de7ae/packages/mongo/oplog_observe_driver.js#L698 * - * This code will check for observer instances in QUERYING mode for more than 2 minutues and will manually set them back + * This code will check for observer instances in QUERYING mode for more than 2 minutes and will manually set them back * to STEADY and force the query again to refresh the data and flush the _writesToCommitWhenWeReachSteady callbacks. */ setInterval(() => { if (debug) { - console.log('Checking for stucked observers'); + console.log('Checking for stuck observers'); } const now = Date.now(); const driver = MongoInternals.defaultRemoteCollectionDriver(); @@ -45,39 +42,3 @@ setInterval(() => { _observeDriver._needToPollQuery(); }); }, interval); - -const incException = throttledCounter((counter) => { - Settings.incrementValueById('Uncaught_Exceptions_Count', counter).catch(console.error); -}, 10000); - -/** - * If some promise is rejected and doesn't have a catch (unhandledRejection) it may cause this finally - * here https://github.com/meteor/meteor/blob/be6e529a739f47446950e045f4547ee60e5de7ae/packages/mongo/oplog_tailing.js#L348 - * to not be executed never ending the oplog worker and freezing the entire process. - * - * The only way to release the process is executing the following code via inspect: - * MongoInternals.defaultRemoteCollectionDriver().mongo._oplogHandle._workerActive = false - * - * Since unhandled rejections are deprecated in NodeJS: - * (node:83382) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections - * that are not handled will terminate the Node.js process with a non-zero exit code. - * we will start respecting this and exit the process to prevent these kind of problems. - */ - -process.on('unhandledRejection', (error) => { - incException(); - - console.error('=== UnHandledPromiseRejection ==='); - console.error(error); - console.error('---------------------------------'); - console.error('Errors like this can cause oplog processing errors.'); - console.error( - 'Setting EXIT_UNHANDLEDPROMISEREJECTION will cause the process to exit allowing your service to automatically restart the process', - ); - console.error('Future node.js versions will automatically exit the process'); - console.error('================================='); - - if (process.env.NODE_ENV === 'development' || process.env.EXIT_UNHANDLEDPROMISEREJECTION) { - process.exit(1); - } -}); From e1e77d7fde3e344058873de7fc3f0ecaeffd7c40 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Thu, 6 Jul 2023 18:58:08 -0300 Subject: [PATCH 63/79] test: remove tests that were not unit tests (#29734) --- .github/workflows/ci-test-unit.yml | 17 +- .../app/models/server/raw/Sessions.tests.js | 1154 ----------------- 2 files changed, 1 insertion(+), 1170 deletions(-) delete mode 100644 apps/meteor/tests/unit/app/models/server/raw/Sessions.tests.js diff --git a/.github/workflows/ci-test-unit.yml b/.github/workflows/ci-test-unit.yml index e38288e906176..f294ac2dda7b4 100644 --- a/.github/workflows/ci-test-unit.yml +++ b/.github/workflows/ci-test-unit.yml @@ -6,10 +6,6 @@ on: node-version: required: true type: string - mongodb-version: - default: "['4.4', '6.0']" - required: false - type: string env: MONGO_URL: mongodb://localhost:27017/rocketchat?replicaSet=rs0&directConnection=true @@ -19,20 +15,9 @@ jobs: test: runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - mongodb-version: ${{ fromJSON(inputs.mongodb-version) }} - - name: MongoDB ${{ matrix.mongodb-version }} + name: Unit Tests steps: - - name: Launch MongoDB - uses: supercharge/mongodb-github-action@1.9.0 - with: - mongodb-version: ${{ matrix.mongodb-version }} - mongodb-replica-set: rs0 - - uses: actions/checkout@v3 - name: Setup NodeJS diff --git a/apps/meteor/tests/unit/app/models/server/raw/Sessions.tests.js b/apps/meteor/tests/unit/app/models/server/raw/Sessions.tests.js deleted file mode 100644 index 05d26da43db4a..0000000000000 --- a/apps/meteor/tests/unit/app/models/server/raw/Sessions.tests.js +++ /dev/null @@ -1,1154 +0,0 @@ -import { expect } from 'chai'; -import { MongoMemoryServer } from 'mongodb-memory-server'; - -const { MongoClient } = require('mongodb'); - -const { aggregates } = require('../../../../../../server/models/raw/Sessions'); - -const sessions_dates = []; -const baseDate = new Date(2018, 6, 1); - -for (let index = 0; index < 365; index++) { - sessions_dates.push({ - _id: `${baseDate.getFullYear()}-${baseDate.getMonth() + 1}-${baseDate.getDate()}`, - year: baseDate.getFullYear(), - month: baseDate.getMonth() + 1, - day: baseDate.getDate(), - }); - baseDate.setDate(baseDate.getDate() + 1); -} - -const DATA = { - sessions: [ - { - _id: 'fNFyFcjszvoN6Grip2', - day: 30, - instanceId: 'HvbqxukP8E65LAGMY', - month: 4, - sessionId: 'kiA4xX33AyzPgpBNs2', - year: 2019, - _updatedAt: new Date('2019-04-30T16:33:24.311Z'), - createdAt: new Date('2019-04-30T00:11:34.047Z'), - device: { - type: 'browser', - name: 'Firefox', - longVersion: '66.0.3', - os: { - name: 'Linux', - version: '12', - }, - version: '66.0.3', - }, - host: 'localhost:3000', - ip: '127.0.0.1', - loginAt: new Date('2019-04-30T00:11:34.047Z'), - type: 'session', - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - lastActivityAt: new Date('2019-04-30T00:16:20.349Z'), - closedAt: new Date('2019-04-30T00:16:20.349Z'), - }, - { - _id: 'fNFyFcjszvoN6Grip', - day: 2, - instanceId: 'HvbqxukP8E65LAGMY', - month: 5, - sessionId: 'kiA4xX33AyzPgpBNs', - year: 2019, - _updatedAt: new Date('2019-05-06T16:33:24.311Z'), - createdAt: new Date('2019-05-03T00:11:34.047Z'), - device: { - type: 'browser', - name: 'Firefox', - longVersion: '66.0.3', - os: { - name: 'Linux', - version: '12', - }, - version: '66.0.3', - }, - host: 'localhost:3000', - ip: '127.0.0.1', - loginAt: new Date('2019-05-03T00:11:34.047Z'), - type: 'session', - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - lastActivityAt: new Date('2019-05-03T00:16:20.349Z'), - closedAt: new Date('2019-05-03T00:16:20.349Z'), - }, - { - _id: 'oZMkfR3gFB6kuKDK2', - day: 2, - instanceId: 'HvbqxukP8E65LAGMY', - month: 5, - sessionId: 'i8uJFekr9np4x88kS', - year: 2019, - _updatedAt: new Date('2019-05-06T16:33:24.311Z'), - createdAt: new Date('2019-05-03T00:16:21.847Z'), - device: { - type: 'browser', - name: 'Chrome', - longVersion: '73.0.3683.103', - os: { - name: 'Mac OS', - version: '10.14.1', - }, - version: '73.0.3683', - }, - host: 'localhost:3000', - ip: '127.0.0.1', - loginAt: new Date('2019-05-03T00:16:21.846Z'), - type: 'session', - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - lastActivityAt: new Date('2019-05-03T00:17:21.081Z'), - closedAt: new Date('2019-05-03T00:17:21.081Z'), - }, - { - _id: 'ABXKoXKTZpPpzLjKd', - day: 2, - instanceId: 'HvbqxukP8E65LAGMY', - month: 5, - sessionId: 'T8MB28cpx2ZjfEDXr', - year: 2019, - _updatedAt: new Date('2019-05-06T16:33:24.311Z'), - createdAt: new Date('2019-05-03T00:17:22.375Z'), - device: { - type: 'browser', - name: 'Chrome', - longVersion: '73.0.3683.103', - os: { - name: 'Mac OS', - version: '10.14.1', - }, - version: '73.0.3683', - }, - host: 'localhost:3000', - ip: '127.0.0.1', - loginAt: new Date('2019-05-03T00:17:22.375Z'), - type: 'session', - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - lastActivityAt: new Date('2019-05-03T01:48:31.695Z'), - closedAt: new Date('2019-05-03T01:48:31.695Z'), - }, - { - _id: 's4ucvvcfBjnTEtYEb', - day: 2, - instanceId: 'HvbqxukP8E65LAGMY', - month: 5, - sessionId: '8mHbJJypgeRG27TYF', - year: 2019, - _updatedAt: new Date('2019-05-06T16:33:24.311Z'), - createdAt: new Date('2019-05-03T01:48:43.521Z'), - device: { - type: 'browser', - name: 'Chrome', - longVersion: '73.0.3683.103', - os: { - name: 'Mac OS', - version: '10.14.1', - }, - version: '73.0.3683', - }, - host: 'localhost:3000', - ip: '127.0.0.1', - loginAt: new Date('2019-05-03T01:48:43.521Z'), - type: 'session', - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - closedAt: new Date('2019-05-03T01:48:43.761Z'), - lastActivityAt: new Date('2019-05-03T01:48:43.761Z'), - }, - { - _id: 'MDs9SzQKmwaDmXL8s', - day: 2, - instanceId: 'HvbqxukP8E65LAGMY', - month: 5, - sessionId: 'GmoBDPKy9RW2eXdCG', - year: 2019, - _updatedAt: new Date('2019-05-06T16:33:24.311Z'), - createdAt: new Date('2019-05-03T01:48:45.064Z'), - device: { - type: 'browser', - name: 'Chrome', - longVersion: '73.0.3683.103', - os: { - name: 'Mac OS', - version: '10.14.1', - }, - version: '73.0.3683', - }, - host: 'localhost:3000', - ip: '127.0.0.1', - loginAt: new Date('2019-05-03T01:48:45.064Z'), - type: 'session', - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - }, - { - _id: 'CJwfxASo62FHDgqog', - day: 2, - instanceId: 'Nmwo2ttFeWZSrowNh', - month: 5, - sessionId: 'LMrrL4sbpNMLWYomA', - year: 2019, - _updatedAt: new Date('2019-05-06T16:33:24.311Z'), - createdAt: new Date('2019-05-03T01:50:31.098Z'), - device: { - type: 'browser', - name: 'Chrome', - longVersion: '73.0.3683.103', - os: { - name: 'Mac OS', - version: '10.14.1', - }, - version: '73.0.3683', - }, - host: 'localhost:3000', - ip: '127.0.0.1', - loginAt: new Date('2019-05-03T01:50:31.092Z'), - type: 'session', - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - closedAt: new Date('2019-05-03T01:50:31.355Z'), - lastActivityAt: new Date('2019-05-03T01:50:31.355Z'), - }, - { - _id: 'iGAcPobWfTQtN6s4K', - day: 1, - instanceId: 'Nmwo2ttFeWZSrowNh', - month: 5, - sessionId: 'AsbjZRLNQMqfbyYFS', - year: 2019, - _updatedAt: new Date('2019-05-06T16:33:24.311Z'), - createdAt: new Date('2019-05-03T01:50:32.765Z'), - device: { - type: 'browser', - name: 'Chrome', - longVersion: '73.0.3683.103', - os: { - name: 'Mac OS', - version: '10.14.1', - }, - version: '73.0.3683', - }, - host: 'localhost:3000', - ip: '127.0.0.1', - loginAt: new Date('2019-05-03T01:50:32.765Z'), - type: 'session', - userId: 'xPZXw9xqM3kKshsse2', - mostImportantRole: 'admin', - lastActivityAt: new Date('2019-05-03T02:59:59.999Z'), - }, - ], - sessions_dates, -}; - -describe('Sessions Aggregates', () => { - let db; - - if (!process.env.MONGO_URL) { - let mongod; - before(async function () { - this.timeout(120000); - const version = '5.0.0'; - console.log(`Starting mongo version ${version}`); - mongod = await MongoMemoryServer.create({ binary: { version } }); - process.env.MONGO_URL = await mongod.getUri(); - }); - - after(async () => { - await mongod.stop(); - }); - } - - before(async () => { - console.log(`Connecting to mongo at ${process.env.MONGO_URL}`); - const client = await MongoClient.connect(process.env.MONGO_URL, { - useUnifiedTopology: true, - useNewUrlParser: true, - }); - db = client.db('test'); - - after(() => { - client.close(); - }); - - await db.dropDatabase(); - - const sessions = db.collection('sessions'); - const sessions_dates = db.collection('sessions_dates'); - - return Promise.all([sessions.insertMany(DATA.sessions), sessions_dates.insertMany(DATA.sessions_dates)]); - }); - - it('should have sessions_dates data saved', () => { - const collection = db.collection('sessions_dates'); - return collection - .find() - .toArray() - .then((docs) => expect(docs.length).to.be.equal(DATA.sessions_dates.length)); - }); - - it('should match sessions between 2018-12-11 and 2019-1-10', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 1, day: 10 }); - - expect($match).to.be.deep.equal({ - $and: [ - { - $or: [{ year: { $gt: 2018 } }, { year: 2018, month: { $gt: 12 } }, { year: 2018, month: 12, day: { $gte: 11 } }], - }, - { - $or: [{ year: { $lt: 2019 } }, { year: 2019, month: { $lt: 1 } }, { year: 2019, month: 1, day: { $lte: 10 } }], - }, - ], - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(31); - expect(docs).to.be.deep.equal([ - { _id: '2018-12-11', year: 2018, month: 12, day: 11 }, - { _id: '2018-12-12', year: 2018, month: 12, day: 12 }, - { _id: '2018-12-13', year: 2018, month: 12, day: 13 }, - { _id: '2018-12-14', year: 2018, month: 12, day: 14 }, - { _id: '2018-12-15', year: 2018, month: 12, day: 15 }, - { _id: '2018-12-16', year: 2018, month: 12, day: 16 }, - { _id: '2018-12-17', year: 2018, month: 12, day: 17 }, - { _id: '2018-12-18', year: 2018, month: 12, day: 18 }, - { _id: '2018-12-19', year: 2018, month: 12, day: 19 }, - { _id: '2018-12-20', year: 2018, month: 12, day: 20 }, - { _id: '2018-12-21', year: 2018, month: 12, day: 21 }, - { _id: '2018-12-22', year: 2018, month: 12, day: 22 }, - { _id: '2018-12-23', year: 2018, month: 12, day: 23 }, - { _id: '2018-12-24', year: 2018, month: 12, day: 24 }, - { _id: '2018-12-25', year: 2018, month: 12, day: 25 }, - { _id: '2018-12-26', year: 2018, month: 12, day: 26 }, - { _id: '2018-12-27', year: 2018, month: 12, day: 27 }, - { _id: '2018-12-28', year: 2018, month: 12, day: 28 }, - { _id: '2018-12-29', year: 2018, month: 12, day: 29 }, - { _id: '2018-12-30', year: 2018, month: 12, day: 30 }, - { _id: '2018-12-31', year: 2018, month: 12, day: 31 }, - { _id: '2019-1-1', year: 2019, month: 1, day: 1 }, - { _id: '2019-1-2', year: 2019, month: 1, day: 2 }, - { _id: '2019-1-3', year: 2019, month: 1, day: 3 }, - { _id: '2019-1-4', year: 2019, month: 1, day: 4 }, - { _id: '2019-1-5', year: 2019, month: 1, day: 5 }, - { _id: '2019-1-6', year: 2019, month: 1, day: 6 }, - { _id: '2019-1-7', year: 2019, month: 1, day: 7 }, - { _id: '2019-1-8', year: 2019, month: 1, day: 8 }, - { _id: '2019-1-9', year: 2019, month: 1, day: 9 }, - { _id: '2019-1-10', year: 2019, month: 1, day: 10 }, - ]); - }); - }); - - it('should match sessions between 2019-1-11 and 2019-2-10', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 10 }); - - expect($match).to.be.deep.equal({ - year: 2019, - $and: [ - { - $or: [{ month: { $gt: 1 } }, { month: 1, day: { $gte: 11 } }], - }, - { - $or: [{ month: { $lt: 2 } }, { month: 2, day: { $lte: 10 } }], - }, - ], - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.deep.equal(31); - expect(docs).to.be.deep.equal([ - { _id: '2019-1-11', year: 2019, month: 1, day: 11 }, - { _id: '2019-1-12', year: 2019, month: 1, day: 12 }, - { _id: '2019-1-13', year: 2019, month: 1, day: 13 }, - { _id: '2019-1-14', year: 2019, month: 1, day: 14 }, - { _id: '2019-1-15', year: 2019, month: 1, day: 15 }, - { _id: '2019-1-16', year: 2019, month: 1, day: 16 }, - { _id: '2019-1-17', year: 2019, month: 1, day: 17 }, - { _id: '2019-1-18', year: 2019, month: 1, day: 18 }, - { _id: '2019-1-19', year: 2019, month: 1, day: 19 }, - { _id: '2019-1-20', year: 2019, month: 1, day: 20 }, - { _id: '2019-1-21', year: 2019, month: 1, day: 21 }, - { _id: '2019-1-22', year: 2019, month: 1, day: 22 }, - { _id: '2019-1-23', year: 2019, month: 1, day: 23 }, - { _id: '2019-1-24', year: 2019, month: 1, day: 24 }, - { _id: '2019-1-25', year: 2019, month: 1, day: 25 }, - { _id: '2019-1-26', year: 2019, month: 1, day: 26 }, - { _id: '2019-1-27', year: 2019, month: 1, day: 27 }, - { _id: '2019-1-28', year: 2019, month: 1, day: 28 }, - { _id: '2019-1-29', year: 2019, month: 1, day: 29 }, - { _id: '2019-1-30', year: 2019, month: 1, day: 30 }, - { _id: '2019-1-31', year: 2019, month: 1, day: 31 }, - { _id: '2019-2-1', year: 2019, month: 2, day: 1 }, - { _id: '2019-2-2', year: 2019, month: 2, day: 2 }, - { _id: '2019-2-3', year: 2019, month: 2, day: 3 }, - { _id: '2019-2-4', year: 2019, month: 2, day: 4 }, - { _id: '2019-2-5', year: 2019, month: 2, day: 5 }, - { _id: '2019-2-6', year: 2019, month: 2, day: 6 }, - { _id: '2019-2-7', year: 2019, month: 2, day: 7 }, - { _id: '2019-2-8', year: 2019, month: 2, day: 8 }, - { _id: '2019-2-9', year: 2019, month: 2, day: 9 }, - { _id: '2019-2-10', year: 2019, month: 2, day: 10 }, - ]); - }); - }); - - it('should match sessions between 2019-5-1 and 2019-5-31', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 5, day: 31 }); - - expect($match).to.be.deep.equal({ - year: 2019, - month: 5, - day: { $gte: 1, $lte: 31 }, - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(31); - expect(docs).to.be.deep.equal([ - { _id: '2019-5-1', year: 2019, month: 5, day: 1 }, - { _id: '2019-5-2', year: 2019, month: 5, day: 2 }, - { _id: '2019-5-3', year: 2019, month: 5, day: 3 }, - { _id: '2019-5-4', year: 2019, month: 5, day: 4 }, - { _id: '2019-5-5', year: 2019, month: 5, day: 5 }, - { _id: '2019-5-6', year: 2019, month: 5, day: 6 }, - { _id: '2019-5-7', year: 2019, month: 5, day: 7 }, - { _id: '2019-5-8', year: 2019, month: 5, day: 8 }, - { _id: '2019-5-9', year: 2019, month: 5, day: 9 }, - { _id: '2019-5-10', year: 2019, month: 5, day: 10 }, - { _id: '2019-5-11', year: 2019, month: 5, day: 11 }, - { _id: '2019-5-12', year: 2019, month: 5, day: 12 }, - { _id: '2019-5-13', year: 2019, month: 5, day: 13 }, - { _id: '2019-5-14', year: 2019, month: 5, day: 14 }, - { _id: '2019-5-15', year: 2019, month: 5, day: 15 }, - { _id: '2019-5-16', year: 2019, month: 5, day: 16 }, - { _id: '2019-5-17', year: 2019, month: 5, day: 17 }, - { _id: '2019-5-18', year: 2019, month: 5, day: 18 }, - { _id: '2019-5-19', year: 2019, month: 5, day: 19 }, - { _id: '2019-5-20', year: 2019, month: 5, day: 20 }, - { _id: '2019-5-21', year: 2019, month: 5, day: 21 }, - { _id: '2019-5-22', year: 2019, month: 5, day: 22 }, - { _id: '2019-5-23', year: 2019, month: 5, day: 23 }, - { _id: '2019-5-24', year: 2019, month: 5, day: 24 }, - { _id: '2019-5-25', year: 2019, month: 5, day: 25 }, - { _id: '2019-5-26', year: 2019, month: 5, day: 26 }, - { _id: '2019-5-27', year: 2019, month: 5, day: 27 }, - { _id: '2019-5-28', year: 2019, month: 5, day: 28 }, - { _id: '2019-5-29', year: 2019, month: 5, day: 29 }, - { _id: '2019-5-30', year: 2019, month: 5, day: 30 }, - { _id: '2019-5-31', year: 2019, month: 5, day: 31 }, - ]); - }); - }); - - it('should match sessions between 2019-4-1 and 2019-4-30', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 4, day: 30 }); - - expect($match).to.be.deep.equal({ - year: 2019, - month: 4, - day: { $gte: 1, $lte: 30 }, - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(30); - expect(docs).to.be.deep.equal([ - { _id: '2019-4-1', year: 2019, month: 4, day: 1 }, - { _id: '2019-4-2', year: 2019, month: 4, day: 2 }, - { _id: '2019-4-3', year: 2019, month: 4, day: 3 }, - { _id: '2019-4-4', year: 2019, month: 4, day: 4 }, - { _id: '2019-4-5', year: 2019, month: 4, day: 5 }, - { _id: '2019-4-6', year: 2019, month: 4, day: 6 }, - { _id: '2019-4-7', year: 2019, month: 4, day: 7 }, - { _id: '2019-4-8', year: 2019, month: 4, day: 8 }, - { _id: '2019-4-9', year: 2019, month: 4, day: 9 }, - { _id: '2019-4-10', year: 2019, month: 4, day: 10 }, - { _id: '2019-4-11', year: 2019, month: 4, day: 11 }, - { _id: '2019-4-12', year: 2019, month: 4, day: 12 }, - { _id: '2019-4-13', year: 2019, month: 4, day: 13 }, - { _id: '2019-4-14', year: 2019, month: 4, day: 14 }, - { _id: '2019-4-15', year: 2019, month: 4, day: 15 }, - { _id: '2019-4-16', year: 2019, month: 4, day: 16 }, - { _id: '2019-4-17', year: 2019, month: 4, day: 17 }, - { _id: '2019-4-18', year: 2019, month: 4, day: 18 }, - { _id: '2019-4-19', year: 2019, month: 4, day: 19 }, - { _id: '2019-4-20', year: 2019, month: 4, day: 20 }, - { _id: '2019-4-21', year: 2019, month: 4, day: 21 }, - { _id: '2019-4-22', year: 2019, month: 4, day: 22 }, - { _id: '2019-4-23', year: 2019, month: 4, day: 23 }, - { _id: '2019-4-24', year: 2019, month: 4, day: 24 }, - { _id: '2019-4-25', year: 2019, month: 4, day: 25 }, - { _id: '2019-4-26', year: 2019, month: 4, day: 26 }, - { _id: '2019-4-27', year: 2019, month: 4, day: 27 }, - { _id: '2019-4-28', year: 2019, month: 4, day: 28 }, - { _id: '2019-4-29', year: 2019, month: 4, day: 29 }, - { _id: '2019-4-30', year: 2019, month: 4, day: 30 }, - ]); - }); - }); - - it('should match sessions between 2019-2-1 and 2019-2-28', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 28 }); - - expect($match).to.be.deep.equal({ - year: 2019, - month: 2, - day: { $gte: 1, $lte: 28 }, - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(28); - expect(docs).to.be.deep.equal([ - { _id: '2019-2-1', year: 2019, month: 2, day: 1 }, - { _id: '2019-2-2', year: 2019, month: 2, day: 2 }, - { _id: '2019-2-3', year: 2019, month: 2, day: 3 }, - { _id: '2019-2-4', year: 2019, month: 2, day: 4 }, - { _id: '2019-2-5', year: 2019, month: 2, day: 5 }, - { _id: '2019-2-6', year: 2019, month: 2, day: 6 }, - { _id: '2019-2-7', year: 2019, month: 2, day: 7 }, - { _id: '2019-2-8', year: 2019, month: 2, day: 8 }, - { _id: '2019-2-9', year: 2019, month: 2, day: 9 }, - { _id: '2019-2-10', year: 2019, month: 2, day: 10 }, - { _id: '2019-2-11', year: 2019, month: 2, day: 11 }, - { _id: '2019-2-12', year: 2019, month: 2, day: 12 }, - { _id: '2019-2-13', year: 2019, month: 2, day: 13 }, - { _id: '2019-2-14', year: 2019, month: 2, day: 14 }, - { _id: '2019-2-15', year: 2019, month: 2, day: 15 }, - { _id: '2019-2-16', year: 2019, month: 2, day: 16 }, - { _id: '2019-2-17', year: 2019, month: 2, day: 17 }, - { _id: '2019-2-18', year: 2019, month: 2, day: 18 }, - { _id: '2019-2-19', year: 2019, month: 2, day: 19 }, - { _id: '2019-2-20', year: 2019, month: 2, day: 20 }, - { _id: '2019-2-21', year: 2019, month: 2, day: 21 }, - { _id: '2019-2-22', year: 2019, month: 2, day: 22 }, - { _id: '2019-2-23', year: 2019, month: 2, day: 23 }, - { _id: '2019-2-24', year: 2019, month: 2, day: 24 }, - { _id: '2019-2-25', year: 2019, month: 2, day: 25 }, - { _id: '2019-2-26', year: 2019, month: 2, day: 26 }, - { _id: '2019-2-27', year: 2019, month: 2, day: 27 }, - { _id: '2019-2-28', year: 2019, month: 2, day: 28 }, - ]); - }); - }); - - it('should match sessions between 2019-1-28 and 2019-2-27', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ year: 2019, month: 2, day: 27 }); - - expect($match).to.be.deep.equal({ - year: 2019, - $and: [ - { - $or: [{ month: { $gt: 1 } }, { month: 1, day: { $gte: 28 } }], - }, - { - $or: [{ month: { $lt: 2 } }, { month: 2, day: { $lte: 27 } }], - }, - ], - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(31); - expect(docs).to.be.deep.equal([ - { _id: '2019-1-28', year: 2019, month: 1, day: 28 }, - { _id: '2019-1-29', year: 2019, month: 1, day: 29 }, - { _id: '2019-1-30', year: 2019, month: 1, day: 30 }, - { _id: '2019-1-31', year: 2019, month: 1, day: 31 }, - { _id: '2019-2-1', year: 2019, month: 2, day: 1 }, - { _id: '2019-2-2', year: 2019, month: 2, day: 2 }, - { _id: '2019-2-3', year: 2019, month: 2, day: 3 }, - { _id: '2019-2-4', year: 2019, month: 2, day: 4 }, - { _id: '2019-2-5', year: 2019, month: 2, day: 5 }, - { _id: '2019-2-6', year: 2019, month: 2, day: 6 }, - { _id: '2019-2-7', year: 2019, month: 2, day: 7 }, - { _id: '2019-2-8', year: 2019, month: 2, day: 8 }, - { _id: '2019-2-9', year: 2019, month: 2, day: 9 }, - { _id: '2019-2-10', year: 2019, month: 2, day: 10 }, - { _id: '2019-2-11', year: 2019, month: 2, day: 11 }, - { _id: '2019-2-12', year: 2019, month: 2, day: 12 }, - { _id: '2019-2-13', year: 2019, month: 2, day: 13 }, - { _id: '2019-2-14', year: 2019, month: 2, day: 14 }, - { _id: '2019-2-15', year: 2019, month: 2, day: 15 }, - { _id: '2019-2-16', year: 2019, month: 2, day: 16 }, - { _id: '2019-2-17', year: 2019, month: 2, day: 17 }, - { _id: '2019-2-18', year: 2019, month: 2, day: 18 }, - { _id: '2019-2-19', year: 2019, month: 2, day: 19 }, - { _id: '2019-2-20', year: 2019, month: 2, day: 20 }, - { _id: '2019-2-21', year: 2019, month: 2, day: 21 }, - { _id: '2019-2-22', year: 2019, month: 2, day: 22 }, - { _id: '2019-2-23', year: 2019, month: 2, day: 23 }, - { _id: '2019-2-24', year: 2019, month: 2, day: 24 }, - { _id: '2019-2-25', year: 2019, month: 2, day: 25 }, - { _id: '2019-2-26', year: 2019, month: 2, day: 26 }, - { _id: '2019-2-27', year: 2019, month: 2, day: 27 }, - ]); - }); - }); - - it('should have sessions data saved', () => { - const collection = db.collection('sessions'); - return collection - .find() - .toArray() - .then((docs) => expect(docs.length).to.be.equal(DATA.sessions.length)); - }); - - it('should generate daily sessions', () => { - const collection = db.collection('sessions'); - return aggregates - .dailySessionsOfYesterday(collection, { year: 2019, month: 5, day: 2 }) - .toArray() - .then(async (docs) => { - docs.forEach((doc) => { - doc._id = `${doc.userId}-${doc.year}-${doc.month}-${doc.day}`; - }); - - await collection.insertMany(docs); - - expect(docs.length).to.be.equal(3); - expect(docs).to.be.deep.equal([ - { - _id: 'xPZXw9xqM3kKshsse-2019-5-2', - time: 5814, - sessions: 3, - devices: [ - { - sessions: 2, - time: 5528, - device: { - type: 'browser', - name: 'Chrome', - longVersion: '73.0.3683.103', - os: { - name: 'Mac OS', - version: '10.14.1', - }, - version: '73.0.3683', - }, - }, - { - sessions: 1, - time: 286, - device: { - type: 'browser', - name: 'Firefox', - longVersion: '66.0.3', - os: { - name: 'Linux', - version: '12', - }, - version: '66.0.3', - }, - }, - ], - type: 'user_daily', - _computedAt: docs[0]._computedAt, - day: 2, - month: 5, - year: 2019, - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - }, - { - _id: 'xPZXw9xqM3kKshsse-2019-4-30', - day: 30, - devices: [ - { - device: { - longVersion: '66.0.3', - name: 'Firefox', - os: { - name: 'Linux', - version: '12', - }, - type: 'browser', - version: '66.0.3', - }, - sessions: 1, - time: 286, - }, - ], - month: 4, - sessions: 1, - time: 286, - type: 'user_daily', - _computedAt: docs[1]._computedAt, - userId: 'xPZXw9xqM3kKshsse', - mostImportantRole: 'user', - year: 2019, - }, - { - _id: 'xPZXw9xqM3kKshsse2-2019-5-1', - time: 4167, - sessions: 1, - devices: [ - { - sessions: 1, - time: 4167, - device: { - type: 'browser', - name: 'Chrome', - longVersion: '73.0.3683.103', - os: { - name: 'Mac OS', - version: '10.14.1', - }, - version: '73.0.3683', - }, - }, - ], - type: 'user_daily', - _computedAt: docs[2]._computedAt, - day: 1, - month: 5, - year: 2019, - userId: 'xPZXw9xqM3kKshsse2', - mostImportantRole: 'admin', - }, - ]); - }); - }); - - it('should have 2 unique users for month 5 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 }).then((docs) => { - expect(docs.length).to.be.equal(1); - expect(docs).to.be.deep.equal([ - { - count: 2, - roles: [ - { - count: 1, - role: 'user', - sessions: 3, - time: 5814, - }, - { - count: 1, - role: 'admin', - sessions: 1, - time: 4167, - }, - ], - sessions: 4, - time: 9981, - }, - ]); - }); - }); - - it('should have 1 unique user for 1st of month 5 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueUsersOfYesterday(collection, { year: 2019, month: 5, day: 1 }).then((docs) => { - expect(docs.length).to.be.equal(1); - expect(docs).to.be.deep.equal([ - { - count: 1, - roles: [ - { - count: 1, - role: 'admin', - sessions: 1, - time: 4167, - }, - ], - sessions: 1, - time: 4167, - }, - ]); - }); - }); - - it('should have 1 unique user for 2nd of month 5 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueUsersOfYesterday(collection, { year: 2019, month: 5, day: 2 }).then((docs) => { - expect(docs.length).to.be.equal(1); - expect(docs).to.be.deep.equal([ - { - count: 1, - roles: [ - { - count: 1, - role: 'user', - sessions: 3, - time: 5814, - }, - ], - sessions: 3, - time: 5814, - }, - ]); - }); - }); - - it('should have 2 unique devices for month 5 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueDevicesOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 }).then((docs) => { - expect(docs.length).to.be.equal(2); - expect(docs).to.be.deep.equal([ - { - count: 3, - time: 9695, - type: 'browser', - name: 'Chrome', - version: '73.0.3683', - }, - { - count: 1, - time: 286, - type: 'browser', - name: 'Firefox', - version: '66.0.3', - }, - ]); - }); - }); - - it('should have 2 unique devices for 2nd of month 5 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueDevicesOfYesterday(collection, { year: 2019, month: 5, day: 2 }).then((docs) => { - expect(docs.length).to.be.equal(2); - expect(docs).to.be.deep.equal([ - { - count: 2, - time: 5528, - type: 'browser', - name: 'Chrome', - version: '73.0.3683', - }, - { - count: 1, - time: 286, - type: 'browser', - name: 'Firefox', - version: '66.0.3', - }, - ]); - }); - }); - - it('should have 2 unique OS for month 5 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueOSOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31 }).then((docs) => { - expect(docs.length).to.be.equal(2); - expect(docs).to.be.deep.equal([ - { - count: 3, - time: 9695, - name: 'Mac OS', - version: '10.14.1', - }, - { - count: 1, - time: 286, - name: 'Linux', - version: '12', - }, - ]); - }); - }); - - it('should have 2 unique OS for 2nd of month 5 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueOSOfYesterday(collection, { year: 2019, month: 5, day: 2 }).then((docs) => { - expect(docs.length).to.be.equal(2); - expect(docs).to.be.deep.equal([ - { - count: 2, - time: 5528, - name: 'Mac OS', - version: '10.14.1', - }, - { - count: 1, - time: 286, - name: 'Linux', - version: '12', - }, - ]); - }); - }); - - it('should match sessions between 2018-12-29 and 2019-1-4', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ - year: 2019, - month: 1, - day: 4, - type: 'week', - }); - - expect($match).to.be.deep.equal({ - $and: [ - { - $or: [{ year: { $gt: 2018 } }, { year: 2018, month: { $gt: 12 } }, { year: 2018, month: 12, day: { $gte: 29 } }], - }, - { - $or: [{ year: { $lt: 2019 } }, { year: 2019, month: { $lt: 1 } }, { year: 2019, month: 1, day: { $lte: 4 } }], - }, - ], - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(7); - expect(docs).to.be.deep.equal([ - { _id: '2018-12-29', year: 2018, month: 12, day: 29 }, - { _id: '2018-12-30', year: 2018, month: 12, day: 30 }, - { _id: '2018-12-31', year: 2018, month: 12, day: 31 }, - { _id: '2019-1-1', year: 2019, month: 1, day: 1 }, - { _id: '2019-1-2', year: 2019, month: 1, day: 2 }, - { _id: '2019-1-3', year: 2019, month: 1, day: 3 }, - { _id: '2019-1-4', year: 2019, month: 1, day: 4 }, - ]); - }); - }); - - it('should match sessions between 2019-1-29 and 2019-2-4', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ - year: 2019, - month: 2, - day: 4, - type: 'week', - }); - - expect($match).to.be.deep.equal({ - year: 2019, - $and: [ - { - $or: [{ month: { $gt: 1 } }, { month: 1, day: { $gte: 29 } }], - }, - { - $or: [{ month: { $lt: 2 } }, { month: 2, day: { $lte: 4 } }], - }, - ], - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(7); - expect(docs).to.be.deep.equal([ - { _id: '2019-1-29', year: 2019, month: 1, day: 29 }, - { _id: '2019-1-30', year: 2019, month: 1, day: 30 }, - { _id: '2019-1-31', year: 2019, month: 1, day: 31 }, - { _id: '2019-2-1', year: 2019, month: 2, day: 1 }, - { _id: '2019-2-2', year: 2019, month: 2, day: 2 }, - { _id: '2019-2-3', year: 2019, month: 2, day: 3 }, - { _id: '2019-2-4', year: 2019, month: 2, day: 4 }, - ]); - }); - }); - - it('should match sessions between 2019-5-1 and 2019-5-7', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ - year: 2019, - month: 5, - day: 7, - type: 'week', - }); - - expect($match).to.be.deep.equal({ - year: 2019, - month: 5, - day: { $gte: 1, $lte: 7 }, - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(7); - expect(docs).to.be.deep.equal([ - { _id: '2019-5-1', year: 2019, month: 5, day: 1 }, - { _id: '2019-5-2', year: 2019, month: 5, day: 2 }, - { _id: '2019-5-3', year: 2019, month: 5, day: 3 }, - { _id: '2019-5-4', year: 2019, month: 5, day: 4 }, - { _id: '2019-5-5', year: 2019, month: 5, day: 5 }, - { _id: '2019-5-6', year: 2019, month: 5, day: 6 }, - { _id: '2019-5-7', year: 2019, month: 5, day: 7 }, - ]); - }); - }); - - it('should match sessions between 2019-5-7 and 2019-5-14', () => { - const collection = db.collection('sessions_dates'); - const $match = aggregates.getMatchOfLastMonthOrWeek({ - year: 2019, - month: 5, - day: 14, - type: 'week', - }); - - expect($match).to.be.deep.equal({ - year: 2019, - month: 5, - day: { $gte: 8, $lte: 14 }, - }); - - return collection - .aggregate([ - { - $match, - }, - ]) - .toArray() - .then((docs) => { - expect(docs.length).to.be.equal(7); - expect(docs).to.be.deep.equal([ - { _id: '2019-5-8', year: 2019, month: 5, day: 8 }, - { _id: '2019-5-9', year: 2019, month: 5, day: 9 }, - { _id: '2019-5-10', year: 2019, month: 5, day: 10 }, - { _id: '2019-5-11', year: 2019, month: 5, day: 11 }, - { _id: '2019-5-12', year: 2019, month: 5, day: 12 }, - { _id: '2019-5-13', year: 2019, month: 5, day: 13 }, - { _id: '2019-5-14', year: 2019, month: 5, day: 14 }, - ]); - }); - }); - - it('should have 0 unique users for the week ending on 5/31 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 31, type: 'week' }).then((docs) => { - expect(docs.length).to.be.equal(0); - }); - }); - - it('should have 2 unique users for the week ending on 5/7 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueUsersOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7, type: 'week' }).then((docs) => { - expect(docs.length).to.be.equal(1); - expect(docs).to.be.deep.equal([ - { - count: 2, - roles: [ - { - count: 1, - role: 'user', - sessions: 3, - time: 5814, - }, - { - count: 1, - role: 'admin', - sessions: 1, - time: 4167, - }, - ], - sessions: 4, - time: 9981, - }, - ]); - }); - }); - - it('should have 2 unique devices for the week ending on 5/7 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueDevicesOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7, type: 'week' }).then((docs) => { - expect(docs.length).to.be.equal(2); - expect(docs).to.be.deep.equal([ - { - count: 3, - time: 9695, - type: 'browser', - name: 'Chrome', - version: '73.0.3683', - }, - { - count: 1, - time: 286, - type: 'browser', - name: 'Firefox', - version: '66.0.3', - }, - ]); - }); - }); - - it('should have 2 unique OS for the week ending on 5/7 of 2019', () => { - const collection = db.collection('sessions'); - return aggregates.getUniqueOSOfLastMonthOrWeek(collection, { year: 2019, month: 5, day: 7 }).then((docs) => { - expect(docs.length).to.be.equal(2); - expect(docs).to.be.deep.equal([ - { - count: 3, - time: 9695, - name: 'Mac OS', - version: '10.14.1', - }, - { - count: 2, - time: 572, - name: 'Linux', - version: '12', - }, - ]); - }); - }); -}); From 0c98a2acd61c9cdb66e6ab597c4bdc216f6b0bbd Mon Sep 17 00:00:00 2001 From: Pedro Berleze Rorato <41977327+PedroRorato@users.noreply.github.com> Date: Thu, 6 Jul 2023 18:58:44 -0300 Subject: [PATCH 64/79] chore: Update VideoConferenceBlock user stack size (#29647) Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com> --- .../src/blocks/VideoConferenceBlock/VideoConferenceBlock.tsx | 2 +- .../src/VideoConfMessage/VideoConfMessageUserStack.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/VideoConferenceBlock.tsx b/packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/VideoConferenceBlock.tsx index 8dfc0449c987b..e595d2706e7b7 100644 --- a/packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/VideoConferenceBlock.tsx +++ b/packages/fuselage-ui-kit/src/blocks/VideoConferenceBlock/VideoConferenceBlock.tsx @@ -24,7 +24,7 @@ import { kitContext } from '../..'; type VideoConferenceBlockProps = BlockProps<UiKit.VideoConferenceBlock>; -const MAX_USERS = 6; +const MAX_USERS = 3; const VideoConferenceBlock = ({ block, diff --git a/packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageUserStack.tsx b/packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageUserStack.tsx index 2ca03a8140405..56f5e0a9fa130 100644 --- a/packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageUserStack.tsx +++ b/packages/ui-video-conf/src/VideoConfMessage/VideoConfMessageUserStack.tsx @@ -3,7 +3,7 @@ import { Avatar, Box } from '@rocket.chat/fuselage'; import { useUserAvatarPath } from '@rocket.chat/ui-contexts'; import { ReactElement, memo } from 'react'; -const MAX_USERS = 6; +const MAX_USERS = 3; const VideoConfMessageUserStack = ({ users }: { users: Serialized<IVideoConferenceUser>[] }): ReactElement => { const getUserAvatarPath = useUserAvatarPath(); From 3b09028b2c885af240c40eaaebf7957f2f7929bc Mon Sep 17 00:00:00 2001 From: Douglas Fabris <devfabris@gmail.com> Date: Thu, 6 Jul 2023 19:08:49 -0300 Subject: [PATCH 65/79] regression: Wrong addon usage on account menu itens (#29754) --- apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx index 6265a380fe674..5e6f4c7d183ab 100644 --- a/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx +++ b/apps/meteor/client/sidebar/header/hooks/useAccountItems.tsx @@ -32,7 +32,7 @@ export const useAccountItems = (): GenericMenuItemProps[] => { content: t('Feature_preview'), onClick: handleFeaturePreview, ...(unseenFeatures > 0 && { - addon: () => ( + addon: ( <Badge variant='primary' aria-label={t('Unseen_features')}> {unseenFeatures} </Badge> From 2ae66ab53a918177ed60d83ef89b4eea25ffb8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Thu, 6 Jul 2023 19:18:33 -0300 Subject: [PATCH 66/79] regression: `UserMenu` add missing translations (#29749) --- apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx b/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx index 4458deb26f421..232011dbe3153 100644 --- a/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx +++ b/apps/meteor/client/sidebar/header/hooks/useUserMenu.tsx @@ -1,4 +1,5 @@ import type { IUser } from '@rocket.chat/core-typings'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import UserMenuHeader from '../UserMenuHeader'; @@ -7,6 +8,8 @@ import { useStatusItems } from './useStatusItems'; import { useThemeItems } from './useThemeItems'; export const useUserMenu = (user: IUser) => { + const t = useTranslation(); + const statusItems = useStatusItems(user); const themeItems = useThemeItems(); const accountItems = useAccountItems(); @@ -17,11 +20,11 @@ export const useUserMenu = (user: IUser) => { items: [], }, { - title: 'Status', + title: t('Status'), items: statusItems, }, { - title: 'Theme', + title: t('Theme'), items: themeItems, }, { From 0f2f37d4db67b87779fa06fe6351d7b4e510ecb7 Mon Sep 17 00:00:00 2001 From: Diego Sampaio <chinello@gmail.com> Date: Thu, 6 Jul 2023 23:24:05 -0300 Subject: [PATCH 67/79] ci: Improve release automation (#29752) --- .changeset/config.json | 2 +- .changeset/nine-yaks-draw.md | 5 ++ .github/workflows/changesets.yml | 51 ------------------- .github/workflows/publish-release.yml | 3 +- package.json | 1 - .../release-action/src/bumpNextVersion.ts | 12 ++--- .../src/fixWorkspaceVersionsBeforePublish.ts | 4 -- packages/release-action/src/gitUtils.ts | 38 +++++++++++++- packages/release-action/src/publishRelease.ts | 17 +++++-- .../release-action/src/startPatchRelease.ts | 2 +- yarn.lock | 33 +----------- 11 files changed, 66 insertions(+), 102 deletions(-) create mode 100644 .changeset/nine-yaks-draw.md delete mode 100644 .github/workflows/changesets.yml diff --git a/.changeset/config.json b/.changeset/config.json index 5dfd3f70619bb..9d77693b3b31b 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,6 +1,6 @@ { "$schema": "https://unpkg.com/@changesets/config@2.3.0/schema.json", - "changelog": ["@changesets/changelog-github", { "repo": "RocketChat/Rocket.Chat" }], + "changelog": "@changesets/changelog-git", "commit": false, "fixed": [ ["@rocket.chat/meteor", "@rocket.chat/core-typings", "@rocket.chat/rest-typings"] diff --git a/.changeset/nine-yaks-draw.md b/.changeset/nine-yaks-draw.md new file mode 100644 index 0000000000000..bd12a98350fbb --- /dev/null +++ b/.changeset/nine-yaks-draw.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/release-action': minor +--- + +Use `release-automation` branch to perform the release diff --git a/.github/workflows/changesets.yml b/.github/workflows/changesets.yml deleted file mode 100644 index 56759167493fb..0000000000000 --- a/.github/workflows/changesets.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Changesets - -on: - push: - branches: - - develop - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - release-versions: - name: ⚙️ Variables Setup - runs-on: ubuntu-latest - outputs: - node-version: ${{ steps.var.outputs.node-version }} - steps: - - uses: Bhacaz/checkout-files@v2 - with: - files: package.json - branch: ${{ github.ref }} - - - id: var - run: | - NODE_VERSION=$(node -p "require('./package.json').engines.node") - echo "NODE_VERSION: ${NODE_VERSION}" - echo "node-version=${NODE_VERSION}" >> $GITHUB_OUTPUT - - release: - name: Release - needs: [release-versions] - runs-on: ubuntu-latest - steps: - - name: Checkout Repo - uses: actions/checkout@v3 - - - name: Setup NodeJS - uses: ./.github/actions/setup-node - with: - node-version: ${{ needs.release-versions.outputs.node-version }} - cache-modules: true - install: true - - - uses: dtinth/setup-github-actions-caching-for-turbo@v1 - - - name: Create Release Pull Request - uses: changesets/action@v1 - with: - title: 'chore: Bump packages' - env: - HUSKY: 0 - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml index cb85da9fad6cd..ba66adc5d29e7 100644 --- a/.github/workflows/publish-release.yml +++ b/.github/workflows/publish-release.yml @@ -3,7 +3,7 @@ name: Publish Final Release on: push: branches: - - master + - release-automation concurrency: ${{ github.workflow }}-${{ github.ref }} @@ -18,6 +18,7 @@ jobs: - name: Checkout Repo uses: actions/checkout@v3 with: + fetch-depth: 0 token: ${{ secrets.CI_PAT }} - name: Setup NodeJS diff --git a/package.json b/package.json index 7a79038a3acf1..a47523eefcea3 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,6 @@ "fuselage": "./fuselage.sh" }, "devDependencies": { - "@changesets/changelog-github": "^0.4.8", "@changesets/cli": "^2.26.1", "@types/chart.js": "^2.9.37", "@types/js-yaml": "^4.0.5", diff --git a/packages/release-action/src/bumpNextVersion.ts b/packages/release-action/src/bumpNextVersion.ts index cc977900b213f..999fa3d7b49ba 100644 --- a/packages/release-action/src/bumpNextVersion.ts +++ b/packages/release-action/src/bumpNextVersion.ts @@ -9,6 +9,7 @@ import { setupOctokit } from './setupOctokit'; import { createNpmFile } from './createNpmFile'; import { getChangelogEntry, bumpFileVersions, readPackageJson } from './utils'; import { fixWorkspaceVersionsBeforePublish } from './fixWorkspaceVersionsBeforePublish'; +import { commitChanges, createBranch, createTag, pushNewBranch } from './gitUtils'; export async function bumpNextVersion({ githubToken, @@ -59,26 +60,25 @@ export async function bumpNextVersion({ await bumpFileVersions(cwd, currentVersion, newVersion); // TODO check if branch exists - await exec('git', ['checkout', '-b', newBranch]); + await createBranch(newBranch); - await exec('git', ['add', '.']); - await exec('git', ['commit', '-m', newVersion]); + await commitChanges(`Release ${newVersion}`); core.info('fix dependencies in workspace packages'); await fixWorkspaceVersionsBeforePublish(); await exec('yarn', ['changeset', 'publish', '--no-git-tag']); - await exec('git', ['tag', newVersion]); + await createTag(newVersion); - await exec('git', ['push', '--force', '--follow-tags', 'origin', `HEAD:refs/heads/${newBranch}`]); + await pushNewBranch(newBranch); if (newVersion.includes('rc.0')) { const finalPrTitle = `Release ${finalVersion}`; core.info('creating pull request'); await octokit.rest.pulls.create({ - base: 'master', + base: 'release-automation', head: newBranch, title: finalPrTitle, body: prBody, diff --git a/packages/release-action/src/fixWorkspaceVersionsBeforePublish.ts b/packages/release-action/src/fixWorkspaceVersionsBeforePublish.ts index 1a5780996fef2..3a45e4fb87949 100644 --- a/packages/release-action/src/fixWorkspaceVersionsBeforePublish.ts +++ b/packages/release-action/src/fixWorkspaceVersionsBeforePublish.ts @@ -41,10 +41,6 @@ export async function fixWorkspaceVersionsBeforePublish() { for (const dependency of dependencies) { const dependencyVersion = packageJson[dependencyType][dependency]; if (dependencyVersion.startsWith('workspace:')) { - if (!dependencyVersion.startsWith('workspace:^')) { - throw new Error(`Unsupported workspace version range: ${dependencyVersion}`); - } - const realVersion = workspaceVersions.get(dependency); if (!realVersion) { throw new Error(`Could not find version for workspace ${dependency}`); diff --git a/packages/release-action/src/gitUtils.ts b/packages/release-action/src/gitUtils.ts index 0550841f2810f..808f6d8885fa3 100644 --- a/packages/release-action/src/gitUtils.ts +++ b/packages/release-action/src/gitUtils.ts @@ -1,6 +1,42 @@ -import { exec } from '@actions/exec'; +import { exec, getExecOutput } from '@actions/exec'; export async function setupGitUser() { await exec('git', ['config', 'user.name', '"rocketchat-github-ci"']); await exec('git', ['config', 'user.email', '"buildmaster@rocket.chat"']); } + +export async function createBranch(newBranch: string) { + await exec('git', ['checkout', '-b', newBranch]); +} + +export async function checkoutBranch(branchName: string) { + await exec('git', ['checkout', branchName]); +} + +export async function mergeBranch(branchName: string) { + await exec('git', ['merge', '--no-edit', branchName]); +} + +export async function commitChanges(commitMessage: string) { + await exec('git', ['add', '.']); + await exec('git', ['commit', '-m', commitMessage]); +} + +export async function createTag(version: string) { + // create an annotated tag so git push --follow-tags will push the tag + await exec('git', ['tag', version, '-m', version]); +} + +export async function getCurrentBranch() { + const { stdout: branchName } = await getExecOutput('git', ['rev-parse', '--abbrev-ref', 'HEAD']); + + return branchName.trim(); +} + +export async function pushChanges() { + await exec('git', ['push', '--follow-tags']); +} + +export async function pushNewBranch(newBranch: string) { + await exec('git', ['push', '--force', '--follow-tags', 'origin', `HEAD:refs/heads/${newBranch}`]); +} diff --git a/packages/release-action/src/publishRelease.ts b/packages/release-action/src/publishRelease.ts index a776c9d74d269..e9d76d19a75e7 100644 --- a/packages/release-action/src/publishRelease.ts +++ b/packages/release-action/src/publishRelease.ts @@ -9,6 +9,7 @@ import { createNpmFile } from './createNpmFile'; import { setupOctokit } from './setupOctokit'; import { bumpFileVersions, getChangelogEntry, readPackageJson } from './utils'; import { fixWorkspaceVersionsBeforePublish } from './fixWorkspaceVersionsBeforePublish'; +import { checkoutBranch, commitChanges, createTag, getCurrentBranch, mergeBranch, pushChanges } from './gitUtils'; export async function publishRelease({ githubToken, @@ -29,7 +30,7 @@ export async function publishRelease({ await createNpmFile(); if (baseRef) { - await exec('git', ['checkout', baseRef]); + await checkoutBranch(baseRef); } const { version: currentVersion } = await readPackageJson(cwd); @@ -73,17 +74,23 @@ export async function publishRelease({ core.info('update version in all files to new'); await bumpFileVersions(cwd, currentVersion, newVersion); - await exec('git', ['add', '.']); - await exec('git', ['commit', '-m', `Release ${newVersion}`]); + await commitChanges(`Release ${newVersion}`); + + // get current branch name + const branchName = await getCurrentBranch(); + + // merge release changes to master + await checkoutBranch('master'); + await mergeBranch(branchName); core.info('fix dependencies in workspace packages'); await fixWorkspaceVersionsBeforePublish(); await exec('yarn', ['changeset', 'publish', '--no-git-tag']); - await exec('git', ['tag', newVersion]); + await createTag(newVersion); - await exec('git', ['push', '--follow-tags']); + await pushChanges(); core.info('create release'); await octokit.rest.repos.createRelease({ diff --git a/packages/release-action/src/startPatchRelease.ts b/packages/release-action/src/startPatchRelease.ts index 05681604e47ce..a499b223608c6 100644 --- a/packages/release-action/src/startPatchRelease.ts +++ b/packages/release-action/src/startPatchRelease.ts @@ -48,7 +48,7 @@ export async function startPatchRelease({ core.info('creating pull request'); await octokit.rest.pulls.create({ - base: 'master', + base: 'release-automation', head: newBranch, title: finalPrTitle, body: '', diff --git a/yarn.lock b/yarn.lock index 7bc670d2dd2af..df384064d532f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4165,17 +4165,6 @@ __metadata: languageName: node linkType: hard -"@changesets/changelog-github@npm:^0.4.8": - version: 0.4.8 - resolution: "@changesets/changelog-github@npm:0.4.8" - dependencies: - "@changesets/get-github-info": ^0.5.2 - "@changesets/types": ^5.2.1 - dotenv: ^8.1.0 - checksum: 8a357cc08757e0eeca267ee05141f68bef936582abef8b78a5d30d99f5a86e41b7d3debba70992b73b2f57b0fc6201ec1cc3c65116930167ee3197b427b865c5 - languageName: node - linkType: hard - "@changesets/cli@npm:^2.26.1": version: 2.26.1 resolution: "@changesets/cli@npm:2.26.1" @@ -4256,16 +4245,6 @@ __metadata: languageName: node linkType: hard -"@changesets/get-github-info@npm:^0.5.2": - version: 0.5.2 - resolution: "@changesets/get-github-info@npm:0.5.2" - dependencies: - dataloader: ^1.4.0 - node-fetch: ^2.5.0 - checksum: 067e07eeaecdbedbd1c715513c4aa6206a941bd1d3af292d067792808c6fa6644caad2b35fba614a44892559c031c234df8028f8d2abd4cb2682d48080ef5df3 - languageName: node - linkType: hard - "@changesets/get-release-plan@npm:^3.0.16": version: 3.0.16 resolution: "@changesets/get-release-plan@npm:3.0.16" @@ -20149,13 +20128,6 @@ __metadata: languageName: node linkType: hard -"dataloader@npm:^1.4.0": - version: 1.4.0 - resolution: "dataloader@npm:1.4.0" - checksum: e2c93d43afde68980efc0cd9ff48e9851116e27a9687f863e02b56d46f7e7868cc762cd6dcbaf4197e1ca850a03651510c165c2ae24b8e9843fd894002ad0e20 - languageName: node - linkType: hard - "date-fns@npm:^2.15.0, date-fns@npm:^2.28.0": version: 2.28.0 resolution: "date-fns@npm:2.28.0" @@ -21058,7 +21030,7 @@ __metadata: languageName: node linkType: hard -"dotenv@npm:^8.0.0, dotenv@npm:^8.1.0": +"dotenv@npm:^8.0.0": version: 8.6.0 resolution: "dotenv@npm:8.6.0" checksum: 38e902c80b0666ab59e9310a3d24ed237029a7ce34d976796349765ac96b8d769f6df19090f1f471b77a25ca391971efde8a1ea63bb83111bd8bec8e5cc9b2cd @@ -30812,7 +30784,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.5.0, node-fetch@npm:^2.6.11": +"node-fetch@npm:^2.6.11": version: 2.6.11 resolution: "node-fetch@npm:2.6.11" dependencies: @@ -35958,7 +35930,6 @@ __metadata: version: 0.0.0-use.local resolution: "rocket.chat@workspace:." dependencies: - "@changesets/changelog-github": ^0.4.8 "@changesets/cli": ^2.26.1 "@types/chart.js": ^2.9.37 "@types/js-yaml": ^4.0.5 From d91e480ba986f2fcc66aa740b2d191a4202b3363 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Fri, 7 Jul 2023 10:24:31 -0300 Subject: [PATCH 68/79] chore: `GenericMenu` logic to add gap (#29751) --- .../client/components/GenericMenu/GenericMenu.tsx | 11 +++++++++-- .../client/components/GenericMenu/GenericMenuItem.tsx | 4 +++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/meteor/client/components/GenericMenu/GenericMenu.tsx b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx index 5a9ad9a0877c7..aba2e7e0eefb3 100644 --- a/apps/meteor/client/components/GenericMenu/GenericMenu.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx @@ -39,6 +39,9 @@ const GenericMenu = ({ title, icon = 'menu', onAction, ...props }: GenericMenuPr const disabledKeys = itemsList.filter(({ disabled }) => disabled).map(({ id }) => id); const handleAction = useHandleMenuAction(itemsList || []); + const hasIcon = itemsList.some(({ icon }) => icon); + const handleItems = (items: GenericMenuItemProps[]) => (hasIcon ? items.map((item) => ({ ...item, gap: !item.icon })) : items); + return ( <> {sections && ( @@ -50,7 +53,11 @@ const GenericMenu = ({ title, icon = 'menu', onAction, ...props }: GenericMenuPr {...props} > {sections.map(({ title, items }, key) => ( - <MenuSection title={typeof title === 'string' && t.has(title) ? t(title) : title} items={items} key={`${title}-${key}`}> + <MenuSection + title={typeof title === 'string' && t.has(title) ? t(title) : title} + items={handleItems(items)} + key={`${title}-${key}`} + > {(item) => ( <MenuItem key={item.id}> <GenericMenuItem {...item} /> @@ -68,7 +75,7 @@ const GenericMenu = ({ title, icon = 'menu', onAction, ...props }: GenericMenuPr {...(disabledKeys && { disabledKeys })} {...props} > - {items.map((item) => ( + {handleItems(items).map((item) => ( <MenuItem key={item.id}> <GenericMenuItem {...item} /> </MenuItem> diff --git a/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx b/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx index ca56b79de2583..ae79d5a4a78bf 100644 --- a/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenuItem.tsx @@ -11,10 +11,12 @@ export type GenericMenuItemProps = { status?: ReactNode; disabled?: boolean; description?: ReactNode; + gap?: boolean; }; -const GenericMenuItem = ({ icon, content, addon, status }: GenericMenuItemProps) => ( +const GenericMenuItem = ({ icon, content, addon, status, gap }: GenericMenuItemProps) => ( <> + {gap && <MenuItemColumn />} {icon && <MenuItemIcon name={icon} />} {status && <MenuItemColumn>{status}</MenuItemColumn>} {content && <MenuItemContent>{content}</MenuItemContent>} From e01bbcca5410374450229dfaa2d64d739dda3a10 Mon Sep 17 00:00:00 2001 From: Jayesh Jain <79307894+jayesh-jain252@users.noreply.github.com> Date: Fri, 7 Jul 2023 21:00:07 +0530 Subject: [PATCH 69/79] fix: mentions and emojis inside bold, italic and strikethrough (#29391) --- .changeset/mentions-emoji-emphasis.md | 6 ++ .../markup/elements/BoldSpan.tsx | 56 ++++++++++----- .../markup/elements/ItalicSpan.tsx | 56 ++++++++++----- .../markup/elements/StrikeSpan.tsx | 56 ++++++++++----- packages/gazzodown/src/elements/BoldSpan.tsx | 60 +++++++++++----- .../gazzodown/src/elements/ImageElement.tsx | 4 +- .../gazzodown/src/elements/ItalicSpan.tsx | 68 ++++++++++++------- .../gazzodown/src/elements/StrikeSpan.tsx | 68 ++++++++++++------- yarn.lock | 6 +- 9 files changed, 260 insertions(+), 120 deletions(-) create mode 100644 .changeset/mentions-emoji-emphasis.md diff --git a/.changeset/mentions-emoji-emphasis.md b/.changeset/mentions-emoji-emphasis.md new file mode 100644 index 0000000000000..044fe46b00e09 --- /dev/null +++ b/.changeset/mentions-emoji-emphasis.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/gazzodown': minor +--- + +Fixed mentions and emojis inside inside bold, italic or strikethrough texts + diff --git a/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/BoldSpan.tsx b/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/BoldSpan.tsx index 362fe269a652a..1a7a24e2fd17b 100644 --- a/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/BoldSpan.tsx +++ b/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/BoldSpan.tsx @@ -4,6 +4,7 @@ import type * as MessageParser from '@rocket.chat/message-parser'; import ItalicSpan from './ItalicSpan'; import LinkSpan from './LinkSpan'; import StrikeSpan from './StrikeSpan'; +import EmojiSpan from './EmojiSpan'; const styles = StyleSheet.create({ bold: { @@ -11,31 +12,52 @@ const styles = StyleSheet.create({ }, }); +type MessageBlock = + | MessageParser.Emoji + | MessageParser.ChannelMention + | MessageParser.UserMention + | MessageParser.Link + | MessageParser.MarkupExcluding<MessageParser.Bold>; + type BoldSpanProps = { - children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Bold>)[]; + children: MessageBlock[]; }; const BoldSpan = ({ children }: BoldSpanProps) => ( - <View style={styles.bold}> + <> {children.map((child, index) => { - switch (child.type) { - case 'LINK': - return <LinkSpan key={index} label={Array.isArray(child.value.label) ? child.value.label : [child.value.label]} />; + if (child.type === 'LINK' || child.type === 'PLAIN_TEXT' || child.type === 'ITALIC' || child.type === 'STRIKE') { + return ( + <View style={styles.bold} key={index}> + {renderBlockComponent(child, index)} + </View> + ); + } + return renderBlockComponent(child, index); + })} + </> +); - case 'PLAIN_TEXT': - return <Text key={index}>{child.value}</Text>; +const renderBlockComponent = (child: MessageBlock, index: number) => { + switch (child.type) { + case 'LINK': + return <LinkSpan key={index} label={Array.isArray(child.value.label) ? child.value.label : [child.value.label]} />; - case 'STRIKE': - return <StrikeSpan key={index} children={child.value} />; + case 'PLAIN_TEXT': + return <Text key={index}>{child.value}</Text>; - case 'ITALIC': - return <ItalicSpan key={index} children={child.value} />; + case 'STRIKE': + return <StrikeSpan key={index} children={child.value} />; - default: - return null; - } - })} - </View> -); + case 'ITALIC': + return <ItalicSpan key={index} children={child.value} />; + + case 'EMOJI': + return <EmojiSpan key={index} {...child} />; + + default: + return null; + } +}; export default BoldSpan; diff --git a/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/ItalicSpan.tsx b/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/ItalicSpan.tsx index d970cde92f404..1913ed98ee762 100644 --- a/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/ItalicSpan.tsx +++ b/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/ItalicSpan.tsx @@ -4,6 +4,7 @@ import type * as MessageParser from '@rocket.chat/message-parser'; import BoldSpan from './BoldSpan'; import LinkSpan from './LinkSpan'; import StrikeSpan from './StrikeSpan'; +import EmojiSpan from './EmojiSpan'; const styles = StyleSheet.create({ italic: { @@ -11,31 +12,52 @@ const styles = StyleSheet.create({ }, }); +type MessageBlock = + | MessageParser.Emoji + | MessageParser.ChannelMention + | MessageParser.UserMention + | MessageParser.Link + | MessageParser.MarkupExcluding<MessageParser.Italic>; + type ItalicSpanProps = { - children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Italic>)[]; + children: MessageBlock[]; }; const ItalicSpan = ({ children }: ItalicSpanProps) => ( - <View style={styles.italic}> + <> {children.map((child, index) => { - switch (child.type) { - case 'LINK': - return <LinkSpan key={index} label={Array.isArray(child.value.label) ? child.value.label : [child.value.label]} />; + if (child.type === 'LINK' || child.type === 'PLAIN_TEXT' || child.type === 'STRIKE' || child.type === 'BOLD') { + return ( + <View style={styles.italic} key={index}> + {renderBlockComponent(child, index)} + </View> + ); + } + return renderBlockComponent(child, index); + })} + </> +); - case 'PLAIN_TEXT': - return <Text key={index}>{child.value}</Text>; +const renderBlockComponent = (child: MessageBlock, index: number) => { + switch (child.type) { + case 'LINK': + return <LinkSpan key={index} label={Array.isArray(child.value.label) ? child.value.label : [child.value.label]} />; - case 'STRIKE': - return <StrikeSpan key={index} children={child.value} />; + case 'PLAIN_TEXT': + return <Text key={index}>{child.value}</Text>; - case 'BOLD': - return <BoldSpan key={index} children={child.value} />; + case 'STRIKE': + return <StrikeSpan key={index} children={child.value} />; - default: - return null; - } - })} - </View> -); + case 'BOLD': + return <BoldSpan key={index} children={child.value} />; + + case 'EMOJI': + return <EmojiSpan key={index} {...child} />; + + default: + return null; + } +}; export default ItalicSpan; diff --git a/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/StrikeSpan.tsx b/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/StrikeSpan.tsx index 0ce411d03dc3b..fe29ba2b6fbc5 100644 --- a/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/StrikeSpan.tsx +++ b/ee/packages/pdf-worker/src/templates/ChatTranscript/markup/elements/StrikeSpan.tsx @@ -4,6 +4,7 @@ import type * as MessageParser from '@rocket.chat/message-parser'; import ItalicSpan from './ItalicSpan'; import LinkSpan from './LinkSpan'; import BoldSpan from './BoldSpan'; +import EmojiSpan from './EmojiSpan'; const styles = StyleSheet.create({ strike: { @@ -11,31 +12,52 @@ const styles = StyleSheet.create({ }, }); +type MessageBlock = + | MessageParser.Emoji + | MessageParser.ChannelMention + | MessageParser.UserMention + | MessageParser.Link + | MessageParser.MarkupExcluding<MessageParser.Strike>; + type StrikeSpanProps = { - children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Strike>)[]; + children: MessageBlock[]; }; const StrikeSpan = ({ children }: StrikeSpanProps) => ( - <View style={styles.strike}> + <> {children.map((child, index) => { - switch (child.type) { - case 'LINK': - return <LinkSpan key={index} label={Array.isArray(child.value.label) ? child.value.label : [child.value.label]} />; + if (child.type === 'LINK' || child.type === 'PLAIN_TEXT' || child.type === 'ITALIC' || child.type === 'BOLD') { + return ( + <View style={styles.strike} key={index}> + {renderBlockComponent(child, index)} + </View> + ); + } + return renderBlockComponent(child, index); + })} + </> +); - case 'PLAIN_TEXT': - return <Text key={index}>{child.value}</Text>; +const renderBlockComponent = (child: MessageBlock, index: number) => { + switch (child.type) { + case 'LINK': + return <LinkSpan key={index} label={Array.isArray(child.value.label) ? child.value.label : [child.value.label]} />; - case 'BOLD': - return <BoldSpan key={index} children={child.value} />; + case 'PLAIN_TEXT': + return <Text key={index}>{child.value}</Text>; - case 'ITALIC': - return <ItalicSpan key={index} children={child.value} />; + case 'ITALIC': + return <ItalicSpan key={index} children={child.value} />; - default: - return null; - } - })} - </View> -); + case 'BOLD': + return <BoldSpan key={index} children={child.value} />; + + case 'EMOJI': + return <EmojiSpan key={index} {...child} />; + + default: + return null; + } +}; export default StrikeSpan; diff --git a/packages/gazzodown/src/elements/BoldSpan.tsx b/packages/gazzodown/src/elements/BoldSpan.tsx index b10611aa74df2..2cc6c60140c2d 100644 --- a/packages/gazzodown/src/elements/BoldSpan.tsx +++ b/packages/gazzodown/src/elements/BoldSpan.tsx @@ -1,36 +1,62 @@ import type * as MessageParser from '@rocket.chat/message-parser'; import type { ReactElement } from 'react'; +import EmojiElement from '../emoji/EmojiElement'; +import ChannelMentionElement from '../mentions/ChannelMentionElement'; +import UserMentionElement from '../mentions/UserMentionElement'; import ItalicSpan from './ItalicSpan'; import LinkSpan from './LinkSpan'; import PlainSpan from './PlainSpan'; import StrikeSpan from './StrikeSpan'; +type MessageBlock = + | MessageParser.Emoji + | MessageParser.ChannelMention + | MessageParser.UserMention + | MessageParser.Link + | MessageParser.MarkupExcluding<MessageParser.Bold>; + type BoldSpanProps = { - children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Bold>)[]; + children: MessageBlock[]; }; const BoldSpan = ({ children }: BoldSpanProps): ReactElement => ( - <strong> + <> {children.map((block, index) => { - switch (block.type) { - case 'LINK': - return <LinkSpan key={index} href={block.value.src.value} label={block.value.label} />; + if (block.type === 'LINK' || block.type === 'PLAIN_TEXT' || block.type === 'STRIKE' || block.type === 'ITALIC') { + return <strong key={index}>{renderBlockComponent(block, index)}</strong>; + } + return renderBlockComponent(block, index); + })} + </> +); - case 'PLAIN_TEXT': - return <PlainSpan key={index} text={block.value} />; +const renderBlockComponent = (block: MessageBlock, index: number): ReactElement | null => { + switch (block.type) { + case 'EMOJI': + return <EmojiElement key={index} {...block} />; - case 'STRIKE': - return <StrikeSpan key={index} children={block.value} />; + case 'MENTION_USER': + return <UserMentionElement key={index} mention={block.value.value} />; - case 'ITALIC': - return <ItalicSpan key={index} children={block.value} />; + case 'MENTION_CHANNEL': + return <ChannelMentionElement key={index} mention={block.value.value} />; - default: - return null; - } - })} - </strong> -); + case 'PLAIN_TEXT': + return <PlainSpan key={index} text={block.value} />; + + case 'LINK': + return <LinkSpan key={index} href={block.value.src.value} label={block.value.label} />; + + case 'STRIKE': + return <StrikeSpan key={index} children={block.value} />; + + case 'ITALIC': + return <ItalicSpan key={index} children={block.value} />; + + default: + return null; + } +}; export default BoldSpan; diff --git a/packages/gazzodown/src/elements/ImageElement.tsx b/packages/gazzodown/src/elements/ImageElement.tsx index 61b4cc18e1094..c42bba91f8841 100644 --- a/packages/gazzodown/src/elements/ImageElement.tsx +++ b/packages/gazzodown/src/elements/ImageElement.tsx @@ -1,7 +1,9 @@ import type * as MessageParser from '@rocket.chat/message-parser'; import { ReactElement, useMemo } from 'react'; -const flattenMarkup = (markup: MessageParser.Markup | MessageParser.Link): string => { +const flattenMarkup = ( + markup: MessageParser.Markup | MessageParser.Link | MessageParser.Emoji | MessageParser.ChannelMention | MessageParser.UserMention, +): string => { switch (markup.type) { case 'PLAIN_TEXT': return markup.value; diff --git a/packages/gazzodown/src/elements/ItalicSpan.tsx b/packages/gazzodown/src/elements/ItalicSpan.tsx index bc0e140983274..82bfddbe0ab1e 100644 --- a/packages/gazzodown/src/elements/ItalicSpan.tsx +++ b/packages/gazzodown/src/elements/ItalicSpan.tsx @@ -1,42 +1,62 @@ import type * as MessageParser from '@rocket.chat/message-parser'; import type { ReactElement } from 'react'; +import EmojiElement from '../emoji/EmojiElement'; +import ChannelMentionElement from '../mentions/ChannelMentionElement'; +import UserMentionElement from '../mentions/UserMentionElement'; import BoldSpan from './BoldSpan'; import LinkSpan from './LinkSpan'; import PlainSpan from './PlainSpan'; import StrikeSpan from './StrikeSpan'; +type MessageBlock = + | MessageParser.Emoji + | MessageParser.ChannelMention + | MessageParser.UserMention + | MessageParser.Link + | MessageParser.MarkupExcluding<MessageParser.Italic>; + type ItalicSpanProps = { - children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Italic>)[]; + children: MessageBlock[]; }; const ItalicSpan = ({ children }: ItalicSpanProps): ReactElement => ( - <em> + <> {children.map((block, index) => { - switch (block.type) { - case 'LINK': - return ( - <LinkSpan - key={index} - href={block.value.src.value} - label={Array.isArray(block.value.label) ? block.value.label : [block.value.label]} - /> - ); - - case 'PLAIN_TEXT': - return <PlainSpan key={index} text={block.value} />; - - case 'STRIKE': - return <StrikeSpan key={index} children={block.value} />; - - case 'BOLD': - return <BoldSpan key={index} children={block.value} />; - - default: - return null; + if (block.type === 'LINK' || block.type === 'PLAIN_TEXT' || block.type === 'STRIKE' || block.type === 'BOLD') { + return <em key={index}>{renderBlockComponent(block, index)}</em>; } + return renderBlockComponent(block, index); })} - </em> + </> ); +const renderBlockComponent = (block: MessageBlock, index: number): ReactElement | null => { + switch (block.type) { + case 'EMOJI': + return <EmojiElement key={index} {...block} />; + + case 'MENTION_USER': + return <UserMentionElement key={index} mention={block.value.value} />; + + case 'MENTION_CHANNEL': + return <ChannelMentionElement key={index} mention={block.value.value} />; + + case 'PLAIN_TEXT': + return <PlainSpan key={index} text={block.value} />; + + case 'LINK': + return <LinkSpan key={index} href={block.value.src.value} label={block.value.label} />; + + case 'STRIKE': + return <StrikeSpan key={index} children={block.value} />; + + case 'BOLD': + return <BoldSpan key={index} children={block.value} />; + + default: + return null; + } +}; + export default ItalicSpan; diff --git a/packages/gazzodown/src/elements/StrikeSpan.tsx b/packages/gazzodown/src/elements/StrikeSpan.tsx index e200e1154839b..302b226d7c192 100644 --- a/packages/gazzodown/src/elements/StrikeSpan.tsx +++ b/packages/gazzodown/src/elements/StrikeSpan.tsx @@ -1,42 +1,62 @@ import type * as MessageParser from '@rocket.chat/message-parser'; import type { ReactElement } from 'react'; +import EmojiElement from '../emoji/EmojiElement'; +import ChannelMentionElement from '../mentions/ChannelMentionElement'; +import UserMentionElement from '../mentions/UserMentionElement'; import BoldSpan from './BoldSpan'; import ItalicSpan from './ItalicSpan'; import LinkSpan from './LinkSpan'; import PlainSpan from './PlainSpan'; +type MessageBlock = + | MessageParser.Emoji + | MessageParser.ChannelMention + | MessageParser.UserMention + | MessageParser.Link + | MessageParser.MarkupExcluding<MessageParser.Strike>; + type StrikeSpanProps = { - children: (MessageParser.Link | MessageParser.MarkupExcluding<MessageParser.Strike>)[]; + children: MessageBlock[]; }; const StrikeSpan = ({ children }: StrikeSpanProps): ReactElement => ( - <del> + <> {children.map((block, index) => { - switch (block.type) { - case 'LINK': - return ( - <LinkSpan - key={index} - href={block.value.src.value} - label={Array.isArray(block.value.label) ? block.value.label : [block.value.label]} - /> - ); - - case 'PLAIN_TEXT': - return <PlainSpan key={index} text={block.value} />; - - case 'BOLD': - return <BoldSpan key={index} children={block.value} />; - - case 'ITALIC': - return <ItalicSpan key={index} children={block.value} />; - - default: - return null; + if (block.type === 'LINK' || block.type === 'PLAIN_TEXT' || block.type === 'ITALIC' || block.type === 'BOLD') { + return <del key={index}>{renderBlockComponent(block, index)}</del>; } + return renderBlockComponent(block, index); })} - </del> + </> ); +const renderBlockComponent = (block: MessageBlock, index: number): ReactElement | null => { + switch (block.type) { + case 'EMOJI': + return <EmojiElement key={index} {...block} />; + + case 'MENTION_USER': + return <UserMentionElement key={index} mention={block.value.value} />; + + case 'MENTION_CHANNEL': + return <ChannelMentionElement key={index} mention={block.value.value} />; + + case 'PLAIN_TEXT': + return <PlainSpan key={index} text={block.value} />; + + case 'LINK': + return <LinkSpan key={index} href={block.value.src.value} label={block.value.label} />; + + case 'ITALIC': + return <ItalicSpan key={index} children={block.value} />; + + case 'BOLD': + return <BoldSpan key={index} children={block.value} />; + + default: + return null; + } +}; + export default StrikeSpan; diff --git a/yarn.lock b/yarn.lock index df384064d532f..78715030ce8be 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9950,11 +9950,11 @@ __metadata: linkType: hard "@rocket.chat/message-parser@npm:next": - version: 0.32.0-dev.277 - resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.277" + version: 0.32.0-dev.296 + resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.296" dependencies: tldts: ~5.7.112 - checksum: c1e34dbe2dd23142c565088a86f4243501364a95de456e0b8add3cec78cd44545747b4ed7add7a1403667c14193a1bac52d2a98add85f0cf3cd9844fc9c03095 + checksum: 2a40090b0b5b94e531da3bd1433c6f60b98f19b85eb9ca81db8706fcf360d8245c97fde2257a6c39e9be7fa4a630eede5abf679847ef3c923594c7156f8eb334 languageName: node linkType: hard From 4eb944cdeb992937a3e5482875fb0211d0303822 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Fri, 7 Jul 2023 13:42:33 -0300 Subject: [PATCH 70/79] refactor: Changed `getUserPreference` to `useUserPreference` on `useVoipSounds` (#29744) --- .../client/providers/CallProvider/hooks/useVoipSounds.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/apps/meteor/client/providers/CallProvider/hooks/useVoipSounds.ts b/apps/meteor/client/providers/CallProvider/hooks/useVoipSounds.ts index 44e9f19e72e66..5ba847cae8d2e 100644 --- a/apps/meteor/client/providers/CallProvider/hooks/useVoipSounds.ts +++ b/apps/meteor/client/providers/CallProvider/hooks/useVoipSounds.ts @@ -1,18 +1,15 @@ -import { useCustomSound, useUser } from '@rocket.chat/ui-contexts'; +import { useCustomSound, useUserPreference } from '@rocket.chat/ui-contexts'; import { useMemo } from 'react'; -import { getUserPreference } from '../../../../app/utils/client'; - type VoipSound = 'telephone' | 'outbound-call-ringing' | 'call-ended'; export const useVoipSounds = () => { const { play, pause } = useCustomSound(); - const user = useUser(); + const audioVolume = useUserPreference<number>('notificationsSoundVolume', 100) || 100; return useMemo( () => ({ play: (soundId: VoipSound, loop = true) => { - const audioVolume = getUserPreference(user, 'notificationsSoundVolume', 100) as number; play(soundId, { volume: Number((audioVolume / 100).toPrecision(2)), loop, @@ -24,6 +21,6 @@ export const useVoipSounds = () => { pause('outbound-call-ringing'); }, }), - [play, pause, user], + [play, pause, audioVolume], ); }; From e6f905d58d9d2326dbf8cf67d6455637cc10dc2b Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Fri, 7 Jul 2023 13:55:41 -0300 Subject: [PATCH 71/79] chore: bump fuselage deps (#29739) --- packages/uikit-playground/package.json | 2 +- yarn.lock | 3598 ++---------------------- 2 files changed, 215 insertions(+), 3385 deletions(-) diff --git a/packages/uikit-playground/package.json b/packages/uikit-playground/package.json index c60b02b9ca00a..39933f5e16edb 100644 --- a/packages/uikit-playground/package.json +++ b/packages/uikit-playground/package.json @@ -14,7 +14,7 @@ "@codemirror/lang-json": "^6.0.1", "@codemirror/tooltip": "^0.19.16", "@lezer/highlight": "^1.1.6", - "@rocket.chat/css-in-js": "^0.31.12", + "@rocket.chat/css-in-js": "next", "@rocket.chat/fuselage": "next", "@rocket.chat/fuselage-hooks": "next", "@rocket.chat/fuselage-polyfills": "next", diff --git a/yarn.lock b/yarn.lock index 78715030ce8be..539e35e375fad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -957,16 +957,7 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3": - version: 7.21.4 - resolution: "@babel/code-frame@npm:7.21.4" - dependencies: - "@babel/highlight": ^7.18.6 - checksum: e5390e6ec1ac58dcef01d4f18eaf1fd2f1325528661ff6d4a5de8979588b9f5a8e852a54a91b923846f7a5c681b217f0a45c2524eb9560553160cd963b7d592c - languageName: node - linkType: hard - -"@babel/code-frame@npm:^7.22.5": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.5, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3": version: 7.22.5 resolution: "@babel/code-frame@npm:7.22.5" dependencies: @@ -975,21 +966,7 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/compat-data@npm:7.21.4" - checksum: 5f8b98c66f2ffba9f3c3a82c0cf354c52a0ec5ad4797b370dc32bdcd6e136ac4febe5e93d76ce76e175632e2dbf6ce9f46319aa689fcfafa41b6e49834fa4b66 - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.21.5": - version: 7.21.7 - resolution: "@babel/compat-data@npm:7.21.7" - checksum: 28747eb3fc084d088ba2db0336f52118cfa730a57bdbac81630cae1f38ad0336605b95b3390325937802f344e0b7fa25e2f1b67e3ee2d7383b877f88dee0e51c - languageName: node - linkType: hard - -"@babel/compat-data@npm:^7.22.5": +"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.5": version: 7.22.5 resolution: "@babel/compat-data@npm:7.22.5" checksum: eb1a47ebf79ae268b4a16903e977be52629339806e248455eb9973897c503a04b701f36a9de64e19750d6e081d0561e77a514c8dc470babbeba59ae94298ed18 @@ -1020,30 +997,7 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.7.5": - version: 7.21.4 - resolution: "@babel/core@npm:7.21.4" - dependencies: - "@ampproject/remapping": ^2.2.0 - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.4 - "@babel/helper-compilation-targets": ^7.21.4 - "@babel/helper-module-transforms": ^7.21.2 - "@babel/helpers": ^7.21.0 - "@babel/parser": ^7.21.4 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.4 - "@babel/types": ^7.21.4 - convert-source-map: ^1.7.0 - debug: ^4.1.0 - gensync: ^1.0.0-beta.2 - json5: ^2.2.2 - semver: ^6.3.0 - checksum: a3beebb2cc79908a02f27a07dc381bcb34e8ecc58fa99f568ad0934c49e12111fc977ee9c5b51eb7ea2da66f63155d37c4dd96b6472eaeecfc35843ccb56bf3d - languageName: node - linkType: hard - -"@babel/core@npm:^7.20.5, @babel/core@npm:~7.22.5": +"@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.20.5, @babel/core@npm:^7.21.4, @babel/core@npm:^7.7.5, @babel/core@npm:~7.22.5": version: 7.22.5 resolution: "@babel/core@npm:7.22.5" dependencies: @@ -1066,29 +1020,6 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.21.4": - version: 7.21.8 - resolution: "@babel/core@npm:7.21.8" - dependencies: - "@ampproject/remapping": ^2.2.0 - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.5 - "@babel/helper-compilation-targets": ^7.21.5 - "@babel/helper-module-transforms": ^7.21.5 - "@babel/helpers": ^7.21.5 - "@babel/parser": ^7.21.8 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 - convert-source-map: ^1.7.0 - debug: ^4.1.0 - gensync: ^1.0.0-beta.2 - json5: ^2.2.2 - semver: ^6.3.0 - checksum: f28118447355af2a90bd340e2e60699f94c8020517eba9b71bf8ebff62fa9e00d63f076e033f9dfb97548053ad62ada45fafb0d96584b1a90e8aef5a3b8241b1 - languageName: node - linkType: hard - "@babel/eslint-parser@npm:^7.22.5, @babel/eslint-parser@npm:~7.22.5": version: 7.22.5 resolution: "@babel/eslint-parser@npm:7.22.5" @@ -1103,31 +1034,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.21.4, @babel/generator@npm:^7.7.2": - version: 7.21.4 - resolution: "@babel/generator@npm:7.21.4" - dependencies: - "@babel/types": ^7.21.4 - "@jridgewell/gen-mapping": ^0.3.2 - "@jridgewell/trace-mapping": ^0.3.17 - jsesc: ^2.5.1 - checksum: 9ffbb526a53bb8469b5402f7b5feac93809b09b2a9f82fcbfcdc5916268a65dae746a1f2479e03ba4fb0776facd7c892191f63baa61ab69b2cfdb24f7b92424d - languageName: node - linkType: hard - -"@babel/generator@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/generator@npm:7.21.5" - dependencies: - "@babel/types": ^7.21.5 - "@jridgewell/gen-mapping": ^0.3.2 - "@jridgewell/trace-mapping": ^0.3.17 - jsesc: ^2.5.1 - checksum: 78af737b9dd701d4c657f9731880430fa1c177767b562f4e8a330a7fe72a4abe857e3d24de4e6d9dafc1f6a11f894162d27e523d7e5948ff9e3925a0ce9867c4 - languageName: node - linkType: hard - -"@babel/generator@npm:^7.22.5": +"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.22.5, @babel/generator@npm:^7.7.2": version: 7.22.5 resolution: "@babel/generator@npm:7.22.5" dependencies: @@ -1139,16 +1046,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" - dependencies: - "@babel/types": ^7.18.6 - checksum: 88ccd15ced475ef2243fdd3b2916a29ea54c5db3cd0cfabf9d1d29ff6e63b7f7cd1c27264137d7a40ac2e978b9b9a542c332e78f40eb72abe737a7400788fc1b - languageName: node - linkType: hard - -"@babel/helper-annotate-as-pure@npm:^7.22.5": +"@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" dependencies: @@ -1157,16 +1055,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.18.6" - dependencies: - "@babel/helper-explode-assignable-expression": ^7.18.6 - "@babel/types": ^7.18.6 - checksum: c4d71356e0adbc20ce9fe7c1e1181ff65a78603f8bba7615745f0417fed86bad7dc0a54a840bc83667c66709b3cb3721edcb9be0d393a298ce4e9eb6d085f3c1 - languageName: node - linkType: hard - "@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.22.5" @@ -1176,37 +1064,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.18.9, @babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/helper-compilation-targets@npm:7.21.4" - dependencies: - "@babel/compat-data": ^7.21.4 - "@babel/helper-validator-option": ^7.21.0 - browserslist: ^4.21.3 - lru-cache: ^5.1.1 - semver: ^6.3.0 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: bf9c7d3e7e6adff9222c05d898724cd4ee91d7eb9d52222c7ad2a22955620c2872cc2d9bdf0e047df8efdb79f4e3af2a06b53f509286145feccc4d10ddc318be - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-compilation-targets@npm:7.21.5" - dependencies: - "@babel/compat-data": ^7.21.5 - "@babel/helper-validator-option": ^7.21.0 - browserslist: ^4.21.3 - lru-cache: ^5.1.1 - semver: ^6.3.0 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 0edecb9c970ddc22ebda1163e77a7f314121bef9e483e0e0d9a5802540eed90d5855b6bf9bce03419b35b2e07c323e62d0353b153fa1ca34f17dbba897a83c25 - languageName: node - linkType: hard - -"@babel/helper-compilation-targets@npm:^7.22.5": +"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-compilation-targets@npm:7.22.5" dependencies: @@ -1221,44 +1079,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.17.6, @babel/helper-create-class-features-plugin@npm:^7.18.6": - version: 7.21.8 - resolution: "@babel/helper-create-class-features-plugin@npm:7.21.8" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-environment-visitor": ^7.21.5 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-member-expression-to-functions": ^7.21.5 - "@babel/helper-optimise-call-expression": ^7.18.6 - "@babel/helper-replace-supers": ^7.21.5 - "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 - "@babel/helper-split-export-declaration": ^7.18.6 - semver: ^6.3.0 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 26b978bd2e741259c0f4a1cc37521ad58728c50d28fe2fc8041d4381497e13a0b686a10e170246855eaf3af08886862e9d93fc27994ef914e13fca0d73efdcb8 - languageName: node - linkType: hard - -"@babel/helper-create-class-features-plugin@npm:^7.21.0": - version: 7.21.4 - resolution: "@babel/helper-create-class-features-plugin@npm:7.21.4" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-member-expression-to-functions": ^7.21.0 - "@babel/helper-optimise-call-expression": ^7.18.6 - "@babel/helper-replace-supers": ^7.20.7 - "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 - "@babel/helper-split-export-declaration": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 9123ca80a4894aafdb1f0bc08e44f6be7b12ed1fbbe99c501b484f9b1a17ff296b6c90c18c222047d53c276f07f17b4de857946fa9d0aa207023b03e4cc716f2 - languageName: node - linkType: hard - -"@babel/helper-create-class-features-plugin@npm:^7.22.5": +"@babel/helper-create-class-features-plugin@npm:^7.17.6, @babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0, @babel/helper-create-class-features-plugin@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-create-class-features-plugin@npm:7.22.5" dependencies: @@ -1277,32 +1098,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6": - version: 7.21.8 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.21.8" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - regexpu-core: ^5.3.1 - semver: ^6.3.0 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 04a686b5897c86339395894c0a9a1ffdce2facaba5173ce7b0a894f775f984ba70d2fa227d309f2be54f7f1286ebd1a0a7051a8b1829521595e4064ee062af65 - languageName: node - linkType: hard - -"@babel/helper-create-regexp-features-plugin@npm:^7.20.5": - version: 7.21.4 - resolution: "@babel/helper-create-regexp-features-plugin@npm:7.21.4" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - regexpu-core: ^5.3.1 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 78334865db2cd1d64d103bd0d96dee2818b0387d10aa973c084e245e829df32652bca530803e397b7158af4c02b9b21d5a9601c29bdfbb8d54a3d4ad894e067b - languageName: node - linkType: hard - -"@babel/helper-create-regexp-features-plugin@npm:^7.22.5": +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.22.5" dependencies: @@ -1333,22 +1129,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-define-polyfill-provider@npm:^0.3.3": - version: 0.3.3 - resolution: "@babel/helper-define-polyfill-provider@npm:0.3.3" - dependencies: - "@babel/helper-compilation-targets": ^7.17.7 - "@babel/helper-plugin-utils": ^7.16.7 - debug: ^4.1.1 - lodash.debounce: ^4.0.8 - resolve: ^1.14.2 - semver: ^6.1.2 - peerDependencies: - "@babel/core": ^7.4.0-0 - checksum: 8e3fe75513302e34f6d92bd67b53890e8545e6c5bca8fe757b9979f09d68d7e259f6daea90dc9e01e332c4f8781bda31c5fe551c82a277f9bc0bec007aed497c - languageName: node - linkType: hard - "@babel/helper-define-polyfill-provider@npm:^0.4.0": version: 0.4.0 resolution: "@babel/helper-define-polyfill-provider@npm:0.4.0" @@ -1365,13 +1145,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.18.9, @babel/helper-environment-visitor@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-environment-visitor@npm:7.21.5" - checksum: e436af7b62956e919066448013a3f7e2cd0b51010c26c50f790124dcd350be81d5597b4e6ed0a4a42d098a27de1e38561cd7998a116a42e7899161192deac9a6 - languageName: node - linkType: hard - "@babel/helper-environment-visitor@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-environment-visitor@npm:7.22.5" @@ -1379,25 +1152,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-explode-assignable-expression@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-explode-assignable-expression@npm:7.18.6" - dependencies: - "@babel/types": ^7.18.6 - checksum: 225cfcc3376a8799023d15dc95000609e9d4e7547b29528c7f7111a0e05493ffb12c15d70d379a0bb32d42752f340233c4115bded6d299bc0c3ab7a12be3d30f - languageName: node - linkType: hard - -"@babel/helper-function-name@npm:^7.18.9, @babel/helper-function-name@npm:^7.19.0, @babel/helper-function-name@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-function-name@npm:7.21.0" - dependencies: - "@babel/template": ^7.20.7 - "@babel/types": ^7.21.0 - checksum: d63e63c3e0e3e8b3138fa47b0cd321148a300ef12b8ee951196994dcd2a492cc708aeda94c2c53759a5c9177fffaac0fd8778791286746f72a000976968daf4e - languageName: node - linkType: hard - "@babel/helper-function-name@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-function-name@npm:7.22.5" @@ -1408,15 +1162,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-hoist-variables@npm:7.18.6" - dependencies: - "@babel/types": ^7.18.6 - checksum: fd9c35bb435fda802bf9ff7b6f2df06308a21277c6dec2120a35b09f9de68f68a33972e2c15505c1a1a04b36ec64c9ace97d4a9e26d6097b76b4396b7c5fa20f - languageName: node - linkType: hard - "@babel/helper-hoist-variables@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-hoist-variables@npm:7.22.5" @@ -1426,24 +1171,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-member-expression-to-functions@npm:^7.20.7, @babel/helper-member-expression-to-functions@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-member-expression-to-functions@npm:7.21.0" - dependencies: - "@babel/types": ^7.21.0 - checksum: 49cbb865098195fe82ba22da3a8fe630cde30dcd8ebf8ad5f9a24a2b685150c6711419879cf9d99b94dad24cff9244d8c2a890d3d7ec75502cd01fe58cff5b5d - languageName: node - linkType: hard - -"@babel/helper-member-expression-to-functions@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-member-expression-to-functions@npm:7.21.5" - dependencies: - "@babel/types": ^7.21.5 - checksum: c404b4a0271c640b7dc8c34af7b683c70a43200259e02330cfc02e79e6b271e9227f35554cd6ad015eabcfa1fea75b9d0b87b69f3d1e6c2af6edd224060b1732 - languageName: node - linkType: hard - "@babel/helper-member-expression-to-functions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-member-expression-to-functions@npm:7.22.5" @@ -1453,16 +1180,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/helper-module-imports@npm:7.21.4" - dependencies: - "@babel/types": ^7.21.4 - checksum: bd330a2edaafeb281fbcd9357652f8d2666502567c0aad71db926e8499c773c9ea9c10dfaae30122452940326d90c8caff5c649ed8e1bf15b23f858758d3abc6 - languageName: node - linkType: hard - -"@babel/helper-module-imports@npm:^7.22.5": +"@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-module-imports@npm:7.22.5" dependencies: @@ -1471,39 +1189,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.18.6, @babel/helper-module-transforms@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-module-transforms@npm:7.21.5" - dependencies: - "@babel/helper-environment-visitor": ^7.21.5 - "@babel/helper-module-imports": ^7.21.4 - "@babel/helper-simple-access": ^7.21.5 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/helper-validator-identifier": ^7.19.1 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 - checksum: 1ccfc88830675a5d485d198e918498f9683cdd46f973fdd4fe1c85b99648fb70f87fca07756c7a05dc201bd9b248c74ced06ea80c9991926ac889f53c3659675 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.20.11, @babel/helper-module-transforms@npm:^7.21.2": - version: 7.21.2 - resolution: "@babel/helper-module-transforms@npm:7.21.2" - dependencies: - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-simple-access": ^7.20.2 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/helper-validator-identifier": ^7.19.1 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.2 - "@babel/types": ^7.21.2 - checksum: 8a1c129a4f90bdf97d8b6e7861732c9580f48f877aaaafbc376ce2482febebcb8daaa1de8bc91676d12886487603f8c62a44f9e90ee76d6cac7f9225b26a49e1 - languageName: node - linkType: hard - -"@babel/helper-module-transforms@npm:^7.22.5": +"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-module-transforms@npm:7.22.5" dependencies: @@ -1519,15 +1205,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-optimise-call-expression@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-optimise-call-expression@npm:7.18.6" - dependencies: - "@babel/types": ^7.18.6 - checksum: e518fe8418571405e21644cfb39cf694f30b6c47b10b006609a92469ae8b8775cbff56f0b19732343e2ea910641091c5a2dc73b56ceba04e116a33b0f8bd2fbd - languageName: node - linkType: hard - "@babel/helper-optimise-call-expression@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-optimise-call-expression@npm:7.22.5" @@ -1544,34 +1221,13 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.16.7, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.21.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": - version: 7.21.5 - resolution: "@babel/helper-plugin-utils@npm:7.21.5" - checksum: 6f086e9a84a50ea7df0d5639c8f9f68505af510ea3258b3c8ac8b175efdfb7f664436cb48996f71791a1350ba68f47ad3424131e8e718c5e2ad45564484cbb36 - languageName: node - linkType: hard - -"@babel/helper-plugin-utils@npm:^7.22.5": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.16.7, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": version: 7.22.5 resolution: "@babel/helper-plugin-utils@npm:7.22.5" checksum: c0fc7227076b6041acd2f0e818145d2e8c41968cc52fb5ca70eed48e21b8fe6dd88a0a91cbddf4951e33647336eb5ae184747ca706817ca3bef5e9e905151ff5 languageName: node linkType: hard -"@babel/helper-remap-async-to-generator@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/helper-remap-async-to-generator@npm:7.18.9" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-wrap-function": ^7.18.9 - "@babel/types": ^7.18.9 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 4be6076192308671b046245899b703ba090dbe7ad03e0bea897bb2944ae5b88e5e85853c9d1f83f643474b54c578d8ac0800b80341a86e8538264a725fbbefec - languageName: node - linkType: hard - "@babel/helper-remap-async-to-generator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-remap-async-to-generator@npm:7.22.5" @@ -1586,35 +1242,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.16.7, @babel/helper-replace-supers@npm:^7.18.6, @babel/helper-replace-supers@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-replace-supers@npm:7.21.5" - dependencies: - "@babel/helper-environment-visitor": ^7.21.5 - "@babel/helper-member-expression-to-functions": ^7.21.5 - "@babel/helper-optimise-call-expression": ^7.18.6 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 - checksum: 4fd343e6f90533743d8e8a1f42e50377b3d6b27f524a27eb97ff28f075e4e55cca2383adb1b0973de358b08022aef0fec4c8d69711e1da43bf9b887b5a893677 - languageName: node - linkType: hard - -"@babel/helper-replace-supers@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/helper-replace-supers@npm:7.20.7" - dependencies: - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-member-expression-to-functions": ^7.20.7 - "@babel/helper-optimise-call-expression": ^7.18.6 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.20.7 - "@babel/types": ^7.20.7 - checksum: b8e0087c9b0c1446e3c6f3f72b73b7e03559c6b570e2cfbe62c738676d9ebd8c369a708cf1a564ef88113b4330750a50232ee1131d303d478b7a5e65e46fbc7c - languageName: node - linkType: hard - -"@babel/helper-replace-supers@npm:^7.22.5": +"@babel/helper-replace-supers@npm:^7.16.7, @babel/helper-replace-supers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-replace-supers@npm:7.22.5" dependencies: @@ -1628,24 +1256,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-simple-access@npm:^7.20.2": - version: 7.20.2 - resolution: "@babel/helper-simple-access@npm:7.20.2" - dependencies: - "@babel/types": ^7.21.5 - checksum: ad1e96ee2e5f654ffee2369a586e5e8d2722bf2d8b028a121b4c33ebae47253f64d420157b9f0a8927aea3a9e0f18c0103e74fdd531815cf3650a0a4adca11a1 - languageName: node - linkType: hard - -"@babel/helper-simple-access@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-simple-access@npm:7.21.5" - dependencies: - "@babel/types": ^7.21.5 - checksum: ad212beaa24be3864c8c95bee02f840222457ccf5419991e2d3e3e39b0f75b77e7e857e0bf4ed428b1cd97acefc87f3831bdb0b9696d5ad0557421f398334fc3 - languageName: node - linkType: hard - "@babel/helper-simple-access@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-simple-access@npm:7.22.5" @@ -1655,16 +1265,7 @@ __metadata: languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0": - version: 7.20.0 - resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.20.0" - dependencies: - "@babel/types": ^7.20.0 - checksum: 34da8c832d1c8a546e45d5c1d59755459ffe43629436707079989599b91e8c19e50e73af7a4bd09c95402d389266731b0d9c5f69e372d8ebd3a709c05c80d7dd - languageName: node - linkType: hard - -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0, @babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5" dependencies: @@ -1673,15 +1274,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-split-export-declaration@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/helper-split-export-declaration@npm:7.18.6" - dependencies: - "@babel/types": ^7.18.6 - checksum: c6d3dede53878f6be1d869e03e9ffbbb36f4897c7cc1527dc96c56d127d834ffe4520a6f7e467f5b6f3c2843ea0e81a7819d66ae02f707f6ac057f3d57943a2b - languageName: node - linkType: hard - "@babel/helper-split-export-declaration@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-split-export-declaration@npm:7.22.5" @@ -1691,27 +1283,13 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.19.4, @babel/helper-string-parser@npm:^7.22.5": +"@babel/helper-string-parser@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-string-parser@npm:7.22.5" checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-string-parser@npm:7.21.5" - checksum: 36c0ded452f3858e67634b81960d4bde1d1cd2a56b82f4ba2926e97864816021c885f111a7cf81de88a0ed025f49d84a393256700e9acbca2d99462d648705d8 - languageName: node - linkType: hard - -"@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": - version: 7.19.1 - resolution: "@babel/helper-validator-identifier@npm:7.19.1" - checksum: 0eca5e86a729162af569b46c6c41a63e18b43dbe09fda1d2a3c8924f7d617116af39cac5e4cd5d431bb760b4dca3c0970e0c444789b1db42bcf1fa41fbad0a3a - languageName: node - linkType: hard - "@babel/helper-validator-identifier@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-validator-identifier@npm:7.22.5" @@ -1719,32 +1297,13 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.16.7, @babel/helper-validator-option@npm:^7.18.6, @babel/helper-validator-option@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helper-validator-option@npm:7.21.0" - checksum: 8ece4c78ffa5461fd8ab6b6e57cc51afad59df08192ed5d84b475af4a7193fc1cb794b59e3e7be64f3cdc4df7ac78bf3dbb20c129d7757ae078e6279ff8c2f07 - languageName: node - linkType: hard - -"@babel/helper-validator-option@npm:^7.22.5": +"@babel/helper-validator-option@npm:^7.16.7, @babel/helper-validator-option@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-validator-option@npm:7.22.5" checksum: bbeca8a85ee86990215c0424997438b388b8d642d69b9f86c375a174d3cdeb270efafd1ff128bc7a1d370923d13b6e45829ba8581c027620e83e3a80c5c414b3 languageName: node linkType: hard -"@babel/helper-wrap-function@npm:^7.18.9": - version: 7.19.0 - resolution: "@babel/helper-wrap-function@npm:7.19.0" - dependencies: - "@babel/helper-function-name": ^7.19.0 - "@babel/template": ^7.18.10 - "@babel/traverse": ^7.19.0 - "@babel/types": ^7.19.0 - checksum: 2453a6b134f12cc779179188c4358a66252c29b634a8195c0cf626e17f9806c3c4c40e159cd8056c2ec82b69b9056a088014fa43d6ccc1aca67da8d9605da8fd - languageName: node - linkType: hard - "@babel/helper-wrap-function@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-wrap-function@npm:7.22.5" @@ -1757,29 +1316,7 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/helpers@npm:7.21.0" - dependencies: - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.0 - "@babel/types": ^7.21.0 - checksum: 9370dad2bb665c551869a08ac87c8bdafad53dbcdce1f5c5d498f51811456a3c005d9857562715151a0f00b2e912ac8d89f56574f837b5689f5f5072221cdf54 - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helpers@npm:7.21.5" - dependencies: - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 - checksum: a6f74b8579713988e7f5adf1a986d8b5255757632ba65b2552f0f609ead5476edb784044c7e4b18f3681ee4818ca9d08c41feb9bd4e828648c25a00deaa1f9e4 - languageName: node - linkType: hard - -"@babel/helpers@npm:^7.22.5": +"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helpers@npm:7.22.5" dependencies: @@ -1790,18 +1327,7 @@ __metadata: languageName: node linkType: hard -"@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/highlight@npm:7.18.6" - dependencies: - "@babel/helper-validator-identifier": ^7.18.6 - chalk: ^2.0.0 - js-tokens: ^4.0.0 - checksum: 92d8ee61549de5ff5120e945e774728e5ccd57fd3b2ed6eace020ec744823d4a98e242be1453d21764a30a14769ecd62170fba28539b211799bbaf232bbb2789 - languageName: node - linkType: hard - -"@babel/highlight@npm:^7.22.5": +"@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.22.5": version: 7.22.5 resolution: "@babel/highlight@npm:7.22.5" dependencies: @@ -1812,25 +1338,7 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/parser@npm:7.21.4" - bin: - parser: ./bin/babel-parser.js - checksum: de610ecd1bff331766d0c058023ca11a4f242bfafefc42caf926becccfb6756637d167c001987ca830dd4b34b93c629a4cef63f8c8c864a8564cdfde1989ac77 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.21.5, @babel/parser@npm:^7.21.8": - version: 7.21.8 - resolution: "@babel/parser@npm:7.21.8" - bin: - parser: ./bin/babel-parser.js - checksum: 1b9a820fedfb6ef179e6ffa1dbc080808882949dec68340a616da2aa354af66ea2886bd68e61bd444d270aa0b24ad6273e3cfaf17d6878c34bf2521becacb353 - languageName: node - linkType: hard - -"@babel/parser@npm:^7.22.5": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.5": version: 7.22.5 resolution: "@babel/parser@npm:7.22.5" bin: @@ -1839,17 +1347,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 845bd280c55a6a91d232cfa54eaf9708ec71e594676fe705794f494bb8b711d833b752b59d1a5c154695225880c23dbc9cab0e53af16fd57807976cd3ff41b8d - languageName: node - linkType: hard - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.22.5" @@ -1861,19 +1358,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.20.7" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 - "@babel/plugin-proposal-optional-chaining": ^7.20.7 - peerDependencies: - "@babel/core": ^7.13.0 - checksum: d610f532210bee5342f5b44a12395ccc6d904e675a297189bc1e401cc185beec09873da523466d7fec34ae1574f7a384235cba1ccc9fe7b89ba094167897c845 - languageName: node - linkType: hard - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.22.5" @@ -1887,21 +1371,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-async-generator-functions@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.20.7" - dependencies: - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-remap-async-to-generator": ^7.18.9 - "@babel/plugin-syntax-async-generators": ^7.8.4 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 111109ee118c9e69982f08d5e119eab04190b36a0f40e22e873802d941956eee66d2aa5a15f5321e51e3f9aa70a91136451b987fe15185ef8cc547ac88937723 - languageName: node - linkType: hard - -"@babel/plugin-proposal-class-properties@npm:^7.12.1, @babel/plugin-proposal-class-properties@npm:^7.18.6": +"@babel/plugin-proposal-class-properties@npm:^7.12.1": version: 7.18.6 resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6" dependencies: @@ -1913,19 +1383,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-class-static-block@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/plugin-proposal-class-static-block@npm:7.21.0" - dependencies: - "@babel/helper-create-class-features-plugin": ^7.21.0 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/plugin-syntax-class-static-block": ^7.14.5 - peerDependencies: - "@babel/core": ^7.12.0 - checksum: 236c0ad089e7a7acab776cc1d355330193314bfcd62e94e78f2df35817c6144d7e0e0368976778afd6b7c13e70b5068fa84d7abbf967d4f182e60d03f9ef802b - languageName: node - linkType: hard - "@babel/plugin-proposal-decorators@npm:^7.12.12": version: 7.17.8 resolution: "@babel/plugin-proposal-decorators@npm:7.17.8" @@ -1941,18 +1398,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-dynamic-import@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-proposal-dynamic-import@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/plugin-syntax-dynamic-import": ^7.8.3 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 96b1c8a8ad8171d39e9ab106be33bde37ae09b22fb2c449afee9a5edf3c537933d79d963dcdc2694d10677cb96da739cdf1b53454e6a5deab9801f28a818bb2f - languageName: node - linkType: hard - "@babel/plugin-proposal-export-default-from@npm:^7.12.1": version: 7.16.7 resolution: "@babel/plugin-proposal-export-default-from@npm:7.16.7" @@ -1965,42 +1410,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-export-namespace-from@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.18.9" - dependencies: - "@babel/helper-plugin-utils": ^7.18.9 - "@babel/plugin-syntax-export-namespace-from": ^7.8.3 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 84ff22bacc5d30918a849bfb7e0e90ae4c5b8d8b65f2ac881803d1cf9068dffbe53bd657b0e4bc4c20b4db301b1c85f1e74183cf29a0dd31e964bd4e97c363ef - languageName: node - linkType: hard - -"@babel/plugin-proposal-json-strings@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-proposal-json-strings@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/plugin-syntax-json-strings": ^7.8.3 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 25ba0e6b9d6115174f51f7c6787e96214c90dd4026e266976b248a2ed417fe50fddae72843ffb3cbe324014a18632ce5648dfac77f089da858022b49fd608cb3 - languageName: node - linkType: hard - -"@babel/plugin-proposal-logical-assignment-operators@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/plugin-proposal-logical-assignment-operators@npm:7.20.7" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: cdd7b8136cc4db3f47714d5266f9e7b592a2ac5a94a5878787ce08890e97c8ab1ca8e94b27bfeba7b0f2b1549a026d9fc414ca2196de603df36fb32633bbdc19 - languageName: node - linkType: hard - "@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.12.1, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.18.6" @@ -2013,18 +1422,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-numeric-separator@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-proposal-numeric-separator@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/plugin-syntax-numeric-separator": ^7.10.4 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: f370ea584c55bf4040e1f78c80b4eeb1ce2e6aaa74f87d1a48266493c33931d0b6222d8cee3a082383d6bb648ab8d6b7147a06f974d3296ef3bc39c7851683ec - languageName: node - linkType: hard - "@babel/plugin-proposal-object-rest-spread@npm:7.12.1": version: 7.12.1 resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.12.1" @@ -2038,7 +1435,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-object-rest-spread@npm:^7.12.1, @babel/plugin-proposal-object-rest-spread@npm:^7.20.7": +"@babel/plugin-proposal-object-rest-spread@npm:^7.12.1": version: 7.20.7 resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.7" dependencies: @@ -2053,19 +1450,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-optional-catch-binding@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-proposal-optional-catch-binding@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 7b5b39fb5d8d6d14faad6cb68ece5eeb2fd550fb66b5af7d7582402f974f5bc3684641f7c192a5a57e0f59acfae4aada6786be1eba030881ddc590666eff4d1e - languageName: node - linkType: hard - -"@babel/plugin-proposal-optional-chaining@npm:^7.12.7, @babel/plugin-proposal-optional-chaining@npm:^7.18.9, @babel/plugin-proposal-optional-chaining@npm:^7.20.7, @babel/plugin-proposal-optional-chaining@npm:^7.21.0": +"@babel/plugin-proposal-optional-chaining@npm:^7.12.7, @babel/plugin-proposal-optional-chaining@npm:^7.18.9": version: 7.21.0 resolution: "@babel/plugin-proposal-optional-chaining@npm:7.21.0" dependencies: @@ -2078,7 +1463,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-private-methods@npm:^7.12.1, @babel/plugin-proposal-private-methods@npm:^7.18.6": +"@babel/plugin-proposal-private-methods@npm:^7.12.1": version: 7.18.6 resolution: "@babel/plugin-proposal-private-methods@npm:7.18.6" dependencies: @@ -2099,7 +1484,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-private-property-in-object@npm:^7.12.1, @babel/plugin-proposal-private-property-in-object@npm:^7.21.0": +"@babel/plugin-proposal-private-property-in-object@npm:^7.12.1": version: 7.21.0 resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.0" dependencies: @@ -2113,7 +1498,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-unicode-property-regex@npm:^7.18.6, @babel/plugin-proposal-unicode-property-regex@npm:^7.4.4": +"@babel/plugin-proposal-unicode-property-regex@npm:^7.4.4": version: 7.18.6 resolution: "@babel/plugin-proposal-unicode-property-regex@npm:7.18.6" dependencies: @@ -2224,17 +1609,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-import-assertions@npm:^7.20.0": - version: 7.20.0 - resolution: "@babel/plugin-syntax-import-assertions@npm:7.20.0" - dependencies: - "@babel/helper-plugin-utils": ^7.19.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 6a86220e0aae40164cd3ffaf80e7c076a1be02a8f3480455dddbae05fda8140f429290027604df7a11b3f3f124866e8a6d69dbfa1dda61ee7377b920ad144d5b - languageName: node - linkType: hard - "@babel/plugin-syntax-import-assertions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-syntax-import-assertions@npm:7.22.5" @@ -2290,18 +1664,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.18.6, @babel/plugin-syntax-jsx@npm:^7.21.4, @babel/plugin-syntax-jsx@npm:^7.7.2": - version: 7.21.4 - resolution: "@babel/plugin-syntax-jsx@npm:7.21.4" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: bb7309402a1d4e155f32aa0cf216e1fa8324d6c4cfd248b03280028a015a10e46b6efd6565f515f8913918a3602b39255999c06046f7d4b8a5106be2165d724a - languageName: node - linkType: hard - -"@babel/plugin-syntax-jsx@npm:^7.22.5": +"@babel/plugin-syntax-jsx@npm:^7.22.5, @babel/plugin-syntax-jsx@npm:^7.7.2": version: 7.22.5 resolution: "@babel/plugin-syntax-jsx@npm:7.22.5" dependencies: @@ -2400,18 +1763,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.20.0, @babel/plugin-syntax-typescript@npm:^7.7.2": - version: 7.21.4 - resolution: "@babel/plugin-syntax-typescript@npm:7.21.4" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: a59ce2477b7ae8c8945dc37dda292fef9ce46a6507b3d76b03ce7f3a6c9451a6567438b20a78ebcb3955d04095fd1ccd767075a863f79fcc30aa34dcfa441fe0 - languageName: node - linkType: hard - -"@babel/plugin-syntax-typescript@npm:^7.22.5": +"@babel/plugin-syntax-typescript@npm:^7.22.5, @babel/plugin-syntax-typescript@npm:^7.7.2": version: 7.22.5 resolution: "@babel/plugin-syntax-typescript@npm:7.22.5" dependencies: @@ -2434,18 +1786,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.12.1, @babel/plugin-transform-arrow-functions@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/plugin-transform-arrow-functions@npm:7.20.7" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: b43cabe3790c2de7710abe32df9a30005eddb2050dadd5d122c6872f679e5710e410f1b90c8f99a2aff7b614cccfecf30e7fd310236686f60d3ed43fd80b9847 - languageName: node - linkType: hard - -"@babel/plugin-transform-arrow-functions@npm:^7.22.5": +"@babel/plugin-transform-arrow-functions@npm:^7.12.1, @babel/plugin-transform-arrow-functions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-arrow-functions@npm:7.22.5" dependencies: @@ -2470,19 +1811,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-async-to-generator@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/plugin-transform-async-to-generator@npm:7.20.7" - dependencies: - "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-remap-async-to-generator": ^7.18.9 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: fe9ee8a5471b4317c1b9ea92410ace8126b52a600d7cfbfe1920dcac6fb0fad647d2e08beb4fd03c630eb54430e6c72db11e283e3eddc49615c68abd39430904 - languageName: node - linkType: hard - "@babel/plugin-transform-async-to-generator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-async-to-generator@npm:7.22.5" @@ -2496,17 +1824,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoped-functions@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 0a0df61f94601e3666bf39f2cc26f5f7b22a94450fb93081edbed967bd752ce3f81d1227fefd3799f5ee2722171b5e28db61379234d1bb85b6ec689589f99d7e - languageName: node - linkType: hard - "@babel/plugin-transform-block-scoped-functions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.22.5" @@ -2518,18 +1835,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.12.12, @babel/plugin-transform-block-scoping@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/plugin-transform-block-scoping@npm:7.21.0" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 15aacaadbecf96b53a750db1be4990b0d89c7f5bc3e1794b63b49fb219638c1fd25d452d15566d7e5ddf5b5f4e1a0a0055c35c1c7aee323c7b114bf49f66f4b0 - languageName: node - linkType: hard - -"@babel/plugin-transform-block-scoping@npm:^7.22.5": +"@babel/plugin-transform-block-scoping@npm:^7.12.12, @babel/plugin-transform-block-scoping@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-block-scoping@npm:7.22.5" dependencies: @@ -2565,26 +1871,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.12.1, @babel/plugin-transform-classes@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/plugin-transform-classes@npm:7.21.0" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-compilation-targets": ^7.20.7 - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-optimise-call-expression": ^7.18.6 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-replace-supers": ^7.20.7 - "@babel/helper-split-export-declaration": ^7.18.6 - globals: ^11.1.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 088ae152074bd0e90f64659169255bfe50393e637ec8765cb2a518848b11b0299e66b91003728fd0a41563a6fdc6b8d548ece698a314fd5447f5489c22e466b7 - languageName: node - linkType: hard - -"@babel/plugin-transform-classes@npm:^7.22.5": +"@babel/plugin-transform-classes@npm:^7.12.1, @babel/plugin-transform-classes@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-classes@npm:7.22.5" dependencies: @@ -2603,18 +1890,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-computed-properties@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/plugin-transform-computed-properties@npm:7.20.7" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/template": ^7.20.7 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: be70e54bda8b469146459f429e5f2bd415023b87b2d5af8b10e48f465ffb02847a3ed162ca60378c004b82db848e4d62e90010d41ded7e7176b6d8d1c2911139 - languageName: node - linkType: hard - "@babel/plugin-transform-computed-properties@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-computed-properties@npm:7.22.5" @@ -2627,18 +1902,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.12.1, @babel/plugin-transform-destructuring@npm:^7.21.3": - version: 7.21.3 - resolution: "@babel/plugin-transform-destructuring@npm:7.21.3" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 43ebbe0bfa20287e34427be7c2200ce096c20913775ea75268fb47fe0e55f9510800587e6052c42fe6dffa0daaad95dd465c3e312fd1ef9785648384c45417ac - languageName: node - linkType: hard - -"@babel/plugin-transform-destructuring@npm:^7.22.5": +"@babel/plugin-transform-destructuring@npm:^7.12.1, @babel/plugin-transform-destructuring@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-destructuring@npm:7.22.5" dependencies: @@ -2649,19 +1913,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.18.6, @babel/plugin-transform-dotall-regex@npm:^7.4.4": - version: 7.18.6 - resolution: "@babel/plugin-transform-dotall-regex@npm:7.18.6" - dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: cbe5d7063eb8f8cca24cd4827bc97f5641166509e58781a5f8aa47fb3d2d786ce4506a30fca2e01f61f18792783a5cb5d96bf5434c3dd1ad0de8c9cc625a53da - languageName: node - linkType: hard - -"@babel/plugin-transform-dotall-regex@npm:^7.22.5": +"@babel/plugin-transform-dotall-regex@npm:^7.22.5, @babel/plugin-transform-dotall-regex@npm:^7.4.4": version: 7.22.5 resolution: "@babel/plugin-transform-dotall-regex@npm:7.22.5" dependencies: @@ -2673,17 +1925,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-duplicate-keys@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-transform-duplicate-keys@npm:7.18.9" - dependencies: - "@babel/helper-plugin-utils": ^7.18.9 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 220bf4a9fec5c4d4a7b1de38810350260e8ea08481bf78332a464a21256a95f0df8cd56025f346238f09b04f8e86d4158fafc9f4af57abaef31637e3b58bd4fe - languageName: node - linkType: hard - "@babel/plugin-transform-duplicate-keys@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-duplicate-keys@npm:7.22.5" @@ -2707,18 +1948,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-exponentiation-operator@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.18.6" - dependencies: - "@babel/helper-builder-binary-assignment-operator-visitor": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 7f70222f6829c82a36005508d34ddbe6fd0974ae190683a8670dd6ff08669aaf51fef2209d7403f9bd543cb2d12b18458016c99a6ed0332ccedb3ea127b01229 - languageName: node - linkType: hard - "@babel/plugin-transform-exponentiation-operator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.22.5" @@ -2755,18 +1984,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.12.1, @babel/plugin-transform-for-of@npm:^7.21.0": - version: 7.21.0 - resolution: "@babel/plugin-transform-for-of@npm:7.21.0" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 2f3f86ca1fab2929fcda6a87e4303d5c635b5f96dc9a45fd4ca083308a3020c79ac33b9543eb4640ef2b79f3586a00ab2d002a7081adb9e9d7440dce30781034 - languageName: node - linkType: hard - -"@babel/plugin-transform-for-of@npm:^7.22.5": +"@babel/plugin-transform-for-of@npm:^7.12.1, @babel/plugin-transform-for-of@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-for-of@npm:7.22.5" dependencies: @@ -2777,19 +1995,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-function-name@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-transform-function-name@npm:7.18.9" - dependencies: - "@babel/helper-compilation-targets": ^7.18.9 - "@babel/helper-function-name": ^7.18.9 - "@babel/helper-plugin-utils": ^7.18.9 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 62dd9c6cdc9714704efe15545e782ee52d74dc73916bf954b4d3bee088fb0ec9e3c8f52e751252433656c09f744b27b757fc06ed99bcde28e8a21600a1d8e597 - languageName: node - linkType: hard - "@babel/plugin-transform-function-name@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-function-name@npm:7.22.5" @@ -2815,17 +2020,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-literals@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-transform-literals@npm:7.18.9" - dependencies: - "@babel/helper-plugin-utils": ^7.18.9 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 3458dd2f1a47ac51d9d607aa18f3d321cbfa8560a985199185bed5a906bb0c61ba85575d386460bac9aed43fdd98940041fae5a67dff286f6f967707cff489f8 - languageName: node - linkType: hard - "@babel/plugin-transform-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-literals@npm:7.22.5" @@ -2849,17 +2043,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-member-expression-literals@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-member-expression-literals@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 35a3d04f6693bc6b298c05453d85ee6e41cc806538acb6928427e0e97ae06059f97d2f07d21495fcf5f70d3c13a242e2ecbd09d5c1fcb1b1a73ff528dcb0b695 - languageName: node - linkType: hard - "@babel/plugin-transform-member-expression-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-member-expression-literals@npm:7.22.5" @@ -2871,18 +2054,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-amd@npm:^7.20.11": - version: 7.20.11 - resolution: "@babel/plugin-transform-modules-amd@npm:7.20.11" - dependencies: - "@babel/helper-module-transforms": ^7.20.11 - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 23665c1c20c8f11c89382b588fb9651c0756d130737a7625baeaadbd3b973bc5bfba1303bedffa8fb99db1e6d848afb01016e1df2b69b18303e946890c790001 - languageName: node - linkType: hard - "@babel/plugin-transform-modules-amd@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-amd@npm:7.22.5" @@ -2895,19 +2066,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-commonjs@npm:^7.21.2": - version: 7.21.2 - resolution: "@babel/plugin-transform-modules-commonjs@npm:7.21.2" - dependencies: - "@babel/helper-module-transforms": ^7.21.2 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-simple-access": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 65aa06e3e3792f39b99eb5f807034693ff0ecf80438580f7ae504f4c4448ef04147b1889ea5e6f60f3ad4a12ebbb57c6f1f979a249dadbd8d11fe22f4441918b - languageName: node - linkType: hard - "@babel/plugin-transform-modules-commonjs@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.22.5" @@ -2921,20 +2079,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-systemjs@npm:^7.20.11": - version: 7.20.11 - resolution: "@babel/plugin-transform-modules-systemjs@npm:7.20.11" - dependencies: - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-module-transforms": ^7.20.11 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-validator-identifier": ^7.19.1 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 4546c47587f88156d66c7eb7808e903cf4bb3f6ba6ac9bc8e3af2e29e92eb9f0b3f44d52043bfd24eb25fa7827fd7b6c8bfeac0cac7584e019b87e1ecbd0e673 - languageName: node - linkType: hard - "@babel/plugin-transform-modules-systemjs@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-systemjs@npm:7.22.5" @@ -2949,18 +2093,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-modules-umd@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-modules-umd@npm:7.18.6" - dependencies: - "@babel/helper-module-transforms": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: c3b6796c6f4579f1ba5ab0cdcc73910c1e9c8e1e773c507c8bb4da33072b3ae5df73c6d68f9126dab6e99c24ea8571e1563f8710d7c421fac1cde1e434c20153 - languageName: node - linkType: hard - "@babel/plugin-transform-modules-umd@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-umd@npm:7.22.5" @@ -2973,18 +2105,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.20.5": - version: 7.20.5 - resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.20.5" - dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.20.5 - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0 - checksum: 528c95fb1087e212f17e1c6456df041b28a83c772b9c93d2e407c9d03b72182b0d9d126770c1d6e0b23aab052599ceaf25ed6a2c0627f4249be34a83f6fae853 - languageName: node - linkType: hard - "@babel/plugin-transform-named-capturing-groups-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.22.5" @@ -2997,17 +2117,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-new-target@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-new-target@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: bd780e14f46af55d0ae8503b3cb81ca86dcc73ed782f177e74f498fff934754f9e9911df1f8f3bd123777eed7c1c1af4d66abab87c8daae5403e7719a6b845d1 - languageName: node - linkType: hard - "@babel/plugin-transform-new-target@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-new-target@npm:7.22.5" @@ -3058,18 +2167,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-object-super@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-object-super@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/helper-replace-supers": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 0fcb04e15deea96ae047c21cb403607d49f06b23b4589055993365ebd7a7d7541334f06bf9642e90075e66efce6ebaf1eb0ef066fbbab802d21d714f1aac3aef - languageName: node - linkType: hard - "@babel/plugin-transform-object-super@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-object-super@npm:7.22.5" @@ -3107,18 +2204,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.12.1, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.21.3": - version: 7.21.3 - resolution: "@babel/plugin-transform-parameters@npm:7.21.3" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: c92128d7b1fcf54e2cab186c196bbbf55a9a6de11a83328dc2602649c9dc6d16ef73712beecd776cd49bfdc624b5f56740f4a53568d3deb9505ec666bc869da3 - languageName: node - linkType: hard - -"@babel/plugin-transform-parameters@npm:^7.22.5": +"@babel/plugin-transform-parameters@npm:^7.12.1, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-parameters@npm:7.22.5" dependencies: @@ -3155,17 +2241,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-property-literals@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-property-literals@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 1c16e64de554703f4b547541de2edda6c01346dd3031d4d29e881aa7733785cd26d53611a4ccf5353f4d3e69097bb0111c0a93ace9e683edd94fea28c4484144 - languageName: node - linkType: hard - "@babel/plugin-transform-property-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-property-literals@npm:7.22.5" @@ -3177,17 +2252,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-display-name@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-react-display-name@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 51c087ab9e41ef71a29335587da28417536c6f816c292e092ffc0e0985d2f032656801d4dd502213ce32481f4ba6c69402993ffa67f0818a07606ff811e4be49 - languageName: node - linkType: hard - "@babel/plugin-transform-react-display-name@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-display-name@npm:7.22.5" @@ -3199,17 +2263,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx-development@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-react-jsx-development@npm:7.18.6" - dependencies: - "@babel/plugin-transform-react-jsx": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: ec9fa65db66f938b75c45e99584367779ac3e0af8afc589187262e1337c7c4205ea312877813ae4df9fb93d766627b8968d74ac2ba702e4883b1dbbe4953ecee - languageName: node - linkType: hard - "@babel/plugin-transform-react-jsx-development@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx-development@npm:7.22.5" @@ -3243,22 +2296,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.12.12, @babel/plugin-transform-react-jsx@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-react-jsx@npm:7.18.6" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-module-imports": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/plugin-syntax-jsx": ^7.18.6 - "@babel/types": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 46129eaf1ab7a7a73e3e8c9d9859b630f5b381c5e19fb1559e2db7b943a7825b6715ad950623fb03fe7bd31ed618ce1d0bd539b13fa030a50c39d5a873a5ba00 - languageName: node - linkType: hard - -"@babel/plugin-transform-react-jsx@npm:^7.22.5": +"@babel/plugin-transform-react-jsx@npm:^7.12.12, @babel/plugin-transform-react-jsx@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx@npm:7.22.5" dependencies: @@ -3273,18 +2311,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-pure-annotations@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.18.6" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 97c4873d409088f437f9084d084615948198dd87fc6723ada0e7e29c5a03623c2f3e03df3f52e7e7d4d23be32a08ea00818bff302812e48713c706713bd06219 - languageName: node - linkType: hard - "@babel/plugin-transform-react-pure-annotations@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.22.5" @@ -3297,18 +2323,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-regenerator@npm:^7.20.5": - version: 7.20.5 - resolution: "@babel/plugin-transform-regenerator@npm:7.20.5" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - regenerator-transform: ^0.15.1 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 13164861e71fb23d84c6270ef5330b03c54d5d661c2c7468f28e21c4f8598558ca0c8c3cb1d996219352946e849d270a61372bc93c8fbe9676e78e3ffd0dea07 - languageName: node - linkType: hard - "@babel/plugin-transform-regenerator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-regenerator@npm:7.22.5" @@ -3321,17 +2335,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-reserved-words@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-reserved-words@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 0738cdc30abdae07c8ec4b233b30c31f68b3ff0eaa40eddb45ae607c066127f5fa99ddad3c0177d8e2832e3a7d3ad115775c62b431ebd6189c40a951b867a80c - languageName: node - linkType: hard - "@babel/plugin-transform-reserved-words@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-reserved-words@npm:7.22.5" @@ -3343,18 +2346,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-shorthand-properties@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: b8e4e8acc2700d1e0d7d5dbfd4fdfb935651913de6be36e6afb7e739d8f9ca539a5150075a0f9b79c88be25ddf45abb912fe7abf525f0b80f5b9d9860de685d7 - languageName: node - linkType: hard - -"@babel/plugin-transform-shorthand-properties@npm:^7.22.5": +"@babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.22.5" dependencies: @@ -3365,19 +2357,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.12.1, @babel/plugin-transform-spread@npm:^7.20.7": - version: 7.20.7 - resolution: "@babel/plugin-transform-spread@npm:7.20.7" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 8ea698a12da15718aac7489d4cde10beb8a3eea1f66167d11ab1e625033641e8b328157fd1a0b55dd6531933a160c01fc2e2e61132a385cece05f26429fd0cc2 - languageName: node - linkType: hard - -"@babel/plugin-transform-spread@npm:^7.22.5": +"@babel/plugin-transform-spread@npm:^7.12.1, @babel/plugin-transform-spread@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-spread@npm:7.22.5" dependencies: @@ -3389,17 +2369,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-sticky-regex@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-sticky-regex@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 68ea18884ae9723443ffa975eb736c8c0d751265859cd3955691253f7fee37d7a0f7efea96c8a062876af49a257a18ea0ed5fea0d95a7b3611ce40f7ee23aee3 - languageName: node - linkType: hard - "@babel/plugin-transform-sticky-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-sticky-regex@npm:7.22.5" @@ -3411,18 +2380,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.12.1, @babel/plugin-transform-template-literals@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-transform-template-literals@npm:7.18.9" - dependencies: - "@babel/helper-plugin-utils": ^7.18.9 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 3d2fcd79b7c345917f69b92a85bdc3ddd68ce2c87dc70c7d61a8373546ccd1f5cb8adc8540b49dfba08e1b82bb7b3bbe23a19efdb2b9c994db2db42906ca9fb2 - languageName: node - linkType: hard - -"@babel/plugin-transform-template-literals@npm:^7.22.5": +"@babel/plugin-transform-template-literals@npm:^7.12.1, @babel/plugin-transform-template-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-template-literals@npm:7.22.5" dependencies: @@ -3433,17 +2391,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typeof-symbol@npm:^7.18.9": - version: 7.18.9 - resolution: "@babel/plugin-transform-typeof-symbol@npm:7.18.9" - dependencies: - "@babel/helper-plugin-utils": ^7.18.9 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: e754e0d8b8a028c52e10c148088606e3f7a9942c57bd648fc0438e5b4868db73c386a5ed47ab6d6f0594aae29ee5ffc2ffc0f7ebee7fae560a066d6dea811cd4 - languageName: node - linkType: hard - "@babel/plugin-transform-typeof-symbol@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-typeof-symbol@npm:7.22.5" @@ -3455,20 +2402,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-typescript@npm:^7.21.3": - version: 7.21.3 - resolution: "@babel/plugin-transform-typescript@npm:7.21.3" - dependencies: - "@babel/helper-annotate-as-pure": ^7.18.6 - "@babel/helper-create-class-features-plugin": ^7.21.0 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/plugin-syntax-typescript": ^7.20.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: c16fd577bf43f633deb76fca2a8527d8ae25968c8efdf327c1955472c3e0257e62992473d1ad7f9ee95379ce2404699af405ea03346055adadd3478ad0ecd117 - languageName: node - linkType: hard - "@babel/plugin-transform-typescript@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-typescript@npm:7.22.5" @@ -3483,17 +2416,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-escapes@npm:^7.18.10": - version: 7.21.5 - resolution: "@babel/plugin-transform-unicode-escapes@npm:7.21.5" - dependencies: - "@babel/helper-plugin-utils": ^7.21.5 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 6504d642d0449a275191b624bd94d3e434ae154e610bf2f0e3c109068b287d2474f68e1da64b47f21d193cd67b27ee4643877d530187670565cac46e29fd257d - languageName: node - linkType: hard - "@babel/plugin-transform-unicode-escapes@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-unicode-escapes@npm:7.22.5" @@ -3517,18 +2439,6 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-unicode-regex@npm:^7.18.6": - version: 7.18.6 - resolution: "@babel/plugin-transform-unicode-regex@npm:7.18.6" - dependencies: - "@babel/helper-create-regexp-features-plugin": ^7.18.6 - "@babel/helper-plugin-utils": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: d9e18d57536a2d317fb0b7c04f8f55347f3cfacb75e636b4c6fa2080ab13a3542771b5120e726b598b815891fc606d1472ac02b749c69fd527b03847f22dc25e - languageName: node - linkType: hard - "@babel/plugin-transform-unicode-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-unicode-regex@npm:7.22.5" @@ -3553,92 +2463,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:^7.12.11, @babel/preset-env@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/preset-env@npm:7.21.4" - dependencies: - "@babel/compat-data": ^7.21.4 - "@babel/helper-compilation-targets": ^7.21.4 - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-validator-option": ^7.21.0 - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.18.6 - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.20.7 - "@babel/plugin-proposal-async-generator-functions": ^7.20.7 - "@babel/plugin-proposal-class-properties": ^7.18.6 - "@babel/plugin-proposal-class-static-block": ^7.21.0 - "@babel/plugin-proposal-dynamic-import": ^7.18.6 - "@babel/plugin-proposal-export-namespace-from": ^7.18.9 - "@babel/plugin-proposal-json-strings": ^7.18.6 - "@babel/plugin-proposal-logical-assignment-operators": ^7.20.7 - "@babel/plugin-proposal-nullish-coalescing-operator": ^7.18.6 - "@babel/plugin-proposal-numeric-separator": ^7.18.6 - "@babel/plugin-proposal-object-rest-spread": ^7.20.7 - "@babel/plugin-proposal-optional-catch-binding": ^7.18.6 - "@babel/plugin-proposal-optional-chaining": ^7.21.0 - "@babel/plugin-proposal-private-methods": ^7.18.6 - "@babel/plugin-proposal-private-property-in-object": ^7.21.0 - "@babel/plugin-proposal-unicode-property-regex": ^7.18.6 - "@babel/plugin-syntax-async-generators": ^7.8.4 - "@babel/plugin-syntax-class-properties": ^7.12.13 - "@babel/plugin-syntax-class-static-block": ^7.14.5 - "@babel/plugin-syntax-dynamic-import": ^7.8.3 - "@babel/plugin-syntax-export-namespace-from": ^7.8.3 - "@babel/plugin-syntax-import-assertions": ^7.20.0 - "@babel/plugin-syntax-json-strings": ^7.8.3 - "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 - "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 - "@babel/plugin-syntax-numeric-separator": ^7.10.4 - "@babel/plugin-syntax-object-rest-spread": ^7.8.3 - "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 - "@babel/plugin-syntax-optional-chaining": ^7.8.3 - "@babel/plugin-syntax-private-property-in-object": ^7.14.5 - "@babel/plugin-syntax-top-level-await": ^7.14.5 - "@babel/plugin-transform-arrow-functions": ^7.20.7 - "@babel/plugin-transform-async-to-generator": ^7.20.7 - "@babel/plugin-transform-block-scoped-functions": ^7.18.6 - "@babel/plugin-transform-block-scoping": ^7.21.0 - "@babel/plugin-transform-classes": ^7.21.0 - "@babel/plugin-transform-computed-properties": ^7.20.7 - "@babel/plugin-transform-destructuring": ^7.21.3 - "@babel/plugin-transform-dotall-regex": ^7.18.6 - "@babel/plugin-transform-duplicate-keys": ^7.18.9 - "@babel/plugin-transform-exponentiation-operator": ^7.18.6 - "@babel/plugin-transform-for-of": ^7.21.0 - "@babel/plugin-transform-function-name": ^7.18.9 - "@babel/plugin-transform-literals": ^7.18.9 - "@babel/plugin-transform-member-expression-literals": ^7.18.6 - "@babel/plugin-transform-modules-amd": ^7.20.11 - "@babel/plugin-transform-modules-commonjs": ^7.21.2 - "@babel/plugin-transform-modules-systemjs": ^7.20.11 - "@babel/plugin-transform-modules-umd": ^7.18.6 - "@babel/plugin-transform-named-capturing-groups-regex": ^7.20.5 - "@babel/plugin-transform-new-target": ^7.18.6 - "@babel/plugin-transform-object-super": ^7.18.6 - "@babel/plugin-transform-parameters": ^7.21.3 - "@babel/plugin-transform-property-literals": ^7.18.6 - "@babel/plugin-transform-regenerator": ^7.20.5 - "@babel/plugin-transform-reserved-words": ^7.18.6 - "@babel/plugin-transform-shorthand-properties": ^7.18.6 - "@babel/plugin-transform-spread": ^7.20.7 - "@babel/plugin-transform-sticky-regex": ^7.18.6 - "@babel/plugin-transform-template-literals": ^7.18.9 - "@babel/plugin-transform-typeof-symbol": ^7.18.9 - "@babel/plugin-transform-unicode-escapes": ^7.18.10 - "@babel/plugin-transform-unicode-regex": ^7.18.6 - "@babel/preset-modules": ^0.1.5 - "@babel/types": ^7.21.4 - babel-plugin-polyfill-corejs2: ^0.3.3 - babel-plugin-polyfill-corejs3: ^0.6.0 - babel-plugin-polyfill-regenerator: ^0.4.1 - core-js-compat: ^3.25.1 - semver: ^6.3.0 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 1e328674c4b39e985fa81e5a8eee9aaab353dea4ff1f28f454c5e27a6498c762e25d42e827f5bfc9d7acf6c9b8bc317b5283aa7c83d9fd03c1a89e5c08f334f9 - languageName: node - linkType: hard - -"@babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:~7.22.5": +"@babel/preset-env@npm:^7.12.11, @babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:^7.21.4, @babel/preset-env@npm:~7.22.5": version: 7.22.5 resolution: "@babel/preset-env@npm:7.22.5" dependencies: @@ -3756,23 +2581,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-react@npm:^7.12.10": - version: 7.18.6 - resolution: "@babel/preset-react@npm:7.18.6" - dependencies: - "@babel/helper-plugin-utils": ^7.18.6 - "@babel/helper-validator-option": ^7.18.6 - "@babel/plugin-transform-react-display-name": ^7.18.6 - "@babel/plugin-transform-react-jsx": ^7.18.6 - "@babel/plugin-transform-react-jsx-development": ^7.18.6 - "@babel/plugin-transform-react-pure-annotations": ^7.18.6 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 540d9cf0a0cc0bb07e6879994e6fb7152f87dafbac880b56b65e2f528134c7ba33e0cd140b58700c77b2ebf4c81fa6468fed0ba391462d75efc7f8c1699bb4c3 - languageName: node - linkType: hard - -"@babel/preset-react@npm:^7.18.6": +"@babel/preset-react@npm:^7.12.10, @babel/preset-react@npm:^7.18.6": version: 7.22.5 resolution: "@babel/preset-react@npm:7.22.5" dependencies: @@ -3788,22 +2597,7 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.12.7, @babel/preset-typescript@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/preset-typescript@npm:7.21.4" - dependencies: - "@babel/helper-plugin-utils": ^7.20.2 - "@babel/helper-validator-option": ^7.21.0 - "@babel/plugin-syntax-jsx": ^7.21.4 - "@babel/plugin-transform-modules-commonjs": ^7.21.2 - "@babel/plugin-transform-typescript": ^7.21.3 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 83b2f2bf7be3a970acd212177525f58bbb1f2e042b675a47d021a675ae27cf00b6b6babfaf3ae5c980592c9ed1b0712e5197796b691905d25c99f9006478ea06 - languageName: node - linkType: hard - -"@babel/preset-typescript@npm:~7.22.5": +"@babel/preset-typescript@npm:^7.12.7, @babel/preset-typescript@npm:^7.21.4, @babel/preset-typescript@npm:~7.22.5": version: 7.22.5 resolution: "@babel/preset-typescript@npm:7.22.5" dependencies: @@ -3818,22 +2612,7 @@ __metadata: languageName: node linkType: hard -"@babel/register@npm:^7.12.1": - version: 7.18.9 - resolution: "@babel/register@npm:7.18.9" - dependencies: - clone-deep: ^4.0.1 - find-cache-dir: ^2.0.0 - make-dir: ^2.1.0 - pirates: ^4.0.5 - source-map-support: ^0.5.16 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 4aeaff97e061a397f632659082ba86c539ef8194697b236d991c10d1c2ea8f73213d3b5b3b2c24625951a1ef726b7a7d2e70f70ffcb37f79ef0c1a745eebef21 - languageName: node - linkType: hard - -"@babel/register@npm:^7.18.9": +"@babel/register@npm:^7.12.1, @babel/register@npm:^7.18.9": version: 7.22.5 resolution: "@babel/register@npm:7.22.5" dependencies: @@ -3864,25 +2643,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": - version: 7.21.0 - resolution: "@babel/runtime@npm:7.21.0" - dependencies: - regenerator-runtime: ^0.13.11 - checksum: 7b33e25bfa9e0e1b9e8828bb61b2d32bdd46b41b07ba7cb43319ad08efc6fda8eb89445193e67d6541814627df0ca59122c0ea795e412b99c5183a0540d338ab - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.20.1": - version: 7.21.5 - resolution: "@babel/runtime@npm:7.21.5" - dependencies: - regenerator-runtime: ^0.13.11 - checksum: 358f2779d3187f5c67ad302e8f8d435412925d0b991d133c7d4a7b1ddd5a3fda1b6f34537cb64628dfd96a27ae46df105bed3895b8d754b88cacdded8d1129dd - languageName: node - linkType: hard - -"@babel/runtime@npm:^7.20.13, @babel/runtime@npm:~7.22.5": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2, @babel/runtime@npm:~7.22.5": version: 7.22.5 resolution: "@babel/runtime@npm:7.22.5" dependencies: @@ -3891,15 +2652,6 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.20.6": - version: 7.20.7 - resolution: "@babel/runtime@npm:7.20.7" - dependencies: - regenerator-runtime: ^0.13.11 - checksum: 4629ce5c46f06cca9cfb9b7fc00d48003335a809888e2b91ec2069a2dcfbfef738480cff32ba81e0b7c290f8918e5c22ddcf2b710001464ee84ba62c7e32a3a3 - languageName: node - linkType: hard - "@babel/runtime@npm:~7.5.4": version: 7.5.5 resolution: "@babel/runtime@npm:7.5.5" @@ -3909,18 +2661,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.12.7, @babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.3.3": - version: 7.20.7 - resolution: "@babel/template@npm:7.20.7" - dependencies: - "@babel/code-frame": ^7.18.6 - "@babel/parser": ^7.20.7 - "@babel/types": ^7.20.7 - checksum: 2eb1a0ab8d415078776bceb3473d07ab746e6bb4c2f6ca46ee70efb284d75c4a32bb0cd6f4f4946dec9711f9c0780e8e5d64b743208deac6f8e9858afadc349e - languageName: node - linkType: hard - -"@babel/template@npm:^7.22.5": +"@babel/template@npm:^7.12.7, @babel/template@npm:^7.22.5, @babel/template@npm:^7.3.3": version: 7.22.5 resolution: "@babel/template@npm:7.22.5" dependencies: @@ -3931,43 +2672,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.19.0, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.4, @babel/traverse@npm:^7.7.2": - version: 7.21.4 - resolution: "@babel/traverse@npm:7.21.4" - dependencies: - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.4 - "@babel/helper-environment-visitor": ^7.18.9 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.21.4 - "@babel/types": ^7.21.4 - debug: ^4.1.0 - globals: ^11.1.0 - checksum: f22f067c2d9b6497abf3d4e53ea71f3aa82a21f2ed434dd69b8c5767f11f2a4c24c8d2f517d2312c9e5248e5c69395fdca1c95a2b3286122c75f5783ddb6f53c - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/traverse@npm:7.21.5" - dependencies: - "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.5 - "@babel/helper-environment-visitor": ^7.21.5 - "@babel/helper-function-name": ^7.21.0 - "@babel/helper-hoist-variables": ^7.18.6 - "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.21.5 - "@babel/types": ^7.21.5 - debug: ^4.1.0 - globals: ^11.1.0 - checksum: b403733fa7d858f0c8e224f0434a6ade641bc469a4f92975363391e796629d5bf53e544761dfe85039aab92d5389ebe7721edb309d7a5bb7df2bf74f37bf9f47 - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.22.5": +"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.22.5, @babel/traverse@npm:^7.7.2": version: 7.22.5 resolution: "@babel/traverse@npm:7.22.5" dependencies: @@ -3985,29 +2690,7 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.7, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.21.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": - version: 7.21.5 - resolution: "@babel/types@npm:7.21.5" - dependencies: - "@babel/helper-string-parser": ^7.21.5 - "@babel/helper-validator-identifier": ^7.19.1 - to-fast-properties: ^2.0.0 - checksum: 43242a99c612d13285ee4af46cc0f1066bcb6ffd38307daef7a76e8c70f36cfc3255eb9e75c8e768b40e761176c313aec4d5c0b9d97a21e494d49d5fd123a9f7 - languageName: node - linkType: hard - -"@babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.2, @babel/types@npm:^7.21.4": - version: 7.21.4 - resolution: "@babel/types@npm:7.21.4" - dependencies: - "@babel/helper-string-parser": ^7.19.4 - "@babel/helper-validator-identifier": ^7.19.1 - to-fast-properties: ^2.0.0 - checksum: 587bc55a91ce003b0f8aa10d70070f8006560d7dc0360dc0406d306a2cb2a10154e2f9080b9c37abec76907a90b330a536406cb75e6bdc905484f37b75c73219 - languageName: node - linkType: hard - -"@babel/types@npm:^7.22.5": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.7, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": version: 7.22.5 resolution: "@babel/types@npm:7.22.5" dependencies: @@ -4642,13 +3325,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/android-arm64@npm:0.17.18" - checksum: ec47777acf96ffe5e36426e5c5715f74e154ddd2a4b2fcd12748250d7b3ded51c5a1a8a5f896f1524e52d3abf4b302aad0b2f30ac23b4efc41de2d01e359a34a - languageName: node - linkType: hard - "@esbuild/android-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm64@npm:0.17.19" @@ -4656,13 +3332,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-arm@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/android-arm@npm:0.17.18" - conditions: os=android & cpu=arm - languageName: node - linkType: hard - "@esbuild/android-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm@npm:0.17.19" @@ -4670,13 +3339,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/android-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/android-x64@npm:0.17.18" - conditions: os=android & cpu=x64 - languageName: node - linkType: hard - "@esbuild/android-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-x64@npm:0.17.19" @@ -4684,13 +3346,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/darwin-arm64@npm:0.17.18" - conditions: os=darwin & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/darwin-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-arm64@npm:0.17.19" @@ -4698,13 +3353,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/darwin-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/darwin-x64@npm:0.17.18" - conditions: os=darwin & cpu=x64 - languageName: node - linkType: hard - "@esbuild/darwin-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-x64@npm:0.17.19" @@ -4712,13 +3360,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/freebsd-arm64@npm:0.17.18" - conditions: os=freebsd & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/freebsd-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-arm64@npm:0.17.19" @@ -4726,13 +3367,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/freebsd-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/freebsd-x64@npm:0.17.18" - conditions: os=freebsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/freebsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-x64@npm:0.17.19" @@ -4740,13 +3374,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-arm64@npm:0.17.18" - conditions: os=linux & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/linux-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm64@npm:0.17.19" @@ -4754,13 +3381,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-arm@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-arm@npm:0.17.18" - conditions: os=linux & cpu=arm - languageName: node - linkType: hard - "@esbuild/linux-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm@npm:0.17.19" @@ -4768,13 +3388,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ia32@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-ia32@npm:0.17.18" - conditions: os=linux & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/linux-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ia32@npm:0.17.19" @@ -4782,13 +3395,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-loong64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-loong64@npm:0.17.18" - conditions: os=linux & cpu=loong64 - languageName: node - linkType: hard - "@esbuild/linux-loong64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-loong64@npm:0.17.19" @@ -4796,13 +3402,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-mips64el@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-mips64el@npm:0.17.18" - conditions: os=linux & cpu=mips64el - languageName: node - linkType: hard - "@esbuild/linux-mips64el@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-mips64el@npm:0.17.19" @@ -4810,13 +3409,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-ppc64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-ppc64@npm:0.17.18" - conditions: os=linux & cpu=ppc64 - languageName: node - linkType: hard - "@esbuild/linux-ppc64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ppc64@npm:0.17.19" @@ -4824,13 +3416,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-riscv64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-riscv64@npm:0.17.18" - conditions: os=linux & cpu=riscv64 - languageName: node - linkType: hard - "@esbuild/linux-riscv64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-riscv64@npm:0.17.19" @@ -4838,13 +3423,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-s390x@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-s390x@npm:0.17.18" - conditions: os=linux & cpu=s390x - languageName: node - linkType: hard - "@esbuild/linux-s390x@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-s390x@npm:0.17.19" @@ -4852,13 +3430,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/linux-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/linux-x64@npm:0.17.18" - conditions: os=linux & cpu=x64 - languageName: node - linkType: hard - "@esbuild/linux-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-x64@npm:0.17.19" @@ -4866,13 +3437,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/netbsd-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/netbsd-x64@npm:0.17.18" - conditions: os=netbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/netbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/netbsd-x64@npm:0.17.19" @@ -4880,13 +3444,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/openbsd-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/openbsd-x64@npm:0.17.18" - conditions: os=openbsd & cpu=x64 - languageName: node - linkType: hard - "@esbuild/openbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/openbsd-x64@npm:0.17.19" @@ -4894,13 +3451,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/sunos-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/sunos-x64@npm:0.17.18" - conditions: os=sunos & cpu=x64 - languageName: node - linkType: hard - "@esbuild/sunos-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/sunos-x64@npm:0.17.19" @@ -4908,13 +3458,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-arm64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/win32-arm64@npm:0.17.18" - conditions: os=win32 & cpu=arm64 - languageName: node - linkType: hard - "@esbuild/win32-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-arm64@npm:0.17.19" @@ -4922,13 +3465,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-ia32@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/win32-ia32@npm:0.17.18" - conditions: os=win32 & cpu=ia32 - languageName: node - linkType: hard - "@esbuild/win32-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-ia32@npm:0.17.19" @@ -4936,13 +3472,6 @@ __metadata: languageName: node linkType: hard -"@esbuild/win32-x64@npm:0.17.18": - version: 0.17.18 - resolution: "@esbuild/win32-x64@npm:0.17.18" - conditions: os=win32 & cpu=x64 - languageName: node - linkType: hard - "@esbuild/win32-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-x64@npm:0.17.19" @@ -5166,15 +3695,6 @@ __metadata: languageName: node linkType: hard -"@internationalized/date@npm:^3.0.1": - version: 3.0.1 - resolution: "@internationalized/date@npm:3.0.1" - dependencies: - "@babel/runtime": ^7.6.2 - checksum: ff51a00550322a5df3d3051e8ffdf3d7741851149e8ba300883e01402249602e87cc50b27b972753d9af88c5374df83c24adf58cae5e269100cb946a3b12cd56 - languageName: node - linkType: hard - "@internationalized/date@npm:^3.2.0": version: 3.2.0 resolution: "@internationalized/date@npm:3.2.0" @@ -5194,16 +3714,6 @@ __metadata: languageName: node linkType: hard -"@internationalized/message@npm:^3.0.9": - version: 3.0.9 - resolution: "@internationalized/message@npm:3.0.9" - dependencies: - "@babel/runtime": ^7.6.2 - intl-messageformat: ^10.1.0 - checksum: b3f7f5a8e1d8df99efb3463ca07edb976ecf95d28de19a47d92fb19c093052b1a092aeaa226dc69d07143854bdbeb8519a0ac8ba8c900c4b0f565151d735ca7f - languageName: node - linkType: hard - "@internationalized/message@npm:^3.1.0": version: 3.1.0 resolution: "@internationalized/message@npm:3.1.0" @@ -5223,15 +3733,6 @@ __metadata: languageName: node linkType: hard -"@internationalized/number@npm:^3.1.1": - version: 3.1.1 - resolution: "@internationalized/number@npm:3.1.1" - dependencies: - "@babel/runtime": ^7.6.2 - checksum: 9979ea1ca7388de75193c9d36f19d928fbcb715d456d153c30cafadd2ce1ceae011f55c966d424f4561ec04de14d3b48b8fe16a9e2737273829a813c4f7203a3 - languageName: node - linkType: hard - "@internationalized/number@npm:^3.2.0": version: 3.2.0 resolution: "@internationalized/number@npm:3.2.0" @@ -5250,15 +3751,6 @@ __metadata: languageName: node linkType: hard -"@internationalized/string@npm:^3.0.0": - version: 3.0.0 - resolution: "@internationalized/string@npm:3.0.0" - dependencies: - "@babel/runtime": ^7.6.2 - checksum: fc347cf80cd4ee009d1c467dca2c6908a919ad152086bf5e8c1a0aede0383fb317695fc5d82abe033ec90ad62108297130b653b63b9529f2e032999798ae4a81 - languageName: node - linkType: hard - "@internationalized/string@npm:^3.1.0": version: 3.1.0 resolution: "@internationalized/string@npm:3.1.0" @@ -5596,20 +4088,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:3.1.0": +"@jridgewell/resolve-uri@npm:3.1.0, @jridgewell/resolve-uri@npm:^3.0.3": version: 3.1.0 resolution: "@jridgewell/resolve-uri@npm:3.1.0" checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:^3.0.3": - version: 3.0.5 - resolution: "@jridgewell/resolve-uri@npm:3.0.5" - checksum: 1ee652b693da7979ac4007926cc3f0a32b657ffeb913e111f44e5b67153d94a2f28a1d560101cc0cf8087625468293a69a00f634a2914e1a6d0817ba2039a913 - languageName: node - linkType: hard - "@jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" @@ -5617,16 +4102,6 @@ __metadata: languageName: node linkType: hard -"@jridgewell/source-map@npm:^0.3.2": - version: 0.3.2 - resolution: "@jridgewell/source-map@npm:0.3.2" - dependencies: - "@jridgewell/gen-mapping": ^0.3.0 - "@jridgewell/trace-mapping": ^0.3.9 - checksum: 1b83f0eb944e77b70559a394d5d3b3f98a81fcc186946aceb3ef42d036762b52ef71493c6c0a3b7c1d2f08785f53ba2df1277fe629a06e6109588ff4cdcf7482 - languageName: node - linkType: hard - "@jridgewell/source-map@npm:^0.3.3": version: 0.3.3 resolution: "@jridgewell/source-map@npm:0.3.3" @@ -5637,20 +4112,13 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:1.4.14": +"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:^1.4.10": - version: 1.4.11 - resolution: "@jridgewell/sourcemap-codec@npm:1.4.11" - checksum: 3b2afaf8400fb07a36db60e901fcce6a746cdec587310ee9035939d89878e57b2dec8173b0b8f63176f647efa352294049a53c49739098eb907ff81fec2547c8 - languageName: node - linkType: hard - "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -5661,7 +4129,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.18 resolution: "@jridgewell/trace-mapping@npm:0.3.18" dependencies: @@ -5709,16 +4177,7 @@ __metadata: languageName: node linkType: hard -"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3": - version: 1.1.5 - resolution: "@lezer/highlight@npm:1.1.5" - dependencies: - "@lezer/common": ^1.0.0 - checksum: 1f0b0a3dc7e1f23d889ce7a61d9ce1ba4d3b307205baf58f97252588df4c6751e4c86d39c20cd0bc7ac39ab82ff78a9251db91148b3b069965ec55d5fe9c4ef5 - languageName: node - linkType: hard - -"@lezer/highlight@npm:^1.1.6": +"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3, @lezer/highlight@npm:^1.1.6": version: 1.1.6 resolution: "@lezer/highlight@npm:1.1.6" dependencies: @@ -6874,24 +5333,6 @@ __metadata: languageName: node linkType: hard -"@react-aria/i18n@npm:^3.6.0": - version: 3.6.0 - resolution: "@react-aria/i18n@npm:3.6.0" - dependencies: - "@babel/runtime": ^7.6.2 - "@internationalized/date": ^3.0.1 - "@internationalized/message": ^3.0.9 - "@internationalized/number": ^3.1.1 - "@internationalized/string": ^3.0.0 - "@react-aria/ssr": ^3.3.0 - "@react-aria/utils": ^3.13.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: ede9cd611e15fe2975556dfe695bdcb67cbcb8d2dfff7677174f86f1418421491fbbbfd8eab40e724a8db24877d2f980df6e50d26d29d5b3e607ca39b42befc3 - languageName: node - linkType: hard - "@react-aria/i18n@npm:^3.7.0, @react-aria/i18n@npm:^3.7.1": version: 3.7.1 resolution: "@react-aria/i18n@npm:3.7.1" @@ -7297,17 +5738,6 @@ __metadata: languageName: node linkType: hard -"@react-aria/ssr@npm:^3.3.0": - version: 3.3.0 - resolution: "@react-aria/ssr@npm:3.3.0" - dependencies: - "@babel/runtime": ^7.6.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 0b7677ef521c65452460601dce3c264b67baa75ef7c99e9755ea55913765054156b6157c9c42e3d56aba86d1704b8b2aeb7672e4084f2f375fe1ec481e33c8c6 - languageName: node - linkType: hard - "@react-aria/ssr@npm:^3.5.0, @react-aria/ssr@npm:^3.6.0": version: 3.6.0 resolution: "@react-aria/ssr@npm:3.6.0" @@ -7462,21 +5892,6 @@ __metadata: languageName: node linkType: hard -"@react-aria/utils@npm:^3.13.3": - version: 3.13.3 - resolution: "@react-aria/utils@npm:3.13.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/ssr": ^3.3.0 - "@react-stately/utils": ^3.5.1 - "@react-types/shared": ^3.14.1 - clsx: ^1.1.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: b6d87ddb8e1d93b00405473099390c854647d81c0419de53cc4a7f02bdcca6d030776fba9f4b241400af13082bafc820dd5ce05c168e8f5a2c43a1b2660fb2ad - languageName: node - linkType: hard - "@react-aria/utils@npm:^3.15.0, @react-aria/utils@npm:^3.16.0": version: 3.16.0 resolution: "@react-aria/utils@npm:3.16.0" @@ -7755,23 +6170,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/calendar@npm:^3.0.2": - version: 3.0.2 - resolution: "@react-stately/calendar@npm:3.0.2" - dependencies: - "@babel/runtime": ^7.6.2 - "@internationalized/date": ^3.0.1 - "@react-stately/utils": ^3.5.1 - "@react-types/calendar": ^3.0.2 - "@react-types/datepicker": ^3.1.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: c093cab8761b1e16603abcde63f78dfefdb7fdf4cc269e41602ab3a7c93f9391d29ac68cc66e030c553305af7d96ff9afa3795123211a59316819937a8181956 - languageName: node - linkType: hard - -"@react-stately/calendar@npm:^3.2.0": +"@react-stately/calendar@npm:^3.0.2, @react-stately/calendar@npm:^3.2.0": version: 3.2.0 resolution: "@react-stately/calendar@npm:3.2.0" dependencies: @@ -7787,21 +6186,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/checkbox@npm:^3.2.1": - version: 3.2.1 - resolution: "@react-stately/checkbox@npm:3.2.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/toggle": ^3.4.1 - "@react-stately/utils": ^3.5.1 - "@react-types/checkbox": ^3.3.3 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 9035b595fa21cc1bef7e04249ec9df2293e93310dd644e4d32087ce19bd77aae38db3e676f6fdffbde875bc9a318f05dd60c61ab6e0d9b524222438e7ef31cd7 - languageName: node - linkType: hard - -"@react-stately/checkbox@npm:^3.4.1": +"@react-stately/checkbox@npm:^3.2.1, @react-stately/checkbox@npm:^3.4.1": version: 3.4.1 resolution: "@react-stately/checkbox@npm:3.4.1" dependencies: @@ -7816,19 +6201,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/collections@npm:^3.4.3": - version: 3.4.3 - resolution: "@react-stately/collections@npm:3.4.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f9045cdac0b20f7d7464ac37c0402511f7c5a727676d0cfefef74a553247d0dd1c816ea5804aac318d85ea5708599f9c9c2e8bd37165b5c6eec100e27f3832b9 - languageName: node - linkType: hard - -"@react-stately/collections@npm:^3.7.0": +"@react-stately/collections@npm:^3.4.3, @react-stately/collections@npm:^3.7.0": version: 3.7.0 resolution: "@react-stately/collections@npm:3.7.0" dependencies: @@ -7858,24 +6231,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/combobox@npm:^3.2.1": - version: 3.2.1 - resolution: "@react-stately/combobox@npm:3.2.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/list": ^3.5.3 - "@react-stately/menu": ^3.4.1 - "@react-stately/select": ^3.3.1 - "@react-stately/utils": ^3.5.1 - "@react-types/combobox": ^3.5.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 3e9a9050e8e20c96ae703876e652d28d2e3cf9dca79008d8e0f9fd096e88f74215add97e7d4aec9fe93afd64ebd676e5593d5178a28ad76c180207740fc47712 - languageName: node - linkType: hard - -"@react-stately/combobox@npm:^3.5.0": +"@react-stately/combobox@npm:^3.2.1, @react-stately/combobox@npm:^3.5.0": version: 3.5.0 resolution: "@react-stately/combobox@npm:3.5.0" dependencies: @@ -7905,24 +6261,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/datepicker@npm:^3.0.2": - version: 3.0.2 - resolution: "@react-stately/datepicker@npm:3.0.2" - dependencies: - "@babel/runtime": ^7.6.2 - "@internationalized/date": ^3.0.1 - "@internationalized/string": ^3.0.0 - "@react-stately/overlays": ^3.4.1 - "@react-stately/utils": ^3.5.1 - "@react-types/datepicker": ^3.1.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: d0250033d8f4625442177eac1ced6fe446877df9607bd1d7bdea11daae47166072304ee66d4ce1fe12886ef24c0cc1983ac5807a1fe07b05a5749d6b8302f47b - languageName: node - linkType: hard - -"@react-stately/datepicker@npm:^3.4.0": +"@react-stately/datepicker@npm:^3.0.2, @react-stately/datepicker@npm:^3.4.0": version: 3.4.0 resolution: "@react-stately/datepicker@npm:3.4.0" dependencies: @@ -7952,20 +6291,6 @@ __metadata: languageName: node linkType: hard -"@react-stately/grid@npm:^3.3.1": - version: 3.3.1 - resolution: "@react-stately/grid@npm:3.3.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/selection": ^3.10.3 - "@react-types/grid": ^3.1.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 84e1f24d2dcac51b1ab99f0ad403c965eb9988fa236054a5c137efb1917a455d56a1b78f820a77c3af38895d60a24884cfeac5a482b36390b629612ee8c7e7f3 - languageName: node - linkType: hard - "@react-stately/grid@npm:^3.6.0": version: 3.6.0 resolution: "@react-stately/grid@npm:3.6.0" @@ -7998,22 +6323,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/list@npm:^3.5.3": - version: 3.5.3 - resolution: "@react-stately/list@npm:3.5.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.3 - "@react-stately/selection": ^3.10.3 - "@react-stately/utils": ^3.5.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 162ba719db06a1649bbeb655c78e8a3f3c17a4c02f3318479ce2cc71940052f4a3cc98e67fd604f48ed89f199c731fb6d7c4d6e7b36d53593a0fc9b38d5e465c - languageName: node - linkType: hard - -"@react-stately/list@npm:^3.8.0": +"@react-stately/list@npm:^3.5.3, @react-stately/list@npm:^3.8.0": version: 3.8.0 resolution: "@react-stately/list@npm:3.8.0" dependencies: @@ -8028,22 +6338,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/menu@npm:^3.4.1": - version: 3.4.1 - resolution: "@react-stately/menu@npm:3.4.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/overlays": ^3.4.1 - "@react-stately/utils": ^3.5.1 - "@react-types/menu": ^3.7.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: a944d6e3a3caf400ffc52738ee8d586db6c6846d0ecc009de4bbedc88202f63d6bbddbd3d577f730f98f28404b077676af4c307f4ba09314c79cf56087a5aa8c - languageName: node - linkType: hard - -"@react-stately/menu@npm:^3.5.1": +"@react-stately/menu@npm:^3.4.1, @react-stately/menu@npm:^3.5.1": version: 3.5.1 resolution: "@react-stately/menu@npm:3.5.1" dependencies: @@ -8058,22 +6353,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/numberfield@npm:^3.2.1": - version: 3.2.1 - resolution: "@react-stately/numberfield@npm:3.2.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@internationalized/number": ^3.1.1 - "@react-stately/utils": ^3.5.1 - "@react-types/numberfield": ^3.3.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 5698d237c8fbe65cc7ab85c586ffadd92d085f15cab542003419deeccc2f13f2aa839dc844df8853648d892d6580fd4dd15a0b0d4eba86a467afbdb8d3c1675f - languageName: node - linkType: hard - -"@react-stately/numberfield@npm:^3.4.1": +"@react-stately/numberfield@npm:^3.2.1, @react-stately/numberfield@npm:^3.4.1": version: 3.4.1 resolution: "@react-stately/numberfield@npm:3.4.1" dependencies: @@ -8088,20 +6368,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/overlays@npm:^3.4.1": - version: 3.4.1 - resolution: "@react-stately/overlays@npm:3.4.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/utils": ^3.5.1 - "@react-types/overlays": ^3.6.3 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 3e0e8711c55198b75cb23a682530969c997fdd21c280a9a1356327ff3806252a70ef13e4efc7734902edfd58d6c2cc9d2624a37d8394ad44e9d33b09186510e3 - languageName: node - linkType: hard - -"@react-stately/overlays@npm:^3.5.1": +"@react-stately/overlays@npm:^3.4.1, @react-stately/overlays@npm:^3.5.1": version: 3.5.1 resolution: "@react-stately/overlays@npm:3.5.1" dependencies: @@ -8128,20 +6395,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/radio@npm:^3.5.1": - version: 3.5.1 - resolution: "@react-stately/radio@npm:3.5.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/utils": ^3.5.1 - "@react-types/radio": ^3.2.3 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 7a60de8afb5d8ccaf33da66613ae55a4b2eca75bacae902574282c33ab66684b1ae5db95b2743fdcc926d1c0464af7e6d837f6a5b85bb00836a9c78ba65c3623 - languageName: node - linkType: hard - -"@react-stately/radio@npm:^3.8.0": +"@react-stately/radio@npm:^3.5.1, @react-stately/radio@npm:^3.8.0": version: 3.8.0 resolution: "@react-stately/radio@npm:3.8.0" dependencies: @@ -8155,21 +6409,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/searchfield@npm:^3.3.1": - version: 3.3.1 - resolution: "@react-stately/searchfield@npm:3.3.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/utils": ^3.5.1 - "@react-types/searchfield": ^3.3.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f52776a294450382ea9f2abacea6b2972ea4f96ef6ffaff33c62f783881ceb74cd6aec959178499d6c7acf49b3a671d1902f0eb9cc4f2dba486ca88f7514693b - languageName: node - linkType: hard - -"@react-stately/searchfield@npm:^3.4.1": +"@react-stately/searchfield@npm:^3.3.1, @react-stately/searchfield@npm:^3.4.1": version: 3.4.1 resolution: "@react-stately/searchfield@npm:3.4.1" dependencies: @@ -8183,25 +6423,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/select@npm:^3.3.1": - version: 3.3.1 - resolution: "@react-stately/select@npm:3.3.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.3 - "@react-stately/list": ^3.5.3 - "@react-stately/menu": ^3.4.1 - "@react-stately/selection": ^3.10.3 - "@react-stately/utils": ^3.5.1 - "@react-types/select": ^3.6.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 0701cadd640fdea8a3a1c7048e459f701fc8ec9c0ef1fb9692fd70faa5bb7ce23475aba988f57dff90a3db71cbbf8b1ba49edc3df43e744550fbd0e2dcc3575f - languageName: node - linkType: hard - -"@react-stately/select@npm:^3.5.0": +"@react-stately/select@npm:^3.3.1, @react-stately/select@npm:^3.5.0": version: 3.5.0 resolution: "@react-stately/select@npm:3.5.0" dependencies: @@ -8219,21 +6441,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/selection@npm:^3.10.3": - version: 3.10.3 - resolution: "@react-stately/selection@npm:3.10.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.3 - "@react-stately/utils": ^3.5.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f65af198fa9199bc6bcf76279e2131b605e3ce449cc61d404de34993c81f499d0aba34916e8e8fd867d01ae60786ea3c3b725f3c73153674812bf29e64c6a531 - languageName: node - linkType: hard - -"@react-stately/selection@npm:^3.13.0": +"@react-stately/selection@npm:^3.10.3, @react-stately/selection@npm:^3.13.0": version: 3.13.0 resolution: "@react-stately/selection@npm:3.13.0" dependencies: @@ -8263,23 +6471,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/slider@npm:^3.2.1": - version: 3.2.1 - resolution: "@react-stately/slider@npm:3.2.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-aria/i18n": ^3.6.0 - "@react-aria/utils": ^3.13.3 - "@react-stately/utils": ^3.5.1 - "@react-types/shared": ^3.14.1 - "@react-types/slider": ^3.2.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 3d20eae41b79e481fc45cb4671b17ea20010f199790c963766a58067df18c1b83b41b1394ff3b053b32306cd952bad12331dec09c2a6a6c0c060f336aafee0ca - languageName: node - linkType: hard - -"@react-stately/slider@npm:^3.3.1": +"@react-stately/slider@npm:^3.2.1, @react-stately/slider@npm:^3.3.1": version: 3.3.1 resolution: "@react-stately/slider@npm:3.3.1" dependencies: @@ -8295,24 +6487,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/table@npm:^3.4.0": - version: 3.4.0 - resolution: "@react-stately/table@npm:3.4.0" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.3 - "@react-stately/grid": ^3.3.1 - "@react-stately/selection": ^3.10.3 - "@react-types/grid": ^3.1.3 - "@react-types/shared": ^3.14.1 - "@react-types/table": ^3.3.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f3571875fe9978d1f99554d8a31b3af3ced6ac84fde77ed175620f5ce76952833b98ee41b383f5098489c41f504942cedcffe604a4ec6158bbe320267eb70d01 - languageName: node - linkType: hard - -"@react-stately/table@npm:^3.9.0": +"@react-stately/table@npm:^3.4.0, @react-stately/table@npm:^3.9.0": version: 3.9.0 resolution: "@react-stately/table@npm:3.9.0" dependencies: @@ -8329,21 +6504,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/tabs@npm:^3.2.1": - version: 3.2.1 - resolution: "@react-stately/tabs@npm:3.2.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/list": ^3.5.3 - "@react-stately/utils": ^3.5.1 - "@react-types/tabs": ^3.1.3 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 593d4ea004ed89156ebf6e2eea401d30e4b06e9eae0f83752550bc5d3d776008577ba0e6baca9791c2e1c0af0f15881a8f95f18923132af08de172cecf097d20 - languageName: node - linkType: hard - -"@react-stately/tabs@npm:^3.4.0": +"@react-stately/tabs@npm:^3.2.1, @react-stately/tabs@npm:^3.4.0": version: 3.4.0 resolution: "@react-stately/tabs@npm:3.4.0" dependencies: @@ -8358,21 +6519,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/toggle@npm:^3.4.1": - version: 3.4.1 - resolution: "@react-stately/toggle@npm:3.4.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/utils": ^3.5.1 - "@react-types/checkbox": ^3.3.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 6cc297ac5c840aa20a6d304947a4d869b857c9dc522b7e77cf798f1815ebd5e5ae1f00aeb812fa452fbbfada1069e814b9e1aaf2751b747f875f8b88d88c21fe - languageName: node - linkType: hard - -"@react-stately/toggle@npm:^3.5.1": +"@react-stately/toggle@npm:^3.4.1, @react-stately/toggle@npm:^3.5.1": version: 3.5.1 resolution: "@react-stately/toggle@npm:3.5.1" dependencies: @@ -8386,21 +6533,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/tooltip@npm:^3.2.1": - version: 3.2.1 - resolution: "@react-stately/tooltip@npm:3.2.1" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/overlays": ^3.4.1 - "@react-stately/utils": ^3.5.1 - "@react-types/tooltip": ^3.2.3 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: dbb650986c11284dc45b6c0940e3a5aecb7d5e1af92828ae93b4ec1441b580461340033f427523b16f216afb815ebdc491f7aae361e5cd3bcc3dcea1268c76ab - languageName: node - linkType: hard - -"@react-stately/tooltip@npm:^3.4.0": +"@react-stately/tooltip@npm:^3.2.1, @react-stately/tooltip@npm:^3.4.0": version: 3.4.0 resolution: "@react-stately/tooltip@npm:3.4.0" dependencies: @@ -8414,22 +6547,7 @@ __metadata: languageName: node linkType: hard -"@react-stately/tree@npm:^3.3.3": - version: 3.3.3 - resolution: "@react-stately/tree@npm:3.3.3" - dependencies: - "@babel/runtime": ^7.6.2 - "@react-stately/collections": ^3.4.3 - "@react-stately/selection": ^3.10.3 - "@react-stately/utils": ^3.5.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 4e1a94cb478124a2443e84dbf0160dd3a5298e79478336f07003b8c5fcdb26043c65a94439a17315cf00e7f66bf6fd5e3e6fbcb44bced3352554d8f7be94899a - languageName: node - linkType: hard - -"@react-stately/tree@npm:^3.6.0": +"@react-stately/tree@npm:^3.3.3, @react-stately/tree@npm:^3.6.0": version: 3.6.0 resolution: "@react-stately/tree@npm:3.6.0" dependencies: @@ -8455,17 +6573,6 @@ __metadata: languageName: node linkType: hard -"@react-stately/utils@npm:^3.5.1": - version: 3.5.1 - resolution: "@react-stately/utils@npm:3.5.1" - dependencies: - "@babel/runtime": ^7.6.2 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f748331ae393f97b3e6fcccd37b767358f49229520b9500f82ed4c620bff36ef3c01d4ba9679ac7b9d6d78c5f6e711186c98bd0e6482ec27a6fbf26c5d0aa3cc - languageName: node - linkType: hard - "@react-stately/utils@npm:^3.6.0": version: 3.6.0 resolution: "@react-stately/utils@npm:3.6.0" @@ -8524,18 +6631,6 @@ __metadata: languageName: node linkType: hard -"@react-types/calendar@npm:^3.0.2": - version: 3.0.2 - resolution: "@react-types/calendar@npm:3.0.2" - dependencies: - "@internationalized/date": ^3.0.1 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: a3fd271d85064837c3b7a495e4048c25da1bbbc21015cfadd970f9959e8c802c9152e25ea772ffd815655e392ce07ce75c688726e70bb4cf6959605bc8257c8e - languageName: node - linkType: hard - "@react-types/calendar@npm:^3.2.0": version: 3.2.0 resolution: "@react-types/calendar@npm:3.2.0" @@ -8548,17 +6643,6 @@ __metadata: languageName: node linkType: hard -"@react-types/checkbox@npm:^3.3.3": - version: 3.3.3 - resolution: "@react-types/checkbox@npm:3.3.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: d1da491ff3bf14f894dbeab5ace3a397ead306d2cc4a820d2a653e038a5628495417feb10a4e07c05dcfce208ae9303c35de7e57d1b21a6b59ca1acca11b80d8 - languageName: node - linkType: hard - "@react-types/checkbox@npm:^3.4.3": version: 3.4.3 resolution: "@react-types/checkbox@npm:3.4.3" @@ -8582,17 +6666,6 @@ __metadata: languageName: node linkType: hard -"@react-types/combobox@npm:^3.5.3": - version: 3.5.3 - resolution: "@react-types/combobox@npm:3.5.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 41e1371f1efa48fe4d56afffeca59d1ed9dad75565c3d67fdf9f6c594529113ce9a8053b95d419682364878a6df0fd6a7178c20e6735778eea2abe74de1ca24f - languageName: node - linkType: hard - "@react-types/combobox@npm:^3.6.1": version: 3.6.1 resolution: "@react-types/combobox@npm:3.6.1" @@ -8604,19 +6677,6 @@ __metadata: languageName: node linkType: hard -"@react-types/datepicker@npm:^3.1.1": - version: 3.1.1 - resolution: "@react-types/datepicker@npm:3.1.1" - dependencies: - "@internationalized/date": ^3.0.1 - "@react-types/overlays": ^3.6.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: a3ab8ae22da8105ffebdebb5c89f212cda4d6f2203f7579cbd733e36afb4d2c7e4f986082becacaa66e7d1b4a99ef109952ddae7760d4bb0685e71d53894e316 - languageName: node - linkType: hard - "@react-types/datepicker@npm:^3.3.0": version: 3.3.0 resolution: "@react-types/datepicker@npm:3.3.0" @@ -8642,17 +6702,6 @@ __metadata: languageName: node linkType: hard -"@react-types/grid@npm:^3.1.3": - version: 3.1.3 - resolution: "@react-types/grid@npm:3.1.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 124b366436160ac7b88368a8be37abf4c703bde3fc1275e720f76d9ee8d0a10825fc5dd314b5eb6bb17b7d0c87091608d9b96d9521329ee5baeb94ab08fa3835 - languageName: node - linkType: hard - "@react-types/grid@npm:^3.1.7": version: 3.1.7 resolution: "@react-types/grid@npm:3.1.7" @@ -8709,18 +6758,6 @@ __metadata: languageName: node linkType: hard -"@react-types/menu@npm:^3.7.1": - version: 3.7.1 - resolution: "@react-types/menu@npm:3.7.1" - dependencies: - "@react-types/overlays": ^3.6.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 349443d1bd23bf64a9af57bef57d8ebfebfc6e82dbcef5cfd8ba778afc998f8dc3cebaae80728e6017b0d12b9e5aaea783254df36dd1b82a048b9e3c0e095795 - languageName: node - linkType: hard - "@react-types/menu@npm:^3.9.0": version: 3.9.0 resolution: "@react-types/menu@npm:3.9.0" @@ -8756,17 +6793,6 @@ __metadata: languageName: node linkType: hard -"@react-types/numberfield@npm:^3.3.3": - version: 3.3.3 - resolution: "@react-types/numberfield@npm:3.3.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: b0f6627157dea0ce8a8fa3434c55bbbc69c4b46c84024d6da6f97091eae45c8b4f9b5b842c2ff82712c1b2e407b4acbd0d476ceda25abd3b9402a0b4573b3b52 - languageName: node - linkType: hard - "@react-types/numberfield@npm:^3.4.1": version: 3.4.1 resolution: "@react-types/numberfield@npm:3.4.1" @@ -8778,17 +6804,6 @@ __metadata: languageName: node linkType: hard -"@react-types/overlays@npm:^3.6.3": - version: 3.6.3 - resolution: "@react-types/overlays@npm:3.6.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 8688db82adeda13e922f9805a5c9bd9f64e97e91c0ebf32409964e9d661828a4bb31907551dcdcd611807efa9824ff78aa8cb2ee4b0acfab001cbff5572336d4 - languageName: node - linkType: hard - "@react-types/overlays@npm:^3.7.1": version: 3.7.1 resolution: "@react-types/overlays@npm:3.7.1" @@ -8822,17 +6837,6 @@ __metadata: languageName: node linkType: hard -"@react-types/radio@npm:^3.2.3": - version: 3.2.3 - resolution: "@react-types/radio@npm:3.2.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: ce37d92a7e6665a9900b232aae68978bf1c82b4dffd30cc896c6382df7a9bb8a501a30f1b819d0630f9cbf21af3cb6f51de05fbaeaef4a1f250e5d39276eba59 - languageName: node - linkType: hard - "@react-types/radio@npm:^3.4.1": version: 3.4.1 resolution: "@react-types/radio@npm:3.4.1" @@ -8844,18 +6848,6 @@ __metadata: languageName: node linkType: hard -"@react-types/searchfield@npm:^3.3.3": - version: 3.3.3 - resolution: "@react-types/searchfield@npm:3.3.3" - dependencies: - "@react-types/shared": ^3.14.1 - "@react-types/textfield": ^3.5.3 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: cee59f6ad1da98cc01b81252ef91ebddf0a46df73e4cb3016474c9ad288a0d7b3de2d4607285de97ec23ecaba50391980268ac69b2def096c1fe3a33ecffc686 - languageName: node - linkType: hard - "@react-types/searchfield@npm:^3.4.1": version: 3.4.1 resolution: "@react-types/searchfield@npm:3.4.1" @@ -8868,17 +6860,6 @@ __metadata: languageName: node linkType: hard -"@react-types/select@npm:^3.6.3": - version: 3.6.3 - resolution: "@react-types/select@npm:3.6.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 472d3086e13ca18857659c5a93e36d5e00c4f1077fd627b16ed93641e6ec39aed77a6cb819e3115616486df3178c2e725aef8dd95cd36fc297819b78111d10b8 - languageName: node - linkType: hard - "@react-types/select@npm:^3.8.0": version: 3.8.0 resolution: "@react-types/select@npm:3.8.0" @@ -8899,16 +6880,7 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:^3.14.1": - version: 3.14.1 - resolution: "@react-types/shared@npm:3.14.1" - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 117fe230f5a26b7fcaf535c1cfb7c4d42416b0f49d0e0b3436fef2a5851234967908c4e884fc5f2a99a04bee2543543348346a04e1f3f45aaa14c42b6f08491a - languageName: node - linkType: hard - -"@react-types/shared@npm:^3.18.0": +"@react-types/shared@npm:^3.14.1, @react-types/shared@npm:^3.18.0": version: 3.18.0 resolution: "@react-types/shared@npm:3.18.0" peerDependencies: @@ -8928,17 +6900,6 @@ __metadata: languageName: node linkType: hard -"@react-types/slider@npm:^3.2.1": - version: 3.2.1 - resolution: "@react-types/slider@npm:3.2.1" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 3c64ab2d99fd14debd74181ab4faef43656b274f00443899564e89f3b4b8d9c327184a9c236e4f69c4efc8cba0eca0a0aeae686dcf9a521a7749bab4e0bbdfbb - languageName: node - linkType: hard - "@react-types/slider@npm:^3.5.0": version: 3.5.0 resolution: "@react-types/slider@npm:3.5.0" @@ -8962,18 +6923,6 @@ __metadata: languageName: node linkType: hard -"@react-types/table@npm:^3.3.1": - version: 3.3.1 - resolution: "@react-types/table@npm:3.3.1" - dependencies: - "@react-types/grid": ^3.1.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 1d3e4f8bac6e886944f67c159224893e63ec500f18aaadc74613d9053382c53fd282a7ee9dc21616b7fc0e1291d6ec7ccae87ebca2abdd19e8b371fb8cb46abc - languageName: node - linkType: hard - "@react-types/table@npm:^3.6.0": version: 3.6.0 resolution: "@react-types/table@npm:3.6.0" @@ -8986,17 +6935,6 @@ __metadata: languageName: node linkType: hard -"@react-types/tabs@npm:^3.1.3": - version: 3.1.3 - resolution: "@react-types/tabs@npm:3.1.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 04a95bfb92d2fe44900135bbdd1d256622c51fc90ebecc3374d29eb69bbb77ec13156d39b9fe1806e66b726cf5bbe9ff64822e04e5f3bcacd2429e27c3c260e1 - languageName: node - linkType: hard - "@react-types/tabs@npm:^3.2.1": version: 3.2.1 resolution: "@react-types/tabs@npm:3.2.1" @@ -9019,17 +6957,6 @@ __metadata: languageName: node linkType: hard -"@react-types/textfield@npm:^3.5.3": - version: 3.5.3 - resolution: "@react-types/textfield@npm:3.5.3" - dependencies: - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: f684821edba64e0b525606590800bf2cb6aea98c7304956ed3b2bbcb129ba7734897a9ca1bd056c2f23bf515399fed654071de6a2037942093c2af1c07fad1a9 - languageName: node - linkType: hard - "@react-types/textfield@npm:^3.7.1": version: 3.7.1 resolution: "@react-types/textfield@npm:3.7.1" @@ -9041,18 +6968,6 @@ __metadata: languageName: node linkType: hard -"@react-types/tooltip@npm:^3.2.3": - version: 3.2.3 - resolution: "@react-types/tooltip@npm:3.2.3" - dependencies: - "@react-types/overlays": ^3.6.3 - "@react-types/shared": ^3.14.1 - peerDependencies: - react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 - checksum: 5079ee2e561c2b9a7cc6e9dd22d48a26b0d61d79114aff730b7fc8348e199ec1db9e2ef96725f1682ebd5e93bbb223b920aa507f0f9997d8bc3df76f315077a6 - languageName: node - linkType: hard - "@react-types/tooltip@npm:^3.4.0": version: 3.4.0 resolution: "@react-types/tooltip@npm:3.4.0" @@ -9371,7 +7286,7 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/css-in-js@npm:^0.31.12, @rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.154": +"@rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/css-in-js@npm:0.31.23" dependencies: @@ -9385,19 +7300,19 @@ __metadata: linkType: hard "@rocket.chat/css-in-js@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/css-in-js@npm:0.31.23-dev.103" + version: 0.31.23-dev.157 + resolution: "@rocket.chat/css-in-js@npm:0.31.23-dev.157" dependencies: "@emotion/hash": ^0.9.0 - "@rocket.chat/css-supports": ~0.31.23-dev.103 - "@rocket.chat/memo": ~0.31.23-dev.103 - "@rocket.chat/stylis-logical-props-middleware": ~0.31.23-dev.103 + "@rocket.chat/css-supports": ~0.31.23-dev.157 + "@rocket.chat/memo": ~0.31.23-dev.157 + "@rocket.chat/stylis-logical-props-middleware": ~0.31.23-dev.157 stylis: ~4.1.3 - checksum: 7fea932e9b60d186722f289989aead15fb6d8af2b11f75a44fefa3158c19d953ad789b161599b33bc5ede7c79c4d6de9b3453ca0dd1fd3ec453fe77891df4e55 + checksum: f573537eced6bd231e897012269c9e256a1ea8d2c9a65bc05806956dd5d2e8ed0586023721754d44ec1c897ab9551da7eae78f48763be92e123b4481036595ae languageName: node linkType: hard -"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.154": +"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/css-supports@npm:0.31.23" dependencies: @@ -9473,9 +7388,9 @@ __metadata: linkType: soft "@rocket.chat/emitter@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/emitter@npm:0.31.23-dev.103" - checksum: 5163602cf6793678e1e8b1165fbf4c77a2d24f2536af87d3ceb8464da99ce9bff2ccede931bfd6fde9857890678b82f530b29b088ed41ddac7f4a60363e46f1b + version: 0.31.23-dev.157 + resolution: "@rocket.chat/emitter@npm:0.31.23-dev.157" + checksum: badd657e886eacd3494a6f40fa67c33c346cbaacc412789ea1703f8ac632f490af2397e4f5c2f0234c3acf5c8fdd112ddb287ba0dd40e392ef4ba74e1ca8cb71 languageName: node linkType: hard @@ -9566,33 +7481,21 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-hooks@npm:next": - version: 0.32.0-dev.274 - resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.274" - dependencies: - use-sync-external-store: ~1.2.0 - peerDependencies: - "@rocket.chat/fuselage-tokens": "*" - react: ^17.0.2 - checksum: 32e0872deb05e8b853aed59cab2cc4b3d3a707f0997009170a12b19d252ca09c203f8139b227c90678c0c06e14228c116ab428b9fba81228d1dab981844d8d54 - languageName: node - linkType: hard - -"@rocket.chat/fuselage-hooks@npm:~0.32.0-dev.242": - version: 0.32.0-dev.242 - resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.242" +"@rocket.chat/fuselage-hooks@npm:next, @rocket.chat/fuselage-hooks@npm:~0.32.0-dev.296": + version: 0.32.0-dev.296 + resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.296" dependencies: use-sync-external-store: ~1.2.0 peerDependencies: "@rocket.chat/fuselage-tokens": "*" react: ^17.0.2 - checksum: f9dcfe53370b55c417668c32b08ddc81dcd6b8fbbc44d7968109906b3053985b34ee7d1ecdbea895b1d2eaaae7960fe2559cf0a6136799ea4725b986cb69895e + checksum: 85f379b87530bd61961794bba9e2b6c458e4101fa9983f3b643245ce8edfcfff6dbda7ecf1fa88ac7d7df6d927a18f113e0e9cdd50a648e9a061c7ac332fc9df languageName: node linkType: hard "@rocket.chat/fuselage-polyfills@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.23-dev.103" + version: 0.31.23-dev.157 + resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.23-dev.157" dependencies: "@juggle/resize-observer": ^3.4.0 clipboard-polyfill: ^3.0.3 @@ -9600,13 +7503,13 @@ __metadata: focus-visible: ^5.2.0 focus-within-polyfill: ^5.2.1 new-event-polyfill: ^1.0.1 - checksum: b4dbda8530718a9d585fbc38ed5d6efcb29d8df64982a7434117e11fc75df39c94faa666fd269cb1139d2431349e23fbb21dc5d7c100abb31f18eee27079d56c + checksum: 14da84dc80002d99736d3a148602cc5d82f1c830fdfef84b3db4aa11822c42a2c96e43a3aecb62a4eb222c750399d9f9349b224601c2f2011d375b5be76a3bf3 languageName: node linkType: hard "@rocket.chat/fuselage-toastbar@npm:next": - version: 0.32.0-dev.303 - resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.303" + version: 0.32.0-dev.357 + resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.357" peerDependencies: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" @@ -9614,21 +7517,14 @@ __metadata: "@rocket.chat/styled": "*" react: ^17.0.2 react-dom: ^17.0.2 - checksum: 479f5c50d1f6ce7a7e4cc4bbd0909f3bc1c3e83e3a9fdeb806d2b0540a17c46e30f8e72b816c954257411bd404f693d83bdf9b2499ce13cee2426d03c1dbec76 + checksum: 4a9c81ee42928b500fccec76542b0d812d9e79ab4ac9a89e8ee77ce19db45db78242218a799214f9248f1e04c636934ee70d44febc089ece38c35ac21837c101 languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:next": - version: 0.32.0-dev.280 - resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.280" - checksum: d8c7537512a7950ff6f0f3e23b3d6a2b82c0caea42d5b6e5599257fe2319cbd6f53a229c61413b025b20f576521aca67d9cba378f15e19edddae11a55133672a - languageName: node - linkType: hard - -"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.330": - version: 0.32.0-dev.330 - resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.330" - checksum: 9733eeb0c8f1d8220e13d20c85e9ecf219b6d22e3c339352a080a19cbdc7aae7828ae591884b070c48047fa984383122d8a816e7102a4c38ba6a6ce73f364452 +"@rocket.chat/fuselage-tokens@npm:next, @rocket.chat/fuselage-tokens@npm:~0.32.0-dev.333": + version: 0.32.0-dev.333 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.333" + checksum: dd19009e75e6154361be566ecea5f328902fdf25a10fdcc9ecd18076e424dd5f7d1a4b4c56b7679fe28e4a4ff77e32107d2f83a2c9c98f968ef6ff18f5de95db languageName: node linkType: hard @@ -9688,14 +7584,14 @@ __metadata: linkType: soft "@rocket.chat/fuselage@npm:next": - version: 0.32.0-dev.380 - resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.380" - dependencies: - "@rocket.chat/css-in-js": ~0.31.23-dev.154 - "@rocket.chat/css-supports": ~0.31.23-dev.154 - "@rocket.chat/fuselage-tokens": ~0.32.0-dev.330 - "@rocket.chat/memo": ~0.31.23-dev.154 - "@rocket.chat/styled": ~0.31.23-dev.154 + version: 0.32.0-dev.383 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.383" + dependencies: + "@rocket.chat/css-in-js": ~0.31.23-dev.157 + "@rocket.chat/css-supports": ~0.31.23-dev.157 + "@rocket.chat/fuselage-tokens": ~0.32.0-dev.333 + "@rocket.chat/memo": ~0.31.23-dev.157 + "@rocket.chat/styled": ~0.31.23-dev.157 invariant: ^2.2.4 react-aria: ~3.23.1 react-keyed-flatten-children: ^1.3.0 @@ -9707,7 +7603,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 07be707ad2fab881852d1b4ac49044915b24d5bf82352cc6e3e57526dfbcd13ec3b2d73b362bbe52d122aa5b45d90371838f1e463a443934d573022ac70a57e6 + checksum: 110c59c012dd019a6edddba2a1df6c4ceff6f5f84d5b4db206656ac06349bd5889ddd8ce2ee475bd91e947e457d28382fe938233891b9c81047ac3452ceedf93 languageName: node linkType: hard @@ -9795,9 +7691,9 @@ __metadata: linkType: soft "@rocket.chat/icons@npm:next": - version: 0.32.0-dev.362 - resolution: "@rocket.chat/icons@npm:0.32.0-dev.362" - checksum: 1c2096913e154d0db68b8ccc933294486ff06e250983809ce165f1497df05deec8b5165b47dbefdd013013b4cdf4a3a20f2ac191155b629e5ec4a7bcab5d8870 + version: 0.32.0-dev.365 + resolution: "@rocket.chat/icons@npm:0.32.0-dev.365" + checksum: 6323cadb2931582cbae4ec4cc271ae881a82915c9a1b0958a9a52c82ed5a5c3dad82699f3e0e22e580bc12695413e772eeb0a6cac670ab0c7863dd73d2f44cb0 languageName: node linkType: hard @@ -9815,14 +7711,14 @@ __metadata: linkType: soft "@rocket.chat/layout@npm:next": - version: 0.32.0-dev.213 - resolution: "@rocket.chat/layout@npm:0.32.0-dev.213" + version: 0.32.0-dev.266 + resolution: "@rocket.chat/layout@npm:0.32.0-dev.266" peerDependencies: "@rocket.chat/fuselage": "*" react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: 6a7e069f669fe7201e12ffb287064fe724e69294010b6cc426d4c5936f7e2c93cfe9ae92ee480e0ce7864cd5af37b5f9163dbdcefb850571adc3242ed897a5f1 + checksum: cbecb1564ea77578071dd2571ae3b004485232f035291c02f1b00fbee6c4ae12955c91a3feeb11b153f6b33ea5662b5691d4b7436cd2c8f40a36a8d990100a62 languageName: node linkType: hard @@ -9923,19 +7819,19 @@ __metadata: linkType: soft "@rocket.chat/logo@npm:next": - version: 0.32.0-dev.279 - resolution: "@rocket.chat/logo@npm:0.32.0-dev.279" + version: 0.32.0-dev.333 + resolution: "@rocket.chat/logo@npm:0.32.0-dev.333" dependencies: - "@rocket.chat/fuselage-hooks": ~0.32.0-dev.242 - "@rocket.chat/styled": ~0.31.23-dev.103 + "@rocket.chat/fuselage-hooks": ~0.32.0-dev.296 + "@rocket.chat/styled": ~0.31.23-dev.157 peerDependencies: react: 17.0.2 react-dom: 17.0.2 - checksum: a79a80a89111633f2c325ace46dc951fe67df94eb192a4ed691e274e19e951f7b22a9a301108f3db4daaac7667f8b9f5f2535a123a0ab72e94796358678acaeb + checksum: 502931f4aea0730e446aa17e22a881c9a19cbd15da4be5ec26c4fed51a4400fea9365ec5880c5fef3dc0f8a7ace6f1c1204f644a0b217b8eb1658de92214d1e6 languageName: node linkType: hard -"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.154": +"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/memo@npm:0.31.23" checksum: 070debb940749a2e4463cf767dd65c6967cea664a5bd67c22a812d611f6c3c46d6fe4bb0bf329e43dcd927493413add37c45ae3b05ec08f0b24e9d7385caebdd @@ -9943,18 +7839,18 @@ __metadata: linkType: hard "@rocket.chat/memo@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/memo@npm:0.31.23-dev.103" - checksum: 44b91bb7e02ffbeb2b094e5f283a1ddc3ca205f6ff01f5d044911b8cfc690b7e1472ee9f296de46bc84dcf2eb64d4c314813aa74d2076ec170ec1321f43145a6 + version: 0.31.23-dev.157 + resolution: "@rocket.chat/memo@npm:0.31.23-dev.157" + checksum: e1939d45e2759522463424c0ad876b5e7570a5c7bce561ee4f5e166b16778f794d54a1e2028dc90b1602ff2d20f32ae889b905e894b68af4df6de6844e1fb216 languageName: node linkType: hard "@rocket.chat/message-parser@npm:next": - version: 0.32.0-dev.296 - resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.296" + version: 0.32.0-dev.331 + resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.331" dependencies: tldts: ~5.7.112 - checksum: 2a40090b0b5b94e531da3bd1433c6f60b98f19b85eb9ca81db8706fcf360d8245c97fde2257a6c39e9be7fa4a630eede5abf679847ef3c923594c7156f8eb334 + checksum: dfb9fc430eba71c32b2ca1ea6357a3d42623a08dc41d9e9d23235fb0c71a939a0255ef3d2860df8f43d89fb23a34cccd0d66c470e01eaec4804b62410dba3808 languageName: node linkType: hard @@ -10432,8 +8328,8 @@ __metadata: linkType: soft "@rocket.chat/onboarding-ui@npm:next": - version: 0.32.0-dev.329 - resolution: "@rocket.chat/onboarding-ui@npm:0.32.0-dev.329" + version: 0.32.0-dev.383 + resolution: "@rocket.chat/onboarding-ui@npm:0.32.0-dev.383" dependencies: i18next: ~21.6.16 react-hook-form: ~7.27.1 @@ -10448,7 +8344,7 @@ __metadata: react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: 3118c3f3bb91db6e30748a1bf031221b2459b611c260446adf5bafcc9e38fa600a8a3366567a88183b2e93286b538233cb585f0d414b53f02e2eb48b8b2ba3ad + checksum: 427d4f4f6265a551e623129a118edc2170e689af12f68bb2c2c3e1b52905fdcacd950f34f7dba59173eef9ece4c21860d8366b76c3dc21a7c23f281bc74b3b9a languageName: node linkType: hard @@ -10725,22 +8621,22 @@ __metadata: linkType: soft "@rocket.chat/string-helpers@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/string-helpers@npm:0.31.23-dev.103" - checksum: b505564152613d5871dae226b67458d69fbacf2ddeb3f6d10cf81124ccccfd75ad60133bc772db6bf20f4b17f7cfe09fc0ea9c846c2f519b031ce22d0da6688e + version: 0.31.23-dev.157 + resolution: "@rocket.chat/string-helpers@npm:0.31.23-dev.157" + checksum: 41d49dcc57aaea77f71928f7c5da8c68deebafe4b33718f98d6af4427c7df6e212bf0dca0703d74d9cf7fb24c65ec8215e37f8240544754963e0a83e5d7f62f6 languageName: node linkType: hard "@rocket.chat/styled@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/styled@npm:0.31.23-dev.103" + version: 0.31.23-dev.157 + resolution: "@rocket.chat/styled@npm:0.31.23-dev.157" dependencies: - "@rocket.chat/css-in-js": ~0.31.23-dev.103 - checksum: aadeb2ffe37e01d694012ec49d301a9a8960b82377fc1daa2c332b657414c4c52839010d3f8ce5114312065b8da0cc41b70bdc93fa1916169c53d333d56428bc + "@rocket.chat/css-in-js": ~0.31.23-dev.157 + checksum: ab4aaa77b8570111d37233954322e036316bde3bf65cf781e242ca936ec84f4dca4fed7d91cbd7ff4b2aab329f812d7887003f721a9c72fd4544bbc9fbf342af languageName: node linkType: hard -"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.154": +"@rocket.chat/styled@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/styled@npm:0.31.23" dependencies: @@ -10749,7 +8645,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.23, @rocket.chat/stylis-logical-props-middleware@npm:~0.31.23-dev.103": +"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.23, @rocket.chat/stylis-logical-props-middleware@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.23" dependencies: @@ -10883,9 +8779,9 @@ __metadata: linkType: soft "@rocket.chat/ui-kit@npm:next": - version: 0.32.0-dev.294 - resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.294" - checksum: cf58890f3fbcfdff3df9436a33c4aa333de34369f89902107dee74c50c11b8af793c9c80a8df5046ffcabf0fd90111f31a6fc2030876e838e43ec4c6a5703fc4 + version: 0.32.0-dev.318 + resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.318" + checksum: 4cb64edb3942635e23ba904da95eb5b742b1cadeb89c1aad7fb2acc60b25886cf83cfcd9c785f6a57959e1e3bcd713ac8014ffe45b70bc6d40b1651a206fe6bc languageName: node linkType: hard @@ -10979,7 +8875,7 @@ __metadata: "@codemirror/lang-json": ^6.0.1 "@codemirror/tooltip": ^0.19.16 "@lezer/highlight": ^1.1.6 - "@rocket.chat/css-in-js": ^0.31.12 + "@rocket.chat/css-in-js": next "@rocket.chat/fuselage": next "@rocket.chat/fuselage-hooks": next "@rocket.chat/fuselage-polyfills": next @@ -12874,33 +10770,7 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7": - version: 7.20.0 - resolution: "@types/babel__core@npm:7.20.0" - dependencies: - "@babel/parser": ^7.20.7 - "@babel/types": ^7.20.7 - "@types/babel__generator": "*" - "@types/babel__template": "*" - "@types/babel__traverse": "*" - checksum: 49b601a0a7637f1f387442c8156bd086cfd10ff4b82b0e1994e73a6396643b5435366fb33d6b604eade8467cca594ef97adcbc412aede90bb112ebe88d0ad6df - languageName: node - linkType: hard - -"@types/babel__core@npm:^7.1.14": - version: 7.1.20 - resolution: "@types/babel__core@npm:7.1.20" - dependencies: - "@babel/parser": ^7.20.7 - "@babel/types": ^7.20.7 - "@types/babel__generator": "*" - "@types/babel__template": "*" - "@types/babel__traverse": "*" - checksum: a09c4f0456552547a5b8a5a009a3daec4d362f622168f8e08bda0ded2da0a65ab0b1642e23c433b3616721f5701dc34a996c5bde5baeaea53eda98f438043f2c - languageName: node - linkType: hard - -"@types/babel__core@npm:~7.20.1": +"@types/babel__core@npm:^7, @types/babel__core@npm:^7.1.14, @types/babel__core@npm:~7.20.1": version: 7.20.1 resolution: "@types/babel__core@npm:7.20.1" dependencies: @@ -13037,14 +10907,7 @@ __metadata: languageName: node linkType: hard -"@types/chai@npm:*": - version: 4.3.1 - resolution: "@types/chai@npm:4.3.1" - checksum: 2ee246b76c469cd620a7a1876a73bc597074361b67d547b4bd96a0c1adb43597ede2d8589ab626192e14349d83cbb646cc11e2c179eeeb43ff11596de94d82c4 - languageName: node - linkType: hard - -"@types/chai@npm:^4.3.5": +"@types/chai@npm:*, @types/chai@npm:^4.3.5": version: 4.3.5 resolution: "@types/chai@npm:4.3.5" checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 @@ -13187,17 +11050,7 @@ __metadata: languageName: node linkType: hard -"@types/eslint@npm:*": - version: 8.37.0 - resolution: "@types/eslint@npm:8.37.0" - dependencies: - "@types/estree": "*" - "@types/json-schema": "*" - checksum: 06d3b3fba12004294591b5c7a52e3cec439472195da54e096076b1f2ddfbb8a445973b9681046dd530a6ac31eca502f635abc1e3ce37d03513089358e6f822ee - languageName: node - linkType: hard - -"@types/eslint@npm:^8.40.2, @types/eslint@npm:~8.40.2": +"@types/eslint@npm:*, @types/eslint@npm:^8.40.2, @types/eslint@npm:~8.40.2": version: 8.40.2 resolution: "@types/eslint@npm:8.40.2" dependencies: @@ -13207,20 +11060,20 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^0.0.51": - version: 0.0.51 - resolution: "@types/estree@npm:0.0.51" - checksum: e56a3bcf759fd9185e992e7fdb3c6a5f81e8ff120e871641607581fb3728d16c811702a7d40fa5f869b7f7b4437ab6a87eb8d98ffafeee51e85bbe955932a189 - languageName: node - linkType: hard - -"@types/estree@npm:^1.0.0": +"@types/estree@npm:*, @types/estree@npm:^1.0.0": version: 1.0.1 resolution: "@types/estree@npm:1.0.1" checksum: e9aa175eacb797216fafce4d41e8202c7a75555bc55232dee0f9903d7171f8f19f0ae7d5191bb1a88cb90e65468be508c0df850a9fb81b4433b293a5a749899d languageName: node linkType: hard +"@types/estree@npm:^0.0.51": + version: 0.0.51 + resolution: "@types/estree@npm:0.0.51" + checksum: e56a3bcf759fd9185e992e7fdb3c6a5f81e8ff120e871641607581fb3728d16c811702a7d40fa5f869b7f7b4437ab6a87eb8d98ffafeee51e85bbe955932a189 + languageName: node + linkType: hard + "@types/express-rate-limit@npm:^5.1.3": version: 5.1.3 resolution: "@types/express-rate-limit@npm:5.1.3" @@ -13230,18 +11083,7 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.18": - version: 4.17.31 - resolution: "@types/express-serve-static-core@npm:4.17.31" - dependencies: - "@types/node": "*" - "@types/qs": "*" - "@types/range-parser": "*" - checksum: 009bfbe1070837454a1056aa710d0390ee5fb8c05dfe5a1691cc3e2ca88dc256f80e1ca27cb51a978681631d2f6431bfc9ec352ea46dd0c6eb183d0170bde5df - languageName: node - linkType: hard - -"@types/express-serve-static-core@npm:^4.17.33": +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.33": version: 4.17.35 resolution: "@types/express-serve-static-core@npm:4.17.35" dependencies: @@ -13253,19 +11095,7 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.8": - version: 4.17.13 - resolution: "@types/express@npm:4.17.13" - dependencies: - "@types/body-parser": "*" - "@types/express-serve-static-core": ^4.17.18 - "@types/qs": "*" - "@types/serve-static": "*" - checksum: 12a2a0e6c4b993fc0854bec665906788aea0d8ee4392389d7a98a5de1eefdd33c9e1e40a91f3afd274011119c506f7b4126acb97fae62ae20b654974d44cba12 - languageName: node - linkType: hard - -"@types/express@npm:^4.17.17": +"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.17, @types/express@npm:^4.17.8": version: 4.17.17 resolution: "@types/express@npm:4.17.17" dependencies: @@ -13434,13 +11264,13 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:*": - version: 29.5.0 - resolution: "@types/jest@npm:29.5.0" +"@types/jest@npm:*, @types/jest@npm:^29.5.1, @types/jest@npm:^29.5.2, @types/jest@npm:~29.5.2": + version: 29.5.2 + resolution: "@types/jest@npm:29.5.2" dependencies: expect: ^29.0.0 pretty-format: ^29.0.0 - checksum: cd877e5c56d299cceb8bfdcbb1a77723c706750dd3c3bc47403bc3599b8faff590a3b009c68bb5b11bf7a8c77d1fb01de5e124329b4a08e65f1cdda28b0ecdb8 + checksum: 7d205599ea3cccc262bad5cc173d3242d6bf8138c99458509230e4ecef07a52d6ddcde5a1dbd49ace655c0af51d2dbadef3748697292ea4d86da19d9e03e19c0 languageName: node linkType: hard @@ -13454,16 +11284,6 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:^29.5.1, @types/jest@npm:^29.5.2, @types/jest@npm:~29.5.2": - version: 29.5.2 - resolution: "@types/jest@npm:29.5.2" - dependencies: - expect: ^29.0.0 - pretty-format: ^29.0.0 - checksum: 7d205599ea3cccc262bad5cc173d3242d6bf8138c99458509230e4ecef07a52d6ddcde5a1dbd49ace655c0af51d2dbadef3748697292ea4d86da19d9e03e19c0 - languageName: node - linkType: hard - "@types/jquery@npm:*": version: 3.5.14 resolution: "@types/jquery@npm:3.5.14" @@ -13596,14 +11416,7 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:*, @types/lodash@npm:^4.14.167": - version: 4.14.182 - resolution: "@types/lodash@npm:4.14.182" - checksum: 7dd137aa9dbabd632408bd37009d984655164fa1ecc3f2b6eb94afe35bf0a5852cbab6183148d883e9c73a958b7fec9a9bcf7c8e45d41195add6a18c34958209 - languageName: node - linkType: hard - -"@types/lodash@npm:^4.14.195": +"@types/lodash@npm:*, @types/lodash@npm:^4.14.167, @types/lodash@npm:^4.14.195": version: 4.14.195 resolution: "@types/lodash@npm:4.14.195" checksum: 39b75ca635b3fa943d17d3d3aabc750babe4c8212485a4df166fe0516e39288e14b0c60afc6e21913cc0e5a84734633c71e617e2bd14eaa1cf51b8d7799c432e @@ -13762,44 +11575,21 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^14.0.10 || ^16.0.0, @types/node@npm:^14.14.20 || ^16.0.0": - version: 16.11.39 - resolution: "@types/node@npm:16.11.39" - checksum: bc97b9773ac6b3194800f990b349fad7f66c6126dacef59291b10a2c8b6813d6f67f947b7e12a6c9952790f7065d576fe38355b8fe034a6af60f317cfc570f69 - languageName: node - linkType: hard - -"@types/node@npm:^14.0.26, @types/node@npm:^14.14.37": - version: 14.18.21 - resolution: "@types/node@npm:14.18.21" - checksum: 4ed35b76609647a4e36a194702e31cdda9ed42174ddaf7937bc5498984e98a99e8a42ea895ea17dd9c5ec18080112c29ab670c34f90eb9f7a4703b85b31e34fa - languageName: node - linkType: hard - -"@types/node@npm:^14.18.51": - version: 14.18.51 - resolution: "@types/node@npm:14.18.51" - checksum: 0960a31d2ac605763fe79c8edcee3cb48257d345ce417c019d84ff5d8cd92dd0937674814ab3f169346b4259c29f640556006bcb2c54cfb3e63fa0cf728d320e - languageName: node - linkType: hard - -"@types/node@npm:^16.18.36": +"@types/node@npm:^14.0.10 || ^16.0.0, @types/node@npm:^14.14.20 || ^16.0.0, @types/node@npm:^16.18.36": version: 16.18.36 resolution: "@types/node@npm:16.18.36" checksum: a9d138fa1269079c60daad6984713dc0b713983f8b34a83edbc6d7957b2e38beab9b2598c9fe99f19d073e20bc212a18aaf82eabdc23ef64dce7d2089a9aab2a languageName: node linkType: hard -"@types/nodemailer@npm:*": - version: 6.4.7 - resolution: "@types/nodemailer@npm:6.4.7" - dependencies: - "@types/node": "*" - checksum: dc2a33a89135e04a5bea4921e8645e8453b90e3c3b05f0646f05071c5951ab697ea49ea1e503a690f04cb0a6abfc54967325c5a4036356793cfbb64ba64fb141 +"@types/node@npm:^14.0.26, @types/node@npm:^14.14.37, @types/node@npm:^14.18.51": + version: 14.18.51 + resolution: "@types/node@npm:14.18.51" + checksum: 0960a31d2ac605763fe79c8edcee3cb48257d345ce417c019d84ff5d8cd92dd0937674814ab3f169346b4259c29f640556006bcb2c54cfb3e63fa0cf728d320e languageName: node linkType: hard -"@types/nodemailer@npm:^6.4.8": +"@types/nodemailer@npm:*, @types/nodemailer@npm:^6.4.8": version: 6.4.8 resolution: "@types/nodemailer@npm:6.4.8" dependencies: @@ -13975,16 +11765,7 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:<18.0.0": - version: 17.0.17 - resolution: "@types/react-dom@npm:17.0.17" - dependencies: - "@types/react": ^17 - checksum: 23caf98aa03e968811560f92a2c8f451694253ebe16b670929b24eaf0e7fa62ba549abe9db0ac028a9d8a9086acd6ab9c6c773f163fa21224845edbc00ba6232 - languageName: node - linkType: hard - -"@types/react-dom@npm:^17.0.20, @types/react-dom@npm:~17.0.20": +"@types/react-dom@npm:<18.0.0, @types/react-dom@npm:^17.0.20, @types/react-dom@npm:~17.0.20": version: 17.0.20 resolution: "@types/react-dom@npm:17.0.20" dependencies: @@ -13993,16 +11774,7 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.0.0": - version: 18.0.10 - resolution: "@types/react-dom@npm:18.0.10" - dependencies: - "@types/react": "*" - checksum: ff8282d5005a0b1cd95fb65bf79d3d8485e4cfe2aaf052129033a178684b940014a3f4536bc20d573f8a01cf4c6f4770c74988cef7c2b5cac3041d9f172647e3 - languageName: node - linkType: hard - -"@types/react-dom@npm:^18.2.5": +"@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.2.5": version: 18.2.5 resolution: "@types/react-dom@npm:18.2.5" dependencies: @@ -14023,18 +11795,7 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^17": - version: 17.0.58 - resolution: "@types/react@npm:17.0.58" - dependencies: - "@types/prop-types": "*" - "@types/scheduler": "*" - csstype: ^3.0.2 - checksum: 4eaf32b86c43f388c681e34a00921c508dd88a1d1022aebfadc5fe802b7c5bed863de1a17eed31e43ca2d65222952dfe79a022055a0e6e4e1ad89fc5a42ec05e - languageName: node - linkType: hard - -"@types/react@npm:^17.0.62, @types/react@npm:~17.0.62": +"@types/react@npm:*, @types/react@npm:^17, @types/react@npm:^17.0.62, @types/react@npm:~17.0.62": version: 17.0.62 resolution: "@types/react@npm:17.0.62" dependencies: @@ -14260,16 +12021,7 @@ __metadata: languageName: node linkType: hard -"@types/testing-library__jest-dom@npm:^5.9.1": - version: 5.14.5 - resolution: "@types/testing-library__jest-dom@npm:5.14.5" - dependencies: - "@types/jest": "*" - checksum: dcb05416758fe88c1f4f3aa97b4699fcb46a5ed8f53c6b81721e66155452a48caf12ecb97dfdfd4130678e65efd66b9fca0ac434b3d63affec84842a84a6bf38 - languageName: node - linkType: hard - -"@types/testing-library__jest-dom@npm:~5.14.6": +"@types/testing-library__jest-dom@npm:^5.9.1, @types/testing-library__jest-dom@npm:~5.14.6": version: 5.14.6 resolution: "@types/testing-library__jest-dom@npm:5.14.6" dependencies: @@ -14422,16 +12174,7 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.1": - version: 8.5.4 - resolution: "@types/ws@npm:8.5.4" - dependencies: - "@types/node": "*" - checksum: fefbad20d211929bb996285c4e6f699b12192548afedbe4930ab4384f8a94577c9cd421acaad163cacd36b88649509970a05a0b8f20615b30c501ed5269038d1 - languageName: node - linkType: hard - -"@types/ws@npm:^8.5.5": +"@types/ws@npm:^8.5.1, @types/ws@npm:^8.5.5": version: 8.5.5 resolution: "@types/ws@npm:8.5.5" dependencies: @@ -14534,16 +12277,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/scope-manager@npm:5.58.0" - dependencies: - "@typescript-eslint/types": 5.58.0 - "@typescript-eslint/visitor-keys": 5.58.0 - checksum: f0d3df5cc3c461fe63ef89ad886b53c239cc7c1d9061d83d8a9d9c8e087e5501eac84bebff8a954728c17ccea191f235686373d54d2b8b6370af2bcf2b18e062 - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/scope-manager@npm:5.60.0" @@ -14571,20 +12304,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/types@npm:5.52.0" - checksum: 018940d61aebf7cf3f7de1b9957446e2ea01f08fe950bef4788c716a3a88f7c42765fe7d80152b0d0428fcd4bd3ace2dfa8c459ba1c59d9a84e951642180f869 - languageName: node - linkType: hard - -"@typescript-eslint/types@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/types@npm:5.58.0" - checksum: 8622a73d73220c4a7111537825f488c0271272032a1d4e129dc722bc6e8b3ec84f64469b2ca3b8dae7da3a9c18953ce1449af51f5f757dad60835eb579ad1d2c - languageName: node - linkType: hard - "@typescript-eslint/types@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/types@npm:5.60.0" @@ -14592,41 +12311,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.52.0" - dependencies: - "@typescript-eslint/types": 5.52.0 - "@typescript-eslint/visitor-keys": 5.52.0 - debug: ^4.3.4 - globby: ^11.1.0 - is-glob: ^4.0.3 - semver: ^7.3.7 - tsutils: ^3.21.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 67d396907fee3d6894e26411a5098a37f07e5d50343189e6361ff7db91c74a7ffe2abd630d11f14c2bda1f4af13edf52b80b11cbccb55b44079c7cec14c9e108 - languageName: node - linkType: hard - -"@typescript-eslint/typescript-estree@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.58.0" - dependencies: - "@typescript-eslint/typescript-estree": 5.52.0 - "@typescript-eslint/utils": 5.52.0 - debug: ^4.3.4 - tsutils: ^3.21.0 - peerDependencies: - eslint: "*" - peerDependenciesMeta: - typescript: - optional: true - checksum: 51b668ec858db0c040a71dff526273945cee4ba5a9b240528d503d02526685882d900cf071c6636a4d9061ed3fd4a7274f7f1a23fba55c4b48b143344b4009c7 - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/typescript-estree@npm:5.60.0" @@ -14645,25 +12329,7 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/utils@npm:5.52.0" - dependencies: - "@eslint-community/eslint-utils": ^4.2.0 - "@types/json-schema": ^7.0.9 - "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.58.0 - "@typescript-eslint/types": 5.58.0 - "@typescript-eslint/typescript-estree": 5.58.0 - eslint-scope: ^5.1.1 - semver: ^7.3.7 - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 01906be5262ece36537e9d586e4d2d4791e05752a9354bcb42b1f5bf965f53daa13309c61c3dff5e201ea28c298e4e01cf0c93738afa0099fea0da3b1d8cb3a5 - languageName: node - linkType: hard - -"@typescript-eslint/utils@npm:5.60.0": +"@typescript-eslint/utils@npm:5.60.0, @typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.45.0, @typescript-eslint/utils@npm:^5.58.0": version: 5.60.0 resolution: "@typescript-eslint/utils@npm:5.60.0" dependencies: @@ -14681,44 +12347,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.45.0, @typescript-eslint/utils@npm:^5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/utils@npm:5.58.0" - dependencies: - "@eslint-community/eslint-utils": ^4.2.0 - "@types/json-schema": ^7.0.9 - "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.58.0 - "@typescript-eslint/types": 5.58.0 - "@typescript-eslint/typescript-estree": 5.58.0 - eslint-scope: ^5.1.1 - semver: ^7.3.7 - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: c618ae67963ecf96b1492c09afaeb363f542f0d6780bcac4af3c26034e3b20034666b2d523aa94821df813aafb57a0b150a7d5c2224fe8257452ad1de2237a58 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:5.52.0": - version: 5.52.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.52.0" - dependencies: - "@typescript-eslint/types": 5.52.0 - eslint-visitor-keys: ^3.3.0 - checksum: 33b44f0cd35b7b47f34e89d52e47b8d8200f55af306b22db4de104d79f65907458ea022e548f50d966e32fea150432ac9c1ae65b3001b0ad2ac8a17c0211f370 - languageName: node - linkType: hard - -"@typescript-eslint/visitor-keys@npm:5.58.0": - version: 5.58.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.58.0" - dependencies: - "@typescript-eslint/types": 5.58.0 - eslint-visitor-keys: ^3.3.0 - checksum: ab2d1f37660559954c840429ef78bbf71834063557e3e68e435005b4987970b9356fdf217ead53f7a57f66f5488dc478062c5c44bf17053a8bf041733539b98f - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/visitor-keys@npm:5.60.0" @@ -14768,16 +12396,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ast@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/ast@npm:1.11.1" - dependencies: - "@webassemblyjs/helper-numbers": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - checksum: 1eee1534adebeece635362f8e834ae03e389281972611408d64be7895fc49f48f98fddbbb5339bf8a72cb101bcb066e8bca3ca1bf1ef47dadf89def0395a8d87 - languageName: node - linkType: hard - "@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": version: 1.11.6 resolution: "@webassemblyjs/ast@npm:1.11.6" @@ -14799,13 +12417,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/floating-point-hex-parser@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.1" - checksum: b8efc6fa08e4787b7f8e682182d84dfdf8da9d9c77cae5d293818bc4a55c1f419a87fa265ab85252b3e6c1fd323d799efea68d825d341a7c365c64bc14750e97 - languageName: node - linkType: hard - "@webassemblyjs/floating-point-hex-parser@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.6" @@ -14820,13 +12431,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-api-error@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-api-error@npm:1.11.1" - checksum: 0792813f0ed4a0e5ee0750e8b5d0c631f08e927f4bdfdd9fe9105dc410c786850b8c61bff7f9f515fdfb149903bec3c976a1310573a4c6866a94d49bc7271959 - languageName: node - linkType: hard - "@webassemblyjs/helper-api-error@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-api-error@npm:1.11.6" @@ -14841,13 +12445,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-buffer@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-buffer@npm:1.11.1" - checksum: a337ee44b45590c3a30db5a8b7b68a717526cf967ada9f10253995294dbd70a58b2da2165222e0b9830cd4fc6e4c833bf441a721128d1fe2e9a7ab26b36003ce - languageName: node - linkType: hard - "@webassemblyjs/helper-buffer@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-buffer@npm:1.11.6" @@ -14887,17 +12484,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-numbers@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-numbers@npm:1.11.1" - dependencies: - "@webassemblyjs/floating-point-hex-parser": 1.11.1 - "@webassemblyjs/helper-api-error": 1.11.1 - "@xtuc/long": 4.2.2 - checksum: 44d2905dac2f14d1e9b5765cf1063a0fa3d57295c6d8930f6c59a36462afecc6e763e8a110b97b342a0f13376166c5d41aa928e6ced92e2f06b071fd0db59d3a - languageName: node - linkType: hard - "@webassemblyjs/helper-numbers@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-numbers@npm:1.11.6" @@ -14909,13 +12495,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-wasm-bytecode@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.1" - checksum: eac400113127832c88f5826bcc3ad1c0db9b3dbd4c51a723cfdb16af6bfcbceb608170fdaac0ab7731a7e18b291be7af68a47fcdb41cfe0260c10857e7413d97 - languageName: node - linkType: hard - "@webassemblyjs/helper-wasm-bytecode@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.6" @@ -14930,18 +12509,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/helper-wasm-section@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-buffer": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - "@webassemblyjs/wasm-gen": 1.11.1 - checksum: 617696cfe8ecaf0532763162aaf748eb69096fb27950219bb87686c6b2e66e11cd0614d95d319d0ab1904bc14ebe4e29068b12c3e7c5e020281379741fe4bedf - languageName: node - linkType: hard - "@webassemblyjs/helper-wasm-section@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.6" @@ -14966,15 +12533,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/ieee754@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/ieee754@npm:1.11.1" - dependencies: - "@xtuc/ieee754": ^1.2.0 - checksum: 23a0ac02a50f244471631802798a816524df17e56b1ef929f0c73e3cde70eaf105a24130105c60aff9d64a24ce3b640dad443d6f86e5967f922943a7115022ec - languageName: node - linkType: hard - "@webassemblyjs/ieee754@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/ieee754@npm:1.11.6" @@ -14993,15 +12551,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/leb128@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/leb128@npm:1.11.1" - dependencies: - "@xtuc/long": 4.2.2 - checksum: 33ccc4ade2f24de07bf31690844d0b1ad224304ee2062b0e464a610b0209c79e0b3009ac190efe0e6bd568b0d1578d7c3047fc1f9d0197c92fc061f56224ff4a - languageName: node - linkType: hard - "@webassemblyjs/leb128@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/leb128@npm:1.11.6" @@ -15020,13 +12569,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/utf8@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/utf8@npm:1.11.1" - checksum: 972c5cfc769d7af79313a6bfb96517253a270a4bf0c33ba486aa43cac43917184fb35e51dfc9e6b5601548cd5931479a42e42c89a13bb591ffabebf30c8a6a0b - languageName: node - linkType: hard - "@webassemblyjs/utf8@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/utf8@npm:1.11.6" @@ -15041,22 +12583,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-edit@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wasm-edit@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-buffer": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - "@webassemblyjs/helper-wasm-section": 1.11.1 - "@webassemblyjs/wasm-gen": 1.11.1 - "@webassemblyjs/wasm-opt": 1.11.1 - "@webassemblyjs/wasm-parser": 1.11.1 - "@webassemblyjs/wast-printer": 1.11.1 - checksum: 6d7d9efaec1227e7ef7585a5d7ff0be5f329f7c1c6b6c0e906b18ed2e9a28792a5635e450aca2d136770d0207225f204eff70a4b8fd879d3ac79e1dcc26dbeb9 - languageName: node - linkType: hard - "@webassemblyjs/wasm-edit@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/wasm-edit@npm:1.9.0" @@ -15089,19 +12615,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-gen@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wasm-gen@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - "@webassemblyjs/ieee754": 1.11.1 - "@webassemblyjs/leb128": 1.11.1 - "@webassemblyjs/utf8": 1.11.1 - checksum: 1f6921e640293bf99fb16b21e09acb59b340a79f986c8f979853a0ae9f0b58557534b81e02ea2b4ef11e929d946708533fd0693c7f3712924128fdafd6465f5b - languageName: node - linkType: hard - "@webassemblyjs/wasm-gen@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/wasm-gen@npm:1.11.6" @@ -15128,18 +12641,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-opt@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wasm-opt@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-buffer": 1.11.1 - "@webassemblyjs/wasm-gen": 1.11.1 - "@webassemblyjs/wasm-parser": 1.11.1 - checksum: 21586883a20009e2b20feb67bdc451bbc6942252e038aae4c3a08e6f67b6bae0f5f88f20bfc7bd0452db5000bacaf5ab42b98cf9aa034a6c70e9fc616142e1db - languageName: node - linkType: hard - "@webassemblyjs/wasm-opt@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/wasm-opt@npm:1.11.6" @@ -15164,20 +12665,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wasm-parser@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wasm-parser@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/helper-api-error": 1.11.1 - "@webassemblyjs/helper-wasm-bytecode": 1.11.1 - "@webassemblyjs/ieee754": 1.11.1 - "@webassemblyjs/leb128": 1.11.1 - "@webassemblyjs/utf8": 1.11.1 - checksum: 1521644065c360e7b27fad9f4bb2df1802d134dd62937fa1f601a1975cde56bc31a57b6e26408b9ee0228626ff3ba1131ae6f74ffb7d718415b6528c5a6dbfc2 - languageName: node - linkType: hard - "@webassemblyjs/wasm-parser@npm:1.11.6, @webassemblyjs/wasm-parser@npm:^1.11.5": version: 1.11.6 resolution: "@webassemblyjs/wasm-parser@npm:1.11.6" @@ -15220,16 +12707,6 @@ __metadata: languageName: node linkType: hard -"@webassemblyjs/wast-printer@npm:1.11.1": - version: 1.11.1 - resolution: "@webassemblyjs/wast-printer@npm:1.11.1" - dependencies: - "@webassemblyjs/ast": 1.11.1 - "@xtuc/long": 4.2.2 - checksum: f15ae4c2441b979a3b4fce78f3d83472fb22350c6dc3fd34bfe7c3da108e0b2360718734d961bba20e7716cb8578e964b870da55b035e209e50ec9db0378a3f7 - languageName: node - linkType: hard - "@webassemblyjs/wast-printer@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/wast-printer@npm:1.11.6" @@ -15284,14 +12761,14 @@ __metadata: languageName: node linkType: hard -"@xmldom/xmldom@npm:0.8.7, @xmldom/xmldom@npm:^0.8.5": +"@xmldom/xmldom@npm:0.8.7": version: 0.8.7 resolution: "@xmldom/xmldom@npm:0.8.7" checksum: 593d4429c2281ee7799adcb6ff8604b68cf30ce0721537e3e380287b423e67c7ac197d90987f932b4fd3febc409ded8435706e7f90fbba6e22e08740477341d1 languageName: node linkType: hard -"@xmldom/xmldom@npm:^0.8.8": +"@xmldom/xmldom@npm:^0.8.5, @xmldom/xmldom@npm:^0.8.8": version: 0.8.8 resolution: "@xmldom/xmldom@npm:0.8.8" checksum: 5f5fc0482fcc599f62e3009516932a265e00f1bb2093fe2c76f3f8d9bfebdd13246f48d4132c9b301c7a573f0fa8712e56aa747dce75b179c2b73f1dde7b5f42 @@ -15386,15 +12863,6 @@ __metadata: languageName: node linkType: hard -"acorn-import-assertions@npm:^1.7.6": - version: 1.8.0 - resolution: "acorn-import-assertions@npm:1.8.0" - peerDependencies: - acorn: ^8 - checksum: 5c4cf7c850102ba7ae0eeae0deb40fb3158c8ca5ff15c0bca43b5c47e307a1de3d8ef761788f881343680ea374631ae9e9615ba8876fee5268dbe068c98bcba6 - languageName: node - linkType: hard - "acorn-import-assertions@npm:^1.9.0": version: 1.9.0 resolution: "acorn-import-assertions@npm:1.9.0" @@ -15445,16 +12913,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.1.0, acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1": - version: 8.8.2 - resolution: "acorn@npm:8.8.2" - bin: - acorn: bin/acorn - checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 - languageName: node - linkType: hard - -"acorn@npm:^8.8.2": +"acorn@npm:^8.1.0, acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2": version: 8.9.0 resolution: "acorn@npm:8.9.0" bin: @@ -15606,7 +13065,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.8.0": +"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.11.0, ajv@npm:^8.8.0": version: 8.12.0 resolution: "ajv@npm:8.12.0" dependencies: @@ -15618,18 +13077,6 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.1, ajv@npm:^8.11.0": - version: 8.11.0 - resolution: "ajv@npm:8.11.0" - dependencies: - fast-deep-equal: ^3.1.1 - json-schema-traverse: ^1.0.0 - require-from-string: ^2.0.2 - uri-js: ^4.2.2 - checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef - languageName: node - linkType: hard - "alphanum-sort@npm:^1.0.0": version: 1.0.2 resolution: "alphanum-sort@npm:1.0.2" @@ -15685,7 +13132,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:4.1.1, ansi-colors@npm:^4.1.1": +"ansi-colors@npm:4.1.1": version: 4.1.1 resolution: "ansi-colors@npm:4.1.1" checksum: 138d04a51076cb085da0a7e2d000c5c0bb09f6e772ed5c65c53cb118d37f6c5f1637506d7155fb5f330f0abcf6f12fa2e489ac3f8cdab9da393bf1bb4f9a32b0 @@ -15699,7 +13146,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:^4.1.3": +"ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" checksum: a9c2ec842038a1fabc7db9ece7d3177e2fe1c5dc6f0c51ecfbf5f39911427b89c00b5dc6b8bd95f82a26e9b16aaae2e83d45f060e98070ce4d1333038edceb0e @@ -16017,19 +13464,7 @@ __metadata: languageName: node linkType: hard -"args@npm:^5.0.1": - version: 5.0.1 - resolution: "args@npm:5.0.1" - dependencies: - camelcase: 5.0.0 - chalk: 2.4.2 - leven: 2.1.0 - mri: 1.1.4 - checksum: 51e2a05f32d15b8e292f000e6b232118df61b8f4fd446b17bb4e99df9ab47fe2c4a01924d7f967a6f08e82f9c19be277b08ed22bceff058aca849144ef8efed3 - languageName: node - linkType: hard - -"args@npm:^5.0.3": +"args@npm:^5.0.1, args@npm:^5.0.3": version: 5.0.3 resolution: "args@npm:5.0.3" dependencies: @@ -16652,19 +14087,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs2@npm:^0.3.3": - version: 0.3.3 - resolution: "babel-plugin-polyfill-corejs2@npm:0.3.3" - dependencies: - "@babel/compat-data": ^7.17.7 - "@babel/helper-define-polyfill-provider": ^0.3.3 - semver: ^6.1.1 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 7db3044993f3dddb3cc3d407bc82e640964a3bfe22de05d90e1f8f7a5cb71460011ab136d3c03c6c1ba428359ebf635688cd6205e28d0469bba221985f5c6179 - languageName: node - linkType: hard - "babel-plugin-polyfill-corejs2@npm:^0.4.3": version: 0.4.3 resolution: "babel-plugin-polyfill-corejs2@npm:0.4.3" @@ -16690,18 +14112,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-corejs3@npm:^0.6.0": - version: 0.6.0 - resolution: "babel-plugin-polyfill-corejs3@npm:0.6.0" - dependencies: - "@babel/helper-define-polyfill-provider": ^0.3.3 - core-js-compat: ^3.25.1 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: 470bb8c59f7c0912bd77fe1b5a2e72f349b3f65bbdee1d60d6eb7e1f4a085c6f24b2dd5ab4ac6c2df6444a96b070ef6790eccc9edb6a2668c60d33133bfb62c6 - languageName: node - linkType: hard - "babel-plugin-polyfill-corejs3@npm:^0.8.1": version: 0.8.1 resolution: "babel-plugin-polyfill-corejs3@npm:0.8.1" @@ -16714,17 +14124,6 @@ __metadata: languageName: node linkType: hard -"babel-plugin-polyfill-regenerator@npm:^0.4.1": - version: 0.4.1 - resolution: "babel-plugin-polyfill-regenerator@npm:0.4.1" - dependencies: - "@babel/helper-define-polyfill-provider": ^0.3.3 - peerDependencies: - "@babel/core": ^7.0.0-0 - checksum: ab0355efbad17d29492503230387679dfb780b63b25408990d2e4cf421012dae61d6199ddc309f4d2409ce4e9d3002d187702700dd8f4f8770ebbba651ed066c - languageName: node - linkType: hard - "babel-plugin-polyfill-regenerator@npm:^0.5.0": version: 0.5.0 resolution: "babel-plugin-polyfill-regenerator@npm:0.5.0" @@ -17187,7 +14586,7 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.2, body-parser@npm:^1.20.2": +"body-parser@npm:1.20.2, body-parser@npm:^1.19.0, body-parser@npm:^1.20.2": version: 1.20.2 resolution: "body-parser@npm:1.20.2" dependencies: @@ -17207,26 +14606,6 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:^1.19.0": - version: 1.20.0 - resolution: "body-parser@npm:1.20.0" - dependencies: - bytes: 3.1.2 - content-type: ~1.0.4 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.10.3 - raw-body: 2.5.1 - type-is: ~1.6.18 - unpipe: 1.0.0 - checksum: 12fffdeac82fe20dddcab7074215d5156e7d02a69ae90cbe9fee1ca3efa2f28ef52097cbea76685ee0a1509c71d85abd0056a08e612c09077cad6277a644cf88 - languageName: node - linkType: hard - "bonjour-service@npm:^1.0.11": version: 1.1.1 resolution: "bonjour-service@npm:1.1.1" @@ -17481,21 +14860,7 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.3": - version: 4.21.3 - resolution: "browserslist@npm:4.21.3" - dependencies: - caniuse-lite: ^1.0.30001370 - electron-to-chromium: ^1.4.202 - node-releases: ^2.0.6 - update-browserslist-db: ^1.0.5 - bin: - browserslist: cli.js - checksum: ff512a7bcca1c530e2854bbdfc7be2791d0fb524097a6340e56e1d5924164c7e4e0a9b070de04cdc4c149d15cb4d4275cb7c626ebbce954278a2823aaad2452a - languageName: node - linkType: hard - -"browserslist@npm:^4.21.5": +"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.3, browserslist@npm:^4.21.5": version: 4.21.9 resolution: "browserslist@npm:4.21.9" dependencies: @@ -18002,14 +15367,7 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001370": - version: 1.0.30001464 - resolution: "caniuse-lite@npm:1.0.30001464" - checksum: 67cdee102c1660d62d7b9dbd4740bb7af096236618f2509fd2e0039d50db5f02fb87c21d90b6d573fdcf50deaf3c84503d009e871502b5c221d0ba1dec18ba11 - languageName: node - linkType: hard - -"caniuse-lite@npm:^1.0.30001503": +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001503": version: 1.0.30001503 resolution: "caniuse-lite@npm:1.0.30001503" checksum: cd5f0af37655ff71ec4ab3c49124d75e0b8b68de625d07ea80e9a82329e616b5203d5dad6865192653be9da50081c06878f081ab069dac0be35adf29aa1599cd @@ -18115,22 +15473,7 @@ __metadata: languageName: node linkType: hard -"chai@npm:>1.9.0": - version: 4.3.6 - resolution: "chai@npm:4.3.6" - dependencies: - assertion-error: ^1.1.0 - check-error: ^1.0.2 - deep-eql: ^3.0.1 - get-func-name: ^2.0.0 - loupe: ^2.3.1 - pathval: ^1.1.1 - type-detect: ^4.0.5 - checksum: acff93fd537f96d4a4d62dd83810285dffcfccb5089e1bf2a1205b28ec82d93dff551368722893cf85004282df10ee68802737c33c90c5493957ed449ed7ce71 - languageName: node - linkType: hard - -"chai@npm:^4.3.7": +"chai@npm:>1.9.0, chai@npm:^4.3.7": version: 4.3.7 resolution: "chai@npm:4.3.7" dependencies: @@ -18414,20 +15757,13 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^3.1.0": +"ci-info@npm:^3.1.0, ci-info@npm:^3.2.0": version: 3.8.0 resolution: "ci-info@npm:3.8.0" checksum: d0a4d3160497cae54294974a7246202244fff031b0a6ea20dd57b10ec510aa17399c41a1b0982142c105f3255aff2173e5c0dd7302ee1b2f28ba3debda375098 languageName: node linkType: hard -"ci-info@npm:^3.2.0": - version: 3.3.0 - resolution: "ci-info@npm:3.3.0" - checksum: c3d86fe374938ecda5093b1ba39acb535d8309185ba3f23587747c6a057e63f45419b406d880304dbc0e1d72392c9a33e42fe9a1e299209bc0ded5efaa232b66 - languageName: node - linkType: hard - "cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": version: 1.0.4 resolution: "cipher-base@npm:1.0.4" @@ -18832,14 +16168,7 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.10, colorette@npm:^2.0.14, colorette@npm:^2.0.7": - version: 2.0.19 - resolution: "colorette@npm:2.0.19" - checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427 - languageName: node - linkType: hard - -"colorette@npm:^2.0.20": +"colorette@npm:^2.0.10, colorette@npm:^2.0.14, colorette@npm:^2.0.20, colorette@npm:^2.0.7": version: 2.0.20 resolution: "colorette@npm:2.0.20" checksum: 0c016fea2b91b733eb9f4bcdb580018f52c0bc0979443dad930e5037a968237ac53d9beb98e218d2e9235834f8eebce7f8e080422d6194e957454255bde71d3d @@ -19117,14 +16446,7 @@ __metadata: languageName: node linkType: hard -"content-type@npm:^1.0.4, content-type@npm:~1.0.4": - version: 1.0.4 - resolution: "content-type@npm:1.0.4" - checksum: 3d93585fda985d1554eca5ebd251994327608d2e200978fdbfba21c0c679914d5faf266d17027de44b34a72c7b0745b18584ecccaa7e1fdfb6a68ac7114f12e0 - languageName: node - linkType: hard - -"content-type@npm:~1.0.5": +"content-type@npm:^1.0.4, content-type@npm:~1.0.4, content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766 @@ -19232,16 +16554,7 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.25.1, core-js-compat@npm:^3.8.1": - version: 3.25.1 - resolution: "core-js-compat@npm:3.25.1" - dependencies: - browserslist: ^4.21.3 - checksum: 34dbec657adc2f660f4cd701709c9c5e27cbd608211c65df09458f80f3e357b9492ba1c5173e17cca72d889dcc6da01268cadf88fb407cf1726e76d301c6143e - languageName: node - linkType: hard - -"core-js-compat@npm:^3.30.1, core-js-compat@npm:^3.30.2": +"core-js-compat@npm:^3.30.1, core-js-compat@npm:^3.30.2, core-js-compat@npm:^3.8.1": version: 3.31.0 resolution: "core-js-compat@npm:3.31.0" dependencies: @@ -19469,7 +16782,7 @@ __metadata: languageName: node linkType: hard -"cross-fetch@npm:3.1.5, cross-fetch@npm:^3.1.5": +"cross-fetch@npm:3.1.5": version: 3.1.5 resolution: "cross-fetch@npm:3.1.5" dependencies: @@ -19478,7 +16791,7 @@ __metadata: languageName: node linkType: hard -"cross-fetch@npm:^3.0.4": +"cross-fetch@npm:^3.0.4, cross-fetch@npm:^3.1.5": version: 3.1.6 resolution: "cross-fetch@npm:3.1.6" dependencies: @@ -20272,14 +17585,7 @@ __metadata: languageName: node linkType: hard -"decode-uri-component@npm:^0.2.0": - version: 0.2.0 - resolution: "decode-uri-component@npm:0.2.0" - checksum: f3749344ab9305ffcfe4bfe300e2dbb61fc6359e2b736812100a3b1b6db0a5668cba31a05e4b45d4d63dbf1a18dfa354cd3ca5bb3ededddabb8cd293f4404f94 - languageName: node - linkType: hard - -"decode-uri-component@npm:^0.2.2": +"decode-uri-component@npm:^0.2.0, decode-uri-component@npm:^0.2.2": version: 0.2.2 resolution: "decode-uri-component@npm:0.2.2" checksum: 95476a7d28f267292ce745eac3524a9079058bbb35767b76e3ee87d42e34cd0275d2eb19d9d08c3e167f97556e8a2872747f5e65cbebcac8b0c98d83e285f139 @@ -20374,15 +17680,6 @@ __metadata: languageName: node linkType: hard -"deep-eql@npm:^3.0.1": - version: 3.0.1 - resolution: "deep-eql@npm:3.0.1" - dependencies: - type-detect: ^4.0.0 - checksum: 4f4c9fb79eb994fb6e81d4aa8b063adc40c00f831588aa65e20857d5d52f15fb23034a6576ecf886f7ff6222d5ae42e71e9b7d57113e0715b1df7ea1e812b125 - languageName: node - linkType: hard - "deep-eql@npm:^4.1.2": version: 4.1.3 resolution: "deep-eql@npm:4.1.3" @@ -20883,14 +18180,7 @@ __metadata: languageName: node linkType: hard -"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0": - version: 2.2.0 - resolution: "domelementtype@npm:2.2.0" - checksum: 24cb386198640cd58aa36f8c987f2ea61859929106d06ffcc8f547e70cb2ed82a6dc56dcb8252b21fba1f1ea07df6e4356d60bfe57f77114ca1aed6828362629 - languageName: node - linkType: hard - -"domelementtype@npm:^2.3.0": +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": version: 2.3.0 resolution: "domelementtype@npm:2.3.0" checksum: ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 @@ -21156,13 +18446,6 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.202": - version: 1.4.249 - resolution: "electron-to-chromium@npm:1.4.249" - checksum: 830a35a157af7ae226f1528d727e369bb13f53bc7a4edefdf718651ace09d7d7b4bd7b70d33b5018b8eff6cf99ee58409b6c4140cd6d56350c1966f280ac5c93 - languageName: node - linkType: hard - "electron-to-chromium@npm:^1.4.431": version: 1.4.433 resolution: "electron-to-chromium@npm:1.4.433" @@ -21351,16 +18634,6 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.10.0": - version: 5.10.0 - resolution: "enhanced-resolve@npm:5.10.0" - dependencies: - graceful-fs: ^4.2.4 - tapable: ^2.2.0 - checksum: 0bb9830704db271610f900e8d79d70a740ea16f251263362b0c91af545576d09fe50103496606c1300a05e588372d6f9780a9bc2e30ce8ef9b827ec8f44687ff - languageName: node - linkType: hard - "enhanced-resolve@npm:^5.15.0": version: 5.15.0 resolution: "enhanced-resolve@npm:5.15.0" @@ -21394,20 +18667,13 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0": +"entities@npm:^4.2.0, entities@npm:^4.4.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7 languageName: node linkType: hard -"entities@npm:^4.4.0": - version: 4.4.0 - resolution: "entities@npm:4.4.0" - checksum: 84d250329f4b56b40fa93ed067b194db21e8815e4eb9b59f43a086f0ecd342814f6bc483de8a77da5d64e0f626033192b1b4f1792232a7ea6b970ebe0f3187c2 - languageName: node - linkType: hard - "entities@npm:~2.0.0": version: 2.0.3 resolution: "entities@npm:2.0.3" @@ -21541,13 +18807,6 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^0.9.0": - version: 0.9.3 - resolution: "es-module-lexer@npm:0.9.3" - checksum: 84bbab23c396281db2c906c766af58b1ae2a1a2599844a504df10b9e8dc77ec800b3211fdaa133ff700f5703d791198807bba25d9667392d27a5e9feda344da8 - languageName: node - linkType: hard - "es-module-lexer@npm:^1.2.1": version: 1.3.0 resolution: "es-module-lexer@npm:1.3.0" @@ -21603,7 +18862,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.17.5": +"esbuild@npm:^0.17.5, esbuild@npm:^0.17.6": version: 0.17.19 resolution: "esbuild@npm:0.17.19" dependencies: @@ -21680,83 +18939,6 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.17.6": - version: 0.17.18 - resolution: "esbuild@npm:0.17.18" - dependencies: - "@esbuild/android-arm": 0.17.18 - "@esbuild/android-arm64": 0.17.18 - "@esbuild/android-x64": 0.17.18 - "@esbuild/darwin-arm64": 0.17.18 - "@esbuild/darwin-x64": 0.17.18 - "@esbuild/freebsd-arm64": 0.17.18 - "@esbuild/freebsd-x64": 0.17.18 - "@esbuild/linux-arm": 0.17.18 - "@esbuild/linux-arm64": 0.17.18 - "@esbuild/linux-ia32": 0.17.18 - "@esbuild/linux-loong64": 0.17.18 - "@esbuild/linux-mips64el": 0.17.18 - "@esbuild/linux-ppc64": 0.17.18 - "@esbuild/linux-riscv64": 0.17.18 - "@esbuild/linux-s390x": 0.17.18 - "@esbuild/linux-x64": 0.17.18 - "@esbuild/netbsd-x64": 0.17.18 - "@esbuild/openbsd-x64": 0.17.18 - "@esbuild/sunos-x64": 0.17.18 - "@esbuild/win32-arm64": 0.17.18 - "@esbuild/win32-ia32": 0.17.18 - "@esbuild/win32-x64": 0.17.18 - dependenciesMeta: - "@esbuild/android-arm": - optional: true - "@esbuild/android-arm64": - optional: true - "@esbuild/android-x64": - optional: true - "@esbuild/darwin-arm64": - optional: true - "@esbuild/darwin-x64": - optional: true - "@esbuild/freebsd-arm64": - optional: true - "@esbuild/freebsd-x64": - optional: true - "@esbuild/linux-arm": - optional: true - "@esbuild/linux-arm64": - optional: true - "@esbuild/linux-ia32": - optional: true - "@esbuild/linux-loong64": - optional: true - "@esbuild/linux-mips64el": - optional: true - "@esbuild/linux-ppc64": - optional: true - "@esbuild/linux-riscv64": - optional: true - "@esbuild/linux-s390x": - optional: true - "@esbuild/linux-x64": - optional: true - "@esbuild/netbsd-x64": - optional: true - "@esbuild/openbsd-x64": - optional: true - "@esbuild/sunos-x64": - optional: true - "@esbuild/win32-arm64": - optional: true - "@esbuild/win32-ia32": - optional: true - "@esbuild/win32-x64": - optional: true - bin: - esbuild: bin/esbuild - checksum: 900b333f649fd89804216fb61fb5a0ffadc6dc37a2ec3b5981b588f72821676ea649a7c0ec785f0dbe6e774080b084c8af5f6ee7adbc1b138faf2a8c35e2c69c - languageName: node - linkType: hard - "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -22088,14 +19270,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0": - version: 3.4.0 - resolution: "eslint-visitor-keys@npm:3.4.0" - checksum: 33159169462d3989321a1ec1e9aaaf6a24cc403d5d347e9886d1b5bfe18ffa1be73bdc6203143a28a606b142b1af49787f33cff0d6d0813eb5f2e8d2e1a6043c - languageName: node - linkType: hard - -"eslint-visitor-keys@npm:^3.4.1": +"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1": version: 3.4.1 resolution: "eslint-visitor-keys@npm:3.4.1" checksum: f05121d868202736b97de7d750847a328fcfa8593b031c95ea89425333db59676ac087fa905eba438d0a3c5769632f828187e0c1a0d271832a2153c1d3661c2c @@ -22326,14 +19501,7 @@ __metadata: languageName: node linkType: hard -"eventemitter2@npm:^6.3.1": - version: 6.4.5 - resolution: "eventemitter2@npm:6.4.5" - checksum: 84504f9cf0cc30205cdd46783fe9df3733435e5097f13070b678023110b5ef07847651808ae280cd94c42cd5976880211c7a40321a8ff8fa56f7c5f9c5c11960 - languageName: node - linkType: hard - -"eventemitter2@npm:^6.4.9": +"eventemitter2@npm:^6.3.1, eventemitter2@npm:^6.4.9": version: 6.4.9 resolution: "eventemitter2@npm:6.4.9" checksum: be59577c1e1c35509c7ba0e2624335c35bbcfd9485b8a977384c6cc6759341ea1a98d3cb9dbaa5cea4fff9b687e504504e3f9c2cc1674cf3bd8a43a7c74ea3eb @@ -22738,20 +19906,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9": - version: 3.2.11 - resolution: "fast-glob@npm:3.2.11" - dependencies: - "@nodelib/fs.stat": ^2.0.2 - "@nodelib/fs.walk": ^1.2.3 - glob-parent: ^5.1.2 - merge2: ^1.3.0 - micromatch: ^4.0.4 - checksum: f473105324a7780a20c06de842e15ddbb41d3cb7e71d1e4fe6e8373204f22245d54f5ab9e2061e6a1c613047345954d29b022e0e76f5c28b1df9858179a0e6d7 - languageName: node - linkType: hard - -"fast-glob@npm:^3.2.12": +"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" dependencies: @@ -23320,17 +20475,7 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.7, follow-redirects@npm:^1.14.8, follow-redirects@npm:^1.14.9": - version: 1.15.1 - resolution: "follow-redirects@npm:1.15.1" - peerDependenciesMeta: - debug: - optional: true - checksum: 6aa4e3e3cdfa3b9314801a1cd192ba756a53479d9d8cca65bf4db3a3e8834e62139245cd2f9566147c8dfe2efff1700d3e6aefd103de4004a7b99985e71dd533 - languageName: node - linkType: hard - -"follow-redirects@npm:^1.15.2": +"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.7, follow-redirects@npm:^1.14.8, follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.2": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -23609,13 +20754,6 @@ __metadata: languageName: node linkType: hard -"fs-monkey@npm:^1.0.3": - version: 1.0.3 - resolution: "fs-monkey@npm:1.0.3" - checksum: cf50804833f9b88a476911ae911fe50f61a98d986df52f890bd97e7262796d023698cb2309fa9b74fdd8974f04315b648748a0a8ee059e7d5257b293bfc409c0 - languageName: node - linkType: hard - "fs-monkey@npm:^1.0.4": version: 1.0.4 resolution: "fs-monkey@npm:1.0.4" @@ -24467,14 +21605,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": - version: 4.2.10 - resolution: "graceful-fs@npm:4.2.10" - checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da - languageName: node - linkType: hard - -"graceful-fs@npm:^4.1.5": +"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -29383,16 +26514,7 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^3.1.2, memfs@npm:^3.4.3": - version: 3.5.0 - resolution: "memfs@npm:3.5.0" - dependencies: - fs-monkey: ^1.0.3 - checksum: 8427db6c3644eeb9119b7a74b232d9a6178d018878acce6f05bd89d95e28b1073c9eeb00127131b0613b07a003e2e7b15b482f9004e548fe06a0aba7aa02515c - languageName: node - linkType: hard - -"memfs@npm:^3.2.2": +"memfs@npm:^3.1.2, memfs@npm:^3.2.2, memfs@npm:^3.4.3": version: 3.5.3 resolution: "memfs@npm:3.5.3" dependencies: @@ -30232,16 +27354,7 @@ __metadata: languageName: node linkType: hard -"moment-timezone@npm:*, moment-timezone@npm:^0.5.x": - version: 0.5.40 - resolution: "moment-timezone@npm:0.5.40" - dependencies: - moment: ">= 2.9.0" - checksum: 6f6be5412b37fd937bb143efe74bf65b2c3f115fd967a6dc13b717a126ed6dd198bff6db6e179d69a089e20ac03ce7622c6b5598dd585005195554487a91b528 - languageName: node - linkType: hard - -"moment-timezone@npm:^0.5.43, moment-timezone@npm:~0.5.43": +"moment-timezone@npm:*, moment-timezone@npm:^0.5.43, moment-timezone@npm:^0.5.x, moment-timezone@npm:~0.5.43": version: 0.5.43 resolution: "moment-timezone@npm:0.5.43" dependencies: @@ -30250,7 +27363,7 @@ __metadata: languageName: node linkType: hard -"moment@npm:>= 2.9.0, moment@npm:^2.10.2, moment@npm:^2.29.1, moment@npm:^2.29.4": +"moment@npm:^2.10.2, moment@npm:^2.29.1, moment@npm:^2.29.4": version: 2.29.4 resolution: "moment@npm:2.29.4" checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e @@ -30498,16 +27611,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.1, nanoid@npm:^3.3.4": - version: 3.3.4 - resolution: "nanoid@npm:3.3.4" - bin: - nanoid: bin/nanoid.cjs - checksum: 2fddd6dee994b7676f008d3ffa4ab16035a754f4bb586c61df5a22cf8c8c94017aadd360368f47d653829e0569a92b129979152ff97af23a558331e47e37cd9c - languageName: node - linkType: hard - -"nanoid@npm:^3.3.6": +"nanoid@npm:^3.3.1, nanoid@npm:^3.3.6": version: 3.3.6 resolution: "nanoid@npm:3.3.6" bin: @@ -30770,7 +27874,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:2.6.7, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": +"node-fetch@npm:2.6.7": version: 2.6.7 resolution: "node-fetch@npm:2.6.7" dependencies: @@ -30784,7 +27888,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.11": +"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.11, node-fetch@npm:^2.6.7": version: 2.6.11 resolution: "node-fetch@npm:2.6.11" dependencies: @@ -30928,13 +28032,6 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.6": - version: 2.0.6 - resolution: "node-releases@npm:2.0.6" - checksum: e86a926dc9fbb3b41b4c4a89d998afdf140e20a4e8dbe6c0a807f7b2948b42ea97d7fd3ad4868041487b6e9ee98409829c6e4d84a734a4215dff060a7fbeb4bf - languageName: node - linkType: hard - "node-rsa@npm:^1.1.1": version: 1.1.1 resolution: "node-rsa@npm:1.1.1" @@ -33438,17 +30535,7 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.0, postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.6": - version: 6.0.10 - resolution: "postcss-selector-parser@npm:6.0.10" - dependencies: - cssesc: ^3.0.0 - util-deprecate: ^1.0.2 - checksum: 46afaa60e3d1998bd7adf6caa374baf857cc58d3ff944e29459c9a9e4680a7fe41597bd5b755fc81d7c388357e9bf67c0251d047c640a09f148e13606b8a8608 - languageName: node - linkType: hard - -"postcss-selector-parser@npm:^6.0.4": +"postcss-selector-parser@npm:^6.0.0, postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.6": version: 6.0.13 resolution: "postcss-selector-parser@npm:6.0.13" dependencies: @@ -33549,7 +30636,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.2.15, postcss@npm:~8.4.24": +"postcss@npm:^8.2.15, postcss@npm:^8.3.11, postcss@npm:^8.4.14, postcss@npm:^8.4.23, postcss@npm:~8.4.24": version: 8.4.24 resolution: "postcss@npm:8.4.24" dependencies: @@ -33560,28 +30647,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.3.11, postcss@npm:^8.4.14": - version: 8.4.16 - resolution: "postcss@npm:8.4.16" - dependencies: - nanoid: ^3.3.4 - picocolors: ^1.0.0 - source-map-js: ^1.0.2 - checksum: 10eee25efd77868036403858577da0cefaf2e0905feeaba5770d5438ccdddba3d01cba8063e96b8aac4c6daa0ed413dd5ae0554a433a3c4db38df1d134cffc1f - languageName: node - linkType: hard - -"postcss@npm:^8.4.23": - version: 8.4.23 - resolution: "postcss@npm:8.4.23" - dependencies: - nanoid: ^3.3.6 - picocolors: ^1.0.0 - source-map-js: ^1.0.2 - checksum: 8bb9d1b2ea6e694f8987d4f18c94617971b2b8d141602725fedcc2222fdc413b776a6e1b969a25d627d7b2681ca5aabb56f59e727ef94072e1b6ac8412105a2f - languageName: node - linkType: hard - "postis@npm:^2.2.0": version: 2.2.0 resolution: "postis@npm:2.2.0" @@ -34151,15 +31216,6 @@ __metadata: languageName: node linkType: hard -"qs@npm:6.10.3": - version: 6.10.3 - resolution: "qs@npm:6.10.3" - dependencies: - side-channel: ^1.0.4 - checksum: 0fac5e6c7191d0295a96d0e83c851aeb015df7e990e4d3b093897d3ac6c94e555dbd0a599739c84d7fa46d7fee282d94ba76943983935cf33bba6769539b8019 - languageName: node - linkType: hard - "qs@npm:6.11.0, qs@npm:^6.10.0, qs@npm:^6.10.3, qs@npm:^6.7.0, qs@npm:^6.9.4, qs@npm:^6.9.6": version: 6.11.0 resolution: "qs@npm:6.11.0" @@ -34455,19 +31511,7 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.1, raw-body@npm:^2.2.0": - version: 2.5.1 - resolution: "raw-body@npm:2.5.1" - dependencies: - bytes: 3.1.2 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - checksum: 5362adff1575d691bb3f75998803a0ffed8c64eabeaa06e54b4ada25a0cd1b2ae7f4f5ec46565d1bec337e08b5ac90c76eaa0758de6f72a633f025d754dec29e - languageName: node - linkType: hard - -"raw-body@npm:2.5.2": +"raw-body@npm:2.5.2, raw-body@npm:^2.2.0": version: 2.5.2 resolution: "raw-body@npm:2.5.2" dependencies: @@ -34491,20 +31535,7 @@ __metadata: languageName: node linkType: hard -"rc-scrollbars@npm:^1.1.5": - version: 1.1.5 - resolution: "rc-scrollbars@npm:1.1.5" - dependencies: - dom-css: ^2.1.0 - raf: ^3.4.1 - peerDependencies: - react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - checksum: d2b3710868f70ed7c154768bd03825b543d7804ef1c95086ee9cd851f66a046e1b4dbc9e153f9797e1706a11d263db4a645eff485d16bf1c095754e6e771f5ad - languageName: node - linkType: hard - -"rc-scrollbars@npm:^1.1.6": +"rc-scrollbars@npm:^1.1.5, rc-scrollbars@npm:^1.1.6": version: 1.1.6 resolution: "rc-scrollbars@npm:1.1.6" dependencies: @@ -35988,21 +33019,7 @@ __metadata: languageName: unknown linkType: soft -"rollup@npm:^3.2.5": - version: 3.21.3 - resolution: "rollup@npm:3.21.3" - dependencies: - fsevents: ~2.3.2 - dependenciesMeta: - fsevents: - optional: true - bin: - rollup: dist/bin/rollup - checksum: 430c0c66f1480586ccea77b190524efb6a3af3da5308c7bc77eca3160c3f387c9a56ebb3eef5bfb24f683764bdfdd3dafdea175d215e4b875bbde752619bec12 - languageName: node - linkType: hard - -"rollup@npm:^3.21.0": +"rollup@npm:^3.2.5, rollup@npm:^3.21.0": version: 3.23.0 resolution: "rollup@npm:3.23.0" dependencies: @@ -36310,18 +33327,7 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.0, schema-utils@npm:^3.1.1": - version: 3.1.1 - resolution: "schema-utils@npm:3.1.1" - dependencies: - "@types/json-schema": ^7.0.8 - ajv: ^6.12.5 - ajv-keywords: ^3.5.2 - checksum: fb73f3d759d43ba033c877628fe9751620a26879f6301d3dbeeb48cf2a65baec5cdf99da65d1bf3b4ff5444b2e59cbe4f81c2456b5e0d2ba7d7fd4aed5da29ce - languageName: node - linkType: hard - -"schema-utils@npm:^3.2.0": +"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0": version: 3.3.0 resolution: "schema-utils@npm:3.3.0" dependencies: @@ -36459,18 +33465,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.x, semver@npm:^7.2, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": - version: 7.3.7 - resolution: "semver@npm:7.3.7" - dependencies: - lru-cache: ^6.0.0 - bin: - semver: bin/semver.js - checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 - languageName: node - linkType: hard - -"semver@npm:^7.5.2": +"semver@npm:7.x, semver@npm:^7.2, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2": version: 7.5.2 resolution: "semver@npm:7.5.2" dependencies: @@ -36531,7 +33526,7 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:6.0.0, serialize-javascript@npm:^6.0.0": +"serialize-javascript@npm:6.0.0": version: 6.0.0 resolution: "serialize-javascript@npm:6.0.0" dependencies: @@ -38552,28 +35547,6 @@ __metadata: languageName: node linkType: hard -"terser-webpack-plugin@npm:^5.1.3": - version: 5.3.5 - resolution: "terser-webpack-plugin@npm:5.3.5" - dependencies: - "@jridgewell/trace-mapping": ^0.3.14 - jest-worker: ^27.4.5 - schema-utils: ^3.1.1 - serialize-javascript: ^6.0.0 - terser: ^5.14.1 - peerDependencies: - webpack: ^5.1.0 - peerDependenciesMeta: - "@swc/core": - optional: true - esbuild: - optional: true - uglify-js: - optional: true - checksum: 611c7b38d6fa0213dc03f48da9efe29c7edd098fc128a64905f7c9b61af8e7c36c13113d46b50be19ee2b8378442f4e1b8b4ddac9bba2cb73499ed32fc0e18f4 - languageName: node - linkType: hard - "terser@npm:^4.1.2, terser@npm:^4.6.3": version: 4.8.0 resolution: "terser@npm:4.8.0" @@ -38587,7 +35560,7 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.10.0, terser@npm:^5.16.8": +"terser@npm:^5.10.0, terser@npm:^5.16.8, terser@npm:^5.3.4": version: 5.18.0 resolution: "terser@npm:5.18.0" dependencies: @@ -38601,20 +35574,6 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.14.1, terser@npm:^5.3.4": - version: 5.14.2 - resolution: "terser@npm:5.14.2" - dependencies: - "@jridgewell/source-map": ^0.3.2 - acorn: ^8.5.0 - commander: ^2.20.0 - source-map-support: ~0.5.20 - bin: - terser: bin/terser - checksum: cabb50a640d6c2cfb351e4f43dc7bf7436f649755bb83eb78b2cacda426d5e0979bd44e6f92d713f3ca0f0866e322739b9ced888ebbce6508ad872d08de74fcc - languageName: node - linkType: hard - "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -38774,20 +35733,13 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.0.6": +"tiny-invariant@npm:^1.0.6, tiny-invariant@npm:^1.2.0": version: 1.3.1 resolution: "tiny-invariant@npm:1.3.1" checksum: 872dbd1ff20a21303a2fd20ce3a15602cfa7fcf9b228bd694a52e2938224313b5385a1078cb667ed7375d1612194feaca81c4ecbe93121ca1baebe344de4f84c languageName: node linkType: hard -"tiny-invariant@npm:^1.2.0": - version: 1.2.0 - resolution: "tiny-invariant@npm:1.2.0" - checksum: e09a718a7c4a499ba592cdac61f015d87427a0867ca07f50c11fd9b623f90cdba18937b515d4a5e4f43dac92370498d7bdaee0d0e7a377a61095e02c4a92eade - languageName: node - linkType: hard - "tinykeys@npm:^1.4.0": version: 1.4.0 resolution: "tinykeys@npm:1.4.0" @@ -39210,14 +36162,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0": - version: 2.5.0 - resolution: "tslib@npm:2.5.0" - checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 - languageName: node - linkType: hard - -"tslib@npm:^2.5.3": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.3": version: 2.5.3 resolution: "tslib@npm:2.5.3" checksum: 88902b309afaf83259131c1e13da1dceb0ad1682a213143a1346a649143924d78cf3760c448b84d796938fd76127183894f8d85cbb3bf9c4fddbfcc140c0003c @@ -39997,20 +36942,6 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.5": - version: 1.0.9 - resolution: "update-browserslist-db@npm:1.0.9" - dependencies: - escalade: ^3.1.1 - picocolors: ^1.0.0 - peerDependencies: - browserslist: ">= 4.21.0" - bin: - browserslist-lint: cli.js - checksum: f625899b236f6a4d7f62b56be1b8da230c5563d1fef84d3ef148f2e1a3f11a5a4b3be4fd7e3703e51274c116194017775b10afb4de09eb2c0d09d36b90f1f578 - languageName: node - linkType: hard - "update-check@npm:1.5.2": version: 1.5.2 resolution: "update-check@npm:1.5.2" @@ -40573,7 +37504,7 @@ __metadata: languageName: node linkType: hard -"vm2@npm:^3.9.19": +"vm2@npm:^3.9.19, vm2@npm:^3.9.8": version: 3.9.19 resolution: "vm2@npm:3.9.19" dependencies: @@ -40585,18 +37516,6 @@ __metadata: languageName: node linkType: hard -"vm2@npm:^3.9.8": - version: 3.9.17 - resolution: "vm2@npm:3.9.17" - dependencies: - acorn: ^8.7.0 - acorn-walk: ^8.2.0 - bin: - vm2: bin/vm2 - checksum: 9a03740a40ab2be5e3348a95fb31512da1a3c85318febb07e5299fa103ff05bcd7b6f458211fa38a1281dc27beccd04ff90355fc1d34fe2ee6ca10d0bb8c6f35 - languageName: node - linkType: hard - "void-elements@npm:3.1.0": version: 3.1.0 resolution: "void-elements@npm:3.1.0" @@ -41030,7 +37949,7 @@ __metadata: languageName: node linkType: hard -"webpack@npm:>=4.0.0 <6.0.0": +"webpack@npm:>=4.0.0 <6.0.0, webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5.9.0": version: 5.88.0 resolution: "webpack@npm:5.88.0" dependencies: @@ -41067,80 +37986,6 @@ __metadata: languageName: node linkType: hard -"webpack@npm:>=4.43.0 <6.0.0": - version: 5.74.0 - resolution: "webpack@npm:5.74.0" - dependencies: - "@types/eslint-scope": ^3.7.3 - "@types/estree": ^0.0.51 - "@webassemblyjs/ast": 1.11.1 - "@webassemblyjs/wasm-edit": 1.11.1 - "@webassemblyjs/wasm-parser": 1.11.1 - acorn: ^8.7.1 - acorn-import-assertions: ^1.7.6 - browserslist: ^4.14.5 - chrome-trace-event: ^1.0.2 - enhanced-resolve: ^5.10.0 - es-module-lexer: ^0.9.0 - eslint-scope: 5.1.1 - events: ^3.2.0 - glob-to-regexp: ^0.4.1 - graceful-fs: ^4.2.9 - json-parse-even-better-errors: ^2.3.1 - loader-runner: ^4.2.0 - mime-types: ^2.1.27 - neo-async: ^2.6.2 - schema-utils: ^3.1.0 - tapable: ^2.1.1 - terser-webpack-plugin: ^5.1.3 - watchpack: ^2.4.0 - webpack-sources: ^3.2.3 - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: 320c41369a75051b19e18c63f408b3dcc481852e992f83d311771c5ec0f05f2946385e8ebef62030cf3587f0a3d2f12779ffdb191569a966847289ba7313f946 - languageName: node - linkType: hard - -"webpack@npm:^5.9.0": - version: 5.87.0 - resolution: "webpack@npm:5.87.0" - dependencies: - "@types/eslint-scope": ^3.7.3 - "@types/estree": ^1.0.0 - "@webassemblyjs/ast": ^1.11.5 - "@webassemblyjs/wasm-edit": ^1.11.5 - "@webassemblyjs/wasm-parser": ^1.11.5 - acorn: ^8.7.1 - acorn-import-assertions: ^1.9.0 - browserslist: ^4.14.5 - chrome-trace-event: ^1.0.2 - enhanced-resolve: ^5.15.0 - es-module-lexer: ^1.2.1 - eslint-scope: 5.1.1 - events: ^3.2.0 - glob-to-regexp: ^0.4.1 - graceful-fs: ^4.2.9 - json-parse-even-better-errors: ^2.3.1 - loader-runner: ^4.2.0 - mime-types: ^2.1.27 - neo-async: ^2.6.2 - schema-utils: ^3.2.0 - tapable: ^2.1.1 - terser-webpack-plugin: ^5.3.7 - watchpack: ^2.4.0 - webpack-sources: ^3.2.3 - peerDependenciesMeta: - webpack-cli: - optional: true - bin: - webpack: bin/webpack.js - checksum: b7d0e390f9d30627e303d54b17cb87b62f49ecffe2d35481f830679904993bae208e23748ffe0e6091b6dd4810562b2f2e88bb0f23b96515d74fb1e3c2898210 - languageName: node - linkType: hard - "websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": version: 0.7.4 resolution: "websocket-driver@npm:0.7.4" @@ -41787,7 +38632,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.0.0, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": +"yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c @@ -41840,22 +38685,7 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.3.1": - version: 17.5.1 - resolution: "yargs@npm:17.5.1" - dependencies: - cliui: ^7.0.2 - escalade: ^3.1.1 - get-caller-file: ^2.0.5 - require-directory: ^2.1.1 - string-width: ^4.2.3 - y18n: ^5.0.5 - yargs-parser: ^21.0.0 - checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde - languageName: node - linkType: hard - -"yargs@npm:^17.7.1": +"yargs@npm:^17.3.1, yargs@npm:^17.7.1": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: From bcd027da67d3a96c8bc8be1f552786cc75dfdafa Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Fri, 7 Jul 2023 14:51:22 -0300 Subject: [PATCH 72/79] test: add unit tests for feature preview hooks (#29755) --- apps/meteor/.mocharc.client.js | 1 + .../client/hooks/useFeaturePreview.spec.tsx | 177 +++++ apps/meteor/client/hooks/useFeaturePreview.ts | 2 +- .../hooks/useFeaturePreviewList.spec.tsx | 89 +++ apps/meteor/jest.client.config.ts | 14 + apps/meteor/package.json | 2 + yarn.lock | 689 +++++++++++++++++- 7 files changed, 972 insertions(+), 2 deletions(-) create mode 100644 apps/meteor/client/hooks/useFeaturePreview.spec.tsx create mode 100644 apps/meteor/client/hooks/useFeaturePreviewList.spec.tsx create mode 100644 apps/meteor/jest.client.config.ts diff --git a/apps/meteor/.mocharc.client.js b/apps/meteor/.mocharc.client.js index 50ef8e2139337..7eff846f683cb 100644 --- a/apps/meteor/.mocharc.client.js +++ b/apps/meteor/.mocharc.client.js @@ -38,4 +38,5 @@ module.exports = { 'tests/unit/lib/**/*.tests.ts', 'tests/unit/client/**/*.test.ts', ], + exclude: ['client/hooks/*.spec.{ts,tsx}'], }; diff --git a/apps/meteor/client/hooks/useFeaturePreview.spec.tsx b/apps/meteor/client/hooks/useFeaturePreview.spec.tsx new file mode 100644 index 0000000000000..0d0cbc5eca524 --- /dev/null +++ b/apps/meteor/client/hooks/useFeaturePreview.spec.tsx @@ -0,0 +1,177 @@ +/* eslint-disable react/no-multi-comp */ +import type { ISetting } from '@rocket.chat/core-typings'; +import type { LoginService } from '@rocket.chat/ui-contexts'; +import { SettingsContext, UserContext } from '@rocket.chat/ui-contexts'; +import { renderHook } from '@testing-library/react-hooks'; +import type { ObjectId } from 'mongodb'; +import type { ContextType } from 'react'; +import React from 'react'; + +import { useFeaturePreview } from './useFeaturePreview'; + +const userContextValue: ContextType<typeof UserContext> = { + userId: 'john.doe', + user: { + _id: 'john.doe', + username: 'john.doe', + name: 'John Doe', + createdAt: new Date(), + active: true, + _updatedAt: new Date(), + roles: ['admin'], + type: 'user', + }, + queryPreference: <T,>(pref: string | ObjectId, defaultValue: T) => [ + () => () => undefined, + () => (typeof pref === 'string' ? undefined : defaultValue), + ], + querySubscriptions: () => [() => () => undefined, () => []], + querySubscription: () => [() => () => undefined, () => undefined], + queryRoom: () => [() => () => undefined, () => undefined], + + queryAllServices: () => [() => (): void => undefined, (): LoginService[] => []], + loginWithService: () => () => Promise.reject('loginWithService not implemented'), + loginWithPassword: async () => Promise.reject('loginWithPassword not implemented'), + loginWithToken: async () => Promise.reject('loginWithToken not implemented'), + logout: () => Promise.resolve(), +}; + +const settingContextValue: ContextType<typeof SettingsContext> = { + hasPrivateAccess: true, + isLoading: false, + querySetting: (_id: string) => [() => () => undefined, () => undefined], + querySettings: () => [() => () => undefined, () => []], + dispatch: async () => undefined, +}; + +it('should return false if featurePreviewEnabled is false', () => { + const { result } = renderHook( + () => { + return useFeaturePreview('quickReactions'); + }, + { + wrapper: ({ children }) => ( + <MockedSettingsContext + settings={{ + Accounts_AllowFeaturePreview: false, + }} + > + <MockedUserContext userPreferences={{}}>{children}</MockedUserContext> + </MockedSettingsContext> + ), + }, + ); + + expect(result.all[0]).toBe(false); +}); + +it('should return false if featurePreviewEnabled is true but feature is not in userPreferences', () => { + const { result } = renderHook( + () => { + return useFeaturePreview('quickReactions'); + }, + { + wrapper: ({ children }) => ( + <MockedSettingsContext + settings={{ + Accounts_AllowFeaturePreview: false, + }} + > + <MockedUserContext + userPreferences={{ + featuresPreview: [ + { + name: 'quickReactions', + value: true, + }, + ], + }} + > + {children} + </MockedUserContext> + </MockedSettingsContext> + ), + }, + ); + + expect(result.all[0]).toBe(false); +}); + +it('should return true if featurePreviewEnabled is true and feature is in userPreferences', () => { + const { result } = renderHook( + () => { + return useFeaturePreview('quickReactions'); + }, + { + wrapper: ({ children }) => ( + <MockedSettingsContext + settings={{ + Accounts_AllowFeaturePreview: true, + }} + > + <MockedUserContext + userPreferences={{ + featuresPreview: [ + { + name: 'quickReactions', + value: true, + }, + ], + }} + > + {children} + </MockedUserContext> + </MockedSettingsContext> + ), + }, + ); + + expect(result.all[0]).toBe(true); +}); + +const createUserContextValue = ({ userPreferences }: { userPreferences?: Record<string, unknown> }): ContextType<typeof UserContext> => { + return { + ...userContextValue, + ...(userPreferences && { queryPreference: (id) => [() => () => undefined, () => userPreferences[id as unknown as string] as any] }), + }; +}; + +const createSettingContextValue = ({ settings }: { settings?: Record<string, ISetting['value']> }): ContextType<typeof SettingsContext> => { + const cache = new Map<string, ISetting['value']>(); + + return { + ...settingContextValue, + ...(settings && { + querySetting: (_id: string) => [ + () => () => undefined, + () => { + if (cache.has(_id)) { + return cache.get(_id) as any; + } + cache.set(_id, { value: settings[_id] } as any); + return cache.get(_id) as any; + }, + ], + }), + }; +}; + +export const MockedSettingsContext = ({ + settings, + children, +}: { + children: React.ReactNode; + settings?: Record<string, ISetting['value']>; +}) => { + return <SettingsContext.Provider value={createSettingContextValue({ settings })}>{children}</SettingsContext.Provider>; +}; + +export const MockedUserContext = ({ + userPreferences, + children, +}: { + children: React.ReactNode; + userPreferences?: Record<string, unknown>; +}) => { + return <UserContext.Provider value={createUserContextValue({ userPreferences })}>{children}</UserContext.Provider>; +}; diff --git a/apps/meteor/client/hooks/useFeaturePreview.ts b/apps/meteor/client/hooks/useFeaturePreview.ts index fcfc4ef85c826..2dcc68262b096 100644 --- a/apps/meteor/client/hooks/useFeaturePreview.ts +++ b/apps/meteor/client/hooks/useFeaturePreview.ts @@ -4,6 +4,7 @@ import type { FeaturesAvailable, FeaturePreviewProps } from './useFeaturePreview export const useFeaturePreview = (featureName: FeaturesAvailable) => { const featurePreviewEnabled = useSetting('Accounts_AllowFeaturePreview'); + const features = useUserPreference<FeaturePreviewProps[]>('featuresPreview'); const currentFeature = features?.find((feature) => feature.name === featureName); @@ -13,7 +14,6 @@ export const useFeaturePreview = (featureName: FeaturesAvailable) => { } if (!currentFeature) { - console.error(`Feature ${featureName} not found`); return false; } diff --git a/apps/meteor/client/hooks/useFeaturePreviewList.spec.tsx b/apps/meteor/client/hooks/useFeaturePreviewList.spec.tsx new file mode 100644 index 0000000000000..2fbcdb4cb6d20 --- /dev/null +++ b/apps/meteor/client/hooks/useFeaturePreviewList.spec.tsx @@ -0,0 +1,89 @@ +import { renderHook } from '@testing-library/react-hooks'; +import React from 'react'; + +import { MockedSettingsContext, MockedUserContext } from './useFeaturePreview.spec'; +import { useFeaturePreviewList, defaultFeaturesPreview } from './useFeaturePreviewList'; + +it('should return the number of unseen features and Accounts_AllowFeaturePreview enabled ', () => { + const { result } = renderHook( + () => { + return useFeaturePreviewList(); + }, + { + wrapper: ({ children }) => ( + <MockedSettingsContext + settings={{ + Accounts_AllowFeaturePreview: true, + }} + > + <MockedUserContext userPreferences={{}}>{children}</MockedUserContext> + </MockedSettingsContext> + ), + }, + ); + + expect(result.all[0]).toEqual( + expect.objectContaining({ + featurePreviewEnabled: true, + unseenFeatures: defaultFeaturesPreview.length, + }), + ); +}); + +it('should return the number of unseen features and Accounts_AllowFeaturePreview disabled ', () => { + const { result } = renderHook( + () => { + return useFeaturePreviewList(); + }, + { + wrapper: ({ children }) => ( + <MockedSettingsContext + settings={{ + Accounts_AllowFeaturePreview: false, + }} + > + <MockedUserContext userPreferences={{}}>{children}</MockedUserContext> + </MockedSettingsContext> + ), + }, + ); + + expect(result.all[0]).toEqual( + expect.objectContaining({ + featurePreviewEnabled: false, + unseenFeatures: 0, + }), + ); +}); + +it('should return 0 unseen features', () => { + const { result } = renderHook( + () => { + return useFeaturePreviewList(); + }, + { + wrapper: ({ children }) => ( + <MockedSettingsContext + settings={{ + Accounts_AllowFeaturePreview: true, + }} + > + <MockedUserContext + userPreferences={{ + featuresPreview: defaultFeaturesPreview, + }} + > + {children} + </MockedUserContext> + </MockedSettingsContext> + ), + }, + ); + + expect(result.all[0]).toEqual( + expect.objectContaining({ + featurePreviewEnabled: true, + unseenFeatures: 0, + }), + ); +}); diff --git a/apps/meteor/jest.client.config.ts b/apps/meteor/jest.client.config.ts new file mode 100644 index 0000000000000..9523d726d9e97 --- /dev/null +++ b/apps/meteor/jest.client.config.ts @@ -0,0 +1,14 @@ +export default { + errorOnDeprecated: true, + + testEnvironment: 'jsdom', + modulePathIgnorePatterns: ['<rootDir>/dist/'], + testMatch: ['<rootDir>/client/hooks/**.spec.[jt]s?(x)', './client/hooks/**.spec.ts', '/client/hooks/**.spec.ts'], + transform: { + '^.+\\.(t|j)sx?$': '@swc/jest', + }, + moduleNameMapper: { + '\\.css$': 'identity-obj-proxy', + '^react($|/.+)': '<rootDir>/node_modules/react$1', + }, +}; diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 4f2422cb3fedc..0370b5327b4f3 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -81,6 +81,7 @@ "@storybook/react": "~6.5.16", "@storybook/testing-library": "0.0.13", "@swc/core": "^1.3.66", + "@swc/jest": "^0.2.26", "@tanstack/react-query-devtools": "^4.19.1", "@testing-library/react": "~12.1.5", "@testing-library/react-hooks": "^8.0.1", @@ -177,6 +178,7 @@ "eslint-plugin-you-dont-need-lodash-underscore": "~6.12.0", "fast-glob": "^3.2.12", "i18next": "^20.6.1", + "jest": "^29.6.1", "jsdom-global": "^3.0.2", "mocha": "^9.2.2", "nyc": "^15.1.0", diff --git a/yarn.lock b/yarn.lock index 539e35e375fad..f855f365525c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3794,6 +3794,20 @@ __metadata: languageName: node linkType: hard +"@jest/console@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/console@npm:29.6.1" + dependencies: + "@jest/types": ^29.6.1 + "@types/node": "*" + chalk: ^4.0.0 + jest-message-util: ^29.6.1 + jest-util: ^29.6.1 + slash: ^3.0.0 + checksum: d0ab23a00947bfb4bff8c0a7e5a7afd16519de16dde3fe7e77b9f13e794c6df7043ecf7fcdde66ac0d2b5fb3262e9cab3d92eaf61f89a12d3b8e3602e06a9902 + languageName: node + linkType: hard + "@jest/core@npm:^29.5.0": version: 29.5.0 resolution: "@jest/core@npm:29.5.0" @@ -3835,6 +3849,47 @@ __metadata: languageName: node linkType: hard +"@jest/core@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/core@npm:29.6.1" + dependencies: + "@jest/console": ^29.6.1 + "@jest/reporters": ^29.6.1 + "@jest/test-result": ^29.6.1 + "@jest/transform": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + ci-info: ^3.2.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + jest-changed-files: ^29.5.0 + jest-config: ^29.6.1 + jest-haste-map: ^29.6.1 + jest-message-util: ^29.6.1 + jest-regex-util: ^29.4.3 + jest-resolve: ^29.6.1 + jest-resolve-dependencies: ^29.6.1 + jest-runner: ^29.6.1 + jest-runtime: ^29.6.1 + jest-snapshot: ^29.6.1 + jest-util: ^29.6.1 + jest-validate: ^29.6.1 + jest-watcher: ^29.6.1 + micromatch: ^4.0.4 + pretty-format: ^29.6.1 + slash: ^3.0.0 + strip-ansi: ^6.0.0 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: 736dcc90c6c58dd9e1d2da122103b851187719ce3b3d4167689c63e68252632cd817712955b52ddaa648eba9c6f98f86cd58677325f0db4185f76899c64d7dac + languageName: node + linkType: hard + "@jest/create-cache-key-function@npm:^27.4.2": version: 27.5.1 resolution: "@jest/create-cache-key-function@npm:27.5.1" @@ -3856,6 +3911,18 @@ __metadata: languageName: node linkType: hard +"@jest/environment@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/environment@npm:29.6.1" + dependencies: + "@jest/fake-timers": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/node": "*" + jest-mock: ^29.6.1 + checksum: fb671f91f27e7aa1ba04983ef87a83f0794a597aba0a57d08cbb1fcb484c2aedc2201e99f85fafe27aec9be78af6f2d1d7e6ea88267938992a1d0f9d4615f5b2 + languageName: node + linkType: hard + "@jest/expect-utils@npm:^29.5.0": version: 29.5.0 resolution: "@jest/expect-utils@npm:29.5.0" @@ -3865,6 +3932,15 @@ __metadata: languageName: node linkType: hard +"@jest/expect-utils@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/expect-utils@npm:29.6.1" + dependencies: + jest-get-type: ^29.4.3 + checksum: 037ee017eca62f7b45e1465fb5c6f9e92d5709a9ac716b8bff0bd294240a54de734e8f968fb69309cc4aef6c83b9552d5a821f3b18371af394bf04783859d706 + languageName: node + linkType: hard + "@jest/expect@npm:^29.5.0": version: 29.5.0 resolution: "@jest/expect@npm:29.5.0" @@ -3875,6 +3951,16 @@ __metadata: languageName: node linkType: hard +"@jest/expect@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/expect@npm:29.6.1" + dependencies: + expect: ^29.6.1 + jest-snapshot: ^29.6.1 + checksum: 5c56977b3cc8489744d97d9dc2dcb196c1dfecc83a058a7ef0fd4f63d68cf120a23d27669272d1e1b184fb4337b85e4ac1fc7f886e3988fdf243d42d73973eac + languageName: node + linkType: hard + "@jest/fake-timers@npm:^29.5.0": version: 29.5.0 resolution: "@jest/fake-timers@npm:29.5.0" @@ -3889,6 +3975,20 @@ __metadata: languageName: node linkType: hard +"@jest/fake-timers@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/fake-timers@npm:29.6.1" + dependencies: + "@jest/types": ^29.6.1 + "@sinonjs/fake-timers": ^10.0.2 + "@types/node": "*" + jest-message-util: ^29.6.1 + jest-mock: ^29.6.1 + jest-util: ^29.6.1 + checksum: 86991276944b7d6c2ada3703a272517f5f8f2f4e2af1fe26065f6db1dac4dc6299729a88c46bcb781dcc1b20504c1d4bbd8119fd8a0838ac81a9a4b5d2c8e429 + languageName: node + linkType: hard + "@jest/globals@npm:^29.5.0": version: 29.5.0 resolution: "@jest/globals@npm:29.5.0" @@ -3901,6 +4001,18 @@ __metadata: languageName: node linkType: hard +"@jest/globals@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/globals@npm:29.6.1" + dependencies: + "@jest/environment": ^29.6.1 + "@jest/expect": ^29.6.1 + "@jest/types": ^29.6.1 + jest-mock: ^29.6.1 + checksum: fcca0b970a8b4894a1cdff0f500a86b45609e72c0a4319875e9504237b839df1a46c44d2f1362c6d87fdc7a05928edcc4b5a3751c9e6648dd70a761cdab64c94 + languageName: node + linkType: hard + "@jest/reporters@npm:^29.5.0": version: 29.5.0 resolution: "@jest/reporters@npm:29.5.0" @@ -3938,6 +4050,43 @@ __metadata: languageName: node linkType: hard +"@jest/reporters@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/reporters@npm:29.6.1" + dependencies: + "@bcoe/v8-coverage": ^0.2.3 + "@jest/console": ^29.6.1 + "@jest/test-result": ^29.6.1 + "@jest/transform": ^29.6.1 + "@jest/types": ^29.6.1 + "@jridgewell/trace-mapping": ^0.3.18 + "@types/node": "*" + chalk: ^4.0.0 + collect-v8-coverage: ^1.0.0 + exit: ^0.1.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + istanbul-lib-coverage: ^3.0.0 + istanbul-lib-instrument: ^5.1.0 + istanbul-lib-report: ^3.0.0 + istanbul-lib-source-maps: ^4.0.0 + istanbul-reports: ^3.1.3 + jest-message-util: ^29.6.1 + jest-util: ^29.6.1 + jest-worker: ^29.6.1 + slash: ^3.0.0 + string-length: ^4.0.1 + strip-ansi: ^6.0.0 + v8-to-istanbul: ^9.0.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: b7dae415f3f6342b4db2671261bbee29af20a829f42135316c3dd548b9ef85290c9bb64a0e3aec4a55486596be1257ac8216a0f8d9794acd43f8b8fb686fc7e3 + languageName: node + linkType: hard + "@jest/schemas@npm:^28.1.3": version: 28.1.3 resolution: "@jest/schemas@npm:28.1.3" @@ -3956,6 +4105,15 @@ __metadata: languageName: node linkType: hard +"@jest/schemas@npm:^29.6.0": + version: 29.6.0 + resolution: "@jest/schemas@npm:29.6.0" + dependencies: + "@sinclair/typebox": ^0.27.8 + checksum: c00511c69cf89138a7d974404d3a5060af375b5a52b9c87215d91873129b382ca11c1ff25bd6d605951404bb381ddce5f8091004a61e76457da35db1f5c51365 + languageName: node + linkType: hard + "@jest/source-map@npm:^29.4.3": version: 29.4.3 resolution: "@jest/source-map@npm:29.4.3" @@ -3967,6 +4125,17 @@ __metadata: languageName: node linkType: hard +"@jest/source-map@npm:^29.6.0": + version: 29.6.0 + resolution: "@jest/source-map@npm:29.6.0" + dependencies: + "@jridgewell/trace-mapping": ^0.3.18 + callsites: ^3.0.0 + graceful-fs: ^4.2.9 + checksum: 9c6c40387410bb70b2fae8124287fc28f6bdd1b2d7f24348e8611e1bb638b404518228a4ce64a582365b589c536ae8e7ebab0126cef59a87874b71061d19783b + languageName: node + linkType: hard + "@jest/test-result@npm:^29.5.0": version: 29.5.0 resolution: "@jest/test-result@npm:29.5.0" @@ -3979,6 +4148,18 @@ __metadata: languageName: node linkType: hard +"@jest/test-result@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/test-result@npm:29.6.1" + dependencies: + "@jest/console": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/istanbul-lib-coverage": ^2.0.0 + collect-v8-coverage: ^1.0.0 + checksum: 9397a3a3410c5df564e79297b1be4fe33807a6157a017a1f74b54a6ef14de1530f12b922299e822e66a82c53269da16661772bffde3d883a78c5eefd2cd6d1cc + languageName: node + linkType: hard + "@jest/test-sequencer@npm:^29.5.0": version: 29.5.0 resolution: "@jest/test-sequencer@npm:29.5.0" @@ -3991,6 +4172,18 @@ __metadata: languageName: node linkType: hard +"@jest/test-sequencer@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/test-sequencer@npm:29.6.1" + dependencies: + "@jest/test-result": ^29.6.1 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.6.1 + slash: ^3.0.0 + checksum: f3437178b5dca0401ed2e990d8b69161442351856d56f5725e009a487f5232b51039f8829673884b9bea61c861120d08a53a36432f4a4b8aab38915a68f7000d + languageName: node + linkType: hard + "@jest/transform@npm:^26.6.2": version: 26.6.2 resolution: "@jest/transform@npm:26.6.2" @@ -4037,6 +4230,29 @@ __metadata: languageName: node linkType: hard +"@jest/transform@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/transform@npm:29.6.1" + dependencies: + "@babel/core": ^7.11.6 + "@jest/types": ^29.6.1 + "@jridgewell/trace-mapping": ^0.3.18 + babel-plugin-istanbul: ^6.1.1 + chalk: ^4.0.0 + convert-source-map: ^2.0.0 + fast-json-stable-stringify: ^2.1.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.6.1 + jest-regex-util: ^29.4.3 + jest-util: ^29.6.1 + micromatch: ^4.0.4 + pirates: ^4.0.4 + slash: ^3.0.0 + write-file-atomic: ^4.0.2 + checksum: 1635cd66e4b3dbba0689ecefabc6137301756c9c12d1d23e25124dd0dd9b4a6a38653d51e825e90f74faa022152ac1eaf200591fb50417aa7e1f7d1d1c2bc11d + languageName: node + linkType: hard + "@jest/types@npm:^26.6.2": version: 26.6.2 resolution: "@jest/types@npm:26.6.2" @@ -4077,6 +4293,20 @@ __metadata: languageName: node linkType: hard +"@jest/types@npm:^29.6.1": + version: 29.6.1 + resolution: "@jest/types@npm:29.6.1" + dependencies: + "@jest/schemas": ^29.6.0 + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^17.0.8 + chalk: ^4.0.0 + checksum: 89fc1ccf71a84fe0da643e0675b1cfe6a6f19ea72e935b2ab1dbdb56ec547e94433fb59b3536d3832a6e156c077865b7176fe9dae707dab9c3d2f9405ba6233c + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.0, @jridgewell/gen-mapping@npm:^0.3.2": version: 0.3.2 resolution: "@jridgewell/gen-mapping@npm:0.3.2" @@ -4129,7 +4359,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.18 resolution: "@jridgewell/trace-mapping@npm:0.3.18" dependencies: @@ -7940,6 +8170,7 @@ __metadata: "@storybook/react": ~6.5.16 "@storybook/testing-library": 0.0.13 "@swc/core": ^1.3.66 + "@swc/jest": ^0.2.26 "@tanstack/react-query": ^4.16.1 "@tanstack/react-query-devtools": ^4.19.1 "@testing-library/react": ~12.1.5 @@ -8108,6 +8339,7 @@ __metadata: imap: ^0.8.19 ip-range-check: ^0.2.0 is-svg: ^4.3.2 + jest: ^29.6.1 jquery: ^3.6.0 jschardet: ^3.0.0 jsdom: ^16.7.0 @@ -8974,6 +9206,13 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.27.8": + version: 0.27.8 + resolution: "@sinclair/typebox@npm:0.27.8" + checksum: 00bd7362a3439021aa1ea51b0e0d0a0e8ca1351a3d54c606b115fdcc49b51b16db6e5f43b4fe7a28c38688523e22a94d49dd31168868b655f0d4d50f032d07a1 + languageName: node + linkType: hard + "@sindresorhus/is@npm:^0.7.0": version: 0.7.0 resolution: "@sindresorhus/is@npm:0.7.0" @@ -13979,6 +14218,23 @@ __metadata: languageName: node linkType: hard +"babel-jest@npm:^29.6.1": + version: 29.6.1 + resolution: "babel-jest@npm:29.6.1" + dependencies: + "@jest/transform": ^29.6.1 + "@types/babel__core": ^7.1.14 + babel-plugin-istanbul: ^6.1.1 + babel-preset-jest: ^29.5.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + slash: ^3.0.0 + peerDependencies: + "@babel/core": ^7.8.0 + checksum: bc46cfba468edde91f34a8292501d4448a39fab72d80d7d95f4349feb114fa21becb01def007d6166de7933ab9633bf5b5e1b72ba6ffeaa991f7abf014a2f61d + languageName: node + linkType: hard + "babel-loader@npm:^8.0.0, babel-loader@npm:^8.3.0": version: 8.3.0 resolution: "babel-loader@npm:8.3.0" @@ -19724,6 +19980,20 @@ __metadata: languageName: node linkType: hard +"expect@npm:^29.6.1": + version: 29.6.1 + resolution: "expect@npm:29.6.1" + dependencies: + "@jest/expect-utils": ^29.6.1 + "@types/node": "*" + jest-get-type: ^29.4.3 + jest-matcher-utils: ^29.6.1 + jest-message-util: ^29.6.1 + jest-util: ^29.6.1 + checksum: 4e712e52c90f6c54e748fd2876be33c43ada6a59088ddf6a1acb08b18b3b97b3a672124684abe32599986d2f2a438d5afad148837ee06ea386d2a4bf0348de78 + languageName: node + linkType: hard + "express-rate-limit@npm:^5.5.1": version: 5.5.1 resolution: "express-rate-limit@npm:5.5.1" @@ -24240,6 +24510,34 @@ __metadata: languageName: node linkType: hard +"jest-circus@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-circus@npm:29.6.1" + dependencies: + "@jest/environment": ^29.6.1 + "@jest/expect": ^29.6.1 + "@jest/test-result": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/node": "*" + chalk: ^4.0.0 + co: ^4.6.0 + dedent: ^0.7.0 + is-generator-fn: ^2.0.0 + jest-each: ^29.6.1 + jest-matcher-utils: ^29.6.1 + jest-message-util: ^29.6.1 + jest-runtime: ^29.6.1 + jest-snapshot: ^29.6.1 + jest-util: ^29.6.1 + p-limit: ^3.1.0 + pretty-format: ^29.6.1 + pure-rand: ^6.0.0 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: f3e39a74b601929448df92037f0599978d4d7a4b8f636f64e8020533d2d2b2f669d6729c80c6efed69341ca26753e5061e9787a0acd6c70af2127a94375ebb76 + languageName: node + linkType: hard + "jest-cli@npm:^29.5.0": version: 29.5.0 resolution: "jest-cli@npm:29.5.0" @@ -24267,6 +24565,33 @@ __metadata: languageName: node linkType: hard +"jest-cli@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-cli@npm:29.6.1" + dependencies: + "@jest/core": ^29.6.1 + "@jest/test-result": ^29.6.1 + "@jest/types": ^29.6.1 + chalk: ^4.0.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + import-local: ^3.0.2 + jest-config: ^29.6.1 + jest-util: ^29.6.1 + jest-validate: ^29.6.1 + prompts: ^2.0.1 + yargs: ^17.3.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: f5854ffea977b9a12520ea71f8d0cc8a626cbb93d7e1e6eea18a2a1f2b25f70f1b6b08a89f11b4dc7dd36a1776a9ac2cf8ec5c7998086f913ee690c06c07c949 + languageName: node + linkType: hard + "jest-config@npm:^29.5.0": version: 29.5.0 resolution: "jest-config@npm:29.5.0" @@ -24305,6 +24630,44 @@ __metadata: languageName: node linkType: hard +"jest-config@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-config@npm:29.6.1" + dependencies: + "@babel/core": ^7.11.6 + "@jest/test-sequencer": ^29.6.1 + "@jest/types": ^29.6.1 + babel-jest: ^29.6.1 + chalk: ^4.0.0 + ci-info: ^3.2.0 + deepmerge: ^4.2.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-circus: ^29.6.1 + jest-environment-node: ^29.6.1 + jest-get-type: ^29.4.3 + jest-regex-util: ^29.4.3 + jest-resolve: ^29.6.1 + jest-runner: ^29.6.1 + jest-util: ^29.6.1 + jest-validate: ^29.6.1 + micromatch: ^4.0.4 + parse-json: ^5.2.0 + pretty-format: ^29.6.1 + slash: ^3.0.0 + strip-json-comments: ^3.1.1 + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: 3a30afeb28cc5658ef9cd95f2551ab8a29641bb6d377eb239cba8e7522eb4611c9a98cdcf173d87f5ad7b5e1ad242c3cd5434a260107bd3c7e8305d05023e05c + languageName: node + linkType: hard + "jest-diff@npm:^27.5.1": version: 27.5.1 resolution: "jest-diff@npm:27.5.1" @@ -24341,6 +24704,18 @@ __metadata: languageName: node linkType: hard +"jest-diff@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-diff@npm:29.6.1" + dependencies: + chalk: ^4.0.0 + diff-sequences: ^29.4.3 + jest-get-type: ^29.4.3 + pretty-format: ^29.6.1 + checksum: c6350178ca27d92c7fd879790fb2525470c1ff1c5d29b1834a240fecd26c6904fb470ebddb98dc96dd85389c56c3b50e6965a1f5203e9236d213886ed9806219 + languageName: node + linkType: hard + "jest-docblock@npm:^29.4.3": version: 29.4.3 resolution: "jest-docblock@npm:29.4.3" @@ -24363,6 +24738,19 @@ __metadata: languageName: node linkType: hard +"jest-each@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-each@npm:29.6.1" + dependencies: + "@jest/types": ^29.6.1 + chalk: ^4.0.0 + jest-get-type: ^29.4.3 + jest-util: ^29.6.1 + pretty-format: ^29.6.1 + checksum: 9d2ea7ed5326ee8c22523b22c66c85fe73754ea39f9b389881956508ee441392c61072a5fbf673e39beddd31d011bb94acae3edc77053ba4f9aa5c060114a5c8 + languageName: node + linkType: hard + "jest-environment-jsdom@npm:~29.5.0": version: 29.5.0 resolution: "jest-environment-jsdom@npm:29.5.0" @@ -24398,6 +24786,20 @@ __metadata: languageName: node linkType: hard +"jest-environment-node@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-environment-node@npm:29.6.1" + dependencies: + "@jest/environment": ^29.6.1 + "@jest/fake-timers": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/node": "*" + jest-mock: ^29.6.1 + jest-util: ^29.6.1 + checksum: a50287e1ff29d131646bd09acc3222ac6ea0ad61e86bf73851d318ef2be0633a421b8558c4a15ddc67e0ffcfc32da7f6a0d8a2ddbfa85453837899dec88d256c + languageName: node + linkType: hard + "jest-fetch-mock@npm:^3.0.3": version: 3.0.3 resolution: "jest-fetch-mock@npm:3.0.3" @@ -24477,6 +24879,29 @@ __metadata: languageName: node linkType: hard +"jest-haste-map@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-haste-map@npm:29.6.1" + dependencies: + "@jest/types": ^29.6.1 + "@types/graceful-fs": ^4.1.3 + "@types/node": "*" + anymatch: ^3.0.3 + fb-watchman: ^2.0.0 + fsevents: ^2.3.2 + graceful-fs: ^4.2.9 + jest-regex-util: ^29.4.3 + jest-util: ^29.6.1 + jest-worker: ^29.6.1 + micromatch: ^4.0.4 + walker: ^1.0.8 + dependenciesMeta: + fsevents: + optional: true + checksum: 7c74d5a0f6aafa9f4e60fae7949d4774770c0243fb529c24f2f4c81229db479fa318dc8b81e8d226865aef1d600af10bd8404dd208e802318434b46f75d5d869 + languageName: node + linkType: hard + "jest-leak-detector@npm:^29.5.0": version: 29.5.0 resolution: "jest-leak-detector@npm:29.5.0" @@ -24487,6 +24912,16 @@ __metadata: languageName: node linkType: hard +"jest-leak-detector@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-leak-detector@npm:29.6.1" + dependencies: + jest-get-type: ^29.4.3 + pretty-format: ^29.6.1 + checksum: 5122d40c248effaede4c9ee3a99046a3f30088fef7bfc4af534678b432455161399357af46deb6423de7e05c6597920d6ee8cd570e26048886a90d541334f8c8 + languageName: node + linkType: hard + "jest-matcher-utils@npm:^27.0.0": version: 27.5.1 resolution: "jest-matcher-utils@npm:27.5.1" @@ -24511,6 +24946,18 @@ __metadata: languageName: node linkType: hard +"jest-matcher-utils@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-matcher-utils@npm:29.6.1" + dependencies: + chalk: ^4.0.0 + jest-diff: ^29.6.1 + jest-get-type: ^29.4.3 + pretty-format: ^29.6.1 + checksum: d2efa6aed6e4820758b732b9fefd315c7fa4508ee690da656e1c5ac4c1a0f4cee5b04c9719ee1fda9aeb883b4209186c145089ced521e715b9fa70afdfa4a9c6 + languageName: node + linkType: hard + "jest-message-util@npm:^29.5.0": version: 29.5.0 resolution: "jest-message-util@npm:29.5.0" @@ -24528,6 +24975,23 @@ __metadata: languageName: node linkType: hard +"jest-message-util@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-message-util@npm:29.6.1" + dependencies: + "@babel/code-frame": ^7.12.13 + "@jest/types": ^29.6.1 + "@types/stack-utils": ^2.0.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + micromatch: ^4.0.4 + pretty-format: ^29.6.1 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: 3e7cb2ff087fe72255292e151d24e4fbb4cd6134885c0a67a4b302f233fe4110bf7580b176f427f05ad7550eb878ed94237209785d09d659a7d171ffa59c068f + languageName: node + linkType: hard + "jest-mock@npm:^27.0.6": version: 27.5.1 resolution: "jest-mock@npm:27.5.1" @@ -24549,6 +25013,17 @@ __metadata: languageName: node linkType: hard +"jest-mock@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-mock@npm:29.6.1" + dependencies: + "@jest/types": ^29.6.1 + "@types/node": "*" + jest-util: ^29.6.1 + checksum: 5e902f1a7eba1eb1a64eb6c19947fe1316834359d9869d0e2644d8979b9cad0465885dc4c9909c471888cddeea835c938cec6263d386d3d1aad720fc74e52ea1 + languageName: node + linkType: hard + "jest-pnp-resolver@npm:^1.2.2": version: 1.2.2 resolution: "jest-pnp-resolver@npm:1.2.2" @@ -24585,6 +25060,16 @@ __metadata: languageName: node linkType: hard +"jest-resolve-dependencies@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-resolve-dependencies@npm:29.6.1" + dependencies: + jest-regex-util: ^29.4.3 + jest-snapshot: ^29.6.1 + checksum: cee0a0fe53fd4531492a526b6ccd32377baad1eff6e6c124f04e9dc920219fd23fd39be88bb9551ee68d5fe92a3af627b423c9bc65a2aa0ac8a223c0e74dbbbb + languageName: node + linkType: hard + "jest-resolve@npm:^29.5.0": version: 29.5.0 resolution: "jest-resolve@npm:29.5.0" @@ -24602,6 +25087,23 @@ __metadata: languageName: node linkType: hard +"jest-resolve@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-resolve@npm:29.6.1" + dependencies: + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.6.1 + jest-pnp-resolver: ^1.2.2 + jest-util: ^29.6.1 + jest-validate: ^29.6.1 + resolve: ^1.20.0 + resolve.exports: ^2.0.0 + slash: ^3.0.0 + checksum: 9ce979a0f4a751bea58caea76415112df2a3f4d58e294019872244728aadd001f0ec20c873a3c805dd8f7c762143b3c14d00f87d124ed87c9981fbf8723090ef + languageName: node + linkType: hard + "jest-runner@npm:^29.5.0": version: 29.5.0 resolution: "jest-runner@npm:29.5.0" @@ -24631,6 +25133,35 @@ __metadata: languageName: node linkType: hard +"jest-runner@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-runner@npm:29.6.1" + dependencies: + "@jest/console": ^29.6.1 + "@jest/environment": ^29.6.1 + "@jest/test-result": ^29.6.1 + "@jest/transform": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/node": "*" + chalk: ^4.0.0 + emittery: ^0.13.1 + graceful-fs: ^4.2.9 + jest-docblock: ^29.4.3 + jest-environment-node: ^29.6.1 + jest-haste-map: ^29.6.1 + jest-leak-detector: ^29.6.1 + jest-message-util: ^29.6.1 + jest-resolve: ^29.6.1 + jest-runtime: ^29.6.1 + jest-util: ^29.6.1 + jest-watcher: ^29.6.1 + jest-worker: ^29.6.1 + p-limit: ^3.1.0 + source-map-support: 0.5.13 + checksum: 0e4dbda26669ae31fee32f8a62b3119bba510f2d17a098d6157b48a73ed2fc9842405bf893f3045c12b3632c7c0e3399fb22684b18ab5566aff4905b26c79a9a + languageName: node + linkType: hard + "jest-runtime@npm:^29.5.0": version: 29.5.0 resolution: "jest-runtime@npm:29.5.0" @@ -24661,6 +25192,36 @@ __metadata: languageName: node linkType: hard +"jest-runtime@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-runtime@npm:29.6.1" + dependencies: + "@jest/environment": ^29.6.1 + "@jest/fake-timers": ^29.6.1 + "@jest/globals": ^29.6.1 + "@jest/source-map": ^29.6.0 + "@jest/test-result": ^29.6.1 + "@jest/transform": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/node": "*" + chalk: ^4.0.0 + cjs-module-lexer: ^1.0.0 + collect-v8-coverage: ^1.0.0 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-haste-map: ^29.6.1 + jest-message-util: ^29.6.1 + jest-mock: ^29.6.1 + jest-regex-util: ^29.4.3 + jest-resolve: ^29.6.1 + jest-snapshot: ^29.6.1 + jest-util: ^29.6.1 + slash: ^3.0.0 + strip-bom: ^4.0.0 + checksum: 7c360c9694467d996f3d6d914fefa0e7bda554adda8c2b9fba31546dba663d71a64eda103ff68120a2422f3c16db8f0bc2c445923fe8fb934f37e53ef74fb429 + languageName: node + linkType: hard + "jest-serializer@npm:^26.6.2": version: 26.6.2 resolution: "jest-serializer@npm:26.6.2" @@ -24702,6 +25263,35 @@ __metadata: languageName: node linkType: hard +"jest-snapshot@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-snapshot@npm:29.6.1" + dependencies: + "@babel/core": ^7.11.6 + "@babel/generator": ^7.7.2 + "@babel/plugin-syntax-jsx": ^7.7.2 + "@babel/plugin-syntax-typescript": ^7.7.2 + "@babel/types": ^7.3.3 + "@jest/expect-utils": ^29.6.1 + "@jest/transform": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/prettier": ^2.1.5 + babel-preset-current-node-syntax: ^1.0.0 + chalk: ^4.0.0 + expect: ^29.6.1 + graceful-fs: ^4.2.9 + jest-diff: ^29.6.1 + jest-get-type: ^29.4.3 + jest-matcher-utils: ^29.6.1 + jest-message-util: ^29.6.1 + jest-util: ^29.6.1 + natural-compare: ^1.4.0 + pretty-format: ^29.6.1 + semver: ^7.5.3 + checksum: e8f69d1fd4a29d354d4dca9eb2a22674b300f8ef509e4f1e75337c880414a00d2bdc9d3849a6855dbb5a76bfbe74603f33435378a3877e69f0838e4cc2244350 + languageName: node + linkType: hard + "jest-util@npm:^26.6.2": version: 26.6.2 resolution: "jest-util@npm:26.6.2" @@ -24730,6 +25320,20 @@ __metadata: languageName: node linkType: hard +"jest-util@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-util@npm:29.6.1" + dependencies: + "@jest/types": ^29.6.1 + "@types/node": "*" + chalk: ^4.0.0 + ci-info: ^3.2.0 + graceful-fs: ^4.2.9 + picomatch: ^2.2.3 + checksum: fc553556c1350c443449cadaba5fb9d604628e8b5ceb6ceaf4e7e08975b24277d0a14bf2e0f956024e03c23e556fcb074659423422a06fbedf2ab52978697ac7 + languageName: node + linkType: hard + "jest-validate@npm:^29.5.0": version: 29.5.0 resolution: "jest-validate@npm:29.5.0" @@ -24744,6 +25348,20 @@ __metadata: languageName: node linkType: hard +"jest-validate@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-validate@npm:29.6.1" + dependencies: + "@jest/types": ^29.6.1 + camelcase: ^6.2.0 + chalk: ^4.0.0 + jest-get-type: ^29.4.3 + leven: ^3.1.0 + pretty-format: ^29.6.1 + checksum: d2491f3f33d9bbc2dcaaa6acbff26f257b59c5eeceb65a52a9c1cec2f679b836ec2a4658b7004c0ef9d90cd0d9bd664e41d5ed6900f932bea742dd8e6b85e7f1 + languageName: node + linkType: hard + "jest-watcher@npm:^29.5.0": version: 29.5.0 resolution: "jest-watcher@npm:29.5.0" @@ -24760,6 +25378,22 @@ __metadata: languageName: node linkType: hard +"jest-watcher@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-watcher@npm:29.6.1" + dependencies: + "@jest/test-result": ^29.6.1 + "@jest/types": ^29.6.1 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + emittery: ^0.13.1 + jest-util: ^29.6.1 + string-length: ^4.0.1 + checksum: 69bd5a602284fdce6eba5486c5c57aca6b511d91cb0907c34c104d6dd931e18ce67baa7f8e280fa473e5d81ea3e7b9e7d94f712c37ab0b3b8cc2aec30676955d + languageName: node + linkType: hard + "jest-websocket-mock@npm:^2.4.0": version: 2.4.0 resolution: "jest-websocket-mock@npm:2.4.0" @@ -24804,6 +25438,18 @@ __metadata: languageName: node linkType: hard +"jest-worker@npm:^29.6.1": + version: 29.6.1 + resolution: "jest-worker@npm:29.6.1" + dependencies: + "@types/node": "*" + jest-util: ^29.6.1 + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: 0af309ea4db17c4c47e84a9246f907960a15577683c005fdeafc8f3c06bc455136f95a6f28fa2a3e924b767eb4dacd9b40915a7707305f88586f099af3ac27a8 + languageName: node + linkType: hard + "jest@npm:^29.5.0, jest@npm:~29.5.0": version: 29.5.0 resolution: "jest@npm:29.5.0" @@ -24823,6 +25469,25 @@ __metadata: languageName: node linkType: hard +"jest@npm:^29.6.1": + version: 29.6.1 + resolution: "jest@npm:29.6.1" + dependencies: + "@jest/core": ^29.6.1 + "@jest/types": ^29.6.1 + import-local: ^3.0.2 + jest-cli: ^29.6.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: 7b8c0ca72f483e00ec19dcf9549f9a9af8ae468ab62925b148d714b58eb52d5fea9a082625193bc833d2d9b64cf65a11f3d37857636c5551af05c10aec4ce71b + languageName: node + linkType: hard + "jmespath@npm:0.16.0": version: 0.16.0 resolution: "jmespath@npm:0.16.0" @@ -30834,6 +31499,17 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^29.6.1": + version: 29.6.1 + resolution: "pretty-format@npm:29.6.1" + dependencies: + "@jest/schemas": ^29.6.0 + ansi-styles: ^5.0.0 + react-is: ^18.0.0 + checksum: 6f923a2379a37a425241dc223d76f671c73c4f37dba158050575a54095867d565c068b441843afdf3d7c37bed9df4bbadf46297976e60d4149972b779474203a + languageName: node + linkType: hard + "pretty-hrtime@npm:^1.0.3": version: 1.0.3 resolution: "pretty-hrtime@npm:1.0.3" @@ -33476,6 +34152,17 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.5.3": + version: 7.5.3 + resolution: "semver@npm:7.5.3" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 9d58db16525e9f749ad0a696a1f27deabaa51f66e91d2fa2b0db3de3e9644e8677de3b7d7a03f4c15bc81521e0c3916d7369e0572dbde250d9bedf5194e2a8a7 + languageName: node + linkType: hard + "semver@npm:~5.3.0": version: 5.3.0 resolution: "semver@npm:5.3.0" From 7f54572d2cc83a8a184c3821d8d64f867a48d694 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlia=20Jaeger=20Foresti?= <60678893+juliajforesti@users.noreply.github.com> Date: Fri, 7 Jul 2023 15:50:11 -0300 Subject: [PATCH 73/79] regression: fix condition to `GenericMenu` gap (#29761) --- .../components/GenericMenu/GenericMenu.tsx | 3 +- yarn.lock | 3572 ++++++++++++++++- 2 files changed, 3373 insertions(+), 202 deletions(-) diff --git a/apps/meteor/client/components/GenericMenu/GenericMenu.tsx b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx index aba2e7e0eefb3..e02d6dc4e7463 100644 --- a/apps/meteor/client/components/GenericMenu/GenericMenu.tsx +++ b/apps/meteor/client/components/GenericMenu/GenericMenu.tsx @@ -40,7 +40,8 @@ const GenericMenu = ({ title, icon = 'menu', onAction, ...props }: GenericMenuPr const handleAction = useHandleMenuAction(itemsList || []); const hasIcon = itemsList.some(({ icon }) => icon); - const handleItems = (items: GenericMenuItemProps[]) => (hasIcon ? items.map((item) => ({ ...item, gap: !item.icon })) : items); + const handleItems = (items: GenericMenuItemProps[]) => + hasIcon ? items.map((item) => ({ ...item, gap: !item.icon && !item.status })) : items; return ( <> diff --git a/yarn.lock b/yarn.lock index f855f365525c8..3dcf48ca6520c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -957,7 +957,16 @@ __metadata: languageName: node linkType: hard -"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.22.5, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3": +"@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.10.4, @babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.18.6, @babel/code-frame@npm:^7.21.4, @babel/code-frame@npm:^7.5.5, @babel/code-frame@npm:^7.8.3": + version: 7.21.4 + resolution: "@babel/code-frame@npm:7.21.4" + dependencies: + "@babel/highlight": ^7.18.6 + checksum: e5390e6ec1ac58dcef01d4f18eaf1fd2f1325528661ff6d4a5de8979588b9f5a8e852a54a91b923846f7a5c681b217f0a45c2524eb9560553160cd963b7d592c + languageName: node + linkType: hard + +"@babel/code-frame@npm:^7.22.5": version: 7.22.5 resolution: "@babel/code-frame@npm:7.22.5" dependencies: @@ -966,7 +975,21 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.22.5": +"@babel/compat-data@npm:^7.17.7, @babel/compat-data@npm:^7.20.5, @babel/compat-data@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/compat-data@npm:7.21.4" + checksum: 5f8b98c66f2ffba9f3c3a82c0cf354c52a0ec5ad4797b370dc32bdcd6e136ac4febe5e93d76ce76e175632e2dbf6ce9f46319aa689fcfafa41b6e49834fa4b66 + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.21.5": + version: 7.21.7 + resolution: "@babel/compat-data@npm:7.21.7" + checksum: 28747eb3fc084d088ba2db0336f52118cfa730a57bdbac81630cae1f38ad0336605b95b3390325937802f344e0b7fa25e2f1b67e3ee2d7383b877f88dee0e51c + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.22.5": version: 7.22.5 resolution: "@babel/compat-data@npm:7.22.5" checksum: eb1a47ebf79ae268b4a16903e977be52629339806e248455eb9973897c503a04b701f36a9de64e19750d6e081d0561e77a514c8dc470babbeba59ae94298ed18 @@ -997,7 +1020,30 @@ __metadata: languageName: node linkType: hard -"@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.20.5, @babel/core@npm:^7.21.4, @babel/core@npm:^7.7.5, @babel/core@npm:~7.22.5": +"@babel/core@npm:^7.1.0, @babel/core@npm:^7.11.6, @babel/core@npm:^7.12.10, @babel/core@npm:^7.12.3, @babel/core@npm:^7.7.5": + version: 7.21.4 + resolution: "@babel/core@npm:7.21.4" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.4 + "@babel/helper-compilation-targets": ^7.21.4 + "@babel/helper-module-transforms": ^7.21.2 + "@babel/helpers": ^7.21.0 + "@babel/parser": ^7.21.4 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.4 + "@babel/types": ^7.21.4 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.2 + semver: ^6.3.0 + checksum: a3beebb2cc79908a02f27a07dc381bcb34e8ecc58fa99f568ad0934c49e12111fc977ee9c5b51eb7ea2da66f63155d37c4dd96b6472eaeecfc35843ccb56bf3d + languageName: node + linkType: hard + +"@babel/core@npm:^7.20.5, @babel/core@npm:~7.22.5": version: 7.22.5 resolution: "@babel/core@npm:7.22.5" dependencies: @@ -1020,6 +1066,29 @@ __metadata: languageName: node linkType: hard +"@babel/core@npm:^7.21.4": + version: 7.21.8 + resolution: "@babel/core@npm:7.21.8" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.5 + "@babel/helper-compilation-targets": ^7.21.5 + "@babel/helper-module-transforms": ^7.21.5 + "@babel/helpers": ^7.21.5 + "@babel/parser": ^7.21.8 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.2 + semver: ^6.3.0 + checksum: f28118447355af2a90bd340e2e60699f94c8020517eba9b71bf8ebff62fa9e00d63f076e033f9dfb97548053ad62ada45fafb0d96584b1a90e8aef5a3b8241b1 + languageName: node + linkType: hard + "@babel/eslint-parser@npm:^7.22.5, @babel/eslint-parser@npm:~7.22.5": version: 7.22.5 resolution: "@babel/eslint-parser@npm:7.22.5" @@ -1034,7 +1103,31 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.22.5, @babel/generator@npm:^7.7.2": +"@babel/generator@npm:^7.12.11, @babel/generator@npm:^7.12.5, @babel/generator@npm:^7.21.4, @babel/generator@npm:^7.7.2": + version: 7.21.4 + resolution: "@babel/generator@npm:7.21.4" + dependencies: + "@babel/types": ^7.21.4 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 9ffbb526a53bb8469b5402f7b5feac93809b09b2a9f82fcbfcdc5916268a65dae746a1f2479e03ba4fb0776facd7c892191f63baa61ab69b2cfdb24f7b92424d + languageName: node + linkType: hard + +"@babel/generator@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/generator@npm:7.21.5" + dependencies: + "@babel/types": ^7.21.5 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 78af737b9dd701d4c657f9731880430fa1c177767b562f4e8a330a7fe72a4abe857e3d24de4e6d9dafc1f6a11f894162d27e523d7e5948ff9e3925a0ce9867c4 + languageName: node + linkType: hard + +"@babel/generator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/generator@npm:7.22.5" dependencies: @@ -1046,7 +1139,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-annotate-as-pure@npm:^7.18.6, @babel/helper-annotate-as-pure@npm:^7.22.5": +"@babel/helper-annotate-as-pure@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-annotate-as-pure@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: 88ccd15ced475ef2243fdd3b2916a29ea54c5db3cd0cfabf9d1d29ff6e63b7f7cd1c27264137d7a40ac2e978b9b9a542c332e78f40eb72abe737a7400788fc1b + languageName: node + linkType: hard + +"@babel/helper-annotate-as-pure@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-annotate-as-pure@npm:7.22.5" dependencies: @@ -1055,6 +1157,16 @@ __metadata: languageName: node linkType: hard +"@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.18.6" + dependencies: + "@babel/helper-explode-assignable-expression": ^7.18.6 + "@babel/types": ^7.18.6 + checksum: c4d71356e0adbc20ce9fe7c1e1181ff65a78603f8bba7615745f0417fed86bad7dc0a54a840bc83667c66709b3cb3721edcb9be0d393a298ce4e9eb6d085f3c1 + languageName: node + linkType: hard + "@babel/helper-builder-binary-assignment-operator-visitor@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-builder-binary-assignment-operator-visitor@npm:7.22.5" @@ -1064,7 +1176,37 @@ __metadata: languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.22.5": +"@babel/helper-compilation-targets@npm:^7.13.0, @babel/helper-compilation-targets@npm:^7.17.7, @babel/helper-compilation-targets@npm:^7.18.9, @babel/helper-compilation-targets@npm:^7.20.7, @babel/helper-compilation-targets@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/helper-compilation-targets@npm:7.21.4" + dependencies: + "@babel/compat-data": ^7.21.4 + "@babel/helper-validator-option": ^7.21.0 + browserslist: ^4.21.3 + lru-cache: ^5.1.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: bf9c7d3e7e6adff9222c05d898724cd4ee91d7eb9d52222c7ad2a22955620c2872cc2d9bdf0e047df8efdb79f4e3af2a06b53f509286145feccc4d10ddc318be + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-compilation-targets@npm:7.21.5" + dependencies: + "@babel/compat-data": ^7.21.5 + "@babel/helper-validator-option": ^7.21.0 + browserslist: ^4.21.3 + lru-cache: ^5.1.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 0edecb9c970ddc22ebda1163e77a7f314121bef9e483e0e0d9a5802540eed90d5855b6bf9bce03419b35b2e07c323e62d0353b153fa1ca34f17dbba897a83c25 + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-compilation-targets@npm:7.22.5" dependencies: @@ -1079,7 +1221,44 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-class-features-plugin@npm:^7.17.6, @babel/helper-create-class-features-plugin@npm:^7.18.6, @babel/helper-create-class-features-plugin@npm:^7.21.0, @babel/helper-create-class-features-plugin@npm:^7.22.5": +"@babel/helper-create-class-features-plugin@npm:^7.17.6, @babel/helper-create-class-features-plugin@npm:^7.18.6": + version: 7.21.8 + resolution: "@babel/helper-create-class-features-plugin@npm:7.21.8" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-environment-visitor": ^7.21.5 + "@babel/helper-function-name": ^7.21.0 + "@babel/helper-member-expression-to-functions": ^7.21.5 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/helper-replace-supers": ^7.21.5 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/helper-split-export-declaration": ^7.18.6 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 26b978bd2e741259c0f4a1cc37521ad58728c50d28fe2fc8041d4381497e13a0b686a10e170246855eaf3af08886862e9d93fc27994ef914e13fca0d73efdcb8 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.21.0": + version: 7.21.4 + resolution: "@babel/helper-create-class-features-plugin@npm:7.21.4" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.21.0 + "@babel/helper-member-expression-to-functions": ^7.21.0 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/helper-replace-supers": ^7.20.7 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/helper-split-export-declaration": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 9123ca80a4894aafdb1f0bc08e44f6be7b12ed1fbbe99c501b484f9b1a17ff296b6c90c18c222047d53c276f07f17b4de857946fa9d0aa207023b03e4cc716f2 + languageName: node + linkType: hard + +"@babel/helper-create-class-features-plugin@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-create-class-features-plugin@npm:7.22.5" dependencies: @@ -1098,7 +1277,32 @@ __metadata: languageName: node linkType: hard -"@babel/helper-create-regexp-features-plugin@npm:^7.18.6, @babel/helper-create-regexp-features-plugin@npm:^7.22.5": +"@babel/helper-create-regexp-features-plugin@npm:^7.18.6": + version: 7.21.8 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.21.8" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + regexpu-core: ^5.3.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 04a686b5897c86339395894c0a9a1ffdce2facaba5173ce7b0a894f775f984ba70d2fa227d309f2be54f7f1286ebd1a0a7051a8b1829521595e4064ee062af65 + languageName: node + linkType: hard + +"@babel/helper-create-regexp-features-plugin@npm:^7.20.5": + version: 7.21.4 + resolution: "@babel/helper-create-regexp-features-plugin@npm:7.21.4" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + regexpu-core: ^5.3.1 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 78334865db2cd1d64d103bd0d96dee2818b0387d10aa973c084e245e829df32652bca530803e397b7158af4c02b9b21d5a9601c29bdfbb8d54a3d4ad894e067b + languageName: node + linkType: hard + +"@babel/helper-create-regexp-features-plugin@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-create-regexp-features-plugin@npm:7.22.5" dependencies: @@ -1129,6 +1333,22 @@ __metadata: languageName: node linkType: hard +"@babel/helper-define-polyfill-provider@npm:^0.3.3": + version: 0.3.3 + resolution: "@babel/helper-define-polyfill-provider@npm:0.3.3" + dependencies: + "@babel/helper-compilation-targets": ^7.17.7 + "@babel/helper-plugin-utils": ^7.16.7 + debug: ^4.1.1 + lodash.debounce: ^4.0.8 + resolve: ^1.14.2 + semver: ^6.1.2 + peerDependencies: + "@babel/core": ^7.4.0-0 + checksum: 8e3fe75513302e34f6d92bd67b53890e8545e6c5bca8fe757b9979f09d68d7e259f6daea90dc9e01e332c4f8781bda31c5fe551c82a277f9bc0bec007aed497c + languageName: node + linkType: hard + "@babel/helper-define-polyfill-provider@npm:^0.4.0": version: 0.4.0 resolution: "@babel/helper-define-polyfill-provider@npm:0.4.0" @@ -1145,6 +1365,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-environment-visitor@npm:^7.18.9, @babel/helper-environment-visitor@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-environment-visitor@npm:7.21.5" + checksum: e436af7b62956e919066448013a3f7e2cd0b51010c26c50f790124dcd350be81d5597b4e6ed0a4a42d098a27de1e38561cd7998a116a42e7899161192deac9a6 + languageName: node + linkType: hard + "@babel/helper-environment-visitor@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-environment-visitor@npm:7.22.5" @@ -1152,6 +1379,25 @@ __metadata: languageName: node linkType: hard +"@babel/helper-explode-assignable-expression@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-explode-assignable-expression@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: 225cfcc3376a8799023d15dc95000609e9d4e7547b29528c7f7111a0e05493ffb12c15d70d379a0bb32d42752f340233c4115bded6d299bc0c3ab7a12be3d30f + languageName: node + linkType: hard + +"@babel/helper-function-name@npm:^7.18.9, @babel/helper-function-name@npm:^7.19.0, @babel/helper-function-name@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helper-function-name@npm:7.21.0" + dependencies: + "@babel/template": ^7.20.7 + "@babel/types": ^7.21.0 + checksum: d63e63c3e0e3e8b3138fa47b0cd321148a300ef12b8ee951196994dcd2a492cc708aeda94c2c53759a5c9177fffaac0fd8778791286746f72a000976968daf4e + languageName: node + linkType: hard + "@babel/helper-function-name@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-function-name@npm:7.22.5" @@ -1162,6 +1408,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-hoist-variables@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-hoist-variables@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: fd9c35bb435fda802bf9ff7b6f2df06308a21277c6dec2120a35b09f9de68f68a33972e2c15505c1a1a04b36ec64c9ace97d4a9e26d6097b76b4396b7c5fa20f + languageName: node + linkType: hard + "@babel/helper-hoist-variables@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-hoist-variables@npm:7.22.5" @@ -1171,6 +1426,24 @@ __metadata: languageName: node linkType: hard +"@babel/helper-member-expression-to-functions@npm:^7.20.7, @babel/helper-member-expression-to-functions@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helper-member-expression-to-functions@npm:7.21.0" + dependencies: + "@babel/types": ^7.21.0 + checksum: 49cbb865098195fe82ba22da3a8fe630cde30dcd8ebf8ad5f9a24a2b685150c6711419879cf9d99b94dad24cff9244d8c2a890d3d7ec75502cd01fe58cff5b5d + languageName: node + linkType: hard + +"@babel/helper-member-expression-to-functions@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-member-expression-to-functions@npm:7.21.5" + dependencies: + "@babel/types": ^7.21.5 + checksum: c404b4a0271c640b7dc8c34af7b683c70a43200259e02330cfc02e79e6b271e9227f35554cd6ad015eabcfa1fea75b9d0b87b69f3d1e6c2af6edd224060b1732 + languageName: node + linkType: hard + "@babel/helper-member-expression-to-functions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-member-expression-to-functions@npm:7.22.5" @@ -1180,7 +1453,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.22.5": +"@babel/helper-module-imports@npm:^7.12.13, @babel/helper-module-imports@npm:^7.18.6, @babel/helper-module-imports@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/helper-module-imports@npm:7.21.4" + dependencies: + "@babel/types": ^7.21.4 + checksum: bd330a2edaafeb281fbcd9357652f8d2666502567c0aad71db926e8499c773c9ea9c10dfaae30122452940326d90c8caff5c649ed8e1bf15b23f858758d3abc6 + languageName: node + linkType: hard + +"@babel/helper-module-imports@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-module-imports@npm:7.22.5" dependencies: @@ -1189,7 +1471,39 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.22.5": +"@babel/helper-module-transforms@npm:^7.12.1, @babel/helper-module-transforms@npm:^7.18.6, @babel/helper-module-transforms@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-module-transforms@npm:7.21.5" + dependencies: + "@babel/helper-environment-visitor": ^7.21.5 + "@babel/helper-module-imports": ^7.21.4 + "@babel/helper-simple-access": ^7.21.5 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/helper-validator-identifier": ^7.19.1 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 + checksum: 1ccfc88830675a5d485d198e918498f9683cdd46f973fdd4fe1c85b99648fb70f87fca07756c7a05dc201bd9b248c74ced06ea80c9991926ac889f53c3659675 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.20.11, @babel/helper-module-transforms@npm:^7.21.2": + version: 7.21.2 + resolution: "@babel/helper-module-transforms@npm:7.21.2" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-simple-access": ^7.20.2 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/helper-validator-identifier": ^7.19.1 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.2 + "@babel/types": ^7.21.2 + checksum: 8a1c129a4f90bdf97d8b6e7861732c9580f48f877aaaafbc376ce2482febebcb8daaa1de8bc91676d12886487603f8c62a44f9e90ee76d6cac7f9225b26a49e1 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-module-transforms@npm:7.22.5" dependencies: @@ -1205,6 +1519,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-optimise-call-expression@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-optimise-call-expression@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: e518fe8418571405e21644cfb39cf694f30b6c47b10b006609a92469ae8b8775cbff56f0b19732343e2ea910641091c5a2dc73b56ceba04e116a33b0f8bd2fbd + languageName: node + linkType: hard + "@babel/helper-optimise-call-expression@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-optimise-call-expression@npm:7.22.5" @@ -1221,13 +1544,34 @@ __metadata: languageName: node linkType: hard -"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.16.7, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.22.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.13.0, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.16.7, @babel/helper-plugin-utils@npm:^7.18.6, @babel/helper-plugin-utils@npm:^7.18.9, @babel/helper-plugin-utils@npm:^7.19.0, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.21.5, @babel/helper-plugin-utils@npm:^7.8.0, @babel/helper-plugin-utils@npm:^7.8.3": + version: 7.21.5 + resolution: "@babel/helper-plugin-utils@npm:7.21.5" + checksum: 6f086e9a84a50ea7df0d5639c8f9f68505af510ea3258b3c8ac8b175efdfb7f664436cb48996f71791a1350ba68f47ad3424131e8e718c5e2ad45564484cbb36 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-plugin-utils@npm:7.22.5" checksum: c0fc7227076b6041acd2f0e818145d2e8c41968cc52fb5ca70eed48e21b8fe6dd88a0a91cbddf4951e33647336eb5ae184747ca706817ca3bef5e9e905151ff5 languageName: node linkType: hard +"@babel/helper-remap-async-to-generator@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/helper-remap-async-to-generator@npm:7.18.9" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-wrap-function": ^7.18.9 + "@babel/types": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 4be6076192308671b046245899b703ba090dbe7ad03e0bea897bb2944ae5b88e5e85853c9d1f83f643474b54c578d8ac0800b80341a86e8538264a725fbbefec + languageName: node + linkType: hard + "@babel/helper-remap-async-to-generator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-remap-async-to-generator@npm:7.22.5" @@ -1242,7 +1586,35 @@ __metadata: languageName: node linkType: hard -"@babel/helper-replace-supers@npm:^7.16.7, @babel/helper-replace-supers@npm:^7.22.5": +"@babel/helper-replace-supers@npm:^7.16.7, @babel/helper-replace-supers@npm:^7.18.6, @babel/helper-replace-supers@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-replace-supers@npm:7.21.5" + dependencies: + "@babel/helper-environment-visitor": ^7.21.5 + "@babel/helper-member-expression-to-functions": ^7.21.5 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 + checksum: 4fd343e6f90533743d8e8a1f42e50377b3d6b27f524a27eb97ff28f075e4e55cca2383adb1b0973de358b08022aef0fec4c8d69711e1da43bf9b887b5a893677 + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/helper-replace-supers@npm:7.20.7" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-member-expression-to-functions": ^7.20.7 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: b8e0087c9b0c1446e3c6f3f72b73b7e03559c6b570e2cfbe62c738676d9ebd8c369a708cf1a564ef88113b4330750a50232ee1131d303d478b7a5e65e46fbc7c + languageName: node + linkType: hard + +"@babel/helper-replace-supers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-replace-supers@npm:7.22.5" dependencies: @@ -1256,6 +1628,24 @@ __metadata: languageName: node linkType: hard +"@babel/helper-simple-access@npm:^7.20.2": + version: 7.20.2 + resolution: "@babel/helper-simple-access@npm:7.20.2" + dependencies: + "@babel/types": ^7.21.5 + checksum: ad1e96ee2e5f654ffee2369a586e5e8d2722bf2d8b028a121b4c33ebae47253f64d420157b9f0a8927aea3a9e0f18c0103e74fdd531815cf3650a0a4adca11a1 + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-simple-access@npm:7.21.5" + dependencies: + "@babel/types": ^7.21.5 + checksum: ad212beaa24be3864c8c95bee02f840222457ccf5419991e2d3e3e39b0f75b77e7e857e0bf4ed428b1cd97acefc87f3831bdb0b9696d5ad0557421f398334fc3 + languageName: node + linkType: hard + "@babel/helper-simple-access@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-simple-access@npm:7.22.5" @@ -1265,7 +1655,16 @@ __metadata: languageName: node linkType: hard -"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0, @babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.20.0": + version: 7.20.0 + resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.20.0" + dependencies: + "@babel/types": ^7.20.0 + checksum: 34da8c832d1c8a546e45d5c1d59755459ffe43629436707079989599b91e8c19e50e73af7a4bd09c95402d389266731b0d9c5f69e372d8ebd3a709c05c80d7dd + languageName: node + linkType: hard + +"@babel/helper-skip-transparent-expression-wrappers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-skip-transparent-expression-wrappers@npm:7.22.5" dependencies: @@ -1274,6 +1673,15 @@ __metadata: languageName: node linkType: hard +"@babel/helper-split-export-declaration@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/helper-split-export-declaration@npm:7.18.6" + dependencies: + "@babel/types": ^7.18.6 + checksum: c6d3dede53878f6be1d869e03e9ffbbb36f4897c7cc1527dc96c56d127d834ffe4520a6f7e467f5b6f3c2843ea0e81a7819d66ae02f707f6ac057f3d57943a2b + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-split-export-declaration@npm:7.22.5" @@ -1283,13 +1691,27 @@ __metadata: languageName: node linkType: hard -"@babel/helper-string-parser@npm:^7.22.5": +"@babel/helper-string-parser@npm:^7.19.4, @babel/helper-string-parser@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-string-parser@npm:7.22.5" checksum: 836851ca5ec813077bbb303acc992d75a360267aa3b5de7134d220411c852a6f17de7c0d0b8c8dcc0f567f67874c00f4528672b2a4f1bc978a3ada64c8c78467 languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helper-string-parser@npm:7.21.5" + checksum: 36c0ded452f3858e67634b81960d4bde1d1cd2a56b82f4ba2926e97864816021c885f111a7cf81de88a0ed025f49d84a393256700e9acbca2d99462d648705d8 + languageName: node + linkType: hard + +"@babel/helper-validator-identifier@npm:^7.18.6, @babel/helper-validator-identifier@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/helper-validator-identifier@npm:7.19.1" + checksum: 0eca5e86a729162af569b46c6c41a63e18b43dbe09fda1d2a3c8924f7d617116af39cac5e4cd5d431bb760b4dca3c0970e0c444789b1db42bcf1fa41fbad0a3a + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-validator-identifier@npm:7.22.5" @@ -1297,13 +1719,32 @@ __metadata: languageName: node linkType: hard -"@babel/helper-validator-option@npm:^7.16.7, @babel/helper-validator-option@npm:^7.22.5": +"@babel/helper-validator-option@npm:^7.16.7, @babel/helper-validator-option@npm:^7.18.6, @babel/helper-validator-option@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helper-validator-option@npm:7.21.0" + checksum: 8ece4c78ffa5461fd8ab6b6e57cc51afad59df08192ed5d84b475af4a7193fc1cb794b59e3e7be64f3cdc4df7ac78bf3dbb20c129d7757ae078e6279ff8c2f07 + languageName: node + linkType: hard + +"@babel/helper-validator-option@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-validator-option@npm:7.22.5" checksum: bbeca8a85ee86990215c0424997438b388b8d642d69b9f86c375a174d3cdeb270efafd1ff128bc7a1d370923d13b6e45829ba8581c027620e83e3a80c5c414b3 languageName: node linkType: hard +"@babel/helper-wrap-function@npm:^7.18.9": + version: 7.19.0 + resolution: "@babel/helper-wrap-function@npm:7.19.0" + dependencies: + "@babel/helper-function-name": ^7.19.0 + "@babel/template": ^7.18.10 + "@babel/traverse": ^7.19.0 + "@babel/types": ^7.19.0 + checksum: 2453a6b134f12cc779179188c4358a66252c29b634a8195c0cf626e17f9806c3c4c40e159cd8056c2ec82b69b9056a088014fa43d6ccc1aca67da8d9605da8fd + languageName: node + linkType: hard + "@babel/helper-wrap-function@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helper-wrap-function@npm:7.22.5" @@ -1316,7 +1757,29 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.22.5": +"@babel/helpers@npm:^7.12.5, @babel/helpers@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helpers@npm:7.21.0" + dependencies: + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.0 + "@babel/types": ^7.21.0 + checksum: 9370dad2bb665c551869a08ac87c8bdafad53dbcdce1f5c5d498f51811456a3c005d9857562715151a0f00b2e912ac8d89f56574f837b5689f5f5072221cdf54 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/helpers@npm:7.21.5" + dependencies: + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.5 + "@babel/types": ^7.21.5 + checksum: a6f74b8579713988e7f5adf1a986d8b5255757632ba65b2552f0f609ead5476edb784044c7e4b18f3681ee4818ca9d08c41feb9bd4e828648c25a00deaa1f9e4 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.22.5": version: 7.22.5 resolution: "@babel/helpers@npm:7.22.5" dependencies: @@ -1327,7 +1790,18 @@ __metadata: languageName: node linkType: hard -"@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.22.5": +"@babel/highlight@npm:^7.10.4, @babel/highlight@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/highlight@npm:7.18.6" + dependencies: + "@babel/helper-validator-identifier": ^7.18.6 + chalk: ^2.0.0 + js-tokens: ^4.0.0 + checksum: 92d8ee61549de5ff5120e945e774728e5ccd57fd3b2ed6eace020ec744823d4a98e242be1453d21764a30a14769ecd62170fba28539b211799bbaf232bbb2789 + languageName: node + linkType: hard + +"@babel/highlight@npm:^7.22.5": version: 7.22.5 resolution: "@babel/highlight@npm:7.22.5" dependencies: @@ -1338,7 +1812,25 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.22.5": +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.12.11, @babel/parser@npm:^7.12.7, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/parser@npm:7.21.4" + bin: + parser: ./bin/babel-parser.js + checksum: de610ecd1bff331766d0c058023ca11a4f242bfafefc42caf926becccfb6756637d167c001987ca830dd4b34b93c629a4cef63f8c8c864a8564cdfde1989ac77 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.21.5, @babel/parser@npm:^7.21.8": + version: 7.21.8 + resolution: "@babel/parser@npm:7.21.8" + bin: + parser: ./bin/babel-parser.js + checksum: 1b9a820fedfb6ef179e6ffa1dbc080808882949dec68340a616da2aa354af66ea2886bd68e61bd444d270aa0b24ad6273e3cfaf17d6878c34bf2521becacb353 + languageName: node + linkType: hard + +"@babel/parser@npm:^7.22.5": version: 7.22.5 resolution: "@babel/parser@npm:7.22.5" bin: @@ -1347,6 +1839,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 845bd280c55a6a91d232cfa54eaf9708ec71e594676fe705794f494bb8b711d833b752b59d1a5c154695225880c23dbc9cab0e53af16fd57807976cd3ff41b8d + languageName: node + linkType: hard + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@npm:7.22.5" @@ -1358,6 +1861,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + "@babel/plugin-proposal-optional-chaining": ^7.20.7 + peerDependencies: + "@babel/core": ^7.13.0 + checksum: d610f532210bee5342f5b44a12395ccc6d904e675a297189bc1e401cc185beec09873da523466d7fec34ae1574f7a384235cba1ccc9fe7b89ba094167897c845 + languageName: node + linkType: hard + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@npm:7.22.5" @@ -1371,7 +1887,21 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-class-properties@npm:^7.12.1": +"@babel/plugin-proposal-async-generator-functions@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-proposal-async-generator-functions@npm:7.20.7" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-remap-async-to-generator": ^7.18.9 + "@babel/plugin-syntax-async-generators": ^7.8.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 111109ee118c9e69982f08d5e119eab04190b36a0f40e22e873802d941956eee66d2aa5a15f5321e51e3f9aa70a91136451b987fe15185ef8cc547ac88937723 + languageName: node + linkType: hard + +"@babel/plugin-proposal-class-properties@npm:^7.12.1, @babel/plugin-proposal-class-properties@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-class-properties@npm:7.18.6" dependencies: @@ -1383,6 +1913,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-class-static-block@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/plugin-proposal-class-static-block@npm:7.21.0" + dependencies: + "@babel/helper-create-class-features-plugin": ^7.21.0 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-class-static-block": ^7.14.5 + peerDependencies: + "@babel/core": ^7.12.0 + checksum: 236c0ad089e7a7acab776cc1d355330193314bfcd62e94e78f2df35817c6144d7e0e0368976778afd6b7c13e70b5068fa84d7abbf967d4f182e60d03f9ef802b + languageName: node + linkType: hard + "@babel/plugin-proposal-decorators@npm:^7.12.12": version: 7.17.8 resolution: "@babel/plugin-proposal-decorators@npm:7.17.8" @@ -1398,6 +1941,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-dynamic-import@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-dynamic-import@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-dynamic-import": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 96b1c8a8ad8171d39e9ab106be33bde37ae09b22fb2c449afee9a5edf3c537933d79d963dcdc2694d10677cb96da739cdf1b53454e6a5deab9801f28a818bb2f + languageName: node + linkType: hard + "@babel/plugin-proposal-export-default-from@npm:^7.12.1": version: 7.16.7 resolution: "@babel/plugin-proposal-export-default-from@npm:7.16.7" @@ -1410,6 +1965,42 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-export-namespace-from@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-proposal-export-namespace-from@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + "@babel/plugin-syntax-export-namespace-from": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 84ff22bacc5d30918a849bfb7e0e90ae4c5b8d8b65f2ac881803d1cf9068dffbe53bd657b0e4bc4c20b4db301b1c85f1e74183cf29a0dd31e964bd4e97c363ef + languageName: node + linkType: hard + +"@babel/plugin-proposal-json-strings@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-json-strings@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-json-strings": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 25ba0e6b9d6115174f51f7c6787e96214c90dd4026e266976b248a2ed417fe50fddae72843ffb3cbe324014a18632ce5648dfac77f089da858022b49fd608cb3 + languageName: node + linkType: hard + +"@babel/plugin-proposal-logical-assignment-operators@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-proposal-logical-assignment-operators@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: cdd7b8136cc4db3f47714d5266f9e7b592a2ac5a94a5878787ce08890e97c8ab1ca8e94b27bfeba7b0f2b1549a026d9fc414ca2196de603df36fb32633bbdc19 + languageName: node + linkType: hard + "@babel/plugin-proposal-nullish-coalescing-operator@npm:^7.12.1, @babel/plugin-proposal-nullish-coalescing-operator@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-nullish-coalescing-operator@npm:7.18.6" @@ -1422,6 +2013,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-proposal-numeric-separator@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-numeric-separator@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-numeric-separator": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: f370ea584c55bf4040e1f78c80b4eeb1ce2e6aaa74f87d1a48266493c33931d0b6222d8cee3a082383d6bb648ab8d6b7147a06f974d3296ef3bc39c7851683ec + languageName: node + linkType: hard + "@babel/plugin-proposal-object-rest-spread@npm:7.12.1": version: 7.12.1 resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.12.1" @@ -1435,7 +2038,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-object-rest-spread@npm:^7.12.1": +"@babel/plugin-proposal-object-rest-spread@npm:^7.12.1, @babel/plugin-proposal-object-rest-spread@npm:^7.20.7": version: 7.20.7 resolution: "@babel/plugin-proposal-object-rest-spread@npm:7.20.7" dependencies: @@ -1450,7 +2053,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-optional-chaining@npm:^7.12.7, @babel/plugin-proposal-optional-chaining@npm:^7.18.9": +"@babel/plugin-proposal-optional-catch-binding@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-proposal-optional-catch-binding@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7b5b39fb5d8d6d14faad6cb68ece5eeb2fd550fb66b5af7d7582402f974f5bc3684641f7c192a5a57e0f59acfae4aada6786be1eba030881ddc590666eff4d1e + languageName: node + linkType: hard + +"@babel/plugin-proposal-optional-chaining@npm:^7.12.7, @babel/plugin-proposal-optional-chaining@npm:^7.18.9, @babel/plugin-proposal-optional-chaining@npm:^7.20.7, @babel/plugin-proposal-optional-chaining@npm:^7.21.0": version: 7.21.0 resolution: "@babel/plugin-proposal-optional-chaining@npm:7.21.0" dependencies: @@ -1463,7 +2078,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-private-methods@npm:^7.12.1": +"@babel/plugin-proposal-private-methods@npm:^7.12.1, @babel/plugin-proposal-private-methods@npm:^7.18.6": version: 7.18.6 resolution: "@babel/plugin-proposal-private-methods@npm:7.18.6" dependencies: @@ -1484,7 +2099,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-private-property-in-object@npm:^7.12.1": +"@babel/plugin-proposal-private-property-in-object@npm:^7.12.1, @babel/plugin-proposal-private-property-in-object@npm:^7.21.0": version: 7.21.0 resolution: "@babel/plugin-proposal-private-property-in-object@npm:7.21.0" dependencies: @@ -1498,7 +2113,7 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-proposal-unicode-property-regex@npm:^7.4.4": +"@babel/plugin-proposal-unicode-property-regex@npm:^7.18.6, @babel/plugin-proposal-unicode-property-regex@npm:^7.4.4": version: 7.18.6 resolution: "@babel/plugin-proposal-unicode-property-regex@npm:7.18.6" dependencies: @@ -1609,6 +2224,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-syntax-import-assertions@npm:^7.20.0": + version: 7.20.0 + resolution: "@babel/plugin-syntax-import-assertions@npm:7.20.0" + dependencies: + "@babel/helper-plugin-utils": ^7.19.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 6a86220e0aae40164cd3ffaf80e7c076a1be02a8f3480455dddbae05fda8140f429290027604df7a11b3f3f124866e8a6d69dbfa1dda61ee7377b920ad144d5b + languageName: node + linkType: hard + "@babel/plugin-syntax-import-assertions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-syntax-import-assertions@npm:7.22.5" @@ -1664,7 +2290,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-jsx@npm:^7.22.5, @babel/plugin-syntax-jsx@npm:^7.7.2": +"@babel/plugin-syntax-jsx@npm:^7.18.6, @babel/plugin-syntax-jsx@npm:^7.21.4, @babel/plugin-syntax-jsx@npm:^7.7.2": + version: 7.21.4 + resolution: "@babel/plugin-syntax-jsx@npm:7.21.4" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bb7309402a1d4e155f32aa0cf216e1fa8324d6c4cfd248b03280028a015a10e46b6efd6565f515f8913918a3602b39255999c06046f7d4b8a5106be2165d724a + languageName: node + linkType: hard + +"@babel/plugin-syntax-jsx@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-syntax-jsx@npm:7.22.5" dependencies: @@ -1763,7 +2400,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-syntax-typescript@npm:^7.22.5, @babel/plugin-syntax-typescript@npm:^7.7.2": +"@babel/plugin-syntax-typescript@npm:^7.20.0, @babel/plugin-syntax-typescript@npm:^7.7.2": + version: 7.21.4 + resolution: "@babel/plugin-syntax-typescript@npm:7.21.4" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: a59ce2477b7ae8c8945dc37dda292fef9ce46a6507b3d76b03ce7f3a6c9451a6567438b20a78ebcb3955d04095fd1ccd767075a863f79fcc30aa34dcfa441fe0 + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-syntax-typescript@npm:7.22.5" dependencies: @@ -1786,7 +2434,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-arrow-functions@npm:^7.12.1, @babel/plugin-transform-arrow-functions@npm:^7.22.5": +"@babel/plugin-transform-arrow-functions@npm:^7.12.1, @babel/plugin-transform-arrow-functions@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-transform-arrow-functions@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b43cabe3790c2de7710abe32df9a30005eddb2050dadd5d122c6872f679e5710e410f1b90c8f99a2aff7b614cccfecf30e7fd310236686f60d3ed43fd80b9847 + languageName: node + linkType: hard + +"@babel/plugin-transform-arrow-functions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-arrow-functions@npm:7.22.5" dependencies: @@ -1811,6 +2470,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-async-to-generator@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-transform-async-to-generator@npm:7.20.7" + dependencies: + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-remap-async-to-generator": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: fe9ee8a5471b4317c1b9ea92410ace8126b52a600d7cfbfe1920dcac6fb0fad647d2e08beb4fd03c630eb54430e6c72db11e283e3eddc49615c68abd39430904 + languageName: node + linkType: hard + "@babel/plugin-transform-async-to-generator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-async-to-generator@npm:7.22.5" @@ -1824,6 +2496,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-block-scoped-functions@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 0a0df61f94601e3666bf39f2cc26f5f7b22a94450fb93081edbed967bd752ce3f81d1227fefd3799f5ee2722171b5e28db61379234d1bb85b6ec689589f99d7e + languageName: node + linkType: hard + "@babel/plugin-transform-block-scoped-functions@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-block-scoped-functions@npm:7.22.5" @@ -1835,7 +2518,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-block-scoping@npm:^7.12.12, @babel/plugin-transform-block-scoping@npm:^7.22.5": +"@babel/plugin-transform-block-scoping@npm:^7.12.12, @babel/plugin-transform-block-scoping@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/plugin-transform-block-scoping@npm:7.21.0" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 15aacaadbecf96b53a750db1be4990b0d89c7f5bc3e1794b63b49fb219638c1fd25d452d15566d7e5ddf5b5f4e1a0a0055c35c1c7aee323c7b114bf49f66f4b0 + languageName: node + linkType: hard + +"@babel/plugin-transform-block-scoping@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-block-scoping@npm:7.22.5" dependencies: @@ -1871,7 +2565,26 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-classes@npm:^7.12.1, @babel/plugin-transform-classes@npm:^7.22.5": +"@babel/plugin-transform-classes@npm:^7.12.1, @babel/plugin-transform-classes@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/plugin-transform-classes@npm:7.21.0" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-compilation-targets": ^7.20.7 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.21.0 + "@babel/helper-optimise-call-expression": ^7.18.6 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-replace-supers": ^7.20.7 + "@babel/helper-split-export-declaration": ^7.18.6 + globals: ^11.1.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 088ae152074bd0e90f64659169255bfe50393e637ec8765cb2a518848b11b0299e66b91003728fd0a41563a6fdc6b8d548ece698a314fd5447f5489c22e466b7 + languageName: node + linkType: hard + +"@babel/plugin-transform-classes@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-classes@npm:7.22.5" dependencies: @@ -1890,6 +2603,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-computed-properties@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-transform-computed-properties@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/template": ^7.20.7 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: be70e54bda8b469146459f429e5f2bd415023b87b2d5af8b10e48f465ffb02847a3ed162ca60378c004b82db848e4d62e90010d41ded7e7176b6d8d1c2911139 + languageName: node + linkType: hard + "@babel/plugin-transform-computed-properties@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-computed-properties@npm:7.22.5" @@ -1902,7 +2627,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-destructuring@npm:^7.12.1, @babel/plugin-transform-destructuring@npm:^7.22.5": +"@babel/plugin-transform-destructuring@npm:^7.12.1, @babel/plugin-transform-destructuring@npm:^7.21.3": + version: 7.21.3 + resolution: "@babel/plugin-transform-destructuring@npm:7.21.3" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 43ebbe0bfa20287e34427be7c2200ce096c20913775ea75268fb47fe0e55f9510800587e6052c42fe6dffa0daaad95dd465c3e312fd1ef9785648384c45417ac + languageName: node + linkType: hard + +"@babel/plugin-transform-destructuring@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-destructuring@npm:7.22.5" dependencies: @@ -1913,7 +2649,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-dotall-regex@npm:^7.22.5, @babel/plugin-transform-dotall-regex@npm:^7.4.4": +"@babel/plugin-transform-dotall-regex@npm:^7.18.6, @babel/plugin-transform-dotall-regex@npm:^7.4.4": + version: 7.18.6 + resolution: "@babel/plugin-transform-dotall-regex@npm:7.18.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: cbe5d7063eb8f8cca24cd4827bc97f5641166509e58781a5f8aa47fb3d2d786ce4506a30fca2e01f61f18792783a5cb5d96bf5434c3dd1ad0de8c9cc625a53da + languageName: node + linkType: hard + +"@babel/plugin-transform-dotall-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-dotall-regex@npm:7.22.5" dependencies: @@ -1925,6 +2673,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-duplicate-keys@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-duplicate-keys@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 220bf4a9fec5c4d4a7b1de38810350260e8ea08481bf78332a464a21256a95f0df8cd56025f346238f09b04f8e86d4158fafc9f4af57abaef31637e3b58bd4fe + languageName: node + linkType: hard + "@babel/plugin-transform-duplicate-keys@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-duplicate-keys@npm:7.22.5" @@ -1948,6 +2707,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-exponentiation-operator@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.18.6" + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7f70222f6829c82a36005508d34ddbe6fd0974ae190683a8670dd6ff08669aaf51fef2209d7403f9bd543cb2d12b18458016c99a6ed0332ccedb3ea127b01229 + languageName: node + linkType: hard + "@babel/plugin-transform-exponentiation-operator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-exponentiation-operator@npm:7.22.5" @@ -1984,7 +2755,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-for-of@npm:^7.12.1, @babel/plugin-transform-for-of@npm:^7.22.5": +"@babel/plugin-transform-for-of@npm:^7.12.1, @babel/plugin-transform-for-of@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/plugin-transform-for-of@npm:7.21.0" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 2f3f86ca1fab2929fcda6a87e4303d5c635b5f96dc9a45fd4ca083308a3020c79ac33b9543eb4640ef2b79f3586a00ab2d002a7081adb9e9d7440dce30781034 + languageName: node + linkType: hard + +"@babel/plugin-transform-for-of@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-for-of@npm:7.22.5" dependencies: @@ -1995,6 +2777,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-function-name@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-function-name@npm:7.18.9" + dependencies: + "@babel/helper-compilation-targets": ^7.18.9 + "@babel/helper-function-name": ^7.18.9 + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 62dd9c6cdc9714704efe15545e782ee52d74dc73916bf954b4d3bee088fb0ec9e3c8f52e751252433656c09f744b27b757fc06ed99bcde28e8a21600a1d8e597 + languageName: node + linkType: hard + "@babel/plugin-transform-function-name@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-function-name@npm:7.22.5" @@ -2020,6 +2815,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-literals@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-literals@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3458dd2f1a47ac51d9d607aa18f3d321cbfa8560a985199185bed5a906bb0c61ba85575d386460bac9aed43fdd98940041fae5a67dff286f6f967707cff489f8 + languageName: node + linkType: hard + "@babel/plugin-transform-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-literals@npm:7.22.5" @@ -2043,6 +2849,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-member-expression-literals@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-member-expression-literals@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 35a3d04f6693bc6b298c05453d85ee6e41cc806538acb6928427e0e97ae06059f97d2f07d21495fcf5f70d3c13a242e2ecbd09d5c1fcb1b1a73ff528dcb0b695 + languageName: node + linkType: hard + "@babel/plugin-transform-member-expression-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-member-expression-literals@npm:7.22.5" @@ -2054,6 +2871,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-amd@npm:^7.20.11": + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-amd@npm:7.20.11" + dependencies: + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 23665c1c20c8f11c89382b588fb9651c0756d130737a7625baeaadbd3b973bc5bfba1303bedffa8fb99db1e6d848afb01016e1df2b69b18303e946890c790001 + languageName: node + linkType: hard + "@babel/plugin-transform-modules-amd@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-amd@npm:7.22.5" @@ -2066,6 +2895,19 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-commonjs@npm:^7.21.2": + version: 7.21.2 + resolution: "@babel/plugin-transform-modules-commonjs@npm:7.21.2" + dependencies: + "@babel/helper-module-transforms": ^7.21.2 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-simple-access": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 65aa06e3e3792f39b99eb5f807034693ff0ecf80438580f7ae504f4c4448ef04147b1889ea5e6f60f3ad4a12ebbb57c6f1f979a249dadbd8d11fe22f4441918b + languageName: node + linkType: hard + "@babel/plugin-transform-modules-commonjs@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-commonjs@npm:7.22.5" @@ -2079,6 +2921,20 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-systemjs@npm:^7.20.11": + version: 7.20.11 + resolution: "@babel/plugin-transform-modules-systemjs@npm:7.20.11" + dependencies: + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-module-transforms": ^7.20.11 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-validator-identifier": ^7.19.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 4546c47587f88156d66c7eb7808e903cf4bb3f6ba6ac9bc8e3af2e29e92eb9f0b3f44d52043bfd24eb25fa7827fd7b6c8bfeac0cac7584e019b87e1ecbd0e673 + languageName: node + linkType: hard + "@babel/plugin-transform-modules-systemjs@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-systemjs@npm:7.22.5" @@ -2093,6 +2949,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-modules-umd@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-modules-umd@npm:7.18.6" + dependencies: + "@babel/helper-module-transforms": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c3b6796c6f4579f1ba5ab0cdcc73910c1e9c8e1e773c507c8bb4da33072b3ae5df73c6d68f9126dab6e99c24ea8571e1563f8710d7c421fac1cde1e434c20153 + languageName: node + linkType: hard + "@babel/plugin-transform-modules-umd@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-modules-umd@npm:7.22.5" @@ -2105,6 +2973,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-named-capturing-groups-regex@npm:^7.20.5": + version: 7.20.5 + resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.20.5" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.20.5 + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 528c95fb1087e212f17e1c6456df041b28a83c772b9c93d2e407c9d03b72182b0d9d126770c1d6e0b23aab052599ceaf25ed6a2c0627f4249be34a83f6fae853 + languageName: node + linkType: hard + "@babel/plugin-transform-named-capturing-groups-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-named-capturing-groups-regex@npm:7.22.5" @@ -2117,6 +2997,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-new-target@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-new-target@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bd780e14f46af55d0ae8503b3cb81ca86dcc73ed782f177e74f498fff934754f9e9911df1f8f3bd123777eed7c1c1af4d66abab87c8daae5403e7719a6b845d1 + languageName: node + linkType: hard + "@babel/plugin-transform-new-target@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-new-target@npm:7.22.5" @@ -2167,6 +3058,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-object-super@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-object-super@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-replace-supers": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 0fcb04e15deea96ae047c21cb403607d49f06b23b4589055993365ebd7a7d7541334f06bf9642e90075e66efce6ebaf1eb0ef066fbbab802d21d714f1aac3aef + languageName: node + linkType: hard + "@babel/plugin-transform-object-super@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-object-super@npm:7.22.5" @@ -2204,7 +3107,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-parameters@npm:^7.12.1, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.22.5": +"@babel/plugin-transform-parameters@npm:^7.12.1, @babel/plugin-transform-parameters@npm:^7.20.7, @babel/plugin-transform-parameters@npm:^7.21.3": + version: 7.21.3 + resolution: "@babel/plugin-transform-parameters@npm:7.21.3" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c92128d7b1fcf54e2cab186c196bbbf55a9a6de11a83328dc2602649c9dc6d16ef73712beecd776cd49bfdc624b5f56740f4a53568d3deb9505ec666bc869da3 + languageName: node + linkType: hard + +"@babel/plugin-transform-parameters@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-parameters@npm:7.22.5" dependencies: @@ -2241,6 +3155,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-property-literals@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-property-literals@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1c16e64de554703f4b547541de2edda6c01346dd3031d4d29e881aa7733785cd26d53611a4ccf5353f4d3e69097bb0111c0a93ace9e683edd94fea28c4484144 + languageName: node + linkType: hard + "@babel/plugin-transform-property-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-property-literals@npm:7.22.5" @@ -2252,6 +3177,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-display-name@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-display-name@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 51c087ab9e41ef71a29335587da28417536c6f816c292e092ffc0e0985d2f032656801d4dd502213ce32481f4ba6c69402993ffa67f0818a07606ff811e4be49 + languageName: node + linkType: hard + "@babel/plugin-transform-react-display-name@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-display-name@npm:7.22.5" @@ -2263,6 +3199,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-jsx-development@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-jsx-development@npm:7.18.6" + dependencies: + "@babel/plugin-transform-react-jsx": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: ec9fa65db66f938b75c45e99584367779ac3e0af8afc589187262e1337c7c4205ea312877813ae4df9fb93d766627b8968d74ac2ba702e4883b1dbbe4953ecee + languageName: node + linkType: hard + "@babel/plugin-transform-react-jsx-development@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx-development@npm:7.22.5" @@ -2296,7 +3243,22 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-react-jsx@npm:^7.12.12, @babel/plugin-transform-react-jsx@npm:^7.22.5": +"@babel/plugin-transform-react-jsx@npm:^7.12.12, @babel/plugin-transform-react-jsx@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-jsx@npm:7.18.6" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/plugin-syntax-jsx": ^7.18.6 + "@babel/types": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 46129eaf1ab7a7a73e3e8c9d9859b630f5b381c5e19fb1559e2db7b943a7825b6715ad950623fb03fe7bd31ed618ce1d0bd539b13fa030a50c39d5a873a5ba00 + languageName: node + linkType: hard + +"@babel/plugin-transform-react-jsx@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-jsx@npm:7.22.5" dependencies: @@ -2311,6 +3273,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-react-pure-annotations@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.18.6" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 97c4873d409088f437f9084d084615948198dd87fc6723ada0e7e29c5a03623c2f3e03df3f52e7e7d4d23be32a08ea00818bff302812e48713c706713bd06219 + languageName: node + linkType: hard + "@babel/plugin-transform-react-pure-annotations@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-react-pure-annotations@npm:7.22.5" @@ -2323,6 +3297,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-regenerator@npm:^7.20.5": + version: 7.20.5 + resolution: "@babel/plugin-transform-regenerator@npm:7.20.5" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + regenerator-transform: ^0.15.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 13164861e71fb23d84c6270ef5330b03c54d5d661c2c7468f28e21c4f8598558ca0c8c3cb1d996219352946e849d270a61372bc93c8fbe9676e78e3ffd0dea07 + languageName: node + linkType: hard + "@babel/plugin-transform-regenerator@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-regenerator@npm:7.22.5" @@ -2335,6 +3321,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-reserved-words@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-reserved-words@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 0738cdc30abdae07c8ec4b233b30c31f68b3ff0eaa40eddb45ae607c066127f5fa99ddad3c0177d8e2832e3a7d3ad115775c62b431ebd6189c40a951b867a80c + languageName: node + linkType: hard + "@babel/plugin-transform-reserved-words@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-reserved-words@npm:7.22.5" @@ -2346,7 +3343,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.22.5": +"@babel/plugin-transform-shorthand-properties@npm:^7.12.1, @babel/plugin-transform-shorthand-properties@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-shorthand-properties@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: b8e4e8acc2700d1e0d7d5dbfd4fdfb935651913de6be36e6afb7e739d8f9ca539a5150075a0f9b79c88be25ddf45abb912fe7abf525f0b80f5b9d9860de685d7 + languageName: node + linkType: hard + +"@babel/plugin-transform-shorthand-properties@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-shorthand-properties@npm:7.22.5" dependencies: @@ -2357,7 +3365,19 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-spread@npm:^7.12.1, @babel/plugin-transform-spread@npm:^7.22.5": +"@babel/plugin-transform-spread@npm:^7.12.1, @babel/plugin-transform-spread@npm:^7.20.7": + version: 7.20.7 + resolution: "@babel/plugin-transform-spread@npm:7.20.7" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-skip-transparent-expression-wrappers": ^7.20.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 8ea698a12da15718aac7489d4cde10beb8a3eea1f66167d11ab1e625033641e8b328157fd1a0b55dd6531933a160c01fc2e2e61132a385cece05f26429fd0cc2 + languageName: node + linkType: hard + +"@babel/plugin-transform-spread@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-spread@npm:7.22.5" dependencies: @@ -2369,6 +3389,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-sticky-regex@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-sticky-regex@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 68ea18884ae9723443ffa975eb736c8c0d751265859cd3955691253f7fee37d7a0f7efea96c8a062876af49a257a18ea0ed5fea0d95a7b3611ce40f7ee23aee3 + languageName: node + linkType: hard + "@babel/plugin-transform-sticky-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-sticky-regex@npm:7.22.5" @@ -2380,7 +3411,18 @@ __metadata: languageName: node linkType: hard -"@babel/plugin-transform-template-literals@npm:^7.12.1, @babel/plugin-transform-template-literals@npm:^7.22.5": +"@babel/plugin-transform-template-literals@npm:^7.12.1, @babel/plugin-transform-template-literals@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-template-literals@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3d2fcd79b7c345917f69b92a85bdc3ddd68ce2c87dc70c7d61a8373546ccd1f5cb8adc8540b49dfba08e1b82bb7b3bbe23a19efdb2b9c994db2db42906ca9fb2 + languageName: node + linkType: hard + +"@babel/plugin-transform-template-literals@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-template-literals@npm:7.22.5" dependencies: @@ -2391,6 +3433,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-typeof-symbol@npm:^7.18.9": + version: 7.18.9 + resolution: "@babel/plugin-transform-typeof-symbol@npm:7.18.9" + dependencies: + "@babel/helper-plugin-utils": ^7.18.9 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: e754e0d8b8a028c52e10c148088606e3f7a9942c57bd648fc0438e5b4868db73c386a5ed47ab6d6f0594aae29ee5ffc2ffc0f7ebee7fae560a066d6dea811cd4 + languageName: node + linkType: hard + "@babel/plugin-transform-typeof-symbol@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-typeof-symbol@npm:7.22.5" @@ -2402,6 +3455,20 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-typescript@npm:^7.21.3": + version: 7.21.3 + resolution: "@babel/plugin-transform-typescript@npm:7.21.3" + dependencies: + "@babel/helper-annotate-as-pure": ^7.18.6 + "@babel/helper-create-class-features-plugin": ^7.21.0 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/plugin-syntax-typescript": ^7.20.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: c16fd577bf43f633deb76fca2a8527d8ae25968c8efdf327c1955472c3e0257e62992473d1ad7f9ee95379ce2404699af405ea03346055adadd3478ad0ecd117 + languageName: node + linkType: hard + "@babel/plugin-transform-typescript@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-typescript@npm:7.22.5" @@ -2416,6 +3483,17 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-unicode-escapes@npm:^7.18.10": + version: 7.21.5 + resolution: "@babel/plugin-transform-unicode-escapes@npm:7.21.5" + dependencies: + "@babel/helper-plugin-utils": ^7.21.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 6504d642d0449a275191b624bd94d3e434ae154e610bf2f0e3c109068b287d2474f68e1da64b47f21d193cd67b27ee4643877d530187670565cac46e29fd257d + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-escapes@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-unicode-escapes@npm:7.22.5" @@ -2439,6 +3517,18 @@ __metadata: languageName: node linkType: hard +"@babel/plugin-transform-unicode-regex@npm:^7.18.6": + version: 7.18.6 + resolution: "@babel/plugin-transform-unicode-regex@npm:7.18.6" + dependencies: + "@babel/helper-create-regexp-features-plugin": ^7.18.6 + "@babel/helper-plugin-utils": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: d9e18d57536a2d317fb0b7c04f8f55347f3cfacb75e636b4c6fa2080ab13a3542771b5120e726b598b815891fc606d1472ac02b749c69fd527b03847f22dc25e + languageName: node + linkType: hard + "@babel/plugin-transform-unicode-regex@npm:^7.22.5": version: 7.22.5 resolution: "@babel/plugin-transform-unicode-regex@npm:7.22.5" @@ -2463,7 +3553,92 @@ __metadata: languageName: node linkType: hard -"@babel/preset-env@npm:^7.12.11, @babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:^7.21.4, @babel/preset-env@npm:~7.22.5": +"@babel/preset-env@npm:^7.12.11, @babel/preset-env@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/preset-env@npm:7.21.4" + dependencies: + "@babel/compat-data": ^7.21.4 + "@babel/helper-compilation-targets": ^7.21.4 + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-validator-option": ^7.21.0 + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": ^7.18.6 + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": ^7.20.7 + "@babel/plugin-proposal-async-generator-functions": ^7.20.7 + "@babel/plugin-proposal-class-properties": ^7.18.6 + "@babel/plugin-proposal-class-static-block": ^7.21.0 + "@babel/plugin-proposal-dynamic-import": ^7.18.6 + "@babel/plugin-proposal-export-namespace-from": ^7.18.9 + "@babel/plugin-proposal-json-strings": ^7.18.6 + "@babel/plugin-proposal-logical-assignment-operators": ^7.20.7 + "@babel/plugin-proposal-nullish-coalescing-operator": ^7.18.6 + "@babel/plugin-proposal-numeric-separator": ^7.18.6 + "@babel/plugin-proposal-object-rest-spread": ^7.20.7 + "@babel/plugin-proposal-optional-catch-binding": ^7.18.6 + "@babel/plugin-proposal-optional-chaining": ^7.21.0 + "@babel/plugin-proposal-private-methods": ^7.18.6 + "@babel/plugin-proposal-private-property-in-object": ^7.21.0 + "@babel/plugin-proposal-unicode-property-regex": ^7.18.6 + "@babel/plugin-syntax-async-generators": ^7.8.4 + "@babel/plugin-syntax-class-properties": ^7.12.13 + "@babel/plugin-syntax-class-static-block": ^7.14.5 + "@babel/plugin-syntax-dynamic-import": ^7.8.3 + "@babel/plugin-syntax-export-namespace-from": ^7.8.3 + "@babel/plugin-syntax-import-assertions": ^7.20.0 + "@babel/plugin-syntax-json-strings": ^7.8.3 + "@babel/plugin-syntax-logical-assignment-operators": ^7.10.4 + "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 + "@babel/plugin-syntax-numeric-separator": ^7.10.4 + "@babel/plugin-syntax-object-rest-spread": ^7.8.3 + "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 + "@babel/plugin-syntax-optional-chaining": ^7.8.3 + "@babel/plugin-syntax-private-property-in-object": ^7.14.5 + "@babel/plugin-syntax-top-level-await": ^7.14.5 + "@babel/plugin-transform-arrow-functions": ^7.20.7 + "@babel/plugin-transform-async-to-generator": ^7.20.7 + "@babel/plugin-transform-block-scoped-functions": ^7.18.6 + "@babel/plugin-transform-block-scoping": ^7.21.0 + "@babel/plugin-transform-classes": ^7.21.0 + "@babel/plugin-transform-computed-properties": ^7.20.7 + "@babel/plugin-transform-destructuring": ^7.21.3 + "@babel/plugin-transform-dotall-regex": ^7.18.6 + "@babel/plugin-transform-duplicate-keys": ^7.18.9 + "@babel/plugin-transform-exponentiation-operator": ^7.18.6 + "@babel/plugin-transform-for-of": ^7.21.0 + "@babel/plugin-transform-function-name": ^7.18.9 + "@babel/plugin-transform-literals": ^7.18.9 + "@babel/plugin-transform-member-expression-literals": ^7.18.6 + "@babel/plugin-transform-modules-amd": ^7.20.11 + "@babel/plugin-transform-modules-commonjs": ^7.21.2 + "@babel/plugin-transform-modules-systemjs": ^7.20.11 + "@babel/plugin-transform-modules-umd": ^7.18.6 + "@babel/plugin-transform-named-capturing-groups-regex": ^7.20.5 + "@babel/plugin-transform-new-target": ^7.18.6 + "@babel/plugin-transform-object-super": ^7.18.6 + "@babel/plugin-transform-parameters": ^7.21.3 + "@babel/plugin-transform-property-literals": ^7.18.6 + "@babel/plugin-transform-regenerator": ^7.20.5 + "@babel/plugin-transform-reserved-words": ^7.18.6 + "@babel/plugin-transform-shorthand-properties": ^7.18.6 + "@babel/plugin-transform-spread": ^7.20.7 + "@babel/plugin-transform-sticky-regex": ^7.18.6 + "@babel/plugin-transform-template-literals": ^7.18.9 + "@babel/plugin-transform-typeof-symbol": ^7.18.9 + "@babel/plugin-transform-unicode-escapes": ^7.18.10 + "@babel/plugin-transform-unicode-regex": ^7.18.6 + "@babel/preset-modules": ^0.1.5 + "@babel/types": ^7.21.4 + babel-plugin-polyfill-corejs2: ^0.3.3 + babel-plugin-polyfill-corejs3: ^0.6.0 + babel-plugin-polyfill-regenerator: ^0.4.1 + core-js-compat: ^3.25.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 1e328674c4b39e985fa81e5a8eee9aaab353dea4ff1f28f454c5e27a6498c762e25d42e827f5bfc9d7acf6c9b8bc317b5283aa7c83d9fd03c1a89e5c08f334f9 + languageName: node + linkType: hard + +"@babel/preset-env@npm:^7.20.2, @babel/preset-env@npm:~7.22.5": version: 7.22.5 resolution: "@babel/preset-env@npm:7.22.5" dependencies: @@ -2581,7 +3756,23 @@ __metadata: languageName: node linkType: hard -"@babel/preset-react@npm:^7.12.10, @babel/preset-react@npm:^7.18.6": +"@babel/preset-react@npm:^7.12.10": + version: 7.18.6 + resolution: "@babel/preset-react@npm:7.18.6" + dependencies: + "@babel/helper-plugin-utils": ^7.18.6 + "@babel/helper-validator-option": ^7.18.6 + "@babel/plugin-transform-react-display-name": ^7.18.6 + "@babel/plugin-transform-react-jsx": ^7.18.6 + "@babel/plugin-transform-react-jsx-development": ^7.18.6 + "@babel/plugin-transform-react-pure-annotations": ^7.18.6 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 540d9cf0a0cc0bb07e6879994e6fb7152f87dafbac880b56b65e2f528134c7ba33e0cd140b58700c77b2ebf4c81fa6468fed0ba391462d75efc7f8c1699bb4c3 + languageName: node + linkType: hard + +"@babel/preset-react@npm:^7.18.6": version: 7.22.5 resolution: "@babel/preset-react@npm:7.22.5" dependencies: @@ -2597,7 +3788,22 @@ __metadata: languageName: node linkType: hard -"@babel/preset-typescript@npm:^7.12.7, @babel/preset-typescript@npm:^7.21.4, @babel/preset-typescript@npm:~7.22.5": +"@babel/preset-typescript@npm:^7.12.7, @babel/preset-typescript@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/preset-typescript@npm:7.21.4" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + "@babel/helper-validator-option": ^7.21.0 + "@babel/plugin-syntax-jsx": ^7.21.4 + "@babel/plugin-transform-modules-commonjs": ^7.21.2 + "@babel/plugin-transform-typescript": ^7.21.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 83b2f2bf7be3a970acd212177525f58bbb1f2e042b675a47d021a675ae27cf00b6b6babfaf3ae5c980592c9ed1b0712e5197796b691905d25c99f9006478ea06 + languageName: node + linkType: hard + +"@babel/preset-typescript@npm:~7.22.5": version: 7.22.5 resolution: "@babel/preset-typescript@npm:7.22.5" dependencies: @@ -2612,7 +3818,22 @@ __metadata: languageName: node linkType: hard -"@babel/register@npm:^7.12.1, @babel/register@npm:^7.18.9": +"@babel/register@npm:^7.12.1": + version: 7.18.9 + resolution: "@babel/register@npm:7.18.9" + dependencies: + clone-deep: ^4.0.1 + find-cache-dir: ^2.0.0 + make-dir: ^2.1.0 + pirates: ^4.0.5 + source-map-support: ^0.5.16 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 4aeaff97e061a397f632659082ba86c539ef8194697b236d991c10d1c2ea8f73213d3b5b3b2c24625951a1ef726b7a7d2e70f70ffcb37f79ef0c1a745eebef21 + languageName: node + linkType: hard + +"@babel/register@npm:^7.18.9": version: 7.22.5 resolution: "@babel/register@npm:7.22.5" dependencies: @@ -2643,7 +3864,25 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.20.1, @babel/runtime@npm:^7.20.13, @babel/runtime@npm:^7.20.6, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2, @babel/runtime@npm:~7.22.5": +"@babel/runtime@npm:^7.0.0, @babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.0, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.5, @babel/runtime@npm:^7.17.2, @babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.5.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.6.2, @babel/runtime@npm:^7.7.6, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2": + version: 7.21.0 + resolution: "@babel/runtime@npm:7.21.0" + dependencies: + regenerator-runtime: ^0.13.11 + checksum: 7b33e25bfa9e0e1b9e8828bb61b2d32bdd46b41b07ba7cb43319ad08efc6fda8eb89445193e67d6541814627df0ca59122c0ea795e412b99c5183a0540d338ab + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.15.4, @babel/runtime@npm:^7.20.1": + version: 7.21.5 + resolution: "@babel/runtime@npm:7.21.5" + dependencies: + regenerator-runtime: ^0.13.11 + checksum: 358f2779d3187f5c67ad302e8f8d435412925d0b991d133c7d4a7b1ddd5a3fda1b6f34537cb64628dfd96a27ae46df105bed3895b8d754b88cacdded8d1129dd + languageName: node + linkType: hard + +"@babel/runtime@npm:^7.20.13, @babel/runtime@npm:~7.22.5": version: 7.22.5 resolution: "@babel/runtime@npm:7.22.5" dependencies: @@ -2652,6 +3891,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.20.6": + version: 7.20.7 + resolution: "@babel/runtime@npm:7.20.7" + dependencies: + regenerator-runtime: ^0.13.11 + checksum: 4629ce5c46f06cca9cfb9b7fc00d48003335a809888e2b91ec2069a2dcfbfef738480cff32ba81e0b7c290f8918e5c22ddcf2b710001464ee84ba62c7e32a3a3 + languageName: node + linkType: hard + "@babel/runtime@npm:~7.5.4": version: 7.5.5 resolution: "@babel/runtime@npm:7.5.5" @@ -2661,7 +3909,18 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.12.7, @babel/template@npm:^7.22.5, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.12.7, @babel/template@npm:^7.18.10, @babel/template@npm:^7.20.7, @babel/template@npm:^7.3.3": + version: 7.20.7 + resolution: "@babel/template@npm:7.20.7" + dependencies: + "@babel/code-frame": ^7.18.6 + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + checksum: 2eb1a0ab8d415078776bceb3473d07ab746e6bb4c2f6ca46ee70efb284d75c4a32bb0cd6f4f4946dec9711f9c0780e8e5d64b743208deac6f8e9858afadc349e + languageName: node + linkType: hard + +"@babel/template@npm:^7.22.5": version: 7.22.5 resolution: "@babel/template@npm:7.22.5" dependencies: @@ -2672,7 +3931,43 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.22.5, @babel/traverse@npm:^7.7.2": +"@babel/traverse@npm:^7.1.6, @babel/traverse@npm:^7.12.11, @babel/traverse@npm:^7.12.9, @babel/traverse@npm:^7.13.0, @babel/traverse@npm:^7.19.0, @babel/traverse@npm:^7.20.7, @babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.4, @babel/traverse@npm:^7.7.2": + version: 7.21.4 + resolution: "@babel/traverse@npm:7.21.4" + dependencies: + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.4 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.21.0 + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/parser": ^7.21.4 + "@babel/types": ^7.21.4 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: f22f067c2d9b6497abf3d4e53ea71f3aa82a21f2ed434dd69b8c5767f11f2a4c24c8d2f517d2312c9e5248e5c69395fdca1c95a2b3286122c75f5783ddb6f53c + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.21.5": + version: 7.21.5 + resolution: "@babel/traverse@npm:7.21.5" + dependencies: + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.5 + "@babel/helper-environment-visitor": ^7.21.5 + "@babel/helper-function-name": ^7.21.0 + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/parser": ^7.21.5 + "@babel/types": ^7.21.5 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: b403733fa7d858f0c8e224f0434a6ade641bc469a4f92975363391e796629d5bf53e544761dfe85039aab92d5389ebe7721edb309d7a5bb7df2bf74f37bf9f47 + languageName: node + linkType: hard + +"@babel/traverse@npm:^7.22.5": version: 7.22.5 resolution: "@babel/traverse@npm:7.22.5" dependencies: @@ -2690,7 +3985,29 @@ __metadata: languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.7, @babel/types@npm:^7.2.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.12.11, @babel/types@npm:^7.12.7, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9, @babel/types@npm:^7.19.0, @babel/types@npm:^7.2.0, @babel/types@npm:^7.21.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4, @babel/types@npm:^7.8.3": + version: 7.21.5 + resolution: "@babel/types@npm:7.21.5" + dependencies: + "@babel/helper-string-parser": ^7.21.5 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: 43242a99c612d13285ee4af46cc0f1066bcb6ffd38307daef7a76e8c70f36cfc3255eb9e75c8e768b40e761176c313aec4d5c0b9d97a21e494d49d5fd123a9f7 + languageName: node + linkType: hard + +"@babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.2, @babel/types@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/types@npm:7.21.4" + dependencies: + "@babel/helper-string-parser": ^7.19.4 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: 587bc55a91ce003b0f8aa10d70070f8006560d7dc0360dc0406d306a2cb2a10154e2f9080b9c37abec76907a90b330a536406cb75e6bdc905484f37b75c73219 + languageName: node + linkType: hard + +"@babel/types@npm:^7.22.5": version: 7.22.5 resolution: "@babel/types@npm:7.22.5" dependencies: @@ -3325,6 +4642,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/android-arm64@npm:0.17.18" + checksum: ec47777acf96ffe5e36426e5c5715f74e154ddd2a4b2fcd12748250d7b3ded51c5a1a8a5f896f1524e52d3abf4b302aad0b2f30ac23b4efc41de2d01e359a34a + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm64@npm:0.17.19" @@ -3332,6 +4656,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/android-arm@npm:0.17.18" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-arm@npm:0.17.19" @@ -3339,6 +4670,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/android-x64@npm:0.17.18" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/android-x64@npm:0.17.19" @@ -3346,6 +4684,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/darwin-arm64@npm:0.17.18" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-arm64@npm:0.17.19" @@ -3353,6 +4698,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/darwin-x64@npm:0.17.18" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/darwin-x64@npm:0.17.19" @@ -3360,6 +4712,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/freebsd-arm64@npm:0.17.18" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-arm64@npm:0.17.19" @@ -3367,6 +4726,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/freebsd-x64@npm:0.17.18" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/freebsd-x64@npm:0.17.19" @@ -3374,6 +4740,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-arm64@npm:0.17.18" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm64@npm:0.17.19" @@ -3381,6 +4754,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-arm@npm:0.17.18" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-arm@npm:0.17.19" @@ -3388,6 +4768,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-ia32@npm:0.17.18" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ia32@npm:0.17.19" @@ -3395,6 +4782,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-loong64@npm:0.17.18" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-loong64@npm:0.17.19" @@ -3402,6 +4796,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-mips64el@npm:0.17.18" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-mips64el@npm:0.17.19" @@ -3409,6 +4810,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-ppc64@npm:0.17.18" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-ppc64@npm:0.17.19" @@ -3416,6 +4824,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-riscv64@npm:0.17.18" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-riscv64@npm:0.17.19" @@ -3423,6 +4838,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-s390x@npm:0.17.18" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-s390x@npm:0.17.19" @@ -3430,6 +4852,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/linux-x64@npm:0.17.18" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/linux-x64@npm:0.17.19" @@ -3437,6 +4866,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/netbsd-x64@npm:0.17.18" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/netbsd-x64@npm:0.17.19" @@ -3444,6 +4880,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/openbsd-x64@npm:0.17.18" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/openbsd-x64@npm:0.17.19" @@ -3451,6 +4894,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/sunos-x64@npm:0.17.18" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/sunos-x64@npm:0.17.19" @@ -3458,6 +4908,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/win32-arm64@npm:0.17.18" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-arm64@npm:0.17.19" @@ -3465,6 +4922,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/win32-ia32@npm:0.17.18" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-ia32@npm:0.17.19" @@ -3472,6 +4936,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.17.18": + version: 0.17.18 + resolution: "@esbuild/win32-x64@npm:0.17.18" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.17.19": version: 0.17.19 resolution: "@esbuild/win32-x64@npm:0.17.19" @@ -3695,6 +5166,15 @@ __metadata: languageName: node linkType: hard +"@internationalized/date@npm:^3.0.1": + version: 3.0.1 + resolution: "@internationalized/date@npm:3.0.1" + dependencies: + "@babel/runtime": ^7.6.2 + checksum: ff51a00550322a5df3d3051e8ffdf3d7741851149e8ba300883e01402249602e87cc50b27b972753d9af88c5374df83c24adf58cae5e269100cb946a3b12cd56 + languageName: node + linkType: hard + "@internationalized/date@npm:^3.2.0": version: 3.2.0 resolution: "@internationalized/date@npm:3.2.0" @@ -3714,6 +5194,16 @@ __metadata: languageName: node linkType: hard +"@internationalized/message@npm:^3.0.9": + version: 3.0.9 + resolution: "@internationalized/message@npm:3.0.9" + dependencies: + "@babel/runtime": ^7.6.2 + intl-messageformat: ^10.1.0 + checksum: b3f7f5a8e1d8df99efb3463ca07edb976ecf95d28de19a47d92fb19c093052b1a092aeaa226dc69d07143854bdbeb8519a0ac8ba8c900c4b0f565151d735ca7f + languageName: node + linkType: hard + "@internationalized/message@npm:^3.1.0": version: 3.1.0 resolution: "@internationalized/message@npm:3.1.0" @@ -3733,6 +5223,15 @@ __metadata: languageName: node linkType: hard +"@internationalized/number@npm:^3.1.1": + version: 3.1.1 + resolution: "@internationalized/number@npm:3.1.1" + dependencies: + "@babel/runtime": ^7.6.2 + checksum: 9979ea1ca7388de75193c9d36f19d928fbcb715d456d153c30cafadd2ce1ceae011f55c966d424f4561ec04de14d3b48b8fe16a9e2737273829a813c4f7203a3 + languageName: node + linkType: hard + "@internationalized/number@npm:^3.2.0": version: 3.2.0 resolution: "@internationalized/number@npm:3.2.0" @@ -3751,6 +5250,15 @@ __metadata: languageName: node linkType: hard +"@internationalized/string@npm:^3.0.0": + version: 3.0.0 + resolution: "@internationalized/string@npm:3.0.0" + dependencies: + "@babel/runtime": ^7.6.2 + checksum: fc347cf80cd4ee009d1c467dca2c6908a919ad152086bf5e8c1a0aede0383fb317695fc5d82abe033ec90ad62108297130b653b63b9529f2e032999798ae4a81 + languageName: node + linkType: hard + "@internationalized/string@npm:^3.1.0": version: 3.1.0 resolution: "@internationalized/string@npm:3.1.0" @@ -4318,13 +5826,20 @@ __metadata: languageName: node linkType: hard -"@jridgewell/resolve-uri@npm:3.1.0, @jridgewell/resolve-uri@npm:^3.0.3": +"@jridgewell/resolve-uri@npm:3.1.0": version: 3.1.0 resolution: "@jridgewell/resolve-uri@npm:3.1.0" checksum: b5ceaaf9a110fcb2780d1d8f8d4a0bfd216702f31c988d8042e5f8fbe353c55d9b0f55a1733afdc64806f8e79c485d2464680ac48a0d9fcadb9548ee6b81d267 languageName: node linkType: hard +"@jridgewell/resolve-uri@npm:^3.0.3": + version: 3.0.5 + resolution: "@jridgewell/resolve-uri@npm:3.0.5" + checksum: 1ee652b693da7979ac4007926cc3f0a32b657ffeb913e111f44e5b67153d94a2f28a1d560101cc0cf8087625468293a69a00f634a2914e1a6d0817ba2039a913 + languageName: node + linkType: hard + "@jridgewell/set-array@npm:^1.0.1": version: 1.1.2 resolution: "@jridgewell/set-array@npm:1.1.2" @@ -4332,6 +5847,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/source-map@npm:^0.3.2": + version: 0.3.2 + resolution: "@jridgewell/source-map@npm:0.3.2" + dependencies: + "@jridgewell/gen-mapping": ^0.3.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 1b83f0eb944e77b70559a394d5d3b3f98a81fcc186946aceb3ef42d036762b52ef71493c6c0a3b7c1d2f08785f53ba2df1277fe629a06e6109588ff4cdcf7482 + languageName: node + linkType: hard + "@jridgewell/source-map@npm:^0.3.3": version: 0.3.3 resolution: "@jridgewell/source-map@npm:0.3.3" @@ -4342,13 +5867,20 @@ __metadata: languageName: node linkType: hard -"@jridgewell/sourcemap-codec@npm:1.4.14, @jridgewell/sourcemap-codec@npm:^1.4.10": +"@jridgewell/sourcemap-codec@npm:1.4.14": version: 1.4.14 resolution: "@jridgewell/sourcemap-codec@npm:1.4.14" checksum: 61100637b6d173d3ba786a5dff019e1a74b1f394f323c1fee337ff390239f053b87266c7a948777f4b1ee68c01a8ad0ab61e5ff4abb5a012a0b091bec391ab97 languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.4.10": + version: 1.4.11 + resolution: "@jridgewell/sourcemap-codec@npm:1.4.11" + checksum: 3b2afaf8400fb07a36db60e901fcce6a746cdec587310ee9035939d89878e57b2dec8173b0b8f63176f647efa352294049a53c49739098eb907ff81fec2547c8 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -4359,7 +5891,7 @@ __metadata: languageName: node linkType: hard -"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.9": +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.14, @jridgewell/trace-mapping@npm:^0.3.15, @jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.18, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.18 resolution: "@jridgewell/trace-mapping@npm:0.3.18" dependencies: @@ -4407,7 +5939,16 @@ __metadata: languageName: node linkType: hard -"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3, @lezer/highlight@npm:^1.1.6": +"@lezer/highlight@npm:^1.0.0, @lezer/highlight@npm:^1.1.3": + version: 1.1.5 + resolution: "@lezer/highlight@npm:1.1.5" + dependencies: + "@lezer/common": ^1.0.0 + checksum: 1f0b0a3dc7e1f23d889ce7a61d9ce1ba4d3b307205baf58f97252588df4c6751e4c86d39c20cd0bc7ac39ab82ff78a9251db91148b3b069965ec55d5fe9c4ef5 + languageName: node + linkType: hard + +"@lezer/highlight@npm:^1.1.6": version: 1.1.6 resolution: "@lezer/highlight@npm:1.1.6" dependencies: @@ -5563,6 +7104,24 @@ __metadata: languageName: node linkType: hard +"@react-aria/i18n@npm:^3.6.0": + version: 3.6.0 + resolution: "@react-aria/i18n@npm:3.6.0" + dependencies: + "@babel/runtime": ^7.6.2 + "@internationalized/date": ^3.0.1 + "@internationalized/message": ^3.0.9 + "@internationalized/number": ^3.1.1 + "@internationalized/string": ^3.0.0 + "@react-aria/ssr": ^3.3.0 + "@react-aria/utils": ^3.13.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ede9cd611e15fe2975556dfe695bdcb67cbcb8d2dfff7677174f86f1418421491fbbbfd8eab40e724a8db24877d2f980df6e50d26d29d5b3e607ca39b42befc3 + languageName: node + linkType: hard + "@react-aria/i18n@npm:^3.7.0, @react-aria/i18n@npm:^3.7.1": version: 3.7.1 resolution: "@react-aria/i18n@npm:3.7.1" @@ -5968,6 +7527,17 @@ __metadata: languageName: node linkType: hard +"@react-aria/ssr@npm:^3.3.0": + version: 3.3.0 + resolution: "@react-aria/ssr@npm:3.3.0" + dependencies: + "@babel/runtime": ^7.6.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 0b7677ef521c65452460601dce3c264b67baa75ef7c99e9755ea55913765054156b6157c9c42e3d56aba86d1704b8b2aeb7672e4084f2f375fe1ec481e33c8c6 + languageName: node + linkType: hard + "@react-aria/ssr@npm:^3.5.0, @react-aria/ssr@npm:^3.6.0": version: 3.6.0 resolution: "@react-aria/ssr@npm:3.6.0" @@ -6122,6 +7692,21 @@ __metadata: languageName: node linkType: hard +"@react-aria/utils@npm:^3.13.3": + version: 3.13.3 + resolution: "@react-aria/utils@npm:3.13.3" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-aria/ssr": ^3.3.0 + "@react-stately/utils": ^3.5.1 + "@react-types/shared": ^3.14.1 + clsx: ^1.1.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: b6d87ddb8e1d93b00405473099390c854647d81c0419de53cc4a7f02bdcca6d030776fba9f4b241400af13082bafc820dd5ce05c168e8f5a2c43a1b2660fb2ad + languageName: node + linkType: hard + "@react-aria/utils@npm:^3.15.0, @react-aria/utils@npm:^3.16.0": version: 3.16.0 resolution: "@react-aria/utils@npm:3.16.0" @@ -6400,7 +7985,23 @@ __metadata: languageName: node linkType: hard -"@react-stately/calendar@npm:^3.0.2, @react-stately/calendar@npm:^3.2.0": +"@react-stately/calendar@npm:^3.0.2": + version: 3.0.2 + resolution: "@react-stately/calendar@npm:3.0.2" + dependencies: + "@babel/runtime": ^7.6.2 + "@internationalized/date": ^3.0.1 + "@react-stately/utils": ^3.5.1 + "@react-types/calendar": ^3.0.2 + "@react-types/datepicker": ^3.1.1 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: c093cab8761b1e16603abcde63f78dfefdb7fdf4cc269e41602ab3a7c93f9391d29ac68cc66e030c553305af7d96ff9afa3795123211a59316819937a8181956 + languageName: node + linkType: hard + +"@react-stately/calendar@npm:^3.2.0": version: 3.2.0 resolution: "@react-stately/calendar@npm:3.2.0" dependencies: @@ -6416,7 +8017,21 @@ __metadata: languageName: node linkType: hard -"@react-stately/checkbox@npm:^3.2.1, @react-stately/checkbox@npm:^3.4.1": +"@react-stately/checkbox@npm:^3.2.1": + version: 3.2.1 + resolution: "@react-stately/checkbox@npm:3.2.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/toggle": ^3.4.1 + "@react-stately/utils": ^3.5.1 + "@react-types/checkbox": ^3.3.3 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 9035b595fa21cc1bef7e04249ec9df2293e93310dd644e4d32087ce19bd77aae38db3e676f6fdffbde875bc9a318f05dd60c61ab6e0d9b524222438e7ef31cd7 + languageName: node + linkType: hard + +"@react-stately/checkbox@npm:^3.4.1": version: 3.4.1 resolution: "@react-stately/checkbox@npm:3.4.1" dependencies: @@ -6431,7 +8046,19 @@ __metadata: languageName: node linkType: hard -"@react-stately/collections@npm:^3.4.3, @react-stately/collections@npm:^3.7.0": +"@react-stately/collections@npm:^3.4.3": + version: 3.4.3 + resolution: "@react-stately/collections@npm:3.4.3" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f9045cdac0b20f7d7464ac37c0402511f7c5a727676d0cfefef74a553247d0dd1c816ea5804aac318d85ea5708599f9c9c2e8bd37165b5c6eec100e27f3832b9 + languageName: node + linkType: hard + +"@react-stately/collections@npm:^3.7.0": version: 3.7.0 resolution: "@react-stately/collections@npm:3.7.0" dependencies: @@ -6461,7 +8088,24 @@ __metadata: languageName: node linkType: hard -"@react-stately/combobox@npm:^3.2.1, @react-stately/combobox@npm:^3.5.0": +"@react-stately/combobox@npm:^3.2.1": + version: 3.2.1 + resolution: "@react-stately/combobox@npm:3.2.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/list": ^3.5.3 + "@react-stately/menu": ^3.4.1 + "@react-stately/select": ^3.3.1 + "@react-stately/utils": ^3.5.1 + "@react-types/combobox": ^3.5.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 3e9a9050e8e20c96ae703876e652d28d2e3cf9dca79008d8e0f9fd096e88f74215add97e7d4aec9fe93afd64ebd676e5593d5178a28ad76c180207740fc47712 + languageName: node + linkType: hard + +"@react-stately/combobox@npm:^3.5.0": version: 3.5.0 resolution: "@react-stately/combobox@npm:3.5.0" dependencies: @@ -6491,7 +8135,24 @@ __metadata: languageName: node linkType: hard -"@react-stately/datepicker@npm:^3.0.2, @react-stately/datepicker@npm:^3.4.0": +"@react-stately/datepicker@npm:^3.0.2": + version: 3.0.2 + resolution: "@react-stately/datepicker@npm:3.0.2" + dependencies: + "@babel/runtime": ^7.6.2 + "@internationalized/date": ^3.0.1 + "@internationalized/string": ^3.0.0 + "@react-stately/overlays": ^3.4.1 + "@react-stately/utils": ^3.5.1 + "@react-types/datepicker": ^3.1.1 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d0250033d8f4625442177eac1ced6fe446877df9607bd1d7bdea11daae47166072304ee66d4ce1fe12886ef24c0cc1983ac5807a1fe07b05a5749d6b8302f47b + languageName: node + linkType: hard + +"@react-stately/datepicker@npm:^3.4.0": version: 3.4.0 resolution: "@react-stately/datepicker@npm:3.4.0" dependencies: @@ -6521,6 +8182,20 @@ __metadata: languageName: node linkType: hard +"@react-stately/grid@npm:^3.3.1": + version: 3.3.1 + resolution: "@react-stately/grid@npm:3.3.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/selection": ^3.10.3 + "@react-types/grid": ^3.1.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 84e1f24d2dcac51b1ab99f0ad403c965eb9988fa236054a5c137efb1917a455d56a1b78f820a77c3af38895d60a24884cfeac5a482b36390b629612ee8c7e7f3 + languageName: node + linkType: hard + "@react-stately/grid@npm:^3.6.0": version: 3.6.0 resolution: "@react-stately/grid@npm:3.6.0" @@ -6553,7 +8228,22 @@ __metadata: languageName: node linkType: hard -"@react-stately/list@npm:^3.5.3, @react-stately/list@npm:^3.8.0": +"@react-stately/list@npm:^3.5.3": + version: 3.5.3 + resolution: "@react-stately/list@npm:3.5.3" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/collections": ^3.4.3 + "@react-stately/selection": ^3.10.3 + "@react-stately/utils": ^3.5.1 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 162ba719db06a1649bbeb655c78e8a3f3c17a4c02f3318479ce2cc71940052f4a3cc98e67fd604f48ed89f199c731fb6d7c4d6e7b36d53593a0fc9b38d5e465c + languageName: node + linkType: hard + +"@react-stately/list@npm:^3.8.0": version: 3.8.0 resolution: "@react-stately/list@npm:3.8.0" dependencies: @@ -6568,7 +8258,22 @@ __metadata: languageName: node linkType: hard -"@react-stately/menu@npm:^3.4.1, @react-stately/menu@npm:^3.5.1": +"@react-stately/menu@npm:^3.4.1": + version: 3.4.1 + resolution: "@react-stately/menu@npm:3.4.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/overlays": ^3.4.1 + "@react-stately/utils": ^3.5.1 + "@react-types/menu": ^3.7.1 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: a944d6e3a3caf400ffc52738ee8d586db6c6846d0ecc009de4bbedc88202f63d6bbddbd3d577f730f98f28404b077676af4c307f4ba09314c79cf56087a5aa8c + languageName: node + linkType: hard + +"@react-stately/menu@npm:^3.5.1": version: 3.5.1 resolution: "@react-stately/menu@npm:3.5.1" dependencies: @@ -6583,7 +8288,22 @@ __metadata: languageName: node linkType: hard -"@react-stately/numberfield@npm:^3.2.1, @react-stately/numberfield@npm:^3.4.1": +"@react-stately/numberfield@npm:^3.2.1": + version: 3.2.1 + resolution: "@react-stately/numberfield@npm:3.2.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@internationalized/number": ^3.1.1 + "@react-stately/utils": ^3.5.1 + "@react-types/numberfield": ^3.3.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 5698d237c8fbe65cc7ab85c586ffadd92d085f15cab542003419deeccc2f13f2aa839dc844df8853648d892d6580fd4dd15a0b0d4eba86a467afbdb8d3c1675f + languageName: node + linkType: hard + +"@react-stately/numberfield@npm:^3.4.1": version: 3.4.1 resolution: "@react-stately/numberfield@npm:3.4.1" dependencies: @@ -6598,7 +8318,20 @@ __metadata: languageName: node linkType: hard -"@react-stately/overlays@npm:^3.4.1, @react-stately/overlays@npm:^3.5.1": +"@react-stately/overlays@npm:^3.4.1": + version: 3.4.1 + resolution: "@react-stately/overlays@npm:3.4.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/utils": ^3.5.1 + "@react-types/overlays": ^3.6.3 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 3e0e8711c55198b75cb23a682530969c997fdd21c280a9a1356327ff3806252a70ef13e4efc7734902edfd58d6c2cc9d2624a37d8394ad44e9d33b09186510e3 + languageName: node + linkType: hard + +"@react-stately/overlays@npm:^3.5.1": version: 3.5.1 resolution: "@react-stately/overlays@npm:3.5.1" dependencies: @@ -6625,7 +8358,20 @@ __metadata: languageName: node linkType: hard -"@react-stately/radio@npm:^3.5.1, @react-stately/radio@npm:^3.8.0": +"@react-stately/radio@npm:^3.5.1": + version: 3.5.1 + resolution: "@react-stately/radio@npm:3.5.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/utils": ^3.5.1 + "@react-types/radio": ^3.2.3 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 7a60de8afb5d8ccaf33da66613ae55a4b2eca75bacae902574282c33ab66684b1ae5db95b2743fdcc926d1c0464af7e6d837f6a5b85bb00836a9c78ba65c3623 + languageName: node + linkType: hard + +"@react-stately/radio@npm:^3.8.0": version: 3.8.0 resolution: "@react-stately/radio@npm:3.8.0" dependencies: @@ -6639,7 +8385,21 @@ __metadata: languageName: node linkType: hard -"@react-stately/searchfield@npm:^3.3.1, @react-stately/searchfield@npm:^3.4.1": +"@react-stately/searchfield@npm:^3.3.1": + version: 3.3.1 + resolution: "@react-stately/searchfield@npm:3.3.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/utils": ^3.5.1 + "@react-types/searchfield": ^3.3.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f52776a294450382ea9f2abacea6b2972ea4f96ef6ffaff33c62f783881ceb74cd6aec959178499d6c7acf49b3a671d1902f0eb9cc4f2dba486ca88f7514693b + languageName: node + linkType: hard + +"@react-stately/searchfield@npm:^3.4.1": version: 3.4.1 resolution: "@react-stately/searchfield@npm:3.4.1" dependencies: @@ -6653,7 +8413,25 @@ __metadata: languageName: node linkType: hard -"@react-stately/select@npm:^3.3.1, @react-stately/select@npm:^3.5.0": +"@react-stately/select@npm:^3.3.1": + version: 3.3.1 + resolution: "@react-stately/select@npm:3.3.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/collections": ^3.4.3 + "@react-stately/list": ^3.5.3 + "@react-stately/menu": ^3.4.1 + "@react-stately/selection": ^3.10.3 + "@react-stately/utils": ^3.5.1 + "@react-types/select": ^3.6.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 0701cadd640fdea8a3a1c7048e459f701fc8ec9c0ef1fb9692fd70faa5bb7ce23475aba988f57dff90a3db71cbbf8b1ba49edc3df43e744550fbd0e2dcc3575f + languageName: node + linkType: hard + +"@react-stately/select@npm:^3.5.0": version: 3.5.0 resolution: "@react-stately/select@npm:3.5.0" dependencies: @@ -6671,7 +8449,21 @@ __metadata: languageName: node linkType: hard -"@react-stately/selection@npm:^3.10.3, @react-stately/selection@npm:^3.13.0": +"@react-stately/selection@npm:^3.10.3": + version: 3.10.3 + resolution: "@react-stately/selection@npm:3.10.3" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/collections": ^3.4.3 + "@react-stately/utils": ^3.5.1 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f65af198fa9199bc6bcf76279e2131b605e3ce449cc61d404de34993c81f499d0aba34916e8e8fd867d01ae60786ea3c3b725f3c73153674812bf29e64c6a531 + languageName: node + linkType: hard + +"@react-stately/selection@npm:^3.13.0": version: 3.13.0 resolution: "@react-stately/selection@npm:3.13.0" dependencies: @@ -6701,7 +8493,23 @@ __metadata: languageName: node linkType: hard -"@react-stately/slider@npm:^3.2.1, @react-stately/slider@npm:^3.3.1": +"@react-stately/slider@npm:^3.2.1": + version: 3.2.1 + resolution: "@react-stately/slider@npm:3.2.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-aria/i18n": ^3.6.0 + "@react-aria/utils": ^3.13.3 + "@react-stately/utils": ^3.5.1 + "@react-types/shared": ^3.14.1 + "@react-types/slider": ^3.2.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 3d20eae41b79e481fc45cb4671b17ea20010f199790c963766a58067df18c1b83b41b1394ff3b053b32306cd952bad12331dec09c2a6a6c0c060f336aafee0ca + languageName: node + linkType: hard + +"@react-stately/slider@npm:^3.3.1": version: 3.3.1 resolution: "@react-stately/slider@npm:3.3.1" dependencies: @@ -6717,7 +8525,24 @@ __metadata: languageName: node linkType: hard -"@react-stately/table@npm:^3.4.0, @react-stately/table@npm:^3.9.0": +"@react-stately/table@npm:^3.4.0": + version: 3.4.0 + resolution: "@react-stately/table@npm:3.4.0" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/collections": ^3.4.3 + "@react-stately/grid": ^3.3.1 + "@react-stately/selection": ^3.10.3 + "@react-types/grid": ^3.1.3 + "@react-types/shared": ^3.14.1 + "@react-types/table": ^3.3.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f3571875fe9978d1f99554d8a31b3af3ced6ac84fde77ed175620f5ce76952833b98ee41b383f5098489c41f504942cedcffe604a4ec6158bbe320267eb70d01 + languageName: node + linkType: hard + +"@react-stately/table@npm:^3.9.0": version: 3.9.0 resolution: "@react-stately/table@npm:3.9.0" dependencies: @@ -6734,7 +8559,21 @@ __metadata: languageName: node linkType: hard -"@react-stately/tabs@npm:^3.2.1, @react-stately/tabs@npm:^3.4.0": +"@react-stately/tabs@npm:^3.2.1": + version: 3.2.1 + resolution: "@react-stately/tabs@npm:3.2.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/list": ^3.5.3 + "@react-stately/utils": ^3.5.1 + "@react-types/tabs": ^3.1.3 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 593d4ea004ed89156ebf6e2eea401d30e4b06e9eae0f83752550bc5d3d776008577ba0e6baca9791c2e1c0af0f15881a8f95f18923132af08de172cecf097d20 + languageName: node + linkType: hard + +"@react-stately/tabs@npm:^3.4.0": version: 3.4.0 resolution: "@react-stately/tabs@npm:3.4.0" dependencies: @@ -6749,7 +8588,21 @@ __metadata: languageName: node linkType: hard -"@react-stately/toggle@npm:^3.4.1, @react-stately/toggle@npm:^3.5.1": +"@react-stately/toggle@npm:^3.4.1": + version: 3.4.1 + resolution: "@react-stately/toggle@npm:3.4.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/utils": ^3.5.1 + "@react-types/checkbox": ^3.3.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 6cc297ac5c840aa20a6d304947a4d869b857c9dc522b7e77cf798f1815ebd5e5ae1f00aeb812fa452fbbfada1069e814b9e1aaf2751b747f875f8b88d88c21fe + languageName: node + linkType: hard + +"@react-stately/toggle@npm:^3.5.1": version: 3.5.1 resolution: "@react-stately/toggle@npm:3.5.1" dependencies: @@ -6763,7 +8616,21 @@ __metadata: languageName: node linkType: hard -"@react-stately/tooltip@npm:^3.2.1, @react-stately/tooltip@npm:^3.4.0": +"@react-stately/tooltip@npm:^3.2.1": + version: 3.2.1 + resolution: "@react-stately/tooltip@npm:3.2.1" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/overlays": ^3.4.1 + "@react-stately/utils": ^3.5.1 + "@react-types/tooltip": ^3.2.3 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: dbb650986c11284dc45b6c0940e3a5aecb7d5e1af92828ae93b4ec1441b580461340033f427523b16f216afb815ebdc491f7aae361e5cd3bcc3dcea1268c76ab + languageName: node + linkType: hard + +"@react-stately/tooltip@npm:^3.4.0": version: 3.4.0 resolution: "@react-stately/tooltip@npm:3.4.0" dependencies: @@ -6777,7 +8644,22 @@ __metadata: languageName: node linkType: hard -"@react-stately/tree@npm:^3.3.3, @react-stately/tree@npm:^3.6.0": +"@react-stately/tree@npm:^3.3.3": + version: 3.3.3 + resolution: "@react-stately/tree@npm:3.3.3" + dependencies: + "@babel/runtime": ^7.6.2 + "@react-stately/collections": ^3.4.3 + "@react-stately/selection": ^3.10.3 + "@react-stately/utils": ^3.5.1 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 4e1a94cb478124a2443e84dbf0160dd3a5298e79478336f07003b8c5fcdb26043c65a94439a17315cf00e7f66bf6fd5e3e6fbcb44bced3352554d8f7be94899a + languageName: node + linkType: hard + +"@react-stately/tree@npm:^3.6.0": version: 3.6.0 resolution: "@react-stately/tree@npm:3.6.0" dependencies: @@ -6803,6 +8685,17 @@ __metadata: languageName: node linkType: hard +"@react-stately/utils@npm:^3.5.1": + version: 3.5.1 + resolution: "@react-stately/utils@npm:3.5.1" + dependencies: + "@babel/runtime": ^7.6.2 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f748331ae393f97b3e6fcccd37b767358f49229520b9500f82ed4c620bff36ef3c01d4ba9679ac7b9d6d78c5f6e711186c98bd0e6482ec27a6fbf26c5d0aa3cc + languageName: node + linkType: hard + "@react-stately/utils@npm:^3.6.0": version: 3.6.0 resolution: "@react-stately/utils@npm:3.6.0" @@ -6861,6 +8754,18 @@ __metadata: languageName: node linkType: hard +"@react-types/calendar@npm:^3.0.2": + version: 3.0.2 + resolution: "@react-types/calendar@npm:3.0.2" + dependencies: + "@internationalized/date": ^3.0.1 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: a3fd271d85064837c3b7a495e4048c25da1bbbc21015cfadd970f9959e8c802c9152e25ea772ffd815655e392ce07ce75c688726e70bb4cf6959605bc8257c8e + languageName: node + linkType: hard + "@react-types/calendar@npm:^3.2.0": version: 3.2.0 resolution: "@react-types/calendar@npm:3.2.0" @@ -6873,6 +8778,17 @@ __metadata: languageName: node linkType: hard +"@react-types/checkbox@npm:^3.3.3": + version: 3.3.3 + resolution: "@react-types/checkbox@npm:3.3.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: d1da491ff3bf14f894dbeab5ace3a397ead306d2cc4a820d2a653e038a5628495417feb10a4e07c05dcfce208ae9303c35de7e57d1b21a6b59ca1acca11b80d8 + languageName: node + linkType: hard + "@react-types/checkbox@npm:^3.4.3": version: 3.4.3 resolution: "@react-types/checkbox@npm:3.4.3" @@ -6896,6 +8812,17 @@ __metadata: languageName: node linkType: hard +"@react-types/combobox@npm:^3.5.3": + version: 3.5.3 + resolution: "@react-types/combobox@npm:3.5.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 41e1371f1efa48fe4d56afffeca59d1ed9dad75565c3d67fdf9f6c594529113ce9a8053b95d419682364878a6df0fd6a7178c20e6735778eea2abe74de1ca24f + languageName: node + linkType: hard + "@react-types/combobox@npm:^3.6.1": version: 3.6.1 resolution: "@react-types/combobox@npm:3.6.1" @@ -6907,6 +8834,19 @@ __metadata: languageName: node linkType: hard +"@react-types/datepicker@npm:^3.1.1": + version: 3.1.1 + resolution: "@react-types/datepicker@npm:3.1.1" + dependencies: + "@internationalized/date": ^3.0.1 + "@react-types/overlays": ^3.6.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: a3ab8ae22da8105ffebdebb5c89f212cda4d6f2203f7579cbd733e36afb4d2c7e4f986082becacaa66e7d1b4a99ef109952ddae7760d4bb0685e71d53894e316 + languageName: node + linkType: hard + "@react-types/datepicker@npm:^3.3.0": version: 3.3.0 resolution: "@react-types/datepicker@npm:3.3.0" @@ -6932,6 +8872,17 @@ __metadata: languageName: node linkType: hard +"@react-types/grid@npm:^3.1.3": + version: 3.1.3 + resolution: "@react-types/grid@npm:3.1.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 124b366436160ac7b88368a8be37abf4c703bde3fc1275e720f76d9ee8d0a10825fc5dd314b5eb6bb17b7d0c87091608d9b96d9521329ee5baeb94ab08fa3835 + languageName: node + linkType: hard + "@react-types/grid@npm:^3.1.7": version: 3.1.7 resolution: "@react-types/grid@npm:3.1.7" @@ -6988,6 +8939,18 @@ __metadata: languageName: node linkType: hard +"@react-types/menu@npm:^3.7.1": + version: 3.7.1 + resolution: "@react-types/menu@npm:3.7.1" + dependencies: + "@react-types/overlays": ^3.6.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 349443d1bd23bf64a9af57bef57d8ebfebfc6e82dbcef5cfd8ba778afc998f8dc3cebaae80728e6017b0d12b9e5aaea783254df36dd1b82a048b9e3c0e095795 + languageName: node + linkType: hard + "@react-types/menu@npm:^3.9.0": version: 3.9.0 resolution: "@react-types/menu@npm:3.9.0" @@ -7023,6 +8986,17 @@ __metadata: languageName: node linkType: hard +"@react-types/numberfield@npm:^3.3.3": + version: 3.3.3 + resolution: "@react-types/numberfield@npm:3.3.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: b0f6627157dea0ce8a8fa3434c55bbbc69c4b46c84024d6da6f97091eae45c8b4f9b5b842c2ff82712c1b2e407b4acbd0d476ceda25abd3b9402a0b4573b3b52 + languageName: node + linkType: hard + "@react-types/numberfield@npm:^3.4.1": version: 3.4.1 resolution: "@react-types/numberfield@npm:3.4.1" @@ -7034,6 +9008,17 @@ __metadata: languageName: node linkType: hard +"@react-types/overlays@npm:^3.6.3": + version: 3.6.3 + resolution: "@react-types/overlays@npm:3.6.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 8688db82adeda13e922f9805a5c9bd9f64e97e91c0ebf32409964e9d661828a4bb31907551dcdcd611807efa9824ff78aa8cb2ee4b0acfab001cbff5572336d4 + languageName: node + linkType: hard + "@react-types/overlays@npm:^3.7.1": version: 3.7.1 resolution: "@react-types/overlays@npm:3.7.1" @@ -7067,6 +9052,17 @@ __metadata: languageName: node linkType: hard +"@react-types/radio@npm:^3.2.3": + version: 3.2.3 + resolution: "@react-types/radio@npm:3.2.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: ce37d92a7e6665a9900b232aae68978bf1c82b4dffd30cc896c6382df7a9bb8a501a30f1b819d0630f9cbf21af3cb6f51de05fbaeaef4a1f250e5d39276eba59 + languageName: node + linkType: hard + "@react-types/radio@npm:^3.4.1": version: 3.4.1 resolution: "@react-types/radio@npm:3.4.1" @@ -7078,6 +9074,18 @@ __metadata: languageName: node linkType: hard +"@react-types/searchfield@npm:^3.3.3": + version: 3.3.3 + resolution: "@react-types/searchfield@npm:3.3.3" + dependencies: + "@react-types/shared": ^3.14.1 + "@react-types/textfield": ^3.5.3 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: cee59f6ad1da98cc01b81252ef91ebddf0a46df73e4cb3016474c9ad288a0d7b3de2d4607285de97ec23ecaba50391980268ac69b2def096c1fe3a33ecffc686 + languageName: node + linkType: hard + "@react-types/searchfield@npm:^3.4.1": version: 3.4.1 resolution: "@react-types/searchfield@npm:3.4.1" @@ -7090,6 +9098,17 @@ __metadata: languageName: node linkType: hard +"@react-types/select@npm:^3.6.3": + version: 3.6.3 + resolution: "@react-types/select@npm:3.6.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 472d3086e13ca18857659c5a93e36d5e00c4f1077fd627b16ed93641e6ec39aed77a6cb819e3115616486df3178c2e725aef8dd95cd36fc297819b78111d10b8 + languageName: node + linkType: hard + "@react-types/select@npm:^3.8.0": version: 3.8.0 resolution: "@react-types/select@npm:3.8.0" @@ -7110,7 +9129,16 @@ __metadata: languageName: node linkType: hard -"@react-types/shared@npm:^3.14.1, @react-types/shared@npm:^3.18.0": +"@react-types/shared@npm:^3.14.1": + version: 3.14.1 + resolution: "@react-types/shared@npm:3.14.1" + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 117fe230f5a26b7fcaf535c1cfb7c4d42416b0f49d0e0b3436fef2a5851234967908c4e884fc5f2a99a04bee2543543348346a04e1f3f45aaa14c42b6f08491a + languageName: node + linkType: hard + +"@react-types/shared@npm:^3.18.0": version: 3.18.0 resolution: "@react-types/shared@npm:3.18.0" peerDependencies: @@ -7130,6 +9158,17 @@ __metadata: languageName: node linkType: hard +"@react-types/slider@npm:^3.2.1": + version: 3.2.1 + resolution: "@react-types/slider@npm:3.2.1" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 3c64ab2d99fd14debd74181ab4faef43656b274f00443899564e89f3b4b8d9c327184a9c236e4f69c4efc8cba0eca0a0aeae686dcf9a521a7749bab4e0bbdfbb + languageName: node + linkType: hard + "@react-types/slider@npm:^3.5.0": version: 3.5.0 resolution: "@react-types/slider@npm:3.5.0" @@ -7153,6 +9192,18 @@ __metadata: languageName: node linkType: hard +"@react-types/table@npm:^3.3.1": + version: 3.3.1 + resolution: "@react-types/table@npm:3.3.1" + dependencies: + "@react-types/grid": ^3.1.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 1d3e4f8bac6e886944f67c159224893e63ec500f18aaadc74613d9053382c53fd282a7ee9dc21616b7fc0e1291d6ec7ccae87ebca2abdd19e8b371fb8cb46abc + languageName: node + linkType: hard + "@react-types/table@npm:^3.6.0": version: 3.6.0 resolution: "@react-types/table@npm:3.6.0" @@ -7165,6 +9216,17 @@ __metadata: languageName: node linkType: hard +"@react-types/tabs@npm:^3.1.3": + version: 3.1.3 + resolution: "@react-types/tabs@npm:3.1.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 04a95bfb92d2fe44900135bbdd1d256622c51fc90ebecc3374d29eb69bbb77ec13156d39b9fe1806e66b726cf5bbe9ff64822e04e5f3bcacd2429e27c3c260e1 + languageName: node + linkType: hard + "@react-types/tabs@npm:^3.2.1": version: 3.2.1 resolution: "@react-types/tabs@npm:3.2.1" @@ -7187,6 +9249,17 @@ __metadata: languageName: node linkType: hard +"@react-types/textfield@npm:^3.5.3": + version: 3.5.3 + resolution: "@react-types/textfield@npm:3.5.3" + dependencies: + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: f684821edba64e0b525606590800bf2cb6aea98c7304956ed3b2bbcb129ba7734897a9ca1bd056c2f23bf515399fed654071de6a2037942093c2af1c07fad1a9 + languageName: node + linkType: hard + "@react-types/textfield@npm:^3.7.1": version: 3.7.1 resolution: "@react-types/textfield@npm:3.7.1" @@ -7198,6 +9271,18 @@ __metadata: languageName: node linkType: hard +"@react-types/tooltip@npm:^3.2.3": + version: 3.2.3 + resolution: "@react-types/tooltip@npm:3.2.3" + dependencies: + "@react-types/overlays": ^3.6.3 + "@react-types/shared": ^3.14.1 + peerDependencies: + react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 + checksum: 5079ee2e561c2b9a7cc6e9dd22d48a26b0d61d79114aff730b7fc8348e199ec1db9e2ef96725f1682ebd5e93bbb223b920aa507f0f9997d8bc3df76f315077a6 + languageName: node + linkType: hard + "@react-types/tooltip@npm:^3.4.0": version: 3.4.0 resolution: "@react-types/tooltip@npm:3.4.0" @@ -7516,7 +9601,7 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.157": +"@rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/css-in-js@npm:0.31.23" dependencies: @@ -7530,19 +9615,19 @@ __metadata: linkType: hard "@rocket.chat/css-in-js@npm:next": - version: 0.31.23-dev.157 - resolution: "@rocket.chat/css-in-js@npm:0.31.23-dev.157" + version: 0.31.23-dev.103 + resolution: "@rocket.chat/css-in-js@npm:0.31.23-dev.103" dependencies: "@emotion/hash": ^0.9.0 - "@rocket.chat/css-supports": ~0.31.23-dev.157 - "@rocket.chat/memo": ~0.31.23-dev.157 - "@rocket.chat/stylis-logical-props-middleware": ~0.31.23-dev.157 + "@rocket.chat/css-supports": ~0.31.23-dev.103 + "@rocket.chat/memo": ~0.31.23-dev.103 + "@rocket.chat/stylis-logical-props-middleware": ~0.31.23-dev.103 stylis: ~4.1.3 - checksum: f573537eced6bd231e897012269c9e256a1ea8d2c9a65bc05806956dd5d2e8ed0586023721754d44ec1c897ab9551da7eae78f48763be92e123b4481036595ae + checksum: 7fea932e9b60d186722f289989aead15fb6d8af2b11f75a44fefa3158c19d953ad789b161599b33bc5ede7c79c4d6de9b3453ca0dd1fd3ec453fe77891df4e55 languageName: node linkType: hard -"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.157": +"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/css-supports@npm:0.31.23" dependencies: @@ -7618,9 +9703,9 @@ __metadata: linkType: soft "@rocket.chat/emitter@npm:next": - version: 0.31.23-dev.157 - resolution: "@rocket.chat/emitter@npm:0.31.23-dev.157" - checksum: badd657e886eacd3494a6f40fa67c33c346cbaacc412789ea1703f8ac632f490af2397e4f5c2f0234c3acf5c8fdd112ddb287ba0dd40e392ef4ba74e1ca8cb71 + version: 0.31.23-dev.103 + resolution: "@rocket.chat/emitter@npm:0.31.23-dev.103" + checksum: 5163602cf6793678e1e8b1165fbf4c77a2d24f2536af87d3ceb8464da99ce9bff2ccede931bfd6fde9857890678b82f530b29b088ed41ddac7f4a60363e46f1b languageName: node linkType: hard @@ -7711,21 +9796,33 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-hooks@npm:next, @rocket.chat/fuselage-hooks@npm:~0.32.0-dev.296": - version: 0.32.0-dev.296 - resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.296" +"@rocket.chat/fuselage-hooks@npm:next": + version: 0.32.0-dev.274 + resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.274" dependencies: use-sync-external-store: ~1.2.0 peerDependencies: "@rocket.chat/fuselage-tokens": "*" react: ^17.0.2 - checksum: 85f379b87530bd61961794bba9e2b6c458e4101fa9983f3b643245ce8edfcfff6dbda7ecf1fa88ac7d7df6d927a18f113e0e9cdd50a648e9a061c7ac332fc9df + checksum: 32e0872deb05e8b853aed59cab2cc4b3d3a707f0997009170a12b19d252ca09c203f8139b227c90678c0c06e14228c116ab428b9fba81228d1dab981844d8d54 + languageName: node + linkType: hard + +"@rocket.chat/fuselage-hooks@npm:~0.32.0-dev.242": + version: 0.32.0-dev.242 + resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.242" + dependencies: + use-sync-external-store: ~1.2.0 + peerDependencies: + "@rocket.chat/fuselage-tokens": "*" + react: ^17.0.2 + checksum: f9dcfe53370b55c417668c32b08ddc81dcd6b8fbbc44d7968109906b3053985b34ee7d1ecdbea895b1d2eaaae7960fe2559cf0a6136799ea4725b986cb69895e languageName: node linkType: hard "@rocket.chat/fuselage-polyfills@npm:next": - version: 0.31.23-dev.157 - resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.23-dev.157" + version: 0.31.23-dev.103 + resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.23-dev.103" dependencies: "@juggle/resize-observer": ^3.4.0 clipboard-polyfill: ^3.0.3 @@ -7733,13 +9830,13 @@ __metadata: focus-visible: ^5.2.0 focus-within-polyfill: ^5.2.1 new-event-polyfill: ^1.0.1 - checksum: 14da84dc80002d99736d3a148602cc5d82f1c830fdfef84b3db4aa11822c42a2c96e43a3aecb62a4eb222c750399d9f9349b224601c2f2011d375b5be76a3bf3 + checksum: b4dbda8530718a9d585fbc38ed5d6efcb29d8df64982a7434117e11fc75df39c94faa666fd269cb1139d2431349e23fbb21dc5d7c100abb31f18eee27079d56c languageName: node linkType: hard "@rocket.chat/fuselage-toastbar@npm:next": - version: 0.32.0-dev.357 - resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.357" + version: 0.32.0-dev.303 + resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.303" peerDependencies: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" @@ -7747,11 +9844,18 @@ __metadata: "@rocket.chat/styled": "*" react: ^17.0.2 react-dom: ^17.0.2 - checksum: 4a9c81ee42928b500fccec76542b0d812d9e79ab4ac9a89e8ee77ce19db45db78242218a799214f9248f1e04c636934ee70d44febc089ece38c35ac21837c101 + checksum: 479f5c50d1f6ce7a7e4cc4bbd0909f3bc1c3e83e3a9fdeb806d2b0540a17c46e30f8e72b816c954257411bd404f693d83bdf9b2499ce13cee2426d03c1dbec76 languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:next, @rocket.chat/fuselage-tokens@npm:~0.32.0-dev.333": +"@rocket.chat/fuselage-tokens@npm:next": + version: 0.32.0-dev.280 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.280" + checksum: d8c7537512a7950ff6f0f3e23b3d6a2b82c0caea42d5b6e5599257fe2319cbd6f53a229c61413b025b20f576521aca67d9cba378f15e19edddae11a55133672a + languageName: node + linkType: hard + +"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.333": version: 0.32.0-dev.333 resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.333" checksum: dd19009e75e6154361be566ecea5f328902fdf25a10fdcc9ecd18076e424dd5f7d1a4b4c56b7679fe28e4a4ff77e32107d2f83a2c9c98f968ef6ff18f5de95db @@ -7921,9 +10025,9 @@ __metadata: linkType: soft "@rocket.chat/icons@npm:next": - version: 0.32.0-dev.365 - resolution: "@rocket.chat/icons@npm:0.32.0-dev.365" - checksum: 6323cadb2931582cbae4ec4cc271ae881a82915c9a1b0958a9a52c82ed5a5c3dad82699f3e0e22e580bc12695413e772eeb0a6cac670ab0c7863dd73d2f44cb0 + version: 0.32.0-dev.362 + resolution: "@rocket.chat/icons@npm:0.32.0-dev.362" + checksum: 1c2096913e154d0db68b8ccc933294486ff06e250983809ce165f1497df05deec8b5165b47dbefdd013013b4cdf4a3a20f2ac191155b629e5ec4a7bcab5d8870 languageName: node linkType: hard @@ -7941,14 +10045,14 @@ __metadata: linkType: soft "@rocket.chat/layout@npm:next": - version: 0.32.0-dev.266 - resolution: "@rocket.chat/layout@npm:0.32.0-dev.266" + version: 0.32.0-dev.213 + resolution: "@rocket.chat/layout@npm:0.32.0-dev.213" peerDependencies: "@rocket.chat/fuselage": "*" react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: cbecb1564ea77578071dd2571ae3b004485232f035291c02f1b00fbee6c4ae12955c91a3feeb11b153f6b33ea5662b5691d4b7436cd2c8f40a36a8d990100a62 + checksum: 6a7e069f669fe7201e12ffb287064fe724e69294010b6cc426d4c5936f7e2c93cfe9ae92ee480e0ce7864cd5af37b5f9163dbdcefb850571adc3242ed897a5f1 languageName: node linkType: hard @@ -8049,19 +10153,19 @@ __metadata: linkType: soft "@rocket.chat/logo@npm:next": - version: 0.32.0-dev.333 - resolution: "@rocket.chat/logo@npm:0.32.0-dev.333" + version: 0.32.0-dev.279 + resolution: "@rocket.chat/logo@npm:0.32.0-dev.279" dependencies: - "@rocket.chat/fuselage-hooks": ~0.32.0-dev.296 - "@rocket.chat/styled": ~0.31.23-dev.157 + "@rocket.chat/fuselage-hooks": ~0.32.0-dev.242 + "@rocket.chat/styled": ~0.31.23-dev.103 peerDependencies: react: 17.0.2 react-dom: 17.0.2 - checksum: 502931f4aea0730e446aa17e22a881c9a19cbd15da4be5ec26c4fed51a4400fea9365ec5880c5fef3dc0f8a7ace6f1c1204f644a0b217b8eb1658de92214d1e6 + checksum: a79a80a89111633f2c325ace46dc951fe67df94eb192a4ed691e274e19e951f7b22a9a301108f3db4daaac7667f8b9f5f2535a123a0ab72e94796358678acaeb languageName: node linkType: hard -"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.157": +"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/memo@npm:0.31.23" checksum: 070debb940749a2e4463cf767dd65c6967cea664a5bd67c22a812d611f6c3c46d6fe4bb0bf329e43dcd927493413add37c45ae3b05ec08f0b24e9d7385caebdd @@ -8069,18 +10173,18 @@ __metadata: linkType: hard "@rocket.chat/memo@npm:next": - version: 0.31.23-dev.157 - resolution: "@rocket.chat/memo@npm:0.31.23-dev.157" - checksum: e1939d45e2759522463424c0ad876b5e7570a5c7bce561ee4f5e166b16778f794d54a1e2028dc90b1602ff2d20f32ae889b905e894b68af4df6de6844e1fb216 + version: 0.31.23-dev.103 + resolution: "@rocket.chat/memo@npm:0.31.23-dev.103" + checksum: 44b91bb7e02ffbeb2b094e5f283a1ddc3ca205f6ff01f5d044911b8cfc690b7e1472ee9f296de46bc84dcf2eb64d4c314813aa74d2076ec170ec1321f43145a6 languageName: node linkType: hard "@rocket.chat/message-parser@npm:next": - version: 0.32.0-dev.331 - resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.331" + version: 0.32.0-dev.296 + resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.296" dependencies: tldts: ~5.7.112 - checksum: dfb9fc430eba71c32b2ca1ea6357a3d42623a08dc41d9e9d23235fb0c71a939a0255ef3d2860df8f43d89fb23a34cccd0d66c470e01eaec4804b62410dba3808 + checksum: 2a40090b0b5b94e531da3bd1433c6f60b98f19b85eb9ca81db8706fcf360d8245c97fde2257a6c39e9be7fa4a630eede5abf679847ef3c923594c7156f8eb334 languageName: node linkType: hard @@ -8560,8 +10664,8 @@ __metadata: linkType: soft "@rocket.chat/onboarding-ui@npm:next": - version: 0.32.0-dev.383 - resolution: "@rocket.chat/onboarding-ui@npm:0.32.0-dev.383" + version: 0.32.0-dev.329 + resolution: "@rocket.chat/onboarding-ui@npm:0.32.0-dev.329" dependencies: i18next: ~21.6.16 react-hook-form: ~7.27.1 @@ -8576,7 +10680,7 @@ __metadata: react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: 427d4f4f6265a551e623129a118edc2170e689af12f68bb2c2c3e1b52905fdcacd950f34f7dba59173eef9ece4c21860d8366b76c3dc21a7c23f281bc74b3b9a + checksum: 3118c3f3bb91db6e30748a1bf031221b2459b611c260446adf5bafcc9e38fa600a8a3366567a88183b2e93286b538233cb585f0d414b53f02e2eb48b8b2ba3ad languageName: node linkType: hard @@ -8853,22 +10957,22 @@ __metadata: linkType: soft "@rocket.chat/string-helpers@npm:next": - version: 0.31.23-dev.157 - resolution: "@rocket.chat/string-helpers@npm:0.31.23-dev.157" - checksum: 41d49dcc57aaea77f71928f7c5da8c68deebafe4b33718f98d6af4427c7df6e212bf0dca0703d74d9cf7fb24c65ec8215e37f8240544754963e0a83e5d7f62f6 + version: 0.31.23-dev.103 + resolution: "@rocket.chat/string-helpers@npm:0.31.23-dev.103" + checksum: b505564152613d5871dae226b67458d69fbacf2ddeb3f6d10cf81124ccccfd75ad60133bc772db6bf20f4b17f7cfe09fc0ea9c846c2f519b031ce22d0da6688e languageName: node linkType: hard "@rocket.chat/styled@npm:next": - version: 0.31.23-dev.157 - resolution: "@rocket.chat/styled@npm:0.31.23-dev.157" + version: 0.31.23-dev.103 + resolution: "@rocket.chat/styled@npm:0.31.23-dev.103" dependencies: - "@rocket.chat/css-in-js": ~0.31.23-dev.157 - checksum: ab4aaa77b8570111d37233954322e036316bde3bf65cf781e242ca936ec84f4dca4fed7d91cbd7ff4b2aab329f812d7887003f721a9c72fd4544bbc9fbf342af + "@rocket.chat/css-in-js": ~0.31.23-dev.103 + checksum: aadeb2ffe37e01d694012ec49d301a9a8960b82377fc1daa2c332b657414c4c52839010d3f8ce5114312065b8da0cc41b70bdc93fa1916169c53d333d56428bc languageName: node linkType: hard -"@rocket.chat/styled@npm:~0.31.23-dev.157": +"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.157": version: 0.31.23 resolution: "@rocket.chat/styled@npm:0.31.23" dependencies: @@ -8877,7 +10981,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.23, @rocket.chat/stylis-logical-props-middleware@npm:~0.31.23-dev.157": +"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.23, @rocket.chat/stylis-logical-props-middleware@npm:~0.31.23-dev.103": version: 0.31.23 resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.23" dependencies: @@ -9011,9 +11115,9 @@ __metadata: linkType: soft "@rocket.chat/ui-kit@npm:next": - version: 0.32.0-dev.318 - resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.318" - checksum: 4cb64edb3942635e23ba904da95eb5b742b1cadeb89c1aad7fb2acc60b25886cf83cfcd9c785f6a57959e1e3bcd713ac8014ffe45b70bc6d40b1651a206fe6bc + version: 0.32.0-dev.294 + resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.294" + checksum: cf58890f3fbcfdff3df9436a33c4aa333de34369f89902107dee74c50c11b8af793c9c80a8df5046ffcabf0fd90111f31a6fc2030876e838e43ec4c6a5703fc4 languageName: node linkType: hard @@ -11009,7 +13113,33 @@ __metadata: languageName: node linkType: hard -"@types/babel__core@npm:^7, @types/babel__core@npm:^7.1.14, @types/babel__core@npm:~7.20.1": +"@types/babel__core@npm:^7": + version: 7.20.0 + resolution: "@types/babel__core@npm:7.20.0" + dependencies: + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + "@types/babel__generator": "*" + "@types/babel__template": "*" + "@types/babel__traverse": "*" + checksum: 49b601a0a7637f1f387442c8156bd086cfd10ff4b82b0e1994e73a6396643b5435366fb33d6b604eade8467cca594ef97adcbc412aede90bb112ebe88d0ad6df + languageName: node + linkType: hard + +"@types/babel__core@npm:^7.1.14": + version: 7.1.20 + resolution: "@types/babel__core@npm:7.1.20" + dependencies: + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + "@types/babel__generator": "*" + "@types/babel__template": "*" + "@types/babel__traverse": "*" + checksum: a09c4f0456552547a5b8a5a009a3daec4d362f622168f8e08bda0ded2da0a65ab0b1642e23c433b3616721f5701dc34a996c5bde5baeaea53eda98f438043f2c + languageName: node + linkType: hard + +"@types/babel__core@npm:~7.20.1": version: 7.20.1 resolution: "@types/babel__core@npm:7.20.1" dependencies: @@ -11146,7 +13276,14 @@ __metadata: languageName: node linkType: hard -"@types/chai@npm:*, @types/chai@npm:^4.3.5": +"@types/chai@npm:*": + version: 4.3.1 + resolution: "@types/chai@npm:4.3.1" + checksum: 2ee246b76c469cd620a7a1876a73bc597074361b67d547b4bd96a0c1adb43597ede2d8589ab626192e14349d83cbb646cc11e2c179eeeb43ff11596de94d82c4 + languageName: node + linkType: hard + +"@types/chai@npm:^4.3.5": version: 4.3.5 resolution: "@types/chai@npm:4.3.5" checksum: c8f26a88c6b5b53a3275c7f5ff8f107028e3cbb9ff26795fff5f3d9dea07106a54ce9e2dce5e40347f7c4cc35657900aaf0c83934a25a1ae12e61e0f5516e431 @@ -11289,30 +13426,40 @@ __metadata: languageName: node linkType: hard -"@types/eslint@npm:*, @types/eslint@npm:^8.40.2, @types/eslint@npm:~8.40.2": - version: 8.40.2 - resolution: "@types/eslint@npm:8.40.2" +"@types/eslint@npm:*": + version: 8.37.0 + resolution: "@types/eslint@npm:8.37.0" dependencies: "@types/estree": "*" "@types/json-schema": "*" - checksum: a4780e45e677e3af21c44a900846996cb6d9ae8f71d51940942a047163ae93a05444392c005f491ed46aa169f3b25f8be125ab42c5d8bdb571154bf62a7c828a + checksum: 06d3b3fba12004294591b5c7a52e3cec439472195da54e096076b1f2ddfbb8a445973b9681046dd530a6ac31eca502f635abc1e3ce37d03513089358e6f822ee languageName: node linkType: hard -"@types/estree@npm:*, @types/estree@npm:^1.0.0": - version: 1.0.1 - resolution: "@types/estree@npm:1.0.1" - checksum: e9aa175eacb797216fafce4d41e8202c7a75555bc55232dee0f9903d7171f8f19f0ae7d5191bb1a88cb90e65468be508c0df850a9fb81b4433b293a5a749899d +"@types/eslint@npm:^8.40.2, @types/eslint@npm:~8.40.2": + version: 8.40.2 + resolution: "@types/eslint@npm:8.40.2" + dependencies: + "@types/estree": "*" + "@types/json-schema": "*" + checksum: a4780e45e677e3af21c44a900846996cb6d9ae8f71d51940942a047163ae93a05444392c005f491ed46aa169f3b25f8be125ab42c5d8bdb571154bf62a7c828a languageName: node linkType: hard -"@types/estree@npm:^0.0.51": +"@types/estree@npm:*, @types/estree@npm:^0.0.51": version: 0.0.51 resolution: "@types/estree@npm:0.0.51" checksum: e56a3bcf759fd9185e992e7fdb3c6a5f81e8ff120e871641607581fb3728d16c811702a7d40fa5f869b7f7b4437ab6a87eb8d98ffafeee51e85bbe955932a189 languageName: node linkType: hard +"@types/estree@npm:^1.0.0": + version: 1.0.1 + resolution: "@types/estree@npm:1.0.1" + checksum: e9aa175eacb797216fafce4d41e8202c7a75555bc55232dee0f9903d7171f8f19f0ae7d5191bb1a88cb90e65468be508c0df850a9fb81b4433b293a5a749899d + languageName: node + linkType: hard + "@types/express-rate-limit@npm:^5.1.3": version: 5.1.3 resolution: "@types/express-rate-limit@npm:5.1.3" @@ -11322,7 +13469,18 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.33": +"@types/express-serve-static-core@npm:*, @types/express-serve-static-core@npm:^4.17.18": + version: 4.17.31 + resolution: "@types/express-serve-static-core@npm:4.17.31" + dependencies: + "@types/node": "*" + "@types/qs": "*" + "@types/range-parser": "*" + checksum: 009bfbe1070837454a1056aa710d0390ee5fb8c05dfe5a1691cc3e2ca88dc256f80e1ca27cb51a978681631d2f6431bfc9ec352ea46dd0c6eb183d0170bde5df + languageName: node + linkType: hard + +"@types/express-serve-static-core@npm:^4.17.33": version: 4.17.35 resolution: "@types/express-serve-static-core@npm:4.17.35" dependencies: @@ -11334,7 +13492,19 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.17, @types/express@npm:^4.17.8": +"@types/express@npm:*, @types/express@npm:^4.17.13, @types/express@npm:^4.17.8": + version: 4.17.13 + resolution: "@types/express@npm:4.17.13" + dependencies: + "@types/body-parser": "*" + "@types/express-serve-static-core": ^4.17.18 + "@types/qs": "*" + "@types/serve-static": "*" + checksum: 12a2a0e6c4b993fc0854bec665906788aea0d8ee4392389d7a98a5de1eefdd33c9e1e40a91f3afd274011119c506f7b4126acb97fae62ae20b654974d44cba12 + languageName: node + linkType: hard + +"@types/express@npm:^4.17.17": version: 4.17.17 resolution: "@types/express@npm:4.17.17" dependencies: @@ -11503,13 +13673,13 @@ __metadata: languageName: node linkType: hard -"@types/jest@npm:*, @types/jest@npm:^29.5.1, @types/jest@npm:^29.5.2, @types/jest@npm:~29.5.2": - version: 29.5.2 - resolution: "@types/jest@npm:29.5.2" +"@types/jest@npm:*": + version: 29.5.0 + resolution: "@types/jest@npm:29.5.0" dependencies: expect: ^29.0.0 pretty-format: ^29.0.0 - checksum: 7d205599ea3cccc262bad5cc173d3242d6bf8138c99458509230e4ecef07a52d6ddcde5a1dbd49ace655c0af51d2dbadef3748697292ea4d86da19d9e03e19c0 + checksum: cd877e5c56d299cceb8bfdcbb1a77723c706750dd3c3bc47403bc3599b8faff590a3b009c68bb5b11bf7a8c77d1fb01de5e124329b4a08e65f1cdda28b0ecdb8 languageName: node linkType: hard @@ -11523,6 +13693,16 @@ __metadata: languageName: node linkType: hard +"@types/jest@npm:^29.5.1, @types/jest@npm:^29.5.2, @types/jest@npm:~29.5.2": + version: 29.5.2 + resolution: "@types/jest@npm:29.5.2" + dependencies: + expect: ^29.0.0 + pretty-format: ^29.0.0 + checksum: 7d205599ea3cccc262bad5cc173d3242d6bf8138c99458509230e4ecef07a52d6ddcde5a1dbd49ace655c0af51d2dbadef3748697292ea4d86da19d9e03e19c0 + languageName: node + linkType: hard + "@types/jquery@npm:*": version: 3.5.14 resolution: "@types/jquery@npm:3.5.14" @@ -11655,7 +13835,14 @@ __metadata: languageName: node linkType: hard -"@types/lodash@npm:*, @types/lodash@npm:^4.14.167, @types/lodash@npm:^4.14.195": +"@types/lodash@npm:*, @types/lodash@npm:^4.14.167": + version: 4.14.182 + resolution: "@types/lodash@npm:4.14.182" + checksum: 7dd137aa9dbabd632408bd37009d984655164fa1ecc3f2b6eb94afe35bf0a5852cbab6183148d883e9c73a958b7fec9a9bcf7c8e45d41195add6a18c34958209 + languageName: node + linkType: hard + +"@types/lodash@npm:^4.14.195": version: 4.14.195 resolution: "@types/lodash@npm:4.14.195" checksum: 39b75ca635b3fa943d17d3d3aabc750babe4c8212485a4df166fe0516e39288e14b0c60afc6e21913cc0e5a84734633c71e617e2bd14eaa1cf51b8d7799c432e @@ -11814,21 +14001,44 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^14.0.10 || ^16.0.0, @types/node@npm:^14.14.20 || ^16.0.0, @types/node@npm:^16.18.36": - version: 16.18.36 - resolution: "@types/node@npm:16.18.36" - checksum: a9d138fa1269079c60daad6984713dc0b713983f8b34a83edbc6d7957b2e38beab9b2598c9fe99f19d073e20bc212a18aaf82eabdc23ef64dce7d2089a9aab2a +"@types/node@npm:^14.0.10 || ^16.0.0, @types/node@npm:^14.14.20 || ^16.0.0": + version: 16.11.39 + resolution: "@types/node@npm:16.11.39" + checksum: bc97b9773ac6b3194800f990b349fad7f66c6126dacef59291b10a2c8b6813d6f67f947b7e12a6c9952790f7065d576fe38355b8fe034a6af60f317cfc570f69 languageName: node linkType: hard -"@types/node@npm:^14.0.26, @types/node@npm:^14.14.37, @types/node@npm:^14.18.51": +"@types/node@npm:^14.0.26, @types/node@npm:^14.14.37": + version: 14.18.21 + resolution: "@types/node@npm:14.18.21" + checksum: 4ed35b76609647a4e36a194702e31cdda9ed42174ddaf7937bc5498984e98a99e8a42ea895ea17dd9c5ec18080112c29ab670c34f90eb9f7a4703b85b31e34fa + languageName: node + linkType: hard + +"@types/node@npm:^14.18.51": version: 14.18.51 resolution: "@types/node@npm:14.18.51" checksum: 0960a31d2ac605763fe79c8edcee3cb48257d345ce417c019d84ff5d8cd92dd0937674814ab3f169346b4259c29f640556006bcb2c54cfb3e63fa0cf728d320e languageName: node linkType: hard -"@types/nodemailer@npm:*, @types/nodemailer@npm:^6.4.8": +"@types/node@npm:^16.18.36": + version: 16.18.36 + resolution: "@types/node@npm:16.18.36" + checksum: a9d138fa1269079c60daad6984713dc0b713983f8b34a83edbc6d7957b2e38beab9b2598c9fe99f19d073e20bc212a18aaf82eabdc23ef64dce7d2089a9aab2a + languageName: node + linkType: hard + +"@types/nodemailer@npm:*": + version: 6.4.7 + resolution: "@types/nodemailer@npm:6.4.7" + dependencies: + "@types/node": "*" + checksum: dc2a33a89135e04a5bea4921e8645e8453b90e3c3b05f0646f05071c5951ab697ea49ea1e503a690f04cb0a6abfc54967325c5a4036356793cfbb64ba64fb141 + languageName: node + linkType: hard + +"@types/nodemailer@npm:^6.4.8": version: 6.4.8 resolution: "@types/nodemailer@npm:6.4.8" dependencies: @@ -12004,7 +14214,16 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:<18.0.0, @types/react-dom@npm:^17.0.20, @types/react-dom@npm:~17.0.20": +"@types/react-dom@npm:<18.0.0": + version: 17.0.17 + resolution: "@types/react-dom@npm:17.0.17" + dependencies: + "@types/react": ^17 + checksum: 23caf98aa03e968811560f92a2c8f451694253ebe16b670929b24eaf0e7fa62ba549abe9db0ac028a9d8a9086acd6ab9c6c773f163fa21224845edbc00ba6232 + languageName: node + linkType: hard + +"@types/react-dom@npm:^17.0.20, @types/react-dom@npm:~17.0.20": version: 17.0.20 resolution: "@types/react-dom@npm:17.0.20" dependencies: @@ -12013,7 +14232,16 @@ __metadata: languageName: node linkType: hard -"@types/react-dom@npm:^18.0.0, @types/react-dom@npm:^18.2.5": +"@types/react-dom@npm:^18.0.0": + version: 18.0.10 + resolution: "@types/react-dom@npm:18.0.10" + dependencies: + "@types/react": "*" + checksum: ff8282d5005a0b1cd95fb65bf79d3d8485e4cfe2aaf052129033a178684b940014a3f4536bc20d573f8a01cf4c6f4770c74988cef7c2b5cac3041d9f172647e3 + languageName: node + linkType: hard + +"@types/react-dom@npm:^18.2.5": version: 18.2.5 resolution: "@types/react-dom@npm:18.2.5" dependencies: @@ -12034,7 +14262,18 @@ __metadata: languageName: node linkType: hard -"@types/react@npm:*, @types/react@npm:^17, @types/react@npm:^17.0.62, @types/react@npm:~17.0.62": +"@types/react@npm:*, @types/react@npm:^17": + version: 17.0.58 + resolution: "@types/react@npm:17.0.58" + dependencies: + "@types/prop-types": "*" + "@types/scheduler": "*" + csstype: ^3.0.2 + checksum: 4eaf32b86c43f388c681e34a00921c508dd88a1d1022aebfadc5fe802b7c5bed863de1a17eed31e43ca2d65222952dfe79a022055a0e6e4e1ad89fc5a42ec05e + languageName: node + linkType: hard + +"@types/react@npm:^17.0.62, @types/react@npm:~17.0.62": version: 17.0.62 resolution: "@types/react@npm:17.0.62" dependencies: @@ -12260,7 +14499,16 @@ __metadata: languageName: node linkType: hard -"@types/testing-library__jest-dom@npm:^5.9.1, @types/testing-library__jest-dom@npm:~5.14.6": +"@types/testing-library__jest-dom@npm:^5.9.1": + version: 5.14.5 + resolution: "@types/testing-library__jest-dom@npm:5.14.5" + dependencies: + "@types/jest": "*" + checksum: dcb05416758fe88c1f4f3aa97b4699fcb46a5ed8f53c6b81721e66155452a48caf12ecb97dfdfd4130678e65efd66b9fca0ac434b3d63affec84842a84a6bf38 + languageName: node + linkType: hard + +"@types/testing-library__jest-dom@npm:~5.14.6": version: 5.14.6 resolution: "@types/testing-library__jest-dom@npm:5.14.6" dependencies: @@ -12413,7 +14661,16 @@ __metadata: languageName: node linkType: hard -"@types/ws@npm:^8.5.1, @types/ws@npm:^8.5.5": +"@types/ws@npm:^8.5.1": + version: 8.5.4 + resolution: "@types/ws@npm:8.5.4" + dependencies: + "@types/node": "*" + checksum: fefbad20d211929bb996285c4e6f699b12192548afedbe4930ab4384f8a94577c9cd421acaad163cacd36b88649509970a05a0b8f20615b30c501ed5269038d1 + languageName: node + linkType: hard + +"@types/ws@npm:^8.5.5": version: 8.5.5 resolution: "@types/ws@npm:8.5.5" dependencies: @@ -12516,6 +14773,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/scope-manager@npm:5.58.0" + dependencies: + "@typescript-eslint/types": 5.58.0 + "@typescript-eslint/visitor-keys": 5.58.0 + checksum: f0d3df5cc3c461fe63ef89ad886b53c239cc7c1d9061d83d8a9d9c8e087e5501eac84bebff8a954728c17ccea191f235686373d54d2b8b6370af2bcf2b18e062 + languageName: node + linkType: hard + "@typescript-eslint/scope-manager@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/scope-manager@npm:5.60.0" @@ -12543,6 +14810,20 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:5.52.0": + version: 5.52.0 + resolution: "@typescript-eslint/types@npm:5.52.0" + checksum: 018940d61aebf7cf3f7de1b9957446e2ea01f08fe950bef4788c716a3a88f7c42765fe7d80152b0d0428fcd4bd3ace2dfa8c459ba1c59d9a84e951642180f869 + languageName: node + linkType: hard + +"@typescript-eslint/types@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/types@npm:5.58.0" + checksum: 8622a73d73220c4a7111537825f488c0271272032a1d4e129dc722bc6e8b3ec84f64469b2ca3b8dae7da3a9c18953ce1449af51f5f757dad60835eb579ad1d2c + languageName: node + linkType: hard + "@typescript-eslint/types@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/types@npm:5.60.0" @@ -12550,6 +14831,41 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:5.52.0": + version: 5.52.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.52.0" + dependencies: + "@typescript-eslint/types": 5.52.0 + "@typescript-eslint/visitor-keys": 5.52.0 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + semver: ^7.3.7 + tsutils: ^3.21.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: 67d396907fee3d6894e26411a5098a37f07e5d50343189e6361ff7db91c74a7ffe2abd630d11f14c2bda1f4af13edf52b80b11cbccb55b44079c7cec14c9e108 + languageName: node + linkType: hard + +"@typescript-eslint/typescript-estree@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/typescript-estree@npm:5.58.0" + dependencies: + "@typescript-eslint/typescript-estree": 5.52.0 + "@typescript-eslint/utils": 5.52.0 + debug: ^4.3.4 + tsutils: ^3.21.0 + peerDependencies: + eslint: "*" + peerDependenciesMeta: + typescript: + optional: true + checksum: 51b668ec858db0c040a71dff526273945cee4ba5a9b240528d503d02526685882d900cf071c6636a4d9061ed3fd4a7274f7f1a23fba55c4b48b143344b4009c7 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/typescript-estree@npm:5.60.0" @@ -12568,7 +14884,25 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.60.0, @typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.45.0, @typescript-eslint/utils@npm:^5.58.0": +"@typescript-eslint/utils@npm:5.52.0": + version: 5.52.0 + resolution: "@typescript-eslint/utils@npm:5.52.0" + dependencies: + "@eslint-community/eslint-utils": ^4.2.0 + "@types/json-schema": ^7.0.9 + "@types/semver": ^7.3.12 + "@typescript-eslint/scope-manager": 5.58.0 + "@typescript-eslint/types": 5.58.0 + "@typescript-eslint/typescript-estree": 5.58.0 + eslint-scope: ^5.1.1 + semver: ^7.3.7 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: 01906be5262ece36537e9d586e4d2d4791e05752a9354bcb42b1f5bf965f53daa13309c61c3dff5e201ea28c298e4e01cf0c93738afa0099fea0da3b1d8cb3a5 + languageName: node + linkType: hard + +"@typescript-eslint/utils@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/utils@npm:5.60.0" dependencies: @@ -12586,6 +14920,44 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^5.10.0, @typescript-eslint/utils@npm:^5.45.0, @typescript-eslint/utils@npm:^5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/utils@npm:5.58.0" + dependencies: + "@eslint-community/eslint-utils": ^4.2.0 + "@types/json-schema": ^7.0.9 + "@types/semver": ^7.3.12 + "@typescript-eslint/scope-manager": 5.58.0 + "@typescript-eslint/types": 5.58.0 + "@typescript-eslint/typescript-estree": 5.58.0 + eslint-scope: ^5.1.1 + semver: ^7.3.7 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: c618ae67963ecf96b1492c09afaeb363f542f0d6780bcac4af3c26034e3b20034666b2d523aa94821df813aafb57a0b150a7d5c2224fe8257452ad1de2237a58 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.52.0": + version: 5.52.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.52.0" + dependencies: + "@typescript-eslint/types": 5.52.0 + eslint-visitor-keys: ^3.3.0 + checksum: 33b44f0cd35b7b47f34e89d52e47b8d8200f55af306b22db4de104d79f65907458ea022e548f50d966e32fea150432ac9c1ae65b3001b0ad2ac8a17c0211f370 + languageName: node + linkType: hard + +"@typescript-eslint/visitor-keys@npm:5.58.0": + version: 5.58.0 + resolution: "@typescript-eslint/visitor-keys@npm:5.58.0" + dependencies: + "@typescript-eslint/types": 5.58.0 + eslint-visitor-keys: ^3.3.0 + checksum: ab2d1f37660559954c840429ef78bbf71834063557e3e68e435005b4987970b9356fdf217ead53f7a57f66f5488dc478062c5c44bf17053a8bf041733539b98f + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:5.60.0": version: 5.60.0 resolution: "@typescript-eslint/visitor-keys@npm:5.60.0" @@ -12635,6 +15007,16 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/ast@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/ast@npm:1.11.1" + dependencies: + "@webassemblyjs/helper-numbers": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + checksum: 1eee1534adebeece635362f8e834ae03e389281972611408d64be7895fc49f48f98fddbbb5339bf8a72cb101bcb066e8bca3ca1bf1ef47dadf89def0395a8d87 + languageName: node + linkType: hard + "@webassemblyjs/ast@npm:1.11.6, @webassemblyjs/ast@npm:^1.11.5": version: 1.11.6 resolution: "@webassemblyjs/ast@npm:1.11.6" @@ -12656,6 +15038,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/floating-point-hex-parser@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.1" + checksum: b8efc6fa08e4787b7f8e682182d84dfdf8da9d9c77cae5d293818bc4a55c1f419a87fa265ab85252b3e6c1fd323d799efea68d825d341a7c365c64bc14750e97 + languageName: node + linkType: hard + "@webassemblyjs/floating-point-hex-parser@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/floating-point-hex-parser@npm:1.11.6" @@ -12670,6 +15059,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-api-error@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-api-error@npm:1.11.1" + checksum: 0792813f0ed4a0e5ee0750e8b5d0c631f08e927f4bdfdd9fe9105dc410c786850b8c61bff7f9f515fdfb149903bec3c976a1310573a4c6866a94d49bc7271959 + languageName: node + linkType: hard + "@webassemblyjs/helper-api-error@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-api-error@npm:1.11.6" @@ -12684,6 +15080,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-buffer@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-buffer@npm:1.11.1" + checksum: a337ee44b45590c3a30db5a8b7b68a717526cf967ada9f10253995294dbd70a58b2da2165222e0b9830cd4fc6e4c833bf441a721128d1fe2e9a7ab26b36003ce + languageName: node + linkType: hard + "@webassemblyjs/helper-buffer@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-buffer@npm:1.11.6" @@ -12723,6 +15126,17 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-numbers@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-numbers@npm:1.11.1" + dependencies: + "@webassemblyjs/floating-point-hex-parser": 1.11.1 + "@webassemblyjs/helper-api-error": 1.11.1 + "@xtuc/long": 4.2.2 + checksum: 44d2905dac2f14d1e9b5765cf1063a0fa3d57295c6d8930f6c59a36462afecc6e763e8a110b97b342a0f13376166c5d41aa928e6ced92e2f06b071fd0db59d3a + languageName: node + linkType: hard + "@webassemblyjs/helper-numbers@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-numbers@npm:1.11.6" @@ -12734,6 +15148,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-wasm-bytecode@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.1" + checksum: eac400113127832c88f5826bcc3ad1c0db9b3dbd4c51a723cfdb16af6bfcbceb608170fdaac0ab7731a7e18b291be7af68a47fcdb41cfe0260c10857e7413d97 + languageName: node + linkType: hard + "@webassemblyjs/helper-wasm-bytecode@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-wasm-bytecode@npm:1.11.6" @@ -12748,6 +15169,18 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/helper-wasm-section@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + checksum: 617696cfe8ecaf0532763162aaf748eb69096fb27950219bb87686c6b2e66e11cd0614d95d319d0ab1904bc14ebe4e29068b12c3e7c5e020281379741fe4bedf + languageName: node + linkType: hard + "@webassemblyjs/helper-wasm-section@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/helper-wasm-section@npm:1.11.6" @@ -12772,6 +15205,15 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/ieee754@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/ieee754@npm:1.11.1" + dependencies: + "@xtuc/ieee754": ^1.2.0 + checksum: 23a0ac02a50f244471631802798a816524df17e56b1ef929f0c73e3cde70eaf105a24130105c60aff9d64a24ce3b640dad443d6f86e5967f922943a7115022ec + languageName: node + linkType: hard + "@webassemblyjs/ieee754@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/ieee754@npm:1.11.6" @@ -12790,6 +15232,15 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/leb128@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/leb128@npm:1.11.1" + dependencies: + "@xtuc/long": 4.2.2 + checksum: 33ccc4ade2f24de07bf31690844d0b1ad224304ee2062b0e464a610b0209c79e0b3009ac190efe0e6bd568b0d1578d7c3047fc1f9d0197c92fc061f56224ff4a + languageName: node + linkType: hard + "@webassemblyjs/leb128@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/leb128@npm:1.11.6" @@ -12808,6 +15259,13 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/utf8@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/utf8@npm:1.11.1" + checksum: 972c5cfc769d7af79313a6bfb96517253a270a4bf0c33ba486aa43cac43917184fb35e51dfc9e6b5601548cd5931479a42e42c89a13bb591ffabebf30c8a6a0b + languageName: node + linkType: hard + "@webassemblyjs/utf8@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/utf8@npm:1.11.6" @@ -12822,6 +15280,22 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wasm-edit@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-edit@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/helper-wasm-section": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + "@webassemblyjs/wasm-opt": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + "@webassemblyjs/wast-printer": 1.11.1 + checksum: 6d7d9efaec1227e7ef7585a5d7ff0be5f329f7c1c6b6c0e906b18ed2e9a28792a5635e450aca2d136770d0207225f204eff70a4b8fd879d3ac79e1dcc26dbeb9 + languageName: node + linkType: hard + "@webassemblyjs/wasm-edit@npm:1.9.0": version: 1.9.0 resolution: "@webassemblyjs/wasm-edit@npm:1.9.0" @@ -12854,6 +15328,19 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wasm-gen@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-gen@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/ieee754": 1.11.1 + "@webassemblyjs/leb128": 1.11.1 + "@webassemblyjs/utf8": 1.11.1 + checksum: 1f6921e640293bf99fb16b21e09acb59b340a79f986c8f979853a0ae9f0b58557534b81e02ea2b4ef11e929d946708533fd0693c7f3712924128fdafd6465f5b + languageName: node + linkType: hard + "@webassemblyjs/wasm-gen@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/wasm-gen@npm:1.11.6" @@ -12880,6 +15367,18 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wasm-opt@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-opt@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-buffer": 1.11.1 + "@webassemblyjs/wasm-gen": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + checksum: 21586883a20009e2b20feb67bdc451bbc6942252e038aae4c3a08e6f67b6bae0f5f88f20bfc7bd0452db5000bacaf5ab42b98cf9aa034a6c70e9fc616142e1db + languageName: node + linkType: hard + "@webassemblyjs/wasm-opt@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/wasm-opt@npm:1.11.6" @@ -12904,6 +15403,20 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wasm-parser@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wasm-parser@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/helper-api-error": 1.11.1 + "@webassemblyjs/helper-wasm-bytecode": 1.11.1 + "@webassemblyjs/ieee754": 1.11.1 + "@webassemblyjs/leb128": 1.11.1 + "@webassemblyjs/utf8": 1.11.1 + checksum: 1521644065c360e7b27fad9f4bb2df1802d134dd62937fa1f601a1975cde56bc31a57b6e26408b9ee0228626ff3ba1131ae6f74ffb7d718415b6528c5a6dbfc2 + languageName: node + linkType: hard + "@webassemblyjs/wasm-parser@npm:1.11.6, @webassemblyjs/wasm-parser@npm:^1.11.5": version: 1.11.6 resolution: "@webassemblyjs/wasm-parser@npm:1.11.6" @@ -12946,6 +15459,16 @@ __metadata: languageName: node linkType: hard +"@webassemblyjs/wast-printer@npm:1.11.1": + version: 1.11.1 + resolution: "@webassemblyjs/wast-printer@npm:1.11.1" + dependencies: + "@webassemblyjs/ast": 1.11.1 + "@xtuc/long": 4.2.2 + checksum: f15ae4c2441b979a3b4fce78f3d83472fb22350c6dc3fd34bfe7c3da108e0b2360718734d961bba20e7716cb8578e964b870da55b035e209e50ec9db0378a3f7 + languageName: node + linkType: hard + "@webassemblyjs/wast-printer@npm:1.11.6": version: 1.11.6 resolution: "@webassemblyjs/wast-printer@npm:1.11.6" @@ -13000,14 +15523,14 @@ __metadata: languageName: node linkType: hard -"@xmldom/xmldom@npm:0.8.7": +"@xmldom/xmldom@npm:0.8.7, @xmldom/xmldom@npm:^0.8.5": version: 0.8.7 resolution: "@xmldom/xmldom@npm:0.8.7" checksum: 593d4429c2281ee7799adcb6ff8604b68cf30ce0721537e3e380287b423e67c7ac197d90987f932b4fd3febc409ded8435706e7f90fbba6e22e08740477341d1 languageName: node linkType: hard -"@xmldom/xmldom@npm:^0.8.5, @xmldom/xmldom@npm:^0.8.8": +"@xmldom/xmldom@npm:^0.8.8": version: 0.8.8 resolution: "@xmldom/xmldom@npm:0.8.8" checksum: 5f5fc0482fcc599f62e3009516932a265e00f1bb2093fe2c76f3f8d9bfebdd13246f48d4132c9b301c7a573f0fa8712e56aa747dce75b179c2b73f1dde7b5f42 @@ -13102,6 +15625,15 @@ __metadata: languageName: node linkType: hard +"acorn-import-assertions@npm:^1.7.6": + version: 1.8.0 + resolution: "acorn-import-assertions@npm:1.8.0" + peerDependencies: + acorn: ^8 + checksum: 5c4cf7c850102ba7ae0eeae0deb40fb3158c8ca5ff15c0bca43b5c47e307a1de3d8ef761788f881343680ea374631ae9e9615ba8876fee5268dbe068c98bcba6 + languageName: node + linkType: hard + "acorn-import-assertions@npm:^1.9.0": version: 1.9.0 resolution: "acorn-import-assertions@npm:1.9.0" @@ -13152,7 +15684,16 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.1.0, acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1, acorn@npm:^8.8.2": +"acorn@npm:^8.1.0, acorn@npm:^8.2.4, acorn@npm:^8.4.1, acorn@npm:^8.5.0, acorn@npm:^8.7.0, acorn@npm:^8.7.1, acorn@npm:^8.8.0, acorn@npm:^8.8.1": + version: 8.8.2 + resolution: "acorn@npm:8.8.2" + bin: + acorn: bin/acorn + checksum: f790b99a1bf63ef160c967e23c46feea7787e531292bb827126334612c234ed489a0dc2c7ba33156416f0ffa8d25bf2b0fdb7f35c2ba60eb3e960572bece4001 + languageName: node + linkType: hard + +"acorn@npm:^8.8.2": version: 8.9.0 resolution: "acorn@npm:8.9.0" bin: @@ -13304,7 +15845,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.0.1, ajv@npm:^8.11.0, ajv@npm:^8.8.0": +"ajv@npm:^8.0.0, ajv@npm:^8.8.0": version: 8.12.0 resolution: "ajv@npm:8.12.0" dependencies: @@ -13316,6 +15857,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:^8.0.1, ajv@npm:^8.11.0": + version: 8.11.0 + resolution: "ajv@npm:8.11.0" + dependencies: + fast-deep-equal: ^3.1.1 + json-schema-traverse: ^1.0.0 + require-from-string: ^2.0.2 + uri-js: ^4.2.2 + checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef + languageName: node + linkType: hard + "alphanum-sort@npm:^1.0.0": version: 1.0.2 resolution: "alphanum-sort@npm:1.0.2" @@ -13371,7 +15924,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:4.1.1": +"ansi-colors@npm:4.1.1, ansi-colors@npm:^4.1.1": version: 4.1.1 resolution: "ansi-colors@npm:4.1.1" checksum: 138d04a51076cb085da0a7e2d000c5c0bb09f6e772ed5c65c53cb118d37f6c5f1637506d7155fb5f330f0abcf6f12fa2e489ac3f8cdab9da393bf1bb4f9a32b0 @@ -13385,7 +15938,7 @@ __metadata: languageName: node linkType: hard -"ansi-colors@npm:^4.1.1, ansi-colors@npm:^4.1.3": +"ansi-colors@npm:^4.1.3": version: 4.1.3 resolution: "ansi-colors@npm:4.1.3" checksum: a9c2ec842038a1fabc7db9ece7d3177e2fe1c5dc6f0c51ecfbf5f39911427b89c00b5dc6b8bd95f82a26e9b16aaae2e83d45f060e98070ce4d1333038edceb0e @@ -13703,7 +16256,19 @@ __metadata: languageName: node linkType: hard -"args@npm:^5.0.1, args@npm:^5.0.3": +"args@npm:^5.0.1": + version: 5.0.1 + resolution: "args@npm:5.0.1" + dependencies: + camelcase: 5.0.0 + chalk: 2.4.2 + leven: 2.1.0 + mri: 1.1.4 + checksum: 51e2a05f32d15b8e292f000e6b232118df61b8f4fd446b17bb4e99df9ab47fe2c4a01924d7f967a6f08e82f9c19be277b08ed22bceff058aca849144ef8efed3 + languageName: node + linkType: hard + +"args@npm:^5.0.3": version: 5.0.3 resolution: "args@npm:5.0.3" dependencies: @@ -14343,6 +16908,19 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-corejs2@npm:^0.3.3": + version: 0.3.3 + resolution: "babel-plugin-polyfill-corejs2@npm:0.3.3" + dependencies: + "@babel/compat-data": ^7.17.7 + "@babel/helper-define-polyfill-provider": ^0.3.3 + semver: ^6.1.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7db3044993f3dddb3cc3d407bc82e640964a3bfe22de05d90e1f8f7a5cb71460011ab136d3c03c6c1ba428359ebf635688cd6205e28d0469bba221985f5c6179 + languageName: node + linkType: hard + "babel-plugin-polyfill-corejs2@npm:^0.4.3": version: 0.4.3 resolution: "babel-plugin-polyfill-corejs2@npm:0.4.3" @@ -14368,6 +16946,18 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-corejs3@npm:^0.6.0": + version: 0.6.0 + resolution: "babel-plugin-polyfill-corejs3@npm:0.6.0" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.3.3 + core-js-compat: ^3.25.1 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 470bb8c59f7c0912bd77fe1b5a2e72f349b3f65bbdee1d60d6eb7e1f4a085c6f24b2dd5ab4ac6c2df6444a96b070ef6790eccc9edb6a2668c60d33133bfb62c6 + languageName: node + linkType: hard + "babel-plugin-polyfill-corejs3@npm:^0.8.1": version: 0.8.1 resolution: "babel-plugin-polyfill-corejs3@npm:0.8.1" @@ -14380,6 +16970,17 @@ __metadata: languageName: node linkType: hard +"babel-plugin-polyfill-regenerator@npm:^0.4.1": + version: 0.4.1 + resolution: "babel-plugin-polyfill-regenerator@npm:0.4.1" + dependencies: + "@babel/helper-define-polyfill-provider": ^0.3.3 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: ab0355efbad17d29492503230387679dfb780b63b25408990d2e4cf421012dae61d6199ddc309f4d2409ce4e9d3002d187702700dd8f4f8770ebbba651ed066c + languageName: node + linkType: hard + "babel-plugin-polyfill-regenerator@npm:^0.5.0": version: 0.5.0 resolution: "babel-plugin-polyfill-regenerator@npm:0.5.0" @@ -14842,7 +17443,7 @@ __metadata: languageName: node linkType: hard -"body-parser@npm:1.20.2, body-parser@npm:^1.19.0, body-parser@npm:^1.20.2": +"body-parser@npm:1.20.2, body-parser@npm:^1.20.2": version: 1.20.2 resolution: "body-parser@npm:1.20.2" dependencies: @@ -14862,6 +17463,26 @@ __metadata: languageName: node linkType: hard +"body-parser@npm:^1.19.0": + version: 1.20.0 + resolution: "body-parser@npm:1.20.0" + dependencies: + bytes: 3.1.2 + content-type: ~1.0.4 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.10.3 + raw-body: 2.5.1 + type-is: ~1.6.18 + unpipe: 1.0.0 + checksum: 12fffdeac82fe20dddcab7074215d5156e7d02a69ae90cbe9fee1ca3efa2f28ef52097cbea76685ee0a1509c71d85abd0056a08e612c09077cad6277a644cf88 + languageName: node + linkType: hard + "bonjour-service@npm:^1.0.11": version: 1.1.1 resolution: "bonjour-service@npm:1.1.1" @@ -15116,7 +17737,21 @@ __metadata: languageName: node linkType: hard -"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.3, browserslist@npm:^4.21.5": +"browserslist@npm:^4.0.0, browserslist@npm:^4.12.0, browserslist@npm:^4.14.5, browserslist@npm:^4.21.3": + version: 4.21.3 + resolution: "browserslist@npm:4.21.3" + dependencies: + caniuse-lite: ^1.0.30001370 + electron-to-chromium: ^1.4.202 + node-releases: ^2.0.6 + update-browserslist-db: ^1.0.5 + bin: + browserslist: cli.js + checksum: ff512a7bcca1c530e2854bbdfc7be2791d0fb524097a6340e56e1d5924164c7e4e0a9b070de04cdc4c149d15cb4d4275cb7c626ebbce954278a2823aaad2452a + languageName: node + linkType: hard + +"browserslist@npm:^4.21.5": version: 4.21.9 resolution: "browserslist@npm:4.21.9" dependencies: @@ -15623,7 +18258,14 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001503": +"caniuse-lite@npm:^1.0.0, caniuse-lite@npm:^1.0.30001109, caniuse-lite@npm:^1.0.30001370": + version: 1.0.30001464 + resolution: "caniuse-lite@npm:1.0.30001464" + checksum: 67cdee102c1660d62d7b9dbd4740bb7af096236618f2509fd2e0039d50db5f02fb87c21d90b6d573fdcf50deaf3c84503d009e871502b5c221d0ba1dec18ba11 + languageName: node + linkType: hard + +"caniuse-lite@npm:^1.0.30001503": version: 1.0.30001503 resolution: "caniuse-lite@npm:1.0.30001503" checksum: cd5f0af37655ff71ec4ab3c49124d75e0b8b68de625d07ea80e9a82329e616b5203d5dad6865192653be9da50081c06878f081ab069dac0be35adf29aa1599cd @@ -15729,7 +18371,22 @@ __metadata: languageName: node linkType: hard -"chai@npm:>1.9.0, chai@npm:^4.3.7": +"chai@npm:>1.9.0": + version: 4.3.6 + resolution: "chai@npm:4.3.6" + dependencies: + assertion-error: ^1.1.0 + check-error: ^1.0.2 + deep-eql: ^3.0.1 + get-func-name: ^2.0.0 + loupe: ^2.3.1 + pathval: ^1.1.1 + type-detect: ^4.0.5 + checksum: acff93fd537f96d4a4d62dd83810285dffcfccb5089e1bf2a1205b28ec82d93dff551368722893cf85004282df10ee68802737c33c90c5493957ed449ed7ce71 + languageName: node + linkType: hard + +"chai@npm:^4.3.7": version: 4.3.7 resolution: "chai@npm:4.3.7" dependencies: @@ -16013,13 +18670,20 @@ __metadata: languageName: node linkType: hard -"ci-info@npm:^3.1.0, ci-info@npm:^3.2.0": +"ci-info@npm:^3.1.0": version: 3.8.0 resolution: "ci-info@npm:3.8.0" checksum: d0a4d3160497cae54294974a7246202244fff031b0a6ea20dd57b10ec510aa17399c41a1b0982142c105f3255aff2173e5c0dd7302ee1b2f28ba3debda375098 languageName: node linkType: hard +"ci-info@npm:^3.2.0": + version: 3.3.0 + resolution: "ci-info@npm:3.3.0" + checksum: c3d86fe374938ecda5093b1ba39acb535d8309185ba3f23587747c6a057e63f45419b406d880304dbc0e1d72392c9a33e42fe9a1e299209bc0ded5efaa232b66 + languageName: node + linkType: hard + "cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": version: 1.0.4 resolution: "cipher-base@npm:1.0.4" @@ -16424,7 +19088,14 @@ __metadata: languageName: node linkType: hard -"colorette@npm:^2.0.10, colorette@npm:^2.0.14, colorette@npm:^2.0.20, colorette@npm:^2.0.7": +"colorette@npm:^2.0.10, colorette@npm:^2.0.14, colorette@npm:^2.0.7": + version: 2.0.19 + resolution: "colorette@npm:2.0.19" + checksum: 888cf5493f781e5fcf54ce4d49e9d7d698f96ea2b2ef67906834bb319a392c667f9ec69f4a10e268d2946d13a9503d2d19b3abaaaf174e3451bfe91fb9d82427 + languageName: node + linkType: hard + +"colorette@npm:^2.0.20": version: 2.0.20 resolution: "colorette@npm:2.0.20" checksum: 0c016fea2b91b733eb9f4bcdb580018f52c0bc0979443dad930e5037a968237ac53d9beb98e218d2e9235834f8eebce7f8e080422d6194e957454255bde71d3d @@ -16702,7 +19373,14 @@ __metadata: languageName: node linkType: hard -"content-type@npm:^1.0.4, content-type@npm:~1.0.4, content-type@npm:~1.0.5": +"content-type@npm:^1.0.4, content-type@npm:~1.0.4": + version: 1.0.4 + resolution: "content-type@npm:1.0.4" + checksum: 3d93585fda985d1554eca5ebd251994327608d2e200978fdbfba21c0c679914d5faf266d17027de44b34a72c7b0745b18584ecccaa7e1fdfb6a68ac7114f12e0 + languageName: node + linkType: hard + +"content-type@npm:~1.0.5": version: 1.0.5 resolution: "content-type@npm:1.0.5" checksum: 566271e0a251642254cde0f845f9dd4f9856e52d988f4eb0d0dcffbb7a1f8ec98de7a5215fc628f3bce30fe2fb6fd2bc064b562d721658c59b544e2d34ea2766 @@ -16810,7 +19488,16 @@ __metadata: languageName: node linkType: hard -"core-js-compat@npm:^3.30.1, core-js-compat@npm:^3.30.2, core-js-compat@npm:^3.8.1": +"core-js-compat@npm:^3.25.1, core-js-compat@npm:^3.8.1": + version: 3.25.1 + resolution: "core-js-compat@npm:3.25.1" + dependencies: + browserslist: ^4.21.3 + checksum: 34dbec657adc2f660f4cd701709c9c5e27cbd608211c65df09458f80f3e357b9492ba1c5173e17cca72d889dcc6da01268cadf88fb407cf1726e76d301c6143e + languageName: node + linkType: hard + +"core-js-compat@npm:^3.30.1, core-js-compat@npm:^3.30.2": version: 3.31.0 resolution: "core-js-compat@npm:3.31.0" dependencies: @@ -17038,7 +19725,7 @@ __metadata: languageName: node linkType: hard -"cross-fetch@npm:3.1.5": +"cross-fetch@npm:3.1.5, cross-fetch@npm:^3.1.5": version: 3.1.5 resolution: "cross-fetch@npm:3.1.5" dependencies: @@ -17047,7 +19734,7 @@ __metadata: languageName: node linkType: hard -"cross-fetch@npm:^3.0.4, cross-fetch@npm:^3.1.5": +"cross-fetch@npm:^3.0.4": version: 3.1.6 resolution: "cross-fetch@npm:3.1.6" dependencies: @@ -17841,7 +20528,14 @@ __metadata: languageName: node linkType: hard -"decode-uri-component@npm:^0.2.0, decode-uri-component@npm:^0.2.2": +"decode-uri-component@npm:^0.2.0": + version: 0.2.0 + resolution: "decode-uri-component@npm:0.2.0" + checksum: f3749344ab9305ffcfe4bfe300e2dbb61fc6359e2b736812100a3b1b6db0a5668cba31a05e4b45d4d63dbf1a18dfa354cd3ca5bb3ededddabb8cd293f4404f94 + languageName: node + linkType: hard + +"decode-uri-component@npm:^0.2.2": version: 0.2.2 resolution: "decode-uri-component@npm:0.2.2" checksum: 95476a7d28f267292ce745eac3524a9079058bbb35767b76e3ee87d42e34cd0275d2eb19d9d08c3e167f97556e8a2872747f5e65cbebcac8b0c98d83e285f139 @@ -17936,6 +20630,15 @@ __metadata: languageName: node linkType: hard +"deep-eql@npm:^3.0.1": + version: 3.0.1 + resolution: "deep-eql@npm:3.0.1" + dependencies: + type-detect: ^4.0.0 + checksum: 4f4c9fb79eb994fb6e81d4aa8b063adc40c00f831588aa65e20857d5d52f15fb23034a6576ecf886f7ff6222d5ae42e71e9b7d57113e0715b1df7ea1e812b125 + languageName: node + linkType: hard + "deep-eql@npm:^4.1.2": version: 4.1.3 resolution: "deep-eql@npm:4.1.3" @@ -18436,7 +21139,14 @@ __metadata: languageName: node linkType: hard -"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0, domelementtype@npm:^2.3.0": +"domelementtype@npm:^2.0.1, domelementtype@npm:^2.2.0": + version: 2.2.0 + resolution: "domelementtype@npm:2.2.0" + checksum: 24cb386198640cd58aa36f8c987f2ea61859929106d06ffcc8f547e70cb2ed82a6dc56dcb8252b21fba1f1ea07df6e4356d60bfe57f77114ca1aed6828362629 + languageName: node + linkType: hard + +"domelementtype@npm:^2.3.0": version: 2.3.0 resolution: "domelementtype@npm:2.3.0" checksum: ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6 @@ -18702,6 +21412,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.4.202": + version: 1.4.249 + resolution: "electron-to-chromium@npm:1.4.249" + checksum: 830a35a157af7ae226f1528d727e369bb13f53bc7a4edefdf718651ace09d7d7b4bd7b70d33b5018b8eff6cf99ee58409b6c4140cd6d56350c1966f280ac5c93 + languageName: node + linkType: hard + "electron-to-chromium@npm:^1.4.431": version: 1.4.433 resolution: "electron-to-chromium@npm:1.4.433" @@ -18890,6 +21607,16 @@ __metadata: languageName: node linkType: hard +"enhanced-resolve@npm:^5.10.0": + version: 5.10.0 + resolution: "enhanced-resolve@npm:5.10.0" + dependencies: + graceful-fs: ^4.2.4 + tapable: ^2.2.0 + checksum: 0bb9830704db271610f900e8d79d70a740ea16f251263362b0c91af545576d09fe50103496606c1300a05e588372d6f9780a9bc2e30ce8ef9b827ec8f44687ff + languageName: node + linkType: hard + "enhanced-resolve@npm:^5.15.0": version: 5.15.0 resolution: "enhanced-resolve@npm:5.15.0" @@ -18923,13 +21650,20 @@ __metadata: languageName: node linkType: hard -"entities@npm:^4.2.0, entities@npm:^4.4.0": +"entities@npm:^4.2.0": version: 4.5.0 resolution: "entities@npm:4.5.0" checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7 languageName: node linkType: hard +"entities@npm:^4.4.0": + version: 4.4.0 + resolution: "entities@npm:4.4.0" + checksum: 84d250329f4b56b40fa93ed067b194db21e8815e4eb9b59f43a086f0ecd342814f6bc483de8a77da5d64e0f626033192b1b4f1792232a7ea6b970ebe0f3187c2 + languageName: node + linkType: hard + "entities@npm:~2.0.0": version: 2.0.3 resolution: "entities@npm:2.0.3" @@ -19063,6 +21797,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^0.9.0": + version: 0.9.3 + resolution: "es-module-lexer@npm:0.9.3" + checksum: 84bbab23c396281db2c906c766af58b1ae2a1a2599844a504df10b9e8dc77ec800b3211fdaa133ff700f5703d791198807bba25d9667392d27a5e9feda344da8 + languageName: node + linkType: hard + "es-module-lexer@npm:^1.2.1": version: 1.3.0 resolution: "es-module-lexer@npm:1.3.0" @@ -19118,7 +21859,7 @@ __metadata: languageName: node linkType: hard -"esbuild@npm:^0.17.5, esbuild@npm:^0.17.6": +"esbuild@npm:^0.17.5": version: 0.17.19 resolution: "esbuild@npm:0.17.19" dependencies: @@ -19195,6 +21936,83 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.17.6": + version: 0.17.18 + resolution: "esbuild@npm:0.17.18" + dependencies: + "@esbuild/android-arm": 0.17.18 + "@esbuild/android-arm64": 0.17.18 + "@esbuild/android-x64": 0.17.18 + "@esbuild/darwin-arm64": 0.17.18 + "@esbuild/darwin-x64": 0.17.18 + "@esbuild/freebsd-arm64": 0.17.18 + "@esbuild/freebsd-x64": 0.17.18 + "@esbuild/linux-arm": 0.17.18 + "@esbuild/linux-arm64": 0.17.18 + "@esbuild/linux-ia32": 0.17.18 + "@esbuild/linux-loong64": 0.17.18 + "@esbuild/linux-mips64el": 0.17.18 + "@esbuild/linux-ppc64": 0.17.18 + "@esbuild/linux-riscv64": 0.17.18 + "@esbuild/linux-s390x": 0.17.18 + "@esbuild/linux-x64": 0.17.18 + "@esbuild/netbsd-x64": 0.17.18 + "@esbuild/openbsd-x64": 0.17.18 + "@esbuild/sunos-x64": 0.17.18 + "@esbuild/win32-arm64": 0.17.18 + "@esbuild/win32-ia32": 0.17.18 + "@esbuild/win32-x64": 0.17.18 + dependenciesMeta: + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 900b333f649fd89804216fb61fb5a0ffadc6dc37a2ec3b5981b588f72821676ea649a7c0ec785f0dbe6e774080b084c8af5f6ee7adbc1b138faf2a8c35e2c69c + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -19526,7 +22344,14 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1": +"eslint-visitor-keys@npm:^3.3.0": + version: 3.4.0 + resolution: "eslint-visitor-keys@npm:3.4.0" + checksum: 33159169462d3989321a1ec1e9aaaf6a24cc403d5d347e9886d1b5bfe18ffa1be73bdc6203143a28a606b142b1af49787f33cff0d6d0813eb5f2e8d2e1a6043c + languageName: node + linkType: hard + +"eslint-visitor-keys@npm:^3.4.1": version: 3.4.1 resolution: "eslint-visitor-keys@npm:3.4.1" checksum: f05121d868202736b97de7d750847a328fcfa8593b031c95ea89425333db59676ac087fa905eba438d0a3c5769632f828187e0c1a0d271832a2153c1d3661c2c @@ -19757,7 +22582,14 @@ __metadata: languageName: node linkType: hard -"eventemitter2@npm:^6.3.1, eventemitter2@npm:^6.4.9": +"eventemitter2@npm:^6.3.1": + version: 6.4.5 + resolution: "eventemitter2@npm:6.4.5" + checksum: 84504f9cf0cc30205cdd46783fe9df3733435e5097f13070b678023110b5ef07847651808ae280cd94c42cd5976880211c7a40321a8ff8fa56f7c5f9c5c11960 + languageName: node + linkType: hard + +"eventemitter2@npm:^6.4.9": version: 6.4.9 resolution: "eventemitter2@npm:6.4.9" checksum: be59577c1e1c35509c7ba0e2624335c35bbcfd9485b8a977384c6cc6759341ea1a98d3cb9dbaa5cea4fff9b687e504504e3f9c2cc1674cf3bd8a43a7c74ea3eb @@ -20176,7 +23008,20 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9": +"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.7, fast-glob@npm:^3.2.9": + version: 3.2.11 + resolution: "fast-glob@npm:3.2.11" + dependencies: + "@nodelib/fs.stat": ^2.0.2 + "@nodelib/fs.walk": ^1.2.3 + glob-parent: ^5.1.2 + merge2: ^1.3.0 + micromatch: ^4.0.4 + checksum: f473105324a7780a20c06de842e15ddbb41d3cb7e71d1e4fe6e8373204f22245d54f5ab9e2061e6a1c613047345954d29b022e0e76f5c28b1df9858179a0e6d7 + languageName: node + linkType: hard + +"fast-glob@npm:^3.2.12": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" dependencies: @@ -20745,7 +23590,17 @@ __metadata: languageName: node linkType: hard -"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.7, follow-redirects@npm:^1.14.8, follow-redirects@npm:^1.14.9, follow-redirects@npm:^1.15.2": +"follow-redirects@npm:^1.0.0, follow-redirects@npm:^1.14.0, follow-redirects@npm:^1.14.4, follow-redirects@npm:^1.14.7, follow-redirects@npm:^1.14.8, follow-redirects@npm:^1.14.9": + version: 1.15.1 + resolution: "follow-redirects@npm:1.15.1" + peerDependenciesMeta: + debug: + optional: true + checksum: 6aa4e3e3cdfa3b9314801a1cd192ba756a53479d9d8cca65bf4db3a3e8834e62139245cd2f9566147c8dfe2efff1700d3e6aefd103de4004a7b99985e71dd533 + languageName: node + linkType: hard + +"follow-redirects@npm:^1.15.2": version: 1.15.2 resolution: "follow-redirects@npm:1.15.2" peerDependenciesMeta: @@ -21024,6 +23879,13 @@ __metadata: languageName: node linkType: hard +"fs-monkey@npm:^1.0.3": + version: 1.0.3 + resolution: "fs-monkey@npm:1.0.3" + checksum: cf50804833f9b88a476911ae911fe50f61a98d986df52f890bd97e7262796d023698cb2309fa9b74fdd8974f04315b648748a0a8ee059e7d5257b293bfc409c0 + languageName: node + linkType: hard + "fs-monkey@npm:^1.0.4": version: 1.0.4 resolution: "fs-monkey@npm:1.0.4" @@ -21875,7 +24737,14 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.5, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": +"graceful-fs@npm:^4.1.10, graceful-fs@npm:^4.1.11, graceful-fs@npm:^4.1.15, graceful-fs@npm:^4.1.2, graceful-fs@npm:^4.1.3, graceful-fs@npm:^4.1.6, graceful-fs@npm:^4.1.9, graceful-fs@npm:^4.2.0, graceful-fs@npm:^4.2.2, graceful-fs@npm:^4.2.4, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": + version: 4.2.10 + resolution: "graceful-fs@npm:4.2.10" + checksum: 3f109d70ae123951905d85032ebeae3c2a5a7a997430df00ea30df0e3a6c60cf6689b109654d6fdacd28810a053348c4d14642da1d075049e6be1ba5216218da + languageName: node + linkType: hard + +"graceful-fs@npm:^4.1.5": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -27179,7 +30048,16 @@ __metadata: languageName: node linkType: hard -"memfs@npm:^3.1.2, memfs@npm:^3.2.2, memfs@npm:^3.4.3": +"memfs@npm:^3.1.2, memfs@npm:^3.4.3": + version: 3.5.0 + resolution: "memfs@npm:3.5.0" + dependencies: + fs-monkey: ^1.0.3 + checksum: 8427db6c3644eeb9119b7a74b232d9a6178d018878acce6f05bd89d95e28b1073c9eeb00127131b0613b07a003e2e7b15b482f9004e548fe06a0aba7aa02515c + languageName: node + linkType: hard + +"memfs@npm:^3.2.2": version: 3.5.3 resolution: "memfs@npm:3.5.3" dependencies: @@ -28019,7 +30897,16 @@ __metadata: languageName: node linkType: hard -"moment-timezone@npm:*, moment-timezone@npm:^0.5.43, moment-timezone@npm:^0.5.x, moment-timezone@npm:~0.5.43": +"moment-timezone@npm:*, moment-timezone@npm:^0.5.x": + version: 0.5.40 + resolution: "moment-timezone@npm:0.5.40" + dependencies: + moment: ">= 2.9.0" + checksum: 6f6be5412b37fd937bb143efe74bf65b2c3f115fd967a6dc13b717a126ed6dd198bff6db6e179d69a089e20ac03ce7622c6b5598dd585005195554487a91b528 + languageName: node + linkType: hard + +"moment-timezone@npm:^0.5.43, moment-timezone@npm:~0.5.43": version: 0.5.43 resolution: "moment-timezone@npm:0.5.43" dependencies: @@ -28028,7 +30915,7 @@ __metadata: languageName: node linkType: hard -"moment@npm:^2.10.2, moment@npm:^2.29.1, moment@npm:^2.29.4": +"moment@npm:>= 2.9.0, moment@npm:^2.10.2, moment@npm:^2.29.1, moment@npm:^2.29.4": version: 2.29.4 resolution: "moment@npm:2.29.4" checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e @@ -28276,7 +31163,16 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.1, nanoid@npm:^3.3.6": +"nanoid@npm:^3.3.1, nanoid@npm:^3.3.4": + version: 3.3.4 + resolution: "nanoid@npm:3.3.4" + bin: + nanoid: bin/nanoid.cjs + checksum: 2fddd6dee994b7676f008d3ffa4ab16035a754f4bb586c61df5a22cf8c8c94017aadd360368f47d653829e0569a92b129979152ff97af23a558331e47e37cd9c + languageName: node + linkType: hard + +"nanoid@npm:^3.3.6": version: 3.3.6 resolution: "nanoid@npm:3.3.6" bin: @@ -28539,7 +31435,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:2.6.7": +"node-fetch@npm:2.6.7, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": version: 2.6.7 resolution: "node-fetch@npm:2.6.7" dependencies: @@ -28553,7 +31449,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.11, node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.6.11": version: 2.6.11 resolution: "node-fetch@npm:2.6.11" dependencies: @@ -28697,6 +31593,13 @@ __metadata: languageName: node linkType: hard +"node-releases@npm:^2.0.6": + version: 2.0.6 + resolution: "node-releases@npm:2.0.6" + checksum: e86a926dc9fbb3b41b4c4a89d998afdf140e20a4e8dbe6c0a807f7b2948b42ea97d7fd3ad4868041487b6e9ee98409829c6e4d84a734a4215dff060a7fbeb4bf + languageName: node + linkType: hard + "node-rsa@npm:^1.1.1": version: 1.1.1 resolution: "node-rsa@npm:1.1.1" @@ -31200,7 +34103,17 @@ __metadata: languageName: node linkType: hard -"postcss-selector-parser@npm:^6.0.0, postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.4, postcss-selector-parser@npm:^6.0.6": +"postcss-selector-parser@npm:^6.0.0, postcss-selector-parser@npm:^6.0.10, postcss-selector-parser@npm:^6.0.2, postcss-selector-parser@npm:^6.0.6": + version: 6.0.10 + resolution: "postcss-selector-parser@npm:6.0.10" + dependencies: + cssesc: ^3.0.0 + util-deprecate: ^1.0.2 + checksum: 46afaa60e3d1998bd7adf6caa374baf857cc58d3ff944e29459c9a9e4680a7fe41597bd5b755fc81d7c388357e9bf67c0251d047c640a09f148e13606b8a8608 + languageName: node + linkType: hard + +"postcss-selector-parser@npm:^6.0.4": version: 6.0.13 resolution: "postcss-selector-parser@npm:6.0.13" dependencies: @@ -31301,7 +34214,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.2.15, postcss@npm:^8.3.11, postcss@npm:^8.4.14, postcss@npm:^8.4.23, postcss@npm:~8.4.24": +"postcss@npm:^8.2.15, postcss@npm:~8.4.24": version: 8.4.24 resolution: "postcss@npm:8.4.24" dependencies: @@ -31312,6 +34225,28 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.3.11, postcss@npm:^8.4.14": + version: 8.4.16 + resolution: "postcss@npm:8.4.16" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 10eee25efd77868036403858577da0cefaf2e0905feeaba5770d5438ccdddba3d01cba8063e96b8aac4c6daa0ed413dd5ae0554a433a3c4db38df1d134cffc1f + languageName: node + linkType: hard + +"postcss@npm:^8.4.23": + version: 8.4.23 + resolution: "postcss@npm:8.4.23" + dependencies: + nanoid: ^3.3.6 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 8bb9d1b2ea6e694f8987d4f18c94617971b2b8d141602725fedcc2222fdc413b776a6e1b969a25d627d7b2681ca5aabb56f59e727ef94072e1b6ac8412105a2f + languageName: node + linkType: hard + "postis@npm:^2.2.0": version: 2.2.0 resolution: "postis@npm:2.2.0" @@ -31892,6 +34827,15 @@ __metadata: languageName: node linkType: hard +"qs@npm:6.10.3": + version: 6.10.3 + resolution: "qs@npm:6.10.3" + dependencies: + side-channel: ^1.0.4 + checksum: 0fac5e6c7191d0295a96d0e83c851aeb015df7e990e4d3b093897d3ac6c94e555dbd0a599739c84d7fa46d7fee282d94ba76943983935cf33bba6769539b8019 + languageName: node + linkType: hard + "qs@npm:6.11.0, qs@npm:^6.10.0, qs@npm:^6.10.3, qs@npm:^6.7.0, qs@npm:^6.9.4, qs@npm:^6.9.6": version: 6.11.0 resolution: "qs@npm:6.11.0" @@ -32187,7 +35131,19 @@ __metadata: languageName: node linkType: hard -"raw-body@npm:2.5.2, raw-body@npm:^2.2.0": +"raw-body@npm:2.5.1, raw-body@npm:^2.2.0": + version: 2.5.1 + resolution: "raw-body@npm:2.5.1" + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + checksum: 5362adff1575d691bb3f75998803a0ffed8c64eabeaa06e54b4ada25a0cd1b2ae7f4f5ec46565d1bec337e08b5ac90c76eaa0758de6f72a633f025d754dec29e + languageName: node + linkType: hard + +"raw-body@npm:2.5.2": version: 2.5.2 resolution: "raw-body@npm:2.5.2" dependencies: @@ -32211,7 +35167,20 @@ __metadata: languageName: node linkType: hard -"rc-scrollbars@npm:^1.1.5, rc-scrollbars@npm:^1.1.6": +"rc-scrollbars@npm:^1.1.5": + version: 1.1.5 + resolution: "rc-scrollbars@npm:1.1.5" + dependencies: + dom-css: ^2.1.0 + raf: ^3.4.1 + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + checksum: d2b3710868f70ed7c154768bd03825b543d7804ef1c95086ee9cd851f66a046e1b4dbc9e153f9797e1706a11d263db4a645eff485d16bf1c095754e6e771f5ad + languageName: node + linkType: hard + +"rc-scrollbars@npm:^1.1.6": version: 1.1.6 resolution: "rc-scrollbars@npm:1.1.6" dependencies: @@ -33695,7 +36664,21 @@ __metadata: languageName: unknown linkType: soft -"rollup@npm:^3.2.5, rollup@npm:^3.21.0": +"rollup@npm:^3.2.5": + version: 3.21.3 + resolution: "rollup@npm:3.21.3" + dependencies: + fsevents: ~2.3.2 + dependenciesMeta: + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 430c0c66f1480586ccea77b190524efb6a3af3da5308c7bc77eca3160c3f387c9a56ebb3eef5bfb24f683764bdfdd3dafdea175d215e4b875bbde752619bec12 + languageName: node + linkType: hard + +"rollup@npm:^3.21.0": version: 3.23.0 resolution: "rollup@npm:3.23.0" dependencies: @@ -34003,7 +36986,18 @@ __metadata: languageName: node linkType: hard -"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.1, schema-utils@npm:^3.2.0": +"schema-utils@npm:^3.0.0, schema-utils@npm:^3.1.0, schema-utils@npm:^3.1.1": + version: 3.1.1 + resolution: "schema-utils@npm:3.1.1" + dependencies: + "@types/json-schema": ^7.0.8 + ajv: ^6.12.5 + ajv-keywords: ^3.5.2 + checksum: fb73f3d759d43ba033c877628fe9751620a26879f6301d3dbeeb48cf2a65baec5cdf99da65d1bf3b4ff5444b2e59cbe4f81c2456b5e0d2ba7d7fd4aed5da29ce + languageName: node + linkType: hard + +"schema-utils@npm:^3.2.0": version: 3.3.0 resolution: "schema-utils@npm:3.3.0" dependencies: @@ -34141,7 +37135,18 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.x, semver@npm:^7.2, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7, semver@npm:^7.5.2": +"semver@npm:7.x, semver@npm:^7.2, semver@npm:^7.2.1, semver@npm:^7.3.2, semver@npm:^7.3.4, semver@npm:^7.3.5, semver@npm:^7.3.7": + version: 7.3.7 + resolution: "semver@npm:7.3.7" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 2fa3e877568cd6ce769c75c211beaed1f9fce80b28338cadd9d0b6c40f2e2862bafd62c19a6cff42f3d54292b7c623277bcab8816a2b5521cf15210d43e75232 + languageName: node + linkType: hard + +"semver@npm:^7.5.2": version: 7.5.2 resolution: "semver@npm:7.5.2" dependencies: @@ -34213,7 +37218,7 @@ __metadata: languageName: node linkType: hard -"serialize-javascript@npm:6.0.0": +"serialize-javascript@npm:6.0.0, serialize-javascript@npm:^6.0.0": version: 6.0.0 resolution: "serialize-javascript@npm:6.0.0" dependencies: @@ -36234,6 +39239,28 @@ __metadata: languageName: node linkType: hard +"terser-webpack-plugin@npm:^5.1.3": + version: 5.3.5 + resolution: "terser-webpack-plugin@npm:5.3.5" + dependencies: + "@jridgewell/trace-mapping": ^0.3.14 + jest-worker: ^27.4.5 + schema-utils: ^3.1.1 + serialize-javascript: ^6.0.0 + terser: ^5.14.1 + peerDependencies: + webpack: ^5.1.0 + peerDependenciesMeta: + "@swc/core": + optional: true + esbuild: + optional: true + uglify-js: + optional: true + checksum: 611c7b38d6fa0213dc03f48da9efe29c7edd098fc128a64905f7c9b61af8e7c36c13113d46b50be19ee2b8378442f4e1b8b4ddac9bba2cb73499ed32fc0e18f4 + languageName: node + linkType: hard + "terser@npm:^4.1.2, terser@npm:^4.6.3": version: 4.8.0 resolution: "terser@npm:4.8.0" @@ -36247,7 +39274,7 @@ __metadata: languageName: node linkType: hard -"terser@npm:^5.10.0, terser@npm:^5.16.8, terser@npm:^5.3.4": +"terser@npm:^5.10.0, terser@npm:^5.16.8": version: 5.18.0 resolution: "terser@npm:5.18.0" dependencies: @@ -36261,6 +39288,20 @@ __metadata: languageName: node linkType: hard +"terser@npm:^5.14.1, terser@npm:^5.3.4": + version: 5.14.2 + resolution: "terser@npm:5.14.2" + dependencies: + "@jridgewell/source-map": ^0.3.2 + acorn: ^8.5.0 + commander: ^2.20.0 + source-map-support: ~0.5.20 + bin: + terser: bin/terser + checksum: cabb50a640d6c2cfb351e4f43dc7bf7436f649755bb83eb78b2cacda426d5e0979bd44e6f92d713f3ca0f0866e322739b9ced888ebbce6508ad872d08de74fcc + languageName: node + linkType: hard + "test-exclude@npm:^6.0.0": version: 6.0.0 resolution: "test-exclude@npm:6.0.0" @@ -36420,13 +39461,20 @@ __metadata: languageName: node linkType: hard -"tiny-invariant@npm:^1.0.6, tiny-invariant@npm:^1.2.0": +"tiny-invariant@npm:^1.0.6": version: 1.3.1 resolution: "tiny-invariant@npm:1.3.1" checksum: 872dbd1ff20a21303a2fd20ce3a15602cfa7fcf9b228bd694a52e2938224313b5385a1078cb667ed7375d1612194feaca81c4ecbe93121ca1baebe344de4f84c languageName: node linkType: hard +"tiny-invariant@npm:^1.2.0": + version: 1.2.0 + resolution: "tiny-invariant@npm:1.2.0" + checksum: e09a718a7c4a499ba592cdac61f015d87427a0867ca07f50c11fd9b623f90cdba18937b515d4a5e4f43dac92370498d7bdaee0d0e7a377a61095e02c4a92eade + languageName: node + linkType: hard + "tinykeys@npm:^1.4.0": version: 1.4.0 resolution: "tinykeys@npm:1.4.0" @@ -36849,7 +39897,14 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.3": +"tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.2.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0": + version: 2.5.0 + resolution: "tslib@npm:2.5.0" + checksum: ae3ed5f9ce29932d049908ebfdf21b3a003a85653a9a140d614da6b767a93ef94f460e52c3d787f0e4f383546981713f165037dc2274df212ea9f8a4541004e1 + languageName: node + linkType: hard + +"tslib@npm:^2.5.3": version: 2.5.3 resolution: "tslib@npm:2.5.3" checksum: 88902b309afaf83259131c1e13da1dceb0ad1682a213143a1346a649143924d78cf3760c448b84d796938fd76127183894f8d85cbb3bf9c4fddbfcc140c0003c @@ -37629,6 +40684,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.0.5": + version: 1.0.9 + resolution: "update-browserslist-db@npm:1.0.9" + dependencies: + escalade: ^3.1.1 + picocolors: ^1.0.0 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + browserslist-lint: cli.js + checksum: f625899b236f6a4d7f62b56be1b8da230c5563d1fef84d3ef148f2e1a3f11a5a4b3be4fd7e3703e51274c116194017775b10afb4de09eb2c0d09d36b90f1f578 + languageName: node + linkType: hard + "update-check@npm:1.5.2": version: 1.5.2 resolution: "update-check@npm:1.5.2" @@ -38191,7 +41260,7 @@ __metadata: languageName: node linkType: hard -"vm2@npm:^3.9.19, vm2@npm:^3.9.8": +"vm2@npm:^3.9.19": version: 3.9.19 resolution: "vm2@npm:3.9.19" dependencies: @@ -38203,6 +41272,18 @@ __metadata: languageName: node linkType: hard +"vm2@npm:^3.9.8": + version: 3.9.17 + resolution: "vm2@npm:3.9.17" + dependencies: + acorn: ^8.7.0 + acorn-walk: ^8.2.0 + bin: + vm2: bin/vm2 + checksum: 9a03740a40ab2be5e3348a95fb31512da1a3c85318febb07e5299fa103ff05bcd7b6f458211fa38a1281dc27beccd04ff90355fc1d34fe2ee6ca10d0bb8c6f35 + languageName: node + linkType: hard + "void-elements@npm:3.1.0": version: 3.1.0 resolution: "void-elements@npm:3.1.0" @@ -38636,7 +41717,7 @@ __metadata: languageName: node linkType: hard -"webpack@npm:>=4.0.0 <6.0.0, webpack@npm:>=4.43.0 <6.0.0, webpack@npm:^5.9.0": +"webpack@npm:>=4.0.0 <6.0.0": version: 5.88.0 resolution: "webpack@npm:5.88.0" dependencies: @@ -38673,6 +41754,80 @@ __metadata: languageName: node linkType: hard +"webpack@npm:>=4.43.0 <6.0.0": + version: 5.74.0 + resolution: "webpack@npm:5.74.0" + dependencies: + "@types/eslint-scope": ^3.7.3 + "@types/estree": ^0.0.51 + "@webassemblyjs/ast": 1.11.1 + "@webassemblyjs/wasm-edit": 1.11.1 + "@webassemblyjs/wasm-parser": 1.11.1 + acorn: ^8.7.1 + acorn-import-assertions: ^1.7.6 + browserslist: ^4.14.5 + chrome-trace-event: ^1.0.2 + enhanced-resolve: ^5.10.0 + es-module-lexer: ^0.9.0 + eslint-scope: 5.1.1 + events: ^3.2.0 + glob-to-regexp: ^0.4.1 + graceful-fs: ^4.2.9 + json-parse-even-better-errors: ^2.3.1 + loader-runner: ^4.2.0 + mime-types: ^2.1.27 + neo-async: ^2.6.2 + schema-utils: ^3.1.0 + tapable: ^2.1.1 + terser-webpack-plugin: ^5.1.3 + watchpack: ^2.4.0 + webpack-sources: ^3.2.3 + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: 320c41369a75051b19e18c63f408b3dcc481852e992f83d311771c5ec0f05f2946385e8ebef62030cf3587f0a3d2f12779ffdb191569a966847289ba7313f946 + languageName: node + linkType: hard + +"webpack@npm:^5.9.0": + version: 5.87.0 + resolution: "webpack@npm:5.87.0" + dependencies: + "@types/eslint-scope": ^3.7.3 + "@types/estree": ^1.0.0 + "@webassemblyjs/ast": ^1.11.5 + "@webassemblyjs/wasm-edit": ^1.11.5 + "@webassemblyjs/wasm-parser": ^1.11.5 + acorn: ^8.7.1 + acorn-import-assertions: ^1.9.0 + browserslist: ^4.14.5 + chrome-trace-event: ^1.0.2 + enhanced-resolve: ^5.15.0 + es-module-lexer: ^1.2.1 + eslint-scope: 5.1.1 + events: ^3.2.0 + glob-to-regexp: ^0.4.1 + graceful-fs: ^4.2.9 + json-parse-even-better-errors: ^2.3.1 + loader-runner: ^4.2.0 + mime-types: ^2.1.27 + neo-async: ^2.6.2 + schema-utils: ^3.2.0 + tapable: ^2.1.1 + terser-webpack-plugin: ^5.3.7 + watchpack: ^2.4.0 + webpack-sources: ^3.2.3 + peerDependenciesMeta: + webpack-cli: + optional: true + bin: + webpack: bin/webpack.js + checksum: b7d0e390f9d30627e303d54b17cb87b62f49ecffe2d35481f830679904993bae208e23748ffe0e6091b6dd4810562b2f2e88bb0f23b96515d74fb1e3c2898210 + languageName: node + linkType: hard + "websocket-driver@npm:>=0.5.1, websocket-driver@npm:^0.7.4": version: 0.7.4 resolution: "websocket-driver@npm:0.7.4" @@ -39319,7 +42474,7 @@ __metadata: languageName: node linkType: hard -"yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": +"yargs-parser@npm:^21.0.0, yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": version: 21.1.1 resolution: "yargs-parser@npm:21.1.1" checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c @@ -39372,7 +42527,22 @@ __metadata: languageName: node linkType: hard -"yargs@npm:^17.3.1, yargs@npm:^17.7.1": +"yargs@npm:^17.3.1": + version: 17.5.1 + resolution: "yargs@npm:17.5.1" + dependencies: + cliui: ^7.0.2 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.0.0 + checksum: 00d58a2c052937fa044834313f07910fd0a115dec5ee35919e857eeee3736b21a4eafa8264535800ba8bac312991ce785ecb8a51f4d2cc8c4676d865af1cfbde + languageName: node + linkType: hard + +"yargs@npm:^17.7.1": version: 17.7.2 resolution: "yargs@npm:17.7.2" dependencies: From 28b41fb07615be4c669ba773c3ab2f4f462b1a66 Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Fri, 7 Jul 2023 15:55:25 -0300 Subject: [PATCH 74/79] fix: Canned Response custom tags break the GUI on enterprise (#29694) Co-authored-by: Kevin Aleman <kaleman960@gmail.com> --- .changeset/kind-coats-worry.md | 5 ++ .../client/components/Omnichannel/Tags.tsx | 51 ++++++++++--------- .../directory/chats/contextualBar/ChatInfo.js | 3 +- .../directory/chats/hooks/useTagsLabels.tsx | 21 ++++++++ .../server/lib/canned-responses.js | 13 +++-- .../ee/app/api-enterprise/server/lib/tags.ts | 33 ++++++++++++ .../server/hooks/afterTagRemoved.ts | 14 +++++ .../livechat-enterprise/server/hooks/index.ts | 1 + .../server/lib/LivechatEnterprise.ts | 1 + apps/meteor/ee/client/hooks/useTagsList.ts | 13 ++--- .../cannedResponses/CannedResponseEdit.tsx | 8 +-- .../modals/CreateCannedResponse/index.tsx | 3 +- .../ee/server/models/raw/CannedResponse.ts | 12 ++++- .../ee/server/models/raw/LivechatTag.ts | 8 ++- apps/meteor/lib/callbacks.ts | 2 + apps/meteor/tests/data/livechat/tags.ts | 9 ++-- .../api/livechat/15-canned-responses.ts | 41 ++++++++++++++- .../src/IOmnichannelCannedResponse.ts | 2 +- .../src/models/ICannedResponseModel.ts | 3 +- .../src/models/ILivechatTagModel.ts | 3 +- 20 files changed, 191 insertions(+), 55 deletions(-) create mode 100644 .changeset/kind-coats-worry.md create mode 100644 apps/meteor/client/views/omnichannel/directory/chats/hooks/useTagsLabels.tsx create mode 100644 apps/meteor/ee/app/api-enterprise/server/lib/tags.ts create mode 100644 apps/meteor/ee/app/livechat-enterprise/server/hooks/afterTagRemoved.ts diff --git a/.changeset/kind-coats-worry.md b/.changeset/kind-coats-worry.md new file mode 100644 index 0000000000000..81140d95124f0 --- /dev/null +++ b/.changeset/kind-coats-worry.md @@ -0,0 +1,5 @@ +--- +'@rocket.chat/meteor': patch +--- + +Fixed Canned Response custom tags breaking the GUI on enterprise diff --git a/apps/meteor/client/components/Omnichannel/Tags.tsx b/apps/meteor/client/components/Omnichannel/Tags.tsx index 3074639cee0d1..19a8dba8d36fe 100644 --- a/apps/meteor/client/components/Omnichannel/Tags.tsx +++ b/apps/meteor/client/components/Omnichannel/Tags.tsx @@ -2,25 +2,21 @@ import { Field, TextInput, Chip, Button } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; import type { ChangeEvent, ReactElement } from 'react'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { useFormsSubscription } from '../../views/omnichannel/additionalForms'; import { FormSkeleton } from './Skeleton'; import { useLivechatTags } from './hooks/useLivechatTags'; -const Tags = ({ - tags = [], - handler, - error, - tagRequired, - department, -}: { +type TagsProps = { tags?: string[]; handler: (value: string[]) => void; error?: string; tagRequired?: boolean; department?: string; -}): ReactElement => { +}; + +const Tags = ({ tags = [], handler, error, tagRequired, department }: TagsProps): ReactElement => { const t = useTranslation(); const forms = useFormsSubscription() as any; @@ -33,16 +29,20 @@ const Tags = ({ department, }); + const customTags = useMemo(() => { + return tags.filter((tag) => !tagsResult?.tags.find((rtag) => rtag._id === tag)); + }, [tags, tagsResult?.tags]); + const dispatchToastMessage = useToastMessageDispatch(); const [tagValue, handleTagValue] = useState(''); - const [paginatedTagValue, handlePaginatedTagValue] = useState<{ label: string; value: string }[]>(); + + const paginatedTagValue = useMemo(() => tags.map((tag) => ({ label: tag, value: tag })), [tags]); const removeTag = (tagToRemove: string): void => { - if (tags) { - const tagsFiltered = tags.filter((tag: string) => tag !== tagToRemove); - handler(tagsFiltered); - } + if (!tags) return; + + handler(tags.filter((tag) => tag !== tagToRemove)); }; const handleTagTextSubmit = useMutableCallback(() => { @@ -56,7 +56,7 @@ const Tags = ({ return; } - if (tags.includes(tagValue)) { + if (tags.some((tag) => tag === tagValue)) { dispatchToastMessage({ type: 'error', message: t('Tag_already_exists') }); return; } @@ -79,8 +79,7 @@ const Tags = ({ <EETagsComponent value={paginatedTagValue} handler={(tags: { label: string; value: string }[]): void => { - handler(tags.map((tag) => tag.label)); - handlePaginatedTagValue(tags); + handler(tags.map((tag) => tag.value)); }} department={department} /> @@ -99,16 +98,18 @@ const Tags = ({ {t('Add')} </Button> </Field.Row> - - <Field.Row justifyContent='flex-start'> - {tags?.map((tag, i) => ( - <Chip key={i} onClick={(): void => removeTag(tag)} mie='x8'> - {tag} - </Chip> - ))} - </Field.Row> </> )} + + {customTags.length > 0 && ( + <Field.Row justifyContent='flex-start'> + {customTags?.map((tag, i) => ( + <Chip key={i} onClick={(): void => removeTag(tag)} mie='x8'> + {tag} + </Chip> + ))} + </Field.Row> + )} </> ); }; diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfo.js b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfo.js index 7c32188e7e343..386ef76552335 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfo.js +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/ChatInfo.js @@ -16,6 +16,7 @@ import Label from '../../../components/Label'; import { AgentField, SlaField, ContactField, SourceField } from '../../components'; import PriorityField from '../../components/PriorityField'; import { useOmnichannelRoomInfo } from '../../hooks/useOmnichannelRoomInfo'; +import { useTagsLabels } from '../hooks/useTagsLabels'; import DepartmentField from './DepartmentField'; import VisitorClientInfo from './VisitorClientInfo'; @@ -33,7 +34,6 @@ function ChatInfo({ id, route }) { const { ts, - tags, closedAt, departmentId, v, @@ -49,6 +49,7 @@ function ChatInfo({ id, route }) { queuedAt, } = room || { room: { v: {} } }; + const tags = useTagsLabels(room?.tags); const routePath = useRoute(route || 'omnichannel-directory'); const canViewCustomFields = usePermission('view-livechat-room-customfields'); const subscription = useUserSubscription(id); diff --git a/apps/meteor/client/views/omnichannel/directory/chats/hooks/useTagsLabels.tsx b/apps/meteor/client/views/omnichannel/directory/chats/hooks/useTagsLabels.tsx new file mode 100644 index 0000000000000..f732a78ba3f57 --- /dev/null +++ b/apps/meteor/client/views/omnichannel/directory/chats/hooks/useTagsLabels.tsx @@ -0,0 +1,21 @@ +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; + +// TODO: Remove this hook when the endpoint is changed +// to return labels instead of tag id's +export const useTagsLabels = (initialTags: string[] = []) => { + const getTags = useEndpoint('GET', '/v1/livechat/tags'); + const { data: tagsData, isInitialLoading } = useQuery(['/v1/livechat/tags'], () => getTags({ text: '' }), { + enabled: Boolean(initialTags.length), + }); + + const labels = useMemo(() => { + const { tags = [] } = tagsData || {}; + return tags.reduce<Record<string, string>>((acc, tag) => ({ ...acc, [tag._id]: tag.name }), {}); + }, [tagsData]); + + return useMemo(() => { + return isInitialLoading ? initialTags : initialTags.map((tag) => labels[tag] || tag); + }, [initialTags, isInitialLoading, labels]); +}; diff --git a/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js b/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js index 9f0872c38f29d..1a7d478b6c3bf 100644 --- a/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js +++ b/apps/meteor/ee/app/api-enterprise/server/lib/canned-responses.js @@ -2,12 +2,13 @@ import { escapeRegExp } from '@rocket.chat/string-helpers'; import { CannedResponse } from '@rocket.chat/models'; import { hasPermissionAsync } from '../../../../../app/authorization/server/functions/hasPermission'; +import { getTagsInformation } from './tags'; import { getDepartmentsWhichUserCanAccess } from '../../../livechat-enterprise/server/api/lib/departments'; export async function findAllCannedResponses({ userId }) { // If the user is an admin or livechat manager, get his own responses and all responses from all departments if (await hasPermissionAsync(userId, 'view-all-canned-responses')) { - return CannedResponse.find({ + const cannedResponses = await CannedResponse.find({ $or: [ { scope: 'user', @@ -21,20 +22,22 @@ export async function findAllCannedResponses({ userId }) { }, ], }).toArray(); + return getTagsInformation(cannedResponses); } // If the user it not any of the previous roles nor an agent, then get only his own responses if (!(await hasPermissionAsync(userId, 'view-agent-canned-responses'))) { - return CannedResponse.find({ + const cannedResponses = await CannedResponse.find({ scope: 'user', userId, }).toArray(); + return getTagsInformation(cannedResponses); } // Last scenario: user is an agent, so get his own responses and those from the departments he is in const accessibleDepartments = await getDepartmentsWhichUserCanAccess(userId); - return CannedResponse.find({ + const cannedResponses = await CannedResponse.find({ $or: [ { scope: 'user', @@ -51,6 +54,8 @@ export async function findAllCannedResponses({ userId }) { }, ], }).toArray(); + + return getTagsInformation(cannedResponses); } export async function findAllCannedResponsesFilter({ userId, shortcut, text, departmentId, scope, createdBy, tags = [], options = {} }) { @@ -124,7 +129,7 @@ export async function findAllCannedResponsesFilter({ userId, shortcut, text, dep }); const [cannedResponses, total] = await Promise.all([cursor.toArray(), totalCount]); return { - cannedResponses, + cannedResponses: await getTagsInformation(cannedResponses), total, }; } diff --git a/apps/meteor/ee/app/api-enterprise/server/lib/tags.ts b/apps/meteor/ee/app/api-enterprise/server/lib/tags.ts new file mode 100644 index 0000000000000..c4ffc97da7f84 --- /dev/null +++ b/apps/meteor/ee/app/api-enterprise/server/lib/tags.ts @@ -0,0 +1,33 @@ +import type { ILivechatTag, IOmnichannelCannedResponse } from '@rocket.chat/core-typings'; +import { LivechatTag } from '@rocket.chat/models'; + +const filterTags = (tags: string[], serverTags: ILivechatTag[]) => { + return tags.reduce((acc, tag) => { + const found = serverTags.find((serverTag) => serverTag._id === tag); + if (found) { + acc.push(found.name); + } else { + acc.push(tag); + } + return acc; + }, [] as string[]); +}; + +export const getTagsInformation = async (cannedResponses: IOmnichannelCannedResponse[]) => { + return Promise.all( + cannedResponses.map(async (cannedResponse) => { + const { tags } = cannedResponse; + + if (!Array.isArray(tags) || !tags.length) { + return cannedResponse; + } + + const serverTags = await LivechatTag.findInIds(tags, { projection: { _id: 1, name: 1 } }).toArray(); + + // Known limitation: if a tag was added and removed before this, it will return the tag id instead of the name + cannedResponse.tags = filterTags(tags, serverTags); + + return cannedResponse; + }), + ); +}; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterTagRemoved.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterTagRemoved.ts new file mode 100644 index 0000000000000..451ce972f62ae --- /dev/null +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/afterTagRemoved.ts @@ -0,0 +1,14 @@ +import { CannedResponse } from '@rocket.chat/models'; + +import { callbacks } from '../../../../../lib/callbacks'; + +callbacks.add( + 'livechat.afterTagRemoved', + async (tag) => { + const { _id } = tag; + + await CannedResponse.removeTagFromCannedResponses(_id); + }, + callbacks.priority.MEDIUM, + 'on-tag-removed-remove-references', +); diff --git a/apps/meteor/ee/app/livechat-enterprise/server/hooks/index.ts b/apps/meteor/ee/app/livechat-enterprise/server/hooks/index.ts index 3eba755b573da..c5bf0a5aa392b 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/hooks/index.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/hooks/index.ts @@ -25,3 +25,4 @@ import './applySimultaneousChatsRestrictions'; import './afterInquiryQueued'; import './sendPdfTranscriptOnClose'; import './applyRoomRestrictions'; +import './afterTagRemoved'; diff --git a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts index 653dfbda74e45..1471ef8d40e13 100644 --- a/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts +++ b/apps/meteor/ee/app/livechat-enterprise/server/lib/LivechatEnterprise.ts @@ -135,6 +135,7 @@ export const LivechatEnterprise = { throw new Meteor.Error('tag-not-found', 'Tag not found', { method: 'livechat:removeTag' }); } + await callbacks.run('livechat.afterTagRemoved', tag); return LivechatTag.removeById(_id); }, diff --git a/apps/meteor/ee/client/hooks/useTagsList.ts b/apps/meteor/ee/client/hooks/useTagsList.ts index 013a320fbadec..229e495342e14 100644 --- a/apps/meteor/ee/client/hooks/useTagsList.ts +++ b/apps/meteor/ee/client/hooks/useTagsList.ts @@ -36,13 +36,14 @@ export const useTagsList = ( count: end + start, ...(options.department && { department: options.department }), }); + return { - items: tags.map((tag: any) => { - tag._updatedAt = new Date(tag._updatedAt); - tag.label = tag.name; - tag.value = { value: tag._id, label: tag.name }; - return tag; - }), + items: tags.map<any>((tag: any) => ({ + _id: tag._id, + label: tag.name, + value: tag._id, + _updatedAt: new Date(tag._updatedAt), + })), itemCount: total, }; }, diff --git a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx index daac8b0533330..721b78d60dd02 100644 --- a/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx +++ b/apps/meteor/ee/client/omnichannel/cannedResponses/CannedResponseEdit.tsx @@ -39,10 +39,7 @@ const CannedResponseEdit: FC<{ _id: data?.cannedResponse ? data.cannedResponse._id : '', shortcut: data ? data.cannedResponse.shortcut : '', text: data ? data.cannedResponse.text : '', - tags: - data?.cannedResponse?.tags && Array.isArray(data.cannedResponse.tags) - ? data.cannedResponse.tags.map((tag) => ({ label: tag, value: tag })) - : [], + tags: data?.cannedResponse?.tags ?? [], scope: data ? data.cannedResponse.scope : 'user', departmentId: data?.cannedResponse?.departmentId ? data.cannedResponse.departmentId : '', }); @@ -100,13 +97,12 @@ const CannedResponseEdit: FC<{ tags: any; departmentId: string; }; - const mappedTags = tags.map((tag: string | { value: string; label: string }) => (typeof tag === 'object' ? tag?.value : tag)); await saveCannedResponse({ ...(_id && { _id }), shortcut, text, scope, - ...(mappedTags.length > 0 && { tags: mappedTags }), + tags, ...(departmentId && { departmentId }), }); dispatchToastMessage({ diff --git a/apps/meteor/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/index.tsx b/apps/meteor/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/index.tsx index fc19de9481356..9285bc5ae942a 100644 --- a/apps/meteor/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/index.tsx +++ b/apps/meteor/ee/client/omnichannel/components/CannedResponse/modals/CreateCannedResponse/index.tsx @@ -77,13 +77,12 @@ const WrapCreateCannedResponseModal: FC<{ data?: any; reloadCannedList?: any }> tags: any; departmentId: string; }; - const mappedTags = tags.map((tag: string | { value: string; label: string }) => (typeof tag === 'object' ? tag?.value : tag)); await saveCannedResponse({ ...(_id && { _id }), shortcut, text, scope, - ...(tags.length > 0 && { tags: mappedTags }), + tags, ...(departmentId && { departmentId }), }); dispatchToastMessage({ diff --git a/apps/meteor/ee/server/models/raw/CannedResponse.ts b/apps/meteor/ee/server/models/raw/CannedResponse.ts index 5fd3f5c639741..e897db928dc27 100644 --- a/apps/meteor/ee/server/models/raw/CannedResponse.ts +++ b/apps/meteor/ee/server/models/raw/CannedResponse.ts @@ -1,6 +1,6 @@ import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings'; import type { ICannedResponseModel } from '@rocket.chat/model-typings'; -import type { Db, DeleteResult, FindCursor, FindOptions, IndexDescription } from 'mongodb'; +import type { Db, DeleteResult, FindCursor, FindOptions, IndexDescription, UpdateFilter } from 'mongodb'; import { BaseRaw } from '../../../../server/models/raw/BaseRaw'; @@ -106,4 +106,14 @@ export class CannedResponseRaw extends BaseRaw<IOmnichannelCannedResponse> imple return this.deleteOne(query); } + + removeTagFromCannedResponses(tagId: string) { + const update: UpdateFilter<IOmnichannelCannedResponse> = { + $pull: { + tags: tagId, + }, + }; + + return this.updateMany({}, update); + } } diff --git a/apps/meteor/ee/server/models/raw/LivechatTag.ts b/apps/meteor/ee/server/models/raw/LivechatTag.ts index 465ed709b598a..a4314c569f46a 100644 --- a/apps/meteor/ee/server/models/raw/LivechatTag.ts +++ b/apps/meteor/ee/server/models/raw/LivechatTag.ts @@ -1,6 +1,6 @@ import type { ILivechatTag } from '@rocket.chat/core-typings'; import type { ILivechatTagModel } from '@rocket.chat/model-typings'; -import type { Db, DeleteResult, FindOptions, IndexDescription } from 'mongodb'; +import type { Db, DeleteResult, FindCursor, FindOptions, IndexDescription } from 'mongodb'; import { BaseRaw } from '../../../../server/models/raw/BaseRaw'; @@ -26,6 +26,12 @@ export class LivechatTagRaw extends BaseRaw<ILivechatTag> implements ILivechatTa return this.findOne(query, options); } + findInIds(ids: string[], options?: FindOptions<ILivechatTag>): FindCursor<ILivechatTag> { + const query = { _id: { $in: ids } }; + + return this.find(query, options); + } + async createOrUpdateTag( _id: string | undefined, { name, description }: { name: string; description?: string }, diff --git a/apps/meteor/lib/callbacks.ts b/apps/meteor/lib/callbacks.ts index 2fad995dfd1f2..ce4c8517d2dbd 100644 --- a/apps/meteor/lib/callbacks.ts +++ b/apps/meteor/lib/callbacks.ts @@ -19,6 +19,7 @@ import type { ILivechatTag, SelectedAgent, InquiryWithAgentInfo, + ILivechatTagRecord, } from '@rocket.chat/core-typings'; import { Random } from '@rocket.chat/random'; @@ -95,6 +96,7 @@ interface EventLikeCallbackSignatures { 'livechat.afterDepartmentDisabled': (department: ILivechatDepartmentRecord) => void; 'livechat.afterDepartmentArchived': (department: Pick<ILivechatDepartmentRecord, '_id'>) => void; 'afterSaveUser': ({ user, oldUser }: { user: IUser; oldUser: IUser | null }) => void; + 'livechat.afterTagRemoved': (tag: ILivechatTagRecord) => void; } /** diff --git a/apps/meteor/tests/data/livechat/tags.ts b/apps/meteor/tests/data/livechat/tags.ts index 4ba5c78025cde..b12cca1f64ae2 100644 --- a/apps/meteor/tests/data/livechat/tags.ts +++ b/apps/meteor/tests/data/livechat/tags.ts @@ -25,8 +25,8 @@ export const saveTags = (departments: string[] = []): Promise<ILivechatTag> => { }); }; -export const removeTag = (id: string): Promise<void> => { - return new Promise((resolve, reject) => { +export const removeTag = (id: string): Promise<boolean> => { + return new Promise((resolve, reject) => { request .post(methodCall(`livechat:removeTag`)) .set(credentials) @@ -43,6 +43,7 @@ export const removeTag = (id: string): Promise<void> => { return reject(err); } resolve(JSON.parse(res.body.message).result); - }); + } + ); }); -}; +}; \ No newline at end of file diff --git a/apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts b/apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts index fffe2f125d190..82b6eb6feb214 100644 --- a/apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts +++ b/apps/meteor/tests/end-to-end/api/livechat/15-canned-responses.ts @@ -6,6 +6,7 @@ import { getCredentials, api, request, credentials } from '../../../data/api-dat import { updatePermission, updateSetting } from '../../../data/permissions.helper'; import { createCannedResponse } from '../../../data/livechat/canned-responses'; import { IS_EE } from '../../../e2e/config/constants'; +import { removeTag, saveTags } from '../../../data/livechat/tags'; (IS_EE ? describe : describe.skip)('[EE] LIVECHAT - Canned responses', function () { this.retries(0); @@ -72,11 +73,11 @@ import { IS_EE } from '../../../e2e/config/constants'; }); it('should return a canned response matching the params provided (tags)', async () => { const response = await createCannedResponse(); - const { body } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': response.tags[0] }).expect(200); + const { body } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': response.tags?.[0] }).expect(200); expect(body).to.have.property('success', true); expect(body.cannedResponses).to.be.an('array').with.lengthOf(1); expect(body.cannedResponses[0]).to.have.property('_id'); - expect(body.cannedResponses[0]).to.have.property('tags').that.is.an('array').which.includes(response.tags[0]); + expect(body.cannedResponses[0]).to.have.property('tags').that.is.an('array').which.includes(response.tags?.[0]); }); it('should return a canned response matching the params provided (text)', async () => { const response = await createCannedResponse(); @@ -129,6 +130,42 @@ import { IS_EE } from '../../../e2e/config/constants'; .send({ shortcut: 'shortcutxx', scope: 'user', tags: ['tag'], text: 'text' }) .expect(400); }); + it('should save a canned response related to an EE tag', async () => { + const tag = await saveTags(); + + const { body } = await request + .post(api('canned-responses')) + .set(credentials) + .send({ shortcut: 'shortcutxxx', scope: 'user', tags: [tag._id], text: 'text' }) + .expect(200); + + expect(body).to.have.property('success', true); + + const { body: getResult } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': tag._id }).expect(200); + + expect(getResult).to.have.property('success', true); + expect(getResult.cannedResponses).to.be.an('array').with.lengthOf(1); + expect(getResult.cannedResponses[0]).to.have.property('_id'); + expect(getResult.cannedResponses[0]).to.have.property('tags').that.is.an('array').which.includes(tag.name); + }); + it('should not include removed tags in the response', async () => { + const tag = await saveTags(); + + const { body } = await request + .post(api('canned-responses')) + .set(credentials) + .send({ shortcut: 'shortcutxxxx', scope: 'user', tags: [tag._id], text: 'text' }) + .expect(200); + + expect(body).to.have.property('success', true); + + await removeTag(tag._id); + + const { body: getResult } = await request.get(api('canned-responses')).set(credentials).query({ 'tags[]': tag._id }).expect(200); + + expect(getResult).to.have.property('success', true); + expect(getResult.cannedResponses).to.be.an('array').with.lengthOf(0); + }); }); describe('[DELETE] canned-responses', () => { diff --git a/packages/core-typings/src/IOmnichannelCannedResponse.ts b/packages/core-typings/src/IOmnichannelCannedResponse.ts index 9887476c8b386..79e6b2b3a6858 100644 --- a/packages/core-typings/src/IOmnichannelCannedResponse.ts +++ b/packages/core-typings/src/IOmnichannelCannedResponse.ts @@ -6,7 +6,7 @@ export interface IOmnichannelCannedResponse extends IRocketChatRecord { shortcut: string; text: string; scope: string; - tags: any; + tags: string[]; // userId is optional, its only required when scope === 'user' userId?: IUser['_id']; departmentId?: ILivechatDepartment['_id']; diff --git a/packages/model-typings/src/models/ICannedResponseModel.ts b/packages/model-typings/src/models/ICannedResponseModel.ts index bd8fd9fb0356e..6cfa582c78259 100644 --- a/packages/model-typings/src/models/ICannedResponseModel.ts +++ b/packages/model-typings/src/models/ICannedResponseModel.ts @@ -1,5 +1,5 @@ import type { IOmnichannelCannedResponse } from '@rocket.chat/core-typings'; -import type { FindOptions, FindCursor, DeleteResult } from 'mongodb'; +import type { FindOptions, FindCursor, DeleteResult, UpdateResult, Document } from 'mongodb'; import type { IBaseModel } from './IBaseModel'; @@ -24,4 +24,5 @@ export interface ICannedResponseModel extends IBaseModel<IOmnichannelCannedRespo _id: string, { shortcut, text, tags, scope, userId, departmentId, createdBy }: Omit<IOmnichannelCannedResponse, '_id' | '_updatedAt' | '_createdAt'>, ): Promise<Omit<IOmnichannelCannedResponse, '_updatedAt' | '_createdAt'>>; + removeTagFromCannedResponses(tagId: string): Promise<UpdateResult | Document>; } diff --git a/packages/model-typings/src/models/ILivechatTagModel.ts b/packages/model-typings/src/models/ILivechatTagModel.ts index 22aa655dbb9e0..270e9ba99e095 100644 --- a/packages/model-typings/src/models/ILivechatTagModel.ts +++ b/packages/model-typings/src/models/ILivechatTagModel.ts @@ -1,10 +1,11 @@ import type { ILivechatTag } from '@rocket.chat/core-typings'; -import type { FindOptions, DeleteResult } from 'mongodb'; +import type { FindOptions, DeleteResult, FindCursor } from 'mongodb'; import type { IBaseModel } from './IBaseModel'; export interface ILivechatTagModel extends IBaseModel<ILivechatTag> { findOneById(_id: string, options?: FindOptions<ILivechatTag>): Promise<ILivechatTag | null>; + findInIds(ids: string[], options?: FindOptions<ILivechatTag>): FindCursor<ILivechatTag>; createOrUpdateTag( _id: string | undefined, { name, description }: { name: string; description?: string }, From 1aebd7cab8640cece57a09ce5e04fca7f5c45555 Mon Sep 17 00:00:00 2001 From: Tasso Evangelista <tasso.evangelista@rocket.chat> Date: Fri, 7 Jul 2023 18:57:26 -0300 Subject: [PATCH 75/79] refactor: Lazy initialization on client-side (#29764) --- apps/meteor/app/emoji-emojione/client/lib.ts | 8 +- .../app/emoji-emojione/lib/emojioneRender.ts | 9 - .../app/emoji-emojione/lib/getEmojiConfig.ts | 281 ++++++++++++++++++ .../app/emoji-emojione/lib/isSetNotNull.ts | 10 + .../app/emoji-emojione/lib/rocketchat.js | 280 ----------------- apps/meteor/app/emoji-emojione/server/lib.ts | 8 +- apps/meteor/app/emoji/client/lib.ts | 5 +- .../client/contexts/EmojiPickerContext.ts | 44 ++- .../client/lib/rooms/roomCoordinator.tsx | 2 +- .../client/providers/EmojiPickerProvider.tsx | 57 ++-- .../composer/EmojiPicker/EmojiPicker.tsx | 18 +- .../client/views/room/hooks/useOpenRoom.ts | 14 +- .../client/views/root/MainLayout/index.ts | 6 +- .../meteor/definition/externals/emojione.d.ts | 50 ++++ apps/meteor/package.json | 1 - yarn.lock | 1 - 16 files changed, 429 insertions(+), 365 deletions(-) delete mode 100644 apps/meteor/app/emoji-emojione/lib/emojioneRender.ts create mode 100644 apps/meteor/app/emoji-emojione/lib/getEmojiConfig.ts create mode 100644 apps/meteor/app/emoji-emojione/lib/isSetNotNull.ts delete mode 100644 apps/meteor/app/emoji-emojione/lib/rocketchat.js create mode 100644 apps/meteor/definition/externals/emojione.d.ts diff --git a/apps/meteor/app/emoji-emojione/client/lib.ts b/apps/meteor/app/emoji-emojione/client/lib.ts index 04671a6db6d80..408b053c20f8f 100644 --- a/apps/meteor/app/emoji-emojione/client/lib.ts +++ b/apps/meteor/app/emoji-emojione/client/lib.ts @@ -1,8 +1,9 @@ import { Meteor } from 'meteor/meteor'; import { emoji } from '../../emoji/client'; -import { getEmojiConfig, isSetNotNull } from '../lib/rocketchat'; import { getUserPreference } from '../../utils/client'; +import { isSetNotNull } from '../lib/isSetNotNull'; +import { getEmojiConfig } from '../lib/getEmojiConfig'; const config = getEmojiConfig(); @@ -18,16 +19,11 @@ if (emoji.packages.emojione) { // RocketChat.emoji.list is the collection of emojis from all emoji packages for (const [key, currentEmoji] of Object.entries(config.emojione.emojioneList)) { - // @ts-expect-error - emojione types currentEmoji.emojiPackage = 'emojione'; - // @ts-expect-error - emojione types emoji.list[key] = currentEmoji; - // @ts-expect-error - emojione types if (currentEmoji.shortnames) { - // @ts-expect-error - emojione types currentEmoji.shortnames.forEach((shortname: string) => { - // @ts-expect-error - emojione types emoji.list[shortname] = currentEmoji; }); } diff --git a/apps/meteor/app/emoji-emojione/lib/emojioneRender.ts b/apps/meteor/app/emoji-emojione/lib/emojioneRender.ts deleted file mode 100644 index 2ed772ce644c3..0000000000000 --- a/apps/meteor/app/emoji-emojione/lib/emojioneRender.ts +++ /dev/null @@ -1,9 +0,0 @@ -import emojione from 'emojione'; - -export function emojioneRender(message: string): string { - return emojione.toImage(message); -} - -export function emojioneRenderFromShort(message: string): string { - return emojione.shortnameToImage(message); -} diff --git a/apps/meteor/app/emoji-emojione/lib/getEmojiConfig.ts b/apps/meteor/app/emoji-emojione/lib/getEmojiConfig.ts new file mode 100644 index 0000000000000..51d2fbc1c4efc --- /dev/null +++ b/apps/meteor/app/emoji-emojione/lib/getEmojiConfig.ts @@ -0,0 +1,281 @@ +import emojione from 'emojione'; +import mem from 'mem'; + +import { emojisByCategory, emojiCategories, toneList } from './emojiPicker'; + +// TODO remove fix below when issue is solved: https://github.com/joypixels/emojione/issues/617 + +// add missing emojis not provided by JS object, but included on emoji.json +emojione.shortnames += + '|:tm:|:copyright:|:registered:|:digit_zero:|:digit_one:|:digit_two:|:digit_three:|:digit_four:|:digit_five:|:digit_six:|:digit_seven:|:digit_eight:|:digit_nine:|:pound_symbol:|:asterisk_symbol:'; +emojione.regShortNames = new RegExp( + `<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(${emojione.shortnames})`, + 'gi', +); + +emojione.emojioneList[':tm:'] = { + uc_base: '2122', + uc_output: '2122-fe0f', + uc_match: '2122-fe0f', + uc_greedy: '2122-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':copyright:'] = { + uc_base: '00a9', + uc_output: '00a9-f0ef', + uc_match: '00a9-fe0f', + uc_greedy: '00a9-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':registered:'] = { + uc_base: '00ae', + uc_output: '00ae-fe0f', + uc_match: '00ae-fe0f', + uc_greedy: '00ae-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_zero:'] = { + uc_base: '0030', + uc_output: '0030-fe0f', + uc_match: '0030-fe0f', + uc_greedy: '0030-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_one:'] = { + uc_base: '0031', + uc_output: '0031-fe0f', + uc_match: '0031-fe0f', + uc_greedy: '0031-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_two:'] = { + uc_base: '0032', + uc_output: '0032-fe0f', + uc_match: '0032-fe0f', + uc_greedy: '0032-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_three:'] = { + uc_base: '0033', + uc_output: '0033-fe0f', + uc_match: '0033-fe0f', + uc_greedy: '0033-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_four:'] = { + uc_base: '0034', + uc_output: '0034-fe0f', + uc_match: '0034-fe0f', + uc_greedy: '0034-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_five:'] = { + uc_base: '0035', + uc_output: '0035-fe0f', + uc_match: '0035-fe0f', + uc_greedy: '0035-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_six:'] = { + uc_base: '0036', + uc_output: '0036-fe0f', + uc_match: '0036-fe0f', + uc_greedy: '0036-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_seven:'] = { + uc_base: '0037', + uc_output: '0037-fe0f', + uc_match: '0037-fe0f', + uc_greedy: '0037-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_eight:'] = { + uc_base: '0038', + uc_output: '0038-fe0f', + uc_match: '0038-fe0f', + uc_greedy: '0038-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':digit_nine:'] = { + uc_base: '0039', + uc_output: '0039-fe0f', + uc_match: '0039-fe0f', + uc_greedy: '0039-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':pound_symbol:'] = { + uc_base: '0023', + uc_output: '0023-fe0f', + uc_match: '0023-fe0f', + uc_greedy: '0023-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; + +emojione.emojioneList[':asterisk_symbol:'] = { + uc_base: '002a', + uc_output: '002a-fe0f', + uc_match: '002a-fe0f', + uc_greedy: '002a-fe0f', + shortnames: [], + category: 'symbols', + emojiPackage: 'emojione', +}; +// end fix + +// fix for :+1: - had to replace all function that does its conversion: https://github.com/joypixels/emojione/blob/4.5.0/lib/js/emojione.js#L249 + +emojione.shortnameConversionMap = mem(emojione.shortnameConversionMap, { maxAge: 1000 }); + +emojione.unicodeCharRegex = mem(emojione.unicodeCharRegex, { maxAge: 1000 }); + +const convertShortName = mem( + (shortname) => { + // the fix is basically adding this .replace(/[+]/g, '\\$&') + if (typeof shortname === 'undefined' || shortname === '' || emojione.shortnames.indexOf(shortname.replace(/[+]/g, '\\$&')) === -1) { + // if the shortname doesnt exist just return the entire match + return shortname; + } + + // map shortname to parent + if (!emojione.emojioneList[shortname]) { + for (const emoji in emojione.emojioneList) { + if (!emojione.emojioneList.hasOwnProperty(emoji) || emoji === '') { + continue; + } + if (emojione.emojioneList[emoji].shortnames.indexOf(shortname) === -1) { + continue; + } + shortname = emoji; + break; + } + } + + const unicode = emojione.emojioneList[shortname].uc_output; + const fname = emojione.emojioneList[shortname].uc_base; + const category = fname.indexOf('-1f3f') >= 0 ? 'diversity' : emojione.emojioneList[shortname].category; + const title = emojione.imageTitleTag ? `title="${shortname}"` : ''; + // const size = ns.spriteSize === '32' || ns.spriteSize === '64' ? ns.spriteSize : '32'; + // if the emoji path has been set, we'll use the provided path, otherwise we'll use the default path + const ePath = + emojione.defaultPathPNG !== emojione.imagePathPNG ? emojione.imagePathPNG : `${emojione.defaultPathPNG + emojione.emojiSize}/`; + + // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname + const alt = emojione.unicodeAlt ? emojione.convert(unicode.toUpperCase()) : shortname; + + if (emojione.sprites) { + return `<span class="emojione emojione-${category} _${fname}" ${title}>${alt}</span>`; + } + return `<img class="emojione" alt="${alt}" ${title} src="${ePath}${fname}${emojione.fileExtension}"/>`; + }, + { maxAge: 1000 }, +); + +const convertUnicode = mem( + (entire, _m1, m2, m3) => { + const mappedUnicode = emojione.mapUnicodeToShort(); + + if (typeof m3 === 'undefined' || m3 === '' || !(emojione.unescapeHTML(m3) in emojione.asciiList)) { + // if the ascii doesnt exist just return the entire match + return entire; + } + + m3 = emojione.unescapeHTML(m3); + const unicode = emojione.asciiList[m3]; + const shortname = mappedUnicode[unicode]; + const category = unicode.indexOf('-1f3f') >= 0 ? 'diversity' : emojione.emojioneList[shortname].category; + const title = emojione.imageTitleTag ? `title="${emojione.escapeHTML(m3)}"` : ''; + // const size = ns.spriteSize === '32' || ns.spriteSize === '64' ? ns.spriteSize : '32'; + // if the emoji path has been set, we'll use the provided path, otherwise we'll use the default path + const ePath = + emojione.defaultPathPNG !== emojione.imagePathPNG ? emojione.imagePathPNG : `${emojione.defaultPathPNG + emojione.emojiSize}/`; + + // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname + const alt = emojione.unicodeAlt ? emojione.convert(unicode.toUpperCase()) : emojione.escapeHTML(m3); + + if (emojione.sprites) { + return `${m2}<span class="emojione emojione-${category} _${unicode}" ${title}>${alt}</span>`; + } + return `${m2}<img class="emojione" alt="${alt}" ${title} src="${ePath}${unicode}${emojione.fileExtension}"/>`; + }, + { maxAge: 1000, cacheKey: JSON.stringify }, +); + +emojione.shortnameToImage = (str) => { + // replace regular shortnames first + str = str.replace(emojione.regShortNames, convertShortName); + + // if ascii smileys are turned on, then we'll replace them! + if (emojione.ascii) { + const asciiRX = emojione.riskyMatchAscii ? emojione.regAsciiRisky : emojione.regAscii; + + return str.replace(asciiRX, convertUnicode); + } + + return str; +}; + +const isEmojiSupported = (str: string) => { + str = str.replace(emojione.regShortNames, convertShortName); + + // if ascii smileys are turned on, then we'll replace them! + if (emojione.ascii) { + const asciiRX = emojione.riskyMatchAscii ? emojione.regAsciiRisky : emojione.regAscii; + + return str.replace(asciiRX, convertUnicode); + } + + return str; +}; + +export const getEmojiConfig = () => ({ + emojione, + emojisByCategory, + emojiCategories, + toneList, + render: emojione.toImage, + renderPicker: emojione.shortnameToImage, + sprites: true, + isEmojiSupported, +}); diff --git a/apps/meteor/app/emoji-emojione/lib/isSetNotNull.ts b/apps/meteor/app/emoji-emojione/lib/isSetNotNull.ts new file mode 100644 index 0000000000000..000be1279e684 --- /dev/null +++ b/apps/meteor/app/emoji-emojione/lib/isSetNotNull.ts @@ -0,0 +1,10 @@ +// http://stackoverflow.com/a/26990347 function isSet() from Gajus +export const isSetNotNull = async (fn: () => unknown) => { + let value; + try { + value = await fn(); + } catch (e) { + value = null; + } + return value !== null && value !== undefined; +}; diff --git a/apps/meteor/app/emoji-emojione/lib/rocketchat.js b/apps/meteor/app/emoji-emojione/lib/rocketchat.js deleted file mode 100644 index 7e415b99efe82..0000000000000 --- a/apps/meteor/app/emoji-emojione/lib/rocketchat.js +++ /dev/null @@ -1,280 +0,0 @@ -import emojione from 'emojione'; -import mem from 'mem'; - -import { emojioneRender, emojioneRenderFromShort } from './emojioneRender'; -import { emojisByCategory, emojiCategories, toneList } from './emojiPicker'; - -// TODO remove fix below when issue is solved: https://github.com/joypixels/emojione/issues/617 - -// add missing emojis not provided by JS object, but included on emoji.json -emojione.shortnames += - '|:tm:|:copyright:|:registered:|:digit_zero:|:digit_one:|:digit_two:|:digit_three:|:digit_four:|:digit_five:|:digit_six:|:digit_seven:|:digit_eight:|:digit_nine:|:pound_symbol:|:asterisk_symbol:'; -emojione.regShortNames = new RegExp( - `<object[^>]*>.*?<\/object>|<span[^>]*>.*?<\/span>|<(?:object|embed|svg|img|div|span|p|a)[^>]*>|(${emojione.shortnames})`, - 'gi', -); - -emojione.emojioneList[':tm:'] = { - uc_base: '2122', - uc_output: '2122-fe0f', - uc_match: '2122-fe0f', - uc_greedy: '2122-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':copyright:'] = { - uc_base: '00a9', - uc_output: '00a9-f0ef', - uc_match: '00a9-fe0f', - uc_greedy: '00a9-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':registered:'] = { - uc_base: '00ae', - uc_output: '00ae-fe0f', - uc_match: '00ae-fe0f', - uc_greedy: '00ae-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_zero:'] = { - uc_base: '0030', - uc_output: '0030-fe0f', - uc_match: '0030-fe0f', - uc_greedy: '0030-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_one:'] = { - uc_base: '0031', - uc_output: '0031-fe0f', - uc_match: '0031-fe0f', - uc_greedy: '0031-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_two:'] = { - uc_base: '0032', - uc_output: '0032-fe0f', - uc_match: '0032-fe0f', - uc_greedy: '0032-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_three:'] = { - uc_base: '0033', - uc_output: '0033-fe0f', - uc_match: '0033-fe0f', - uc_greedy: '0033-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_four:'] = { - uc_base: '0034', - uc_output: '0034-fe0f', - uc_match: '0034-fe0f', - uc_greedy: '0034-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_five:'] = { - uc_base: '0035', - uc_output: '0035-fe0f', - uc_match: '0035-fe0f', - uc_greedy: '0035-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_six:'] = { - uc_base: '0036', - uc_output: '0036-fe0f', - uc_match: '0036-fe0f', - uc_greedy: '0036-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_seven:'] = { - uc_base: '0037', - uc_output: '0037-fe0f', - uc_match: '0037-fe0f', - uc_greedy: '0037-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_eight:'] = { - uc_base: '0038', - uc_output: '0038-fe0f', - uc_match: '0038-fe0f', - uc_greedy: '0038-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':digit_nine:'] = { - uc_base: '0039', - uc_output: '0039-fe0f', - uc_match: '0039-fe0f', - uc_greedy: '0039-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':pound_symbol:'] = { - uc_base: '0023', - uc_output: '0023-fe0f', - uc_match: '0023-fe0f', - uc_greedy: '0023-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; - -emojione.emojioneList[':asterisk_symbol:'] = { - uc_base: '002a', - uc_output: '002a-fe0f', - uc_match: '002a-fe0f', - uc_greedy: '002a-fe0f', - shortnames: [], - category: 'symbols', - emojiPackage: 'emojione', -}; -// end fix - -// fix for :+1: - had to replace all function that does its conversion: https://github.com/joypixels/emojione/blob/4.5.0/lib/js/emojione.js#L249 -(function (ns) { - ns.shortnameConversionMap = mem(ns.shortnameConversionMap, { maxAge: 1000 }); - - ns.unicodeCharRegex = mem(ns.unicodeCharRegex, { maxAge: 1000 }); - - const convertShortName = mem( - function (shortname) { - // the fix is basically adding this .replace(/[+]/g, '\\$&') - if (typeof shortname === 'undefined' || shortname === '' || ns.shortnames.indexOf(shortname.replace(/[+]/g, '\\$&')) === -1) { - // if the shortname doesnt exist just return the entire match - return shortname; - } - - // map shortname to parent - if (!ns.emojioneList[shortname]) { - for (const emoji in ns.emojioneList) { - if (!ns.emojioneList.hasOwnProperty(emoji) || emoji === '') { - continue; - } - if (ns.emojioneList[emoji].shortnames.indexOf(shortname) === -1) { - continue; - } - shortname = emoji; - break; - } - } - - const unicode = ns.emojioneList[shortname].uc_output; - const fname = ns.emojioneList[shortname].uc_base; - const category = fname.indexOf('-1f3f') >= 0 ? 'diversity' : ns.emojioneList[shortname].category; - const title = ns.imageTitleTag ? `title="${shortname}"` : ''; - // const size = ns.spriteSize === '32' || ns.spriteSize === '64' ? ns.spriteSize : '32'; - // if the emoji path has been set, we'll use the provided path, otherwise we'll use the default path - const ePath = ns.defaultPathPNG !== ns.imagePathPNG ? ns.imagePathPNG : `${ns.defaultPathPNG + ns.emojiSize}/`; - - // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname - const alt = ns.unicodeAlt ? ns.convert(unicode.toUpperCase()) : shortname; - - if (ns.sprites) { - return `<span class="emojione emojione-${category} _${fname}" ${title}>${alt}</span>`; - } - return `<img class="emojione" alt="${alt}" ${title} src="${ePath}${fname}${ns.fileExtension}"/>`; - }, - { maxAge: 1000 }, - ); - - const convertUnicode = mem( - function (entire, m1, m2, m3) { - const mappedUnicode = ns.mapUnicodeToShort(); - - if (typeof m3 === 'undefined' || m3 === '' || !(ns.unescapeHTML(m3) in ns.asciiList)) { - // if the ascii doesnt exist just return the entire match - return entire; - } - - m3 = ns.unescapeHTML(m3); - const unicode = ns.asciiList[m3]; - const shortname = mappedUnicode[unicode]; - const category = unicode.indexOf('-1f3f') >= 0 ? 'diversity' : ns.emojioneList[shortname].category; - const title = ns.imageTitleTag ? `title="${ns.escapeHTML(m3)}"` : ''; - // const size = ns.spriteSize === '32' || ns.spriteSize === '64' ? ns.spriteSize : '32'; - // if the emoji path has been set, we'll use the provided path, otherwise we'll use the default path - const ePath = ns.defaultPathPNG !== ns.imagePathPNG ? ns.imagePathPNG : `${ns.defaultPathPNG + ns.emojiSize}/`; - - // depending on the settings, we'll either add the native unicode as the alt tag, otherwise the shortname - const alt = ns.unicodeAlt ? ns.convert(unicode.toUpperCase()) : ns.escapeHTML(m3); - - if (ns.sprites) { - return `${m2}<span class="emojione emojione-${category} _${unicode}" ${title}>${alt}</span>`; - } - return `${m2}<img class="emojione" alt="${alt}" ${title} src="${ePath}${unicode}${ns.fileExtension}"/>`; - }, - { maxAge: 1000, cacheKey: JSON.stringify }, - ); - - ns.shortnameToImage = function (str) { - // replace regular shortnames first - str = str.replace(ns.regShortNames, convertShortName); - - // if ascii smileys are turned on, then we'll replace them! - if (ns.ascii) { - const asciiRX = ns.riskyMatchAscii ? ns.regAsciiRisky : ns.regAscii; - - return str.replace(asciiRX, convertUnicode); - } - - return str; - }; -})(emojione); - -export function getEmojiConfig() { - return { - emojione, - emojisByCategory, - emojiCategories, - toneList, - render: emojioneRender, - renderPicker: emojioneRenderFromShort, - sprites: true, - }; -} - -// http://stackoverflow.com/a/26990347 function isSet() from Gajus -export async function isSetNotNull(fn) { - let value; - try { - value = await fn(); - } catch (e) { - value = null; - } - return value !== null && value !== undefined; -} diff --git a/apps/meteor/app/emoji-emojione/server/lib.ts b/apps/meteor/app/emoji-emojione/server/lib.ts index 4f1808eba60a3..b80b9318a828a 100644 --- a/apps/meteor/app/emoji-emojione/server/lib.ts +++ b/apps/meteor/app/emoji-emojione/server/lib.ts @@ -1,7 +1,8 @@ import { Meteor } from 'meteor/meteor'; import { emoji } from '../../emoji/server'; -import { getEmojiConfig, isSetNotNull } from '../lib/rocketchat'; +import { getEmojiConfig } from '../lib/getEmojiConfig'; +import { isSetNotNull } from '../lib/isSetNotNull'; import { getUserPreference } from '../../utils/server'; const config = getEmojiConfig(); @@ -20,16 +21,11 @@ if (emoji.packages.emojione) { for (const key in config.emojione.emojioneList) { if (config.emojione.emojioneList.hasOwnProperty(key)) { const currentEmoji = config.emojione.emojioneList[key]; - // @ts-expect-error - emojione types currentEmoji.emojiPackage = 'emojione'; - // @ts-expect-error - emojione types emoji.list[key] = currentEmoji; - // @ts-expect-error - emojione types if (currentEmoji.shortnames) { - // @ts-expect-error - emojione types currentEmoji.shortnames.forEach((shortname: string) => { - // @ts-expect-error - emojione types emoji.list[shortname] = currentEmoji; }); } diff --git a/apps/meteor/app/emoji/client/lib.ts b/apps/meteor/app/emoji/client/lib.ts index 55b858cb3e11b..1d2397c9568d3 100644 --- a/apps/meteor/app/emoji/client/lib.ts +++ b/apps/meteor/app/emoji/client/lib.ts @@ -1,4 +1,5 @@ -import { emojioneRender } from '../../emoji-emojione/lib/emojioneRender'; +import emojione from 'emojione'; + import type { EmojiPackages } from '../lib/rocketchat'; export const emoji: EmojiPackages = { @@ -10,7 +11,7 @@ export const emoji: EmojiPackages = { recent: [], }, toneList: {}, - render: emojioneRender, + render: emojione.toImage, renderPicker(emojiToRender) { const correctPackage = emoji.list[emojiToRender].emojiPackage; if (!correctPackage) { diff --git a/apps/meteor/client/contexts/EmojiPickerContext.ts b/apps/meteor/client/contexts/EmojiPickerContext.ts index 50490182d519e..4c4257c3dd3d7 100644 --- a/apps/meteor/client/contexts/EmojiPickerContext.ts +++ b/apps/meteor/client/contexts/EmojiPickerContext.ts @@ -10,7 +10,7 @@ type EmojiPickerContextValue = { handlePreview: (emoji: string, name: string) => void; handleRemovePreview: () => void; addRecentEmoji: (emoji: string) => void; - emojiListByCategory: EmojiByCategory[]; + getEmojiListsByCategory: () => EmojiByCategory[]; recentEmojis: string[]; setRecentEmojis: (emoji: string[]) => void; actualTone: number; @@ -44,16 +44,32 @@ export const usePreviewEmoji = () => ({ handleRemovePreview: useEmojiPickerContext().handleRemovePreview, }); -export const useEmojiPickerData = () => ({ - addRecentEmoji: useEmojiPickerContext().addRecentEmoji, - emojiListByCategory: useEmojiPickerContext().emojiListByCategory, - recentEmojis: useEmojiPickerContext().recentEmojis, - setRecentEmojis: useEmojiPickerContext().setRecentEmojis, - actualTone: useEmojiPickerContext().actualTone, - currentCategory: useEmojiPickerContext().currentCategory, - setCurrentCategory: useEmojiPickerContext().setCurrentCategory, - customItemsLimit: useEmojiPickerContext().customItemsLimit, - setCustomItemsLimit: useEmojiPickerContext().setCustomItemsLimit, - setActualTone: useEmojiPickerContext().setActualTone, - quickReactions: useEmojiPickerContext().quickReactions, -}); +export const useEmojiPickerData = () => { + const { + actualTone, + addRecentEmoji, + currentCategory, + customItemsLimit, + getEmojiListsByCategory, + quickReactions, + recentEmojis, + setActualTone, + setCurrentCategory, + setCustomItemsLimit, + setRecentEmojis, + } = useEmojiPickerContext(); + + return { + addRecentEmoji, + getEmojiListsByCategory, + recentEmojis, + setRecentEmojis, + actualTone, + currentCategory, + setCurrentCategory, + customItemsLimit, + setCustomItemsLimit, + setActualTone, + quickReactions, + }; +}; diff --git a/apps/meteor/client/lib/rooms/roomCoordinator.tsx b/apps/meteor/client/lib/rooms/roomCoordinator.tsx index 9a2e3d14ab475..31eda2982a125 100644 --- a/apps/meteor/client/lib/rooms/roomCoordinator.tsx +++ b/apps/meteor/client/lib/rooms/roomCoordinator.tsx @@ -19,7 +19,7 @@ import type { import { RoomCoordinator } from '../../../lib/rooms/coordinator'; import { router } from '../../providers/RouterProvider'; import RoomRoute from '../../views/room/RoomRoute'; -import MainLayout from '../../views/root/MainLayout/MainLayout'; +import MainLayout from '../../views/root/MainLayout'; import { appLayout } from '../appLayout'; class RoomCoordinatorClient extends RoomCoordinator { diff --git a/apps/meteor/client/providers/EmojiPickerProvider.tsx b/apps/meteor/client/providers/EmojiPickerProvider.tsx index 5c9f3a8e99464..84ffa3394c10a 100644 --- a/apps/meteor/client/providers/EmojiPickerProvider.tsx +++ b/apps/meteor/client/providers/EmojiPickerProvider.tsx @@ -1,5 +1,5 @@ import { useDebouncedState, useLocalStorage } from '@rocket.chat/fuselage-hooks'; -import type { ReactNode, ReactElement } from 'react'; +import type { ReactNode, ReactElement, ContextType } from 'react'; import React, { useState, useCallback, useMemo, useEffect } from 'react'; import type { EmojiByCategory } from '../../app/emoji/client'; @@ -14,7 +14,6 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen const [emojiToPreview, setEmojiToPreview] = useDebouncedState<{ emoji: string; name: string } | null>(null, 100); const [recentEmojis, setRecentEmojis] = useLocalStorage<string[]>('emoji.recent', []); const [actualTone, setActualTone] = useLocalStorage('emoji.tone', 0); - const [emojiListByCategory, setEmojiListByCategory] = useState<EmojiByCategory[]>([]); const [currentCategory, setCurrentCategory] = useState('recent'); const [customItemsLimit, setCustomItemsLimit] = useState(DEFAULT_ITEMS_LIMIT); @@ -40,26 +39,37 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen [frequentEmojis, setFrequentEmojis], ); + const [getEmojiListsByCategory, setEmojiListsByCategoryGetter] = useState<() => EmojiByCategory[]>(() => () => []); + // TODO: improve this update const updateEmojiListByCategory = useCallback( (categoryKey: string, limit: number = DEFAULT_ITEMS_LIMIT) => { - const result = emojiListByCategory.map((category) => { - return categoryKey === category.key - ? { - ...category, - emojis: { - list: createEmojiList(category.key, null, recentEmojis, setRecentEmojis), - limit: category.key === CUSTOM_CATEGORY ? limit | customItemsLimit : null, - }, - } - : category; - }); - - setEmojiListByCategory(result); + setEmojiListsByCategoryGetter( + (getEmojiListsByCategory) => () => + getEmojiListsByCategory().map((category) => + categoryKey === category.key + ? { + ...category, + emojis: { + list: createEmojiList(category.key, null, recentEmojis, setRecentEmojis), + limit: category.key === CUSTOM_CATEGORY ? limit | customItemsLimit : null, + }, + } + : category, + ), + ); }, - [customItemsLimit, emojiListByCategory, recentEmojis, setRecentEmojis], + [customItemsLimit, recentEmojis, setRecentEmojis], ); + useEffect(() => { + if (recentEmojis?.length > 0) { + updateRecent(recentEmojis); + } + + setEmojiListsByCategoryGetter(() => () => createPickerEmojis(customItemsLimit, actualTone, recentEmojis, setRecentEmojis)); + }, [actualTone, recentEmojis, customItemsLimit, currentCategory, setRecentEmojis, frequentEmojis]); + const addRecentEmoji = useCallback( (_emoji: string) => { addFrequentEmojis(_emoji); @@ -83,21 +93,12 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen [recentEmojis, setRecentEmojis, updateEmojiListByCategory, addFrequentEmojis], ); - useEffect(() => { - if (recentEmojis?.length > 0) { - updateRecent(recentEmojis); - } - - const emojis = createPickerEmojis(customItemsLimit, actualTone, recentEmojis, setRecentEmojis); - setEmojiListByCategory(emojis); - }, [actualTone, recentEmojis, customItemsLimit, currentCategory, setRecentEmojis, frequentEmojis]); - const open = useCallback((ref: Element, callback: (emoji: string) => void) => { return setEmojiPicker(<EmojiPicker reference={ref} onClose={() => setEmojiPicker(null)} onPickEmoji={(emoji) => callback(emoji)} />); }, []); const contextValue = useMemo( - () => ({ + (): ContextType<typeof EmojiPickerContext> => ({ isOpen: emojiPicker !== null, close: () => setEmojiPicker(null), open, @@ -105,7 +106,7 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen handlePreview: (emoji: string, name: string) => setEmojiToPreview({ emoji, name }), handleRemovePreview: () => setEmojiToPreview(null), addRecentEmoji, - emojiListByCategory, + getEmojiListsByCategory, recentEmojis, setRecentEmojis, actualTone, @@ -122,7 +123,7 @@ const EmojiPickerProvider = ({ children }: { children: ReactNode }): ReactElemen emojiToPreview, setEmojiToPreview, addRecentEmoji, - emojiListByCategory, + getEmojiListsByCategory, recentEmojis, setRecentEmojis, actualTone, diff --git a/apps/meteor/client/views/composer/EmojiPicker/EmojiPicker.tsx b/apps/meteor/client/views/composer/EmojiPicker/EmojiPicker.tsx index cd37a05699fb2..47d8ba8492018 100644 --- a/apps/meteor/client/views/composer/EmojiPicker/EmojiPicker.tsx +++ b/apps/meteor/client/views/composer/EmojiPicker/EmojiPicker.tsx @@ -11,7 +11,7 @@ import { } from '@rocket.chat/ui-client'; import { useTranslation, usePermission, useRoute } from '@rocket.chat/ui-contexts'; import type { ChangeEvent, KeyboardEvent, MouseEvent, RefObject } from 'react'; -import React, { useLayoutEffect, useState, useEffect, useRef, useCallback } from 'react'; +import React, { useLayoutEffect, useState, useEffect, useRef } from 'react'; import type { VirtuosoHandle } from 'react-virtuoso'; import type { EmojiItem, EmojiCategoryPosition } from '../../../../app/emoji/client'; @@ -59,13 +59,13 @@ const EmojiPicker = ({ reference, onClose, onPickEmoji }: EmojiPickerProps) => { setRecentEmojis, actualTone, currentCategory, - emojiListByCategory, + getEmojiListsByCategory, customItemsLimit, setActualTone, setCustomItemsLimit, } = useEmojiPickerData(); - useEffect(() => () => handleRemovePreview(), []); + useEffect(() => () => handleRemovePreview(), [handleRemovePreview]); const scrollCategories = useMediaQuery('(width < 340px)'); @@ -126,16 +126,12 @@ const EmojiPicker = ({ reference, onClose, onPickEmoji }: EmojiPickerProps) => { onClose(); }; - const showInitialCategory = useCallback((customEmojiList) => { - handleGoToCategory(customEmojiList.length > 0 ? 0 : 1); - }, []); - useEffect(() => { if (recentEmojis.length === 0 && currentCategory === 'recent') { - const customEmojiList = emojiListByCategory.filter(({ key }) => key === 'rocket'); - showInitialCategory(customEmojiList); + const customEmojiList = getEmojiListsByCategory().filter(({ key }) => key === 'rocket'); + handleGoToCategory(customEmojiList.length > 0 ? 0 : 1); } - }, [actualTone, recentEmojis, emojiListByCategory, currentCategory, setRecentEmojis, showInitialCategory]); + }, [actualTone, recentEmojis, getEmojiListsByCategory, currentCategory, setRecentEmojis]); const handleSearch = (e: ChangeEvent<HTMLInputElement>) => { setSearchTerm(e.target.value); @@ -216,7 +212,7 @@ const EmojiPicker = ({ reference, onClose, onPickEmoji }: EmojiPickerProps) => { {!searching && ( <CategoriesResult ref={virtuosoRef} - emojiListByCategory={emojiListByCategory} + emojiListByCategory={getEmojiListsByCategory()} categoriesPosition={categoriesPosition} customItemsLimit={customItemsLimit} handleLoadMore={handleLoadMore} diff --git a/apps/meteor/client/views/room/hooks/useOpenRoom.ts b/apps/meteor/client/views/room/hooks/useOpenRoom.ts index 4e109759593a1..7fa0114bcd64c 100644 --- a/apps/meteor/client/views/room/hooks/useOpenRoom.ts +++ b/apps/meteor/client/views/room/hooks/useOpenRoom.ts @@ -3,15 +3,10 @@ import { useMethod, useRoute, useSetting, useUser } from '@rocket.chat/ui-contex import { useQuery } from '@tanstack/react-query'; import { useRef } from 'react'; -import { ChatRoom, ChatSubscription } from '../../../../app/models/client'; -import { LegacyRoomManager } from '../../../../app/ui-utils/client'; import { roomFields } from '../../../../lib/publishFields'; import { omit } from '../../../../lib/utils/omit'; -import { RoomManager } from '../../../lib/RoomManager'; import { NotAuthorizedError } from '../../../lib/errors/NotAuthorizedError'; import { RoomNotFoundError } from '../../../lib/errors/RoomNotFoundError'; -import { fireGlobalEvent } from '../../../lib/utils/fireGlobalEvent'; -import { waitUntilFind } from '../../../lib/utils/waitUntilFind'; export function useOpenRoom({ type, reference }: { type: RoomType; reference: string }) { const user = useUser(); @@ -53,6 +48,8 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st } } + const { ChatRoom, ChatSubscription } = await import('../../../../app/models/client'); + ChatRoom.upsert({ _id: roomData._id }, { $set, $unset }); const room = ChatRoom.findOne({ _id: roomData._id }); @@ -60,12 +57,17 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st throw new TypeError('room is undefined'); } + const { LegacyRoomManager } = await import('../../../../app/ui-utils/client'); + if (room._id !== reference && type === 'd') { // Redirect old url using username to rid await LegacyRoomManager.close(type + reference); throw new RoomNotFoundError(undefined, { rid: room._id }); } + const { RoomManager } = await import('../../../lib/RoomManager'); + const { fireGlobalEvent } = await import('../../../lib/utils/fireGlobalEvent'); + unsubscribeFromRoomOpenedEvent.current(); unsubscribeFromRoomOpenedEvent.current = RoomManager.once('opened', () => fireGlobalEvent('room-opened', omit(room, 'usernames'))); @@ -95,6 +97,8 @@ export function useOpenRoom({ type, reference }: { type: RoomType; reference: st } const { rid } = await createDirectMessage(...reference.split(', ')); + const { ChatSubscription } = await import('../../../../app/models/client'); + const { waitUntilFind } = await import('../../../lib/utils/waitUntilFind'); await waitUntilFind(() => ChatSubscription.findOne({ rid })); directRoute.push({ rid }, (prev) => prev); }, diff --git a/apps/meteor/client/views/root/MainLayout/index.ts b/apps/meteor/client/views/root/MainLayout/index.ts index ed5671ffcf21e..4b36da185ab3b 100644 --- a/apps/meteor/client/views/root/MainLayout/index.ts +++ b/apps/meteor/client/views/root/MainLayout/index.ts @@ -1 +1,5 @@ -export { default } from './MainLayout'; +import { lazy } from 'react'; + +const MainLayout = lazy(() => import('./MainLayout')); + +export default MainLayout; diff --git a/apps/meteor/definition/externals/emojione.d.ts b/apps/meteor/definition/externals/emojione.d.ts new file mode 100644 index 0000000000000..6e9c9deab0c77 --- /dev/null +++ b/apps/meteor/definition/externals/emojione.d.ts @@ -0,0 +1,50 @@ +declare module 'emojione' { + export as namespace emojione; + + export let sprites: boolean; + export let imagePathSVG: string; + export let imagePathSVGSprites: string; + export let imageType: 'png' | 'svg'; + export let unicodeAlt: boolean; + export let ascii: boolean; + export let unicodeRegexp: string; + export let cacheBustParam: string; + export let emojioneList: { + [key: string]: { + uc_base: string; + uc_output: string; + uc_match: string; + uc_greedy: string; + shortnames: string[]; + category: string; + emojiPackage: string; + }; + }; + + type Unicode = string; + type MappedUnicode = Record<Unicode, string>; + + export let regShortNames: RegExp; + export let shortnames: string; + export const imageTitleTag: boolean; + export const defaultPathPNG: string; + export let imagePathPNG: string; + export const emojiSize: string; + export const fileExtension: string; + export const asciiList: Record<string, Unicode>; + export const riskyMatchAscii: boolean; + export const regAsciiRisky: RegExp; + export const regAscii: RegExp; + + export function toShort(str: string): string; + export function toImage(str: string): string; + export function shortnameToImage(str: string): string; + export function unicodeToImage(str: string): string; + export function shortnameToUnicode(str: string): string; + export function shortnameConversionMap(): unknown; + export function unicodeCharRegex(): unknown; + export function convert(str: string): string; + export function mapUnicodeToShort(): MappedUnicode; + export function escapeHTML(str: string): string; + export function unescapeHTML(str: string): string; +} diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 0370b5327b4f3..80289d401c386 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -104,7 +104,6 @@ "@types/cssom": "^0.4.1", "@types/dompurify": "^2.3.3", "@types/ejson": "^2.2.0", - "@types/emojione": "^2.2.6", "@types/express": "^4.17.17", "@types/express-rate-limit": "^5.1.3", "@types/fibers": "^3.1.1", diff --git a/yarn.lock b/yarn.lock index 3dcf48ca6520c..49aef6230cd1f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10299,7 +10299,6 @@ __metadata: "@types/cssom": ^0.4.1 "@types/dompurify": ^2.3.3 "@types/ejson": ^2.2.0 - "@types/emojione": ^2.2.6 "@types/express": ^4.17.17 "@types/express-rate-limit": ^5.1.3 "@types/fibers": ^3.1.1 From ad014d1e347104c48823895e7c93c1d350463deb Mon Sep 17 00:00:00 2001 From: Douglas Fabris <devfabris@gmail.com> Date: Fri, 7 Jul 2023 20:37:02 -0300 Subject: [PATCH 76/79] chore: `CloudRoute` to typescript (#29762) --- apps/meteor/app/emoji/client/helpers.ts | 2 +- apps/meteor/client/lib/constants.ts | 1 + .../views/admin/cloud/{CloudRoute.js => CloudRoute.tsx} | 4 ++-- apps/meteor/client/views/admin/cloud/CopyStep.tsx | 4 ++-- apps/meteor/client/views/admin/cloud/RegisterWorkspace.tsx | 3 +-- .../views/admin/cloud/components/RegisterWorkspaceMenu.tsx | 4 ++-- apps/meteor/client/views/admin/cloud/constants.js | 1 - 7 files changed, 9 insertions(+), 10 deletions(-) rename apps/meteor/client/views/admin/cloud/{CloudRoute.js => CloudRoute.tsx} (92%) delete mode 100644 apps/meteor/client/views/admin/cloud/constants.js diff --git a/apps/meteor/app/emoji/client/helpers.ts b/apps/meteor/app/emoji/client/helpers.ts index d9fba8720d1c1..35badda26a732 100644 --- a/apps/meteor/app/emoji/client/helpers.ts +++ b/apps/meteor/app/emoji/client/helpers.ts @@ -159,7 +159,7 @@ export const updateRecent = (recentList: string[]) => { const getEmojiRender = (emojiName: string) => { const emojiPackageName = emoji.list[emojiName]?.emojiPackage; const emojiPackage = emoji.packages[emojiPackageName]; - return emojiPackage.render(emojiName); + return emojiPackage?.render(emojiName); }; export const getFrequentEmoji = (frequentEmoji: string[]) => { diff --git a/apps/meteor/client/lib/constants.ts b/apps/meteor/client/lib/constants.ts index 89052eea93acb..7e437deb7fd21 100644 --- a/apps/meteor/client/lib/constants.ts +++ b/apps/meteor/client/lib/constants.ts @@ -1,3 +1,4 @@ export const USER_STATUS_TEXT_MAX_LENGTH = 120; export const BIO_TEXT_MAX_LENGTH = 260; export const VIDEOCONF_STACK_MAX_USERS = 6; +export const CLOUD_CONSOLE_URL = 'https://cloud.rocket.chat'; diff --git a/apps/meteor/client/views/admin/cloud/CloudRoute.js b/apps/meteor/client/views/admin/cloud/CloudRoute.tsx similarity index 92% rename from apps/meteor/client/views/admin/cloud/CloudRoute.js rename to apps/meteor/client/views/admin/cloud/CloudRoute.tsx index 705136294a3b3..89bf5f1df772c 100644 --- a/apps/meteor/client/views/admin/cloud/CloudRoute.js +++ b/apps/meteor/client/views/admin/cloud/CloudRoute.tsx @@ -4,7 +4,7 @@ import React from 'react'; import NotAuthorizedPage from '../../notAuthorized/NotAuthorizedPage'; import RegisterWorkspace from './RegisterWorkspace'; -function CloudRoute() { +const CloudRoute = () => { const canManageCloud = usePermission('manage-cloud'); if (!canManageCloud) { @@ -12,6 +12,6 @@ function CloudRoute() { } return <RegisterWorkspace />; -} +}; export default CloudRoute; diff --git a/apps/meteor/client/views/admin/cloud/CopyStep.tsx b/apps/meteor/client/views/admin/cloud/CopyStep.tsx index 96b6e14eaac55..4232572ea7bf8 100644 --- a/apps/meteor/client/views/admin/cloud/CopyStep.tsx +++ b/apps/meteor/client/views/admin/cloud/CopyStep.tsx @@ -5,7 +5,7 @@ import type { FC } from 'react'; import React, { useEffect, useState, useRef } from 'react'; import MarkdownText from '../../../components/MarkdownText'; -import { cloudConsoleUrl } from './constants'; +import { CLOUD_CONSOLE_URL } from '../../../lib/constants'; type CopyStepProps = { onNextButtonClick: () => void; @@ -61,7 +61,7 @@ const CopyStep: FC<CopyStepProps> = ({ onNextButtonClick }) => { <Icon name='copy' /> {t('Copy')} </Button> </Box> - <MarkdownText preserveHtml={true} content={t('Cloud_click_here', { cloudConsoleUrl })} /> + <MarkdownText preserveHtml={true} content={t('Cloud_click_here', { CLOUD_CONSOLE_URL })} /> </Modal.Content> <Modal.Footer> <Modal.FooterControllers> diff --git a/apps/meteor/client/views/admin/cloud/RegisterWorkspace.tsx b/apps/meteor/client/views/admin/cloud/RegisterWorkspace.tsx index 9e05ef111be52..9e4e479f16f9c 100644 --- a/apps/meteor/client/views/admin/cloud/RegisterWorkspace.tsx +++ b/apps/meteor/client/views/admin/cloud/RegisterWorkspace.tsx @@ -2,7 +2,6 @@ import { Box, Tag } from '@rocket.chat/fuselage'; import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useSetModal, useMethod, useTranslation } from '@rocket.chat/ui-contexts'; import { useQuery } from '@tanstack/react-query'; -import type { ReactNode } from 'react'; import React from 'react'; import Page from '../../../components/Page'; @@ -12,7 +11,7 @@ import RegisterWorkspaceMenu from './components/RegisterWorkspaceMenu'; import ConnectWorkspaceModal from './modals/ConnectWorkspaceModal'; import RegisterWorkspaceModal from './modals/RegisterWorkspaceModal'; -const RegisterWorkspace = (): ReactNode => { +const RegisterWorkspace = () => { const t = useTranslation(); const setModal = useSetModal(); diff --git a/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx b/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx index b24cd12bbab26..a3de1c5384c8b 100644 --- a/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx +++ b/apps/meteor/client/views/admin/cloud/components/RegisterWorkspaceMenu.tsx @@ -3,7 +3,7 @@ import { useSetModal, useTranslation } from '@rocket.chat/ui-contexts'; import React from 'react'; import { useExternalLink } from '../../../../hooks/useExternalLink'; -import { cloudConsoleUrl } from '../constants'; +import { CLOUD_CONSOLE_URL } from '../../../../lib/constants'; import RegisteredWorkspaceModal from '../modals/RegisteredWorkspaceModal'; type RegisterWorkspaceMenuProps = { @@ -35,7 +35,7 @@ const RegisterWorkspaceMenu = ({ <ButtonGroup> {isWorkspaceRegistered && isConnectedToCloud && ( <> - <Button role='link' onClick={() => handleLinkClick(cloudConsoleUrl)}> + <Button role='link' onClick={() => handleLinkClick(CLOUD_CONSOLE_URL)}> <Icon name='new-window' size='x20' pie={4} /> {t('Cloud')} </Button> diff --git a/apps/meteor/client/views/admin/cloud/constants.js b/apps/meteor/client/views/admin/cloud/constants.js deleted file mode 100644 index d9cf214b19dbc..0000000000000 --- a/apps/meteor/client/views/admin/cloud/constants.js +++ /dev/null @@ -1 +0,0 @@ -export const cloudConsoleUrl = 'https://cloud.rocket.chat'; From 5ff71b3f82199eef39e8dd5ed12bcbc453186b6f Mon Sep 17 00:00:00 2001 From: Aleksander Nicacio da Silva <aleksander.silva@rocket.chat> Date: Sat, 8 Jul 2023 00:06:49 -0300 Subject: [PATCH 77/79] regression: Current Chats tags filter not showing department tags (#29758) --- .../omnichannel/currentChats/FilterByText.tsx | 2 +- .../chats/contextualBar/RoomEdit/RoomEdit.tsx | 2 +- apps/meteor/ee/client/hooks/useTagsList.ts | 17 ++++++++++------- .../tags/AutoCompleteTagsMultiple.js | 7 +++++-- .../client/omnichannel/tags/CurrentChatTags.tsx | 6 ++++-- 5 files changed, 21 insertions(+), 13 deletions(-) diff --git a/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx b/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx index 6587563caa5f9..3659f77b394f9 100644 --- a/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx +++ b/apps/meteor/client/views/omnichannel/currentChats/FilterByText.tsx @@ -150,7 +150,7 @@ const FilterByText: FilterByTextType = ({ setFilter, reload, customFields, setCu <Box display='flex' flexDirection='row' marginBlockStart='x8' {...props}> <Box display='flex' mie='x8' flexGrow={1} flexDirection='column'> <Label mb='x4'>{t('Tags')}</Label> - <EETagsComponent value={tags} handler={handleTags} /> + <EETagsComponent value={tags} handler={handleTags} viewAll /> </Box> </Box> )} diff --git a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit/RoomEdit.tsx b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit/RoomEdit.tsx index 435e29743469f..433af2135068f 100644 --- a/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit/RoomEdit.tsx +++ b/apps/meteor/client/views/omnichannel/directory/chats/contextualBar/RoomEdit/RoomEdit.tsx @@ -134,7 +134,7 @@ function RoomEdit({ room, visitor, reload, reloadInfo, onClose }: RoomEditProps) </Field> <Field> - <Tags tags={tagsField.value} handler={tagsField.onChange} /> + <Tags tags={tagsField.value} handler={tagsField.onChange} department={room.departmentId} /> </Field> {SlaPoliciesSelect && !!slaPolicies?.length && ( diff --git a/apps/meteor/ee/client/hooks/useTagsList.ts b/apps/meteor/ee/client/hooks/useTagsList.ts index 229e495342e14..78dfc4de00937 100644 --- a/apps/meteor/ee/client/hooks/useTagsList.ts +++ b/apps/meteor/ee/client/hooks/useTagsList.ts @@ -9,16 +9,18 @@ import { RecordList } from '../../../client/lib/lists/RecordList'; type TagsListOptions = { filter: string; department?: string; + viewAll?: boolean; }; -export const useTagsList = ( - options: TagsListOptions, -): { +type UseTagsListResult = { itemsList: RecordList<ILivechatTagRecord>; initialItemCount: number; reload: () => void; loadMoreItems: (start: number, end: number) => void; -} => { +}; + +export const useTagsList = (options: TagsListOptions): UseTagsListResult => { + const { viewAll, department, filter } = options; const [itemsList, setItemsList] = useState(() => new RecordList<ILivechatTagRecord>()); const reload = useCallback(() => setItemsList(new RecordList<ILivechatTagRecord>()), []); @@ -31,10 +33,11 @@ export const useTagsList = ( const fetchData = useCallback( async (start, end) => { const { tags, total } = await getTags({ - text: options.filter, + text: filter, offset: start, count: end + start, - ...(options.department && { department: options.department }), + ...(viewAll && { viewAll: 'true' }), + ...(department && { department }), }); return { @@ -47,7 +50,7 @@ export const useTagsList = ( itemCount: total, }; }, - [getTags, options.filter, options.department], + [getTags, filter, viewAll, department], ); const { loadMoreItems, initialItemCount } = useScrollableRecordList(itemsList, fetchData, 25); diff --git a/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js b/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js index 5f7fe4a4ffe67..086d796152fc4 100644 --- a/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js +++ b/apps/meteor/ee/client/omnichannel/tags/AutoCompleteTagsMultiple.js @@ -8,7 +8,7 @@ import { AsyncStatePhase } from '../../../../client/hooks/useAsyncState'; import { useTagsList } from '../../hooks/useTagsList'; const AutoCompleteTagMultiple = (props) => { - const { value, onlyMyTags = false, onChange = () => {}, department } = props; + const { value, onlyMyTags = false, onChange = () => {}, department, viewAll = false } = props; const t = useTranslation(); const [tagsFilter, setTagsFilter] = useState(''); @@ -16,7 +16,10 @@ const AutoCompleteTagMultiple = (props) => { const debouncedTagsFilter = useDebouncedValue(tagsFilter, 500); const { itemsList: tagsList, loadMoreItems: loadMoreTags } = useTagsList( - useMemo(() => ({ filter: debouncedTagsFilter, onlyMyTags, department }), [debouncedTagsFilter, onlyMyTags, department]), + useMemo( + () => ({ filter: debouncedTagsFilter, onlyMyTags, department, viewAll }), + [debouncedTagsFilter, onlyMyTags, department, viewAll], + ), ); const { phase: tagsPhase, items: tagsItems, itemCount: tagsTotal } = useRecordList(tagsList); diff --git a/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx b/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx index 7253764c52b64..61c1d11af9479 100644 --- a/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx +++ b/apps/meteor/ee/client/omnichannel/tags/CurrentChatTags.tsx @@ -3,8 +3,10 @@ import React from 'react'; import AutoCompleteTagsMultiple from './AutoCompleteTagsMultiple'; -const CurrentChatTags: FC<{ value: Array<string>; handler: () => void; department?: string }> = ({ value, handler, department }) => ( - <AutoCompleteTagsMultiple onChange={handler} value={value} department={department} /> +type CurrentChatTagsProps = { value: Array<string>; handler: () => void; department?: string; viewAll?: boolean }; + +const CurrentChatTags: FC<CurrentChatTagsProps> = ({ value, handler, department, viewAll }) => ( + <AutoCompleteTagsMultiple onChange={handler} value={value} department={department} viewAll={viewAll} /> ); export default CurrentChatTags; From 9f18c611846fbdf7e6b8d5ab72d0b2a38c8612bd Mon Sep 17 00:00:00 2001 From: Ujjwal Solanki <75205571+ujjwalsolankii@users.noreply.github.com> Date: Mon, 10 Jul 2023 02:11:13 +0530 Subject: [PATCH 78/79] docs: Updated README file (#29770) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f943581a9b1f7..a63baba65dd00 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ yarn turbo run ms After initialized, you can access the server at http://localhost:4000 -> ⚠️ Check more detailed informations in the [Rocket.Chat Environment Setup](https://developer.rocket.chat/rocket.chat/rocket-chat-environment-setup) guide +> ⚠️ Check more detailed information in the [Rocket.Chat Environment Setup](https://developer.rocket.chat/rocket.chat/rocket-chat-environment-setup) guide # 💻 Installation From 3df4d266e4cb206bc61010a76d0acd312889ce21 Mon Sep 17 00:00:00 2001 From: Guilherme Gazzo <guilhermegazzo@gmail.com> Date: Sun, 9 Jul 2023 22:27:53 -0300 Subject: [PATCH 79/79] chore: bump fuselage packages --- yarn.lock | 155 ++++++++++++++++++++++++------------------------------ 1 file changed, 69 insertions(+), 86 deletions(-) diff --git a/yarn.lock b/yarn.lock index 49aef6230cd1f..4f3bd5af46f9d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9601,7 +9601,7 @@ __metadata: languageName: unknown linkType: soft -"@rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.103, @rocket.chat/css-in-js@npm:~0.31.23-dev.157": +"@rocket.chat/css-in-js@npm:^0.31.23, @rocket.chat/css-in-js@npm:~0.31.23-dev.160": version: 0.31.23 resolution: "@rocket.chat/css-in-js@npm:0.31.23" dependencies: @@ -9615,19 +9615,19 @@ __metadata: linkType: hard "@rocket.chat/css-in-js@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/css-in-js@npm:0.31.23-dev.103" + version: 0.31.23-dev.160 + resolution: "@rocket.chat/css-in-js@npm:0.31.23-dev.160" dependencies: "@emotion/hash": ^0.9.0 - "@rocket.chat/css-supports": ~0.31.23-dev.103 - "@rocket.chat/memo": ~0.31.23-dev.103 - "@rocket.chat/stylis-logical-props-middleware": ~0.31.23-dev.103 + "@rocket.chat/css-supports": ~0.31.23-dev.160 + "@rocket.chat/memo": ~0.31.23-dev.160 + "@rocket.chat/stylis-logical-props-middleware": ~0.31.23-dev.160 stylis: ~4.1.3 - checksum: 7fea932e9b60d186722f289989aead15fb6d8af2b11f75a44fefa3158c19d953ad789b161599b33bc5ede7c79c4d6de9b3453ca0dd1fd3ec453fe77891df4e55 + checksum: b7aad2a07fd37516e3b8d5ef8b5e61be24283c0c52731c0a0a5b09b4a254976eea741867acf5329446aa88d7f14a3344089cea637c777a8f741da59ef40392fd languageName: node linkType: hard -"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.103, @rocket.chat/css-supports@npm:~0.31.23-dev.157": +"@rocket.chat/css-supports@npm:^0.31.23, @rocket.chat/css-supports@npm:~0.31.23-dev.160": version: 0.31.23 resolution: "@rocket.chat/css-supports@npm:0.31.23" dependencies: @@ -9703,9 +9703,9 @@ __metadata: linkType: soft "@rocket.chat/emitter@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/emitter@npm:0.31.23-dev.103" - checksum: 5163602cf6793678e1e8b1165fbf4c77a2d24f2536af87d3ceb8464da99ce9bff2ccede931bfd6fde9857890678b82f530b29b088ed41ddac7f4a60363e46f1b + version: 0.31.23-dev.160 + resolution: "@rocket.chat/emitter@npm:0.31.23-dev.160" + checksum: ddd5f86dee05445be8001498842021c974d755ba560e48c5c9d661cf59948f73fa823307b2829842d334d5b86924c9468a6ec0f704ad0e17895e64be5aa5cd3d languageName: node linkType: hard @@ -9796,33 +9796,21 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/fuselage-hooks@npm:next": - version: 0.32.0-dev.274 - resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.274" +"@rocket.chat/fuselage-hooks@npm:next, @rocket.chat/fuselage-hooks@npm:~0.32.0-dev.299": + version: 0.32.0-dev.299 + resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.299" dependencies: use-sync-external-store: ~1.2.0 peerDependencies: "@rocket.chat/fuselage-tokens": "*" react: ^17.0.2 - checksum: 32e0872deb05e8b853aed59cab2cc4b3d3a707f0997009170a12b19d252ca09c203f8139b227c90678c0c06e14228c116ab428b9fba81228d1dab981844d8d54 - languageName: node - linkType: hard - -"@rocket.chat/fuselage-hooks@npm:~0.32.0-dev.242": - version: 0.32.0-dev.242 - resolution: "@rocket.chat/fuselage-hooks@npm:0.32.0-dev.242" - dependencies: - use-sync-external-store: ~1.2.0 - peerDependencies: - "@rocket.chat/fuselage-tokens": "*" - react: ^17.0.2 - checksum: f9dcfe53370b55c417668c32b08ddc81dcd6b8fbbc44d7968109906b3053985b34ee7d1ecdbea895b1d2eaaae7960fe2559cf0a6136799ea4725b986cb69895e + checksum: e83001544fe7118fc8dff2fa9a82dbeb2a4153f0d7c21b11dd332805326fe0433682d983914f42622807120bd1390acaf49957625216f357623ddf9e84a69170 languageName: node linkType: hard "@rocket.chat/fuselage-polyfills@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.23-dev.103" + version: 0.31.23-dev.160 + resolution: "@rocket.chat/fuselage-polyfills@npm:0.31.23-dev.160" dependencies: "@juggle/resize-observer": ^3.4.0 clipboard-polyfill: ^3.0.3 @@ -9830,13 +9818,13 @@ __metadata: focus-visible: ^5.2.0 focus-within-polyfill: ^5.2.1 new-event-polyfill: ^1.0.1 - checksum: b4dbda8530718a9d585fbc38ed5d6efcb29d8df64982a7434117e11fc75df39c94faa666fd269cb1139d2431349e23fbb21dc5d7c100abb31f18eee27079d56c + checksum: 2fcd4c1720279898a2686753ac3978e23a8928ebcd93f05aca081324c8a602ddead41deacfb3a44c4ec5d426401c6960e3871723707a741e750a3c9040e5ceaf languageName: node linkType: hard "@rocket.chat/fuselage-toastbar@npm:next": - version: 0.32.0-dev.303 - resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.303" + version: 0.32.0-dev.360 + resolution: "@rocket.chat/fuselage-toastbar@npm:0.32.0-dev.360" peerDependencies: "@rocket.chat/fuselage": "*" "@rocket.chat/fuselage-hooks": "*" @@ -9844,21 +9832,14 @@ __metadata: "@rocket.chat/styled": "*" react: ^17.0.2 react-dom: ^17.0.2 - checksum: 479f5c50d1f6ce7a7e4cc4bbd0909f3bc1c3e83e3a9fdeb806d2b0540a17c46e30f8e72b816c954257411bd404f693d83bdf9b2499ce13cee2426d03c1dbec76 - languageName: node - linkType: hard - -"@rocket.chat/fuselage-tokens@npm:next": - version: 0.32.0-dev.280 - resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.280" - checksum: d8c7537512a7950ff6f0f3e23b3d6a2b82c0caea42d5b6e5599257fe2319cbd6f53a229c61413b025b20f576521aca67d9cba378f15e19edddae11a55133672a + checksum: b2372a9231cf3f7c4b7e37b6441f936c5c137cfb0d0f0c424ba42bcc10c07898b7d7e5b5feb508ce2ecf0806baedbaf101c61352391db563ceaef6f85d852da5 languageName: node linkType: hard -"@rocket.chat/fuselage-tokens@npm:~0.32.0-dev.333": - version: 0.32.0-dev.333 - resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.333" - checksum: dd19009e75e6154361be566ecea5f328902fdf25a10fdcc9ecd18076e424dd5f7d1a4b4c56b7679fe28e4a4ff77e32107d2f83a2c9c98f968ef6ff18f5de95db +"@rocket.chat/fuselage-tokens@npm:next, @rocket.chat/fuselage-tokens@npm:~0.32.0-dev.336": + version: 0.32.0-dev.336 + resolution: "@rocket.chat/fuselage-tokens@npm:0.32.0-dev.336" + checksum: 2cf52211b3a3ee9a6111db46bc05918244190be7a6fef482011dccc0cc9a0777362dd91010e9df45c67b2c3bcaf00e35a7fbdddfdb5e7f683a19baa1fa8211d0 languageName: node linkType: hard @@ -9918,14 +9899,14 @@ __metadata: linkType: soft "@rocket.chat/fuselage@npm:next": - version: 0.32.0-dev.383 - resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.383" - dependencies: - "@rocket.chat/css-in-js": ~0.31.23-dev.157 - "@rocket.chat/css-supports": ~0.31.23-dev.157 - "@rocket.chat/fuselage-tokens": ~0.32.0-dev.333 - "@rocket.chat/memo": ~0.31.23-dev.157 - "@rocket.chat/styled": ~0.31.23-dev.157 + version: 0.32.0-dev.386 + resolution: "@rocket.chat/fuselage@npm:0.32.0-dev.386" + dependencies: + "@rocket.chat/css-in-js": ~0.31.23-dev.160 + "@rocket.chat/css-supports": ~0.31.23-dev.160 + "@rocket.chat/fuselage-tokens": ~0.32.0-dev.336 + "@rocket.chat/memo": ~0.31.23-dev.160 + "@rocket.chat/styled": ~0.31.23-dev.160 invariant: ^2.2.4 react-aria: ~3.23.1 react-keyed-flatten-children: ^1.3.0 @@ -9937,7 +9918,7 @@ __metadata: react: ^17.0.2 react-dom: ^17.0.2 react-virtuoso: 1.2.4 - checksum: 110c59c012dd019a6edddba2a1df6c4ceff6f5f84d5b4db206656ac06349bd5889ddd8ce2ee475bd91e947e457d28382fe938233891b9c81047ac3452ceedf93 + checksum: 3753fe5939b7fdead82076b824959bc7ed537349f810c104432d2ab1f7aa166a374642664e82e2cdc9efa3d1b4e1807c5ed12777cf7e875de24fca4f0ed58149 languageName: node linkType: hard @@ -10025,9 +10006,9 @@ __metadata: linkType: soft "@rocket.chat/icons@npm:next": - version: 0.32.0-dev.362 - resolution: "@rocket.chat/icons@npm:0.32.0-dev.362" - checksum: 1c2096913e154d0db68b8ccc933294486ff06e250983809ce165f1497df05deec8b5165b47dbefdd013013b4cdf4a3a20f2ac191155b629e5ec4a7bcab5d8870 + version: 0.32.0-dev.368 + resolution: "@rocket.chat/icons@npm:0.32.0-dev.368" + checksum: 3d60bc3b234eb74e5f216df880dad588558b643ba47db3001e1196c796947c68739ded0dcbe1ee13336ab23540cb11789e5f2528fcd39e64f16b83e851fcbd7e languageName: node linkType: hard @@ -10045,14 +10026,14 @@ __metadata: linkType: soft "@rocket.chat/layout@npm:next": - version: 0.32.0-dev.213 - resolution: "@rocket.chat/layout@npm:0.32.0-dev.213" + version: 0.32.0-dev.269 + resolution: "@rocket.chat/layout@npm:0.32.0-dev.269" peerDependencies: "@rocket.chat/fuselage": "*" react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: 6a7e069f669fe7201e12ffb287064fe724e69294010b6cc426d4c5936f7e2c93cfe9ae92ee480e0ce7864cd5af37b5f9163dbdcefb850571adc3242ed897a5f1 + checksum: a5ffa877ef19b63154c1c65fc4fece7dd35a08ede4cd1cb88dca1820a8732f15a8b0838c470af08dcb7efc80d7fc17f0e44b4f8be2855707d7c5c7b2b1d441d5 languageName: node linkType: hard @@ -10153,19 +10134,19 @@ __metadata: linkType: soft "@rocket.chat/logo@npm:next": - version: 0.32.0-dev.279 - resolution: "@rocket.chat/logo@npm:0.32.0-dev.279" + version: 0.32.0-dev.336 + resolution: "@rocket.chat/logo@npm:0.32.0-dev.336" dependencies: - "@rocket.chat/fuselage-hooks": ~0.32.0-dev.242 - "@rocket.chat/styled": ~0.31.23-dev.103 + "@rocket.chat/fuselage-hooks": ~0.32.0-dev.299 + "@rocket.chat/styled": ~0.31.23-dev.160 peerDependencies: react: 17.0.2 react-dom: 17.0.2 - checksum: a79a80a89111633f2c325ace46dc951fe67df94eb192a4ed691e274e19e951f7b22a9a301108f3db4daaac7667f8b9f5f2535a123a0ab72e94796358678acaeb + checksum: ac7b8b84ea675a0e6edeb8b108dd2bfbf9877b08144b2d2426f0bf371c8269e446763252c6e3533bcf7a3bc4118805e45067ca768cc01341db267682a810187c languageName: node linkType: hard -"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.103, @rocket.chat/memo@npm:~0.31.23-dev.157": +"@rocket.chat/memo@npm:^0.31.23, @rocket.chat/memo@npm:~0.31.23-dev.160": version: 0.31.23 resolution: "@rocket.chat/memo@npm:0.31.23" checksum: 070debb940749a2e4463cf767dd65c6967cea664a5bd67c22a812d611f6c3c46d6fe4bb0bf329e43dcd927493413add37c45ae3b05ec08f0b24e9d7385caebdd @@ -10173,18 +10154,18 @@ __metadata: linkType: hard "@rocket.chat/memo@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/memo@npm:0.31.23-dev.103" - checksum: 44b91bb7e02ffbeb2b094e5f283a1ddc3ca205f6ff01f5d044911b8cfc690b7e1472ee9f296de46bc84dcf2eb64d4c314813aa74d2076ec170ec1321f43145a6 + version: 0.31.23-dev.160 + resolution: "@rocket.chat/memo@npm:0.31.23-dev.160" + checksum: 9179e92af5cb662dcb9d8db3d9b0309fb415d5e82ba663bd689105a2a7ada14ca6c7630d3b509e982b2096949e1f4f9fb51afc821e0481dc5563f6c3d5a8257e languageName: node linkType: hard "@rocket.chat/message-parser@npm:next": - version: 0.32.0-dev.296 - resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.296" + version: 0.32.0-dev.334 + resolution: "@rocket.chat/message-parser@npm:0.32.0-dev.334" dependencies: tldts: ~5.7.112 - checksum: 2a40090b0b5b94e531da3bd1433c6f60b98f19b85eb9ca81db8706fcf360d8245c97fde2257a6c39e9be7fa4a630eede5abf679847ef3c923594c7156f8eb334 + checksum: bdbb288f963e72152554ee4af3ae2e57f7c3955c083065b202246286a91f3439fe0eb9a75a5c3b2bea889ac0e98750deaf4b3e95e170ba969ab2a42cf21d9f61 languageName: node linkType: hard @@ -10663,8 +10644,8 @@ __metadata: linkType: soft "@rocket.chat/onboarding-ui@npm:next": - version: 0.32.0-dev.329 - resolution: "@rocket.chat/onboarding-ui@npm:0.32.0-dev.329" + version: 0.32.0-dev.386 + resolution: "@rocket.chat/onboarding-ui@npm:0.32.0-dev.386" dependencies: i18next: ~21.6.16 react-hook-form: ~7.27.1 @@ -10679,7 +10660,7 @@ __metadata: react: 17.0.2 react-dom: 17.0.2 react-i18next: ~11.15.4 - checksum: 3118c3f3bb91db6e30748a1bf031221b2459b611c260446adf5bafcc9e38fa600a8a3366567a88183b2e93286b538233cb585f0d414b53f02e2eb48b8b2ba3ad + checksum: a919f076b7b27f79628fc3f1a63beb6abe6352a19be4d187742f656255670c757326f1732094628d381727b026e4e8f94ba4b900878e028bed927233825f0aba languageName: node linkType: hard @@ -10956,22 +10937,22 @@ __metadata: linkType: soft "@rocket.chat/string-helpers@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/string-helpers@npm:0.31.23-dev.103" - checksum: b505564152613d5871dae226b67458d69fbacf2ddeb3f6d10cf81124ccccfd75ad60133bc772db6bf20f4b17f7cfe09fc0ea9c846c2f519b031ce22d0da6688e + version: 0.31.23-dev.160 + resolution: "@rocket.chat/string-helpers@npm:0.31.23-dev.160" + checksum: abe26244891bb29243cf8a7db66cdcb44e80a0c6b6e1d38e56efaa1e6bdd5a61f818398473dec1cfdd1f1941dca8b03e641b7ec3460715800a79ae37bd2545f6 languageName: node linkType: hard "@rocket.chat/styled@npm:next": - version: 0.31.23-dev.103 - resolution: "@rocket.chat/styled@npm:0.31.23-dev.103" + version: 0.31.23-dev.160 + resolution: "@rocket.chat/styled@npm:0.31.23-dev.160" dependencies: - "@rocket.chat/css-in-js": ~0.31.23-dev.103 - checksum: aadeb2ffe37e01d694012ec49d301a9a8960b82377fc1daa2c332b657414c4c52839010d3f8ce5114312065b8da0cc41b70bdc93fa1916169c53d333d56428bc + "@rocket.chat/css-in-js": ~0.31.23-dev.160 + checksum: 5e64b9ff9e3cfcd0a885e92071d69dfd6bd46c6d0f23c5490b8bd93cf8c0254e41d83e708e9d523cadb89e84ce789d0d4757d810f38dd7a718375c875e51670d languageName: node linkType: hard -"@rocket.chat/styled@npm:~0.31.23-dev.103, @rocket.chat/styled@npm:~0.31.23-dev.157": +"@rocket.chat/styled@npm:~0.31.23-dev.160": version: 0.31.23 resolution: "@rocket.chat/styled@npm:0.31.23" dependencies: @@ -10980,7 +10961,7 @@ __metadata: languageName: node linkType: hard -"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.23, @rocket.chat/stylis-logical-props-middleware@npm:~0.31.23-dev.103": +"@rocket.chat/stylis-logical-props-middleware@npm:^0.31.23, @rocket.chat/stylis-logical-props-middleware@npm:~0.31.23-dev.160": version: 0.31.23 resolution: "@rocket.chat/stylis-logical-props-middleware@npm:0.31.23" dependencies: @@ -11114,9 +11095,11 @@ __metadata: linkType: soft "@rocket.chat/ui-kit@npm:next": - version: 0.32.0-dev.294 - resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.294" - checksum: cf58890f3fbcfdff3df9436a33c4aa333de34369f89902107dee74c50c11b8af793c9c80a8df5046ffcabf0fd90111f31a6fc2030876e838e43ec4c6a5703fc4 + version: 0.32.0-dev.321 + resolution: "@rocket.chat/ui-kit@npm:0.32.0-dev.321" + peerDependencies: + "@rocket.chat/icons": "*" + checksum: 9e237ac9c3553455d2167ef945070fcdfa90c3403599cc0e6a0c0de6d5663851ce29f4cfc9e0b6809a711620431a2a9f69398041ddffcbc41fe99a90aa1ae483 languageName: node linkType: hard