diff --git a/apps/meteor/client/components/PlanTag.tsx b/apps/meteor/client/components/PlanTag.tsx index 8331f1d6e7b2b..74867244794aa 100644 --- a/apps/meteor/client/components/PlanTag.tsx +++ b/apps/meteor/client/components/PlanTag.tsx @@ -7,13 +7,14 @@ import { useIsEnterprise } from '../hooks/useIsEnterprise'; function PlanTag(): ReactElement { const [plans, setPlans] = useState([]); - const isEnterprise = useIsEnterprise(); + const { data } = useIsEnterprise(); + useEffect(() => { const developmentTag = process.env.NODE_ENV === 'development' ? 'Development' : null; - const enterpriseTag = isEnterprise ? 'Enterprise' : null; + const enterpriseTag = data?.isEnterprise ? 'Enterprise' : null; setPlans([developmentTag, enterpriseTag].filter(isTruthy)); - }, [setPlans, isEnterprise]); + }, [setPlans, data?.isEnterprise]); return ( <> diff --git a/apps/meteor/client/hooks/useIsEnterprise.ts b/apps/meteor/client/hooks/useIsEnterprise.ts index 1c72f2c31ddce..532624848c96c 100644 --- a/apps/meteor/client/hooks/useIsEnterprise.ts +++ b/apps/meteor/client/hooks/useIsEnterprise.ts @@ -1,8 +1,12 @@ +import { OperationResult } from '@rocket.chat/rest-typings'; import { useEndpoint } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { useQuery, UseQueryResult } from '@tanstack/react-query'; -export const useIsEnterprise = (): boolean => { +export const useIsEnterprise = (): UseQueryResult> => { const isEnterpriseEdition = useEndpoint('GET', '/v1/licenses.isEnterprise'); - const { data } = useQuery(['licenses.isEnterprise'], () => isEnterpriseEdition(), { keepPreviousData: true }); - return Boolean(data?.isEnterprise); + return useQuery(['licenses', 'isEnterprise'], () => isEnterpriseEdition(), { + keepPreviousData: true, + refetchOnWindowFocus: false, + staleTime: Infinity, + }); }; diff --git a/apps/meteor/client/hooks/useLicense.ts b/apps/meteor/client/hooks/useLicense.ts new file mode 100644 index 0000000000000..27fc49fcefa11 --- /dev/null +++ b/apps/meteor/client/hooks/useLicense.ts @@ -0,0 +1,13 @@ +import { OperationResult } from '@rocket.chat/rest-typings'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery, UseQueryResult } from '@tanstack/react-query'; + +export const useLicense = (): UseQueryResult> => { + const getLicenses = useEndpoint('GET', '/v1/licenses.get'); + + return useQuery(['licenses', 'getLicenses'], () => getLicenses(), { + staleTime: Infinity, + keepPreviousData: true, + refetchOnWindowFocus: false, + }); +}; diff --git a/apps/meteor/client/hooks/useRegistrationStatus.ts b/apps/meteor/client/hooks/useRegistrationStatus.ts new file mode 100644 index 0000000000000..8bd783714d85c --- /dev/null +++ b/apps/meteor/client/hooks/useRegistrationStatus.ts @@ -0,0 +1,13 @@ +import { OperationResult } from '@rocket.chat/rest-typings'; +import { useEndpoint } from '@rocket.chat/ui-contexts'; +import { useQuery, UseQueryResult } from '@tanstack/react-query'; + +export const useRegistrationStatus = (): UseQueryResult> => { + const getRegistrationStatus = useEndpoint('GET', '/v1/cloud.registrationStatus'); + + return useQuery(['getRegistrationStatus'], () => getRegistrationStatus(), { + refetchOnWindowFocus: false, + keepPreviousData: true, + staleTime: Infinity, + }); +}; diff --git a/apps/meteor/client/providers/SettingsProvider.tsx b/apps/meteor/client/providers/SettingsProvider.tsx index e270ac8e18f64..e6c7fc5f4ac03 100644 --- a/apps/meteor/client/providers/SettingsProvider.tsx +++ b/apps/meteor/client/providers/SettingsProvider.tsx @@ -3,6 +3,7 @@ import { Tracker } from 'meteor/tracker'; import React, { useCallback, useEffect, useMemo, useState, FunctionComponent } from 'react'; import { createReactiveSubscriptionFactory } from '../lib/createReactiveSubscriptionFactory'; +import { queryClient } from '../lib/queryClient'; import { PrivateSettingsCachedCollection } from '../lib/settings/PrivateSettingsCachedCollection'; import { PublicSettingsCachedCollection } from '../lib/settings/PublicSettingsCachedCollection'; @@ -80,9 +81,23 @@ const SettingsProvider: FunctionComponent = ({ children, [cachedCollection], ); + const settingsChangeCallback = (changes: { _id: string }[]): void => { + changes.forEach((val) => { + switch (val._id) { + case 'Enterprise_License': + queryClient.invalidateQueries(['licenses']); + break; + + default: + break; + } + }); + }; + const saveSettings = useMethod('saveSettings'); const dispatch = useCallback( async (changes) => { + await settingsChangeCallback(changes); await saveSettings(changes); }, [saveSettings], diff --git a/apps/meteor/client/views/admin/cloud/ConnectToCloudSection.js b/apps/meteor/client/views/admin/cloud/ConnectToCloudSection.js index 943652e26b5ad..4a3751314f880 100644 --- a/apps/meteor/client/views/admin/cloud/ConnectToCloudSection.js +++ b/apps/meteor/client/views/admin/cloud/ConnectToCloudSection.js @@ -4,6 +4,7 @@ import { useToastMessageDispatch, useSetting, useMethod, useTranslation } from ' import React, { useState } from 'react'; import Subtitle from '../../../components/Subtitle'; +import { queryClient } from '../../../lib/queryClient'; function ConnectToCloudSection({ onRegisterStatusChange, ...props }) { const t = useTranslation(); @@ -36,6 +37,7 @@ function ConnectToCloudSection({ onRegisterStatusChange, ...props }) { dispatchToastMessage({ type: 'error', message: error }); } finally { await (onRegisterStatusChange && onRegisterStatusChange()); + queryClient.invalidateQueries(['getRegistrationStatus']); setConnecting(false); } }; diff --git a/apps/meteor/client/views/admin/cloud/PasteStep.tsx b/apps/meteor/client/views/admin/cloud/PasteStep.tsx index e30664210454e..897756115ab7a 100644 --- a/apps/meteor/client/views/admin/cloud/PasteStep.tsx +++ b/apps/meteor/client/views/admin/cloud/PasteStep.tsx @@ -2,6 +2,8 @@ import { Box, Button, Scrollable, Throbber, Modal } from '@rocket.chat/fuselage' import { useToastMessageDispatch, useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ChangeEvent, FC, useState } from 'react'; +import { queryClient } from '../../../lib/queryClient'; + type PasteStepProps = { onBackButtonClick: () => void; onFinish: () => void; @@ -25,6 +27,7 @@ const PasteStep: FC = ({ onBackButtonClick, onFinish }) => { try { await registerManually({ cloudBlob: cloudKey }); + queryClient.invalidateQueries(['licenses']); dispatchToastMessage({ type: 'success', message: t('Cloud_register_success') }); } catch (error) { dispatchToastMessage({ type: 'error', message: t('Cloud_register_error') }); diff --git a/apps/meteor/client/views/admin/info/InformationPage.tsx b/apps/meteor/client/views/admin/info/InformationPage.tsx index 981f540a4f58c..e3f3a44e0def3 100644 --- a/apps/meteor/client/views/admin/info/InformationPage.tsx +++ b/apps/meteor/client/views/admin/info/InformationPage.tsx @@ -30,13 +30,13 @@ const InformationPage = memo(function InformationPage({ }: InformationPageProps) { const t = useTranslation(); - const isEnterprise = useIsEnterprise(); + const { data } = useIsEnterprise(); if (!info) { return null; } - const warningMultipleInstances = !isEnterprise && !statistics?.msEnabled && statistics?.instanceCount > 1; + const warningMultipleInstances = !data?.isEnterprise && !statistics?.msEnabled && statistics?.instanceCount > 1; const alertOplogForMultipleInstances = warningMultipleInstances && !statistics.oplogEnabled; return ( diff --git a/apps/meteor/client/views/admin/info/LicenseCard.tsx b/apps/meteor/client/views/admin/info/LicenseCard.tsx index 37ad2c9be553a..d92f912e26ddf 100644 --- a/apps/meteor/client/views/admin/info/LicenseCard.tsx +++ b/apps/meteor/client/views/admin/info/LicenseCard.tsx @@ -5,8 +5,7 @@ import { useSetModal, useSetting, useTranslation } from '@rocket.chat/ui-context import React, { ReactElement } from 'react'; import PlanTag from '../../../components/PlanTag'; -import { AsyncStatePhase } from '../../../hooks/useAsyncState'; -import { useEndpointData } from '../../../hooks/useEndpointData'; +import { useLicense } from '../../../hooks/useLicense'; import Feature from './Feature'; import OfflineLicenseModal from './OfflineLicenseModal'; @@ -19,10 +18,9 @@ const LicenseCard = (): ReactElement => { const isAirGapped = true; - const { value, phase, error } = useEndpointData('/v1/licenses.get'); - const endpointLoading = phase === AsyncStatePhase.LOADING; + const { data, isError, isLoading } = useLicense(); - const { modules = [] } = endpointLoading || error || !value?.licenses.length ? {} : value.licenses[0]; + const { modules = [] } = isLoading || isError || !data?.licenses?.length ? {} : data?.licenses[0]; const hasEngagement = modules.includes('engagement-dashboard'); const hasOmnichannel = modules.includes('livechat-enterprise'); @@ -52,7 +50,7 @@ const LicenseCard = (): ReactElement => { {t('Features')} - {endpointLoading ? ( + {isLoading ? ( <> diff --git a/apps/meteor/client/views/admin/info/OfflineLicenseModal.tsx b/apps/meteor/client/views/admin/info/OfflineLicenseModal.tsx index 0ad7180ede129..6a2377647db3a 100644 --- a/apps/meteor/client/views/admin/info/OfflineLicenseModal.tsx +++ b/apps/meteor/client/views/admin/info/OfflineLicenseModal.tsx @@ -3,6 +3,8 @@ import { useMutableCallback } from '@rocket.chat/fuselage-hooks'; import { useEndpoint, useToastMessageDispatch, useTranslation } from '@rocket.chat/ui-contexts'; import React, { ComponentProps, FormEvent, ReactElement, useState } from 'react'; +import { queryClient } from '../../../lib/queryClient'; + type OfflineLicenseModalProps = { onClose: () => void; license: string; @@ -38,10 +40,11 @@ const OfflineLicenseModal = ({ onClose, license, licenseStatus, ...props }: Offl const handleApplyLicense = useMutableCallback(async () => { setLastSetLicense(newLicense); - try { setIsUpdating(true); await addLicense({ license: newLicense }); + queryClient.invalidateQueries(['licenses']); + dispatchToastMessage({ type: 'success', message: t('Cloud_License_applied_successfully') }); onClose(); } catch (error) { diff --git a/apps/meteor/client/views/admin/settings/EditableSettingsProvider.tsx b/apps/meteor/client/views/admin/settings/EditableSettingsProvider.tsx index 4ea376af684ce..81cbf33d55514 100644 --- a/apps/meteor/client/views/admin/settings/EditableSettingsProvider.tsx +++ b/apps/meteor/client/views/admin/settings/EditableSettingsProvider.tsx @@ -181,7 +181,9 @@ const EditableSettingsProvider: FunctionComponent Tracker.flush(); }); - const isEnterprise = useIsEnterprise(); + const { data } = useIsEnterprise(); + + const isEnterprise = data?.isEnterprise ?? false; const contextValue = useMemo( () => ({ diff --git a/apps/meteor/client/views/hooks/useUpgradeTabParams.ts b/apps/meteor/client/views/hooks/useUpgradeTabParams.ts index 5a9d946f58434..f624ca5708e9a 100644 --- a/apps/meteor/client/views/hooks/useUpgradeTabParams.ts +++ b/apps/meteor/client/views/hooks/useUpgradeTabParams.ts @@ -1,28 +1,22 @@ -import { useSetting, useEndpoint } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { useSetting } from '@rocket.chat/ui-contexts'; import { format } from 'date-fns'; import { getUpgradeTabType, UpgradeTabVariant } from '../../../lib/upgradeTab'; +import { useLicense } from '../../hooks/useLicense'; +import { useRegistrationStatus } from '../../hooks/useRegistrationStatus'; export const useUpgradeTabParams = (): { tabType: UpgradeTabVariant | false; trialEndDate: string | undefined; isLoading: boolean } => { - const getRegistrationStatus = useEndpoint('GET', '/v1/cloud.registrationStatus'); - const getLicenses = useEndpoint('GET', '/v1/licenses.get'); const cloudWorkspaceHadTrial = useSetting('Cloud_Workspace_Had_Trial') as boolean; - const { data: registrationStatusData } = useQuery(['registrationStatus'], () => getRegistrationStatus()); - const { data: getValidLicensesData, isSuccess } = useQuery(['licenses'], () => getLicenses(), { - enabled: !!registrationStatusData, - }); - - const { registrationStatus } = registrationStatusData ?? {}; - const { licenses } = getValidLicensesData ?? {}; + const { data: licensesData, isSuccess: isSuccessLicense } = useLicense(); + const { data: registrationStatusData, isSuccess: isSuccessRegistrationStatus } = useRegistrationStatus(); - const registered = registrationStatus?.workspaceRegistered ?? false; - const hasValidLicense = (licenses?.length ?? 0) > 0; + const registered = registrationStatusData?.registrationStatus?.workspaceRegistered ?? false; + const hasValidLicense = (licensesData?.licenses?.length ?? 0) > 0; const hadExpiredTrials = cloudWorkspaceHadTrial ?? false; - const trialLicense = licenses?.find(({ meta }) => meta?.trial); - const isTrial = licenses?.every(({ meta }) => meta?.trial) ?? false; + const trialLicense = licensesData?.licenses?.find(({ meta }) => meta?.trial); + const isTrial = licensesData?.licenses?.every(({ meta }) => meta?.trial) ?? false; const trialEndDate = trialLicense?.meta ? format(new Date(trialLicense.meta.trialEnd), 'yyyy-MM-dd') : undefined; const upgradeTabType = getUpgradeTabType({ @@ -32,5 +26,5 @@ export const useUpgradeTabParams = (): { tabType: UpgradeTabVariant | false; tri isTrial, }); - return { tabType: upgradeTabType, trialEndDate, isLoading: !isSuccess }; + return { tabType: upgradeTabType, trialEndDate, isLoading: !isSuccessLicense && !isSuccessRegistrationStatus }; }; diff --git a/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx b/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx index 3907e696ab6c2..21ebec6df6f05 100644 --- a/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx +++ b/apps/meteor/client/views/setupWizard/providers/SetupWizardProvider.tsx @@ -15,6 +15,7 @@ import React, { useCallback, useMemo, useState, ReactElement, ContextType } from import { callbacks } from '../../../../lib/callbacks'; import { validateEmail } from '../../../../lib/emailValidator'; +import { queryClient } from '../../../lib/queryClient'; import { SetupWizardContext } from '../contexts/SetupWizardContext'; import { useParameters } from '../hooks/useParameters'; import { useStepRouting } from '../hooks/useStepRouting'; @@ -171,6 +172,8 @@ const SetupWizardProvider = ({ children }: { children: ReactElement }): ReactEle try { await saveOrganizationData(); const { intentData } = await createRegistrationIntent({ resend, email }); + queryClient.invalidateQueries(['licenses']); + queryClient.invalidateQueries(['getRegistrationStatus']); setSetupWizardData((prevState) => ({ ...prevState, diff --git a/apps/meteor/ee/client/sidebar/footer/SidebarFooterWatermark.tsx b/apps/meteor/ee/client/sidebar/footer/SidebarFooterWatermark.tsx index 635543ee96364..418dbcd6a5997 100644 --- a/apps/meteor/ee/client/sidebar/footer/SidebarFooterWatermark.tsx +++ b/apps/meteor/ee/client/sidebar/footer/SidebarFooterWatermark.tsx @@ -1,16 +1,15 @@ import { Box } from '@rocket.chat/fuselage'; -import { useEndpoint, useTranslation } from '@rocket.chat/ui-contexts'; -import { useQuery } from '@tanstack/react-query'; +import { useTranslation } from '@rocket.chat/ui-contexts'; import React, { ReactElement } from 'react'; +import { useIsEnterprise } from '../../../../client/hooks/useIsEnterprise'; + export const SidebarFooterWatermark = (): ReactElement | null => { const t = useTranslation(); - const isEnterpriseEdition = useEndpoint('GET', '/v1/licenses.isEnterprise'); - const result = useQuery(['licenses.isEnterprise'], () => isEnterpriseEdition(), { - refetchOnWindowFocus: false, - }); - if (!result.isSuccess || result.isLoading || result.data.isEnterprise) { + const { isLoading, isError, data } = useIsEnterprise(); + + if (isError || isLoading || data?.isEnterprise) { return null; } diff --git a/yarn.lock b/yarn.lock index 5acb9b88d913e..a3975214bbbd2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23669,7 +23669,7 @@ __metadata: optional: true bin: lessc: ./bin/lessc - checksum: 61568b56b5289fdcfe3d51baf3c13e7db7140022c0a37ef0ae343169f0de927a4b4f4272bc10c20101796e8ee79e934e024051321bba93b3ae071f734309bd98 + checksum: c9b8c0e865427112c48a9cac36f14964e130577743c29d56a6d93b5812b70846b04ccaa364acf1e8d75cee3855215ec0a2d8d9de569c80e774f10b6245f39b7d languageName: node linkType: hard