diff --git a/.changeset/wicked-socks-hide.md b/.changeset/wicked-socks-hide.md new file mode 100644 index 000000000000..3f425c3228e2 --- /dev/null +++ b/.changeset/wicked-socks-hide.md @@ -0,0 +1,7 @@ +--- +"@rocket.chat/meteor": minor +"@rocket.chat/core-typings": minor +"@rocket.chat/rest-typings": minor +--- + +Adds a new callout in the subscription page to inform users of subscription upgrade eligibility when applicable. diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/fetchWorkspaceSyncPayload.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/fetchWorkspaceSyncPayload.ts new file mode 100644 index 000000000000..7d01f6305c83 --- /dev/null +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/fetchWorkspaceSyncPayload.ts @@ -0,0 +1,42 @@ +import type { Cloud, Serialized } from '@rocket.chat/core-typings'; +import { serverFetch as fetch } from '@rocket.chat/server-fetch'; +import { v, compile } from 'suretype'; + +import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError'; +import { settings } from '../../../../settings/server'; + +const workspaceSyncPayloadSchema = v.object({ + workspaceId: v.string().required(), + publicKey: v.string(), + license: v.string().required(), +}); + +const assertWorkspaceSyncPayload = compile(workspaceSyncPayloadSchema); + +export async function fetchWorkspaceSyncPayload({ + token, + data, +}: { + token: string; + data: Cloud.WorkspaceSyncRequestPayload; +}): Promise> { + const workspaceRegistrationClientUri = settings.get('Cloud_Workspace_Registration_Client_Uri'); + const response = await fetch(`${workspaceRegistrationClientUri}/sync`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + }, + body: data, + }); + + if (!response.ok) { + const { error } = await response.json(); + throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`); + } + + const payload = await response.json(); + + assertWorkspaceSyncPayload(payload); + + return payload; +} diff --git a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts index fc55fc9e34fd..f28821b08387 100644 --- a/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts +++ b/apps/meteor/app/cloud/server/functions/syncWorkspace/syncCloudData.ts @@ -1,57 +1,14 @@ -import type { Cloud, Serialized } from '@rocket.chat/core-typings'; import { DuplicatedLicenseError } from '@rocket.chat/license'; -import { serverFetch as fetch } from '@rocket.chat/server-fetch'; -import { v, compile } from 'suretype'; +import { Settings } from '@rocket.chat/models'; import { callbacks } from '../../../../../lib/callbacks'; import { CloudWorkspaceAccessError } from '../../../../../lib/errors/CloudWorkspaceAccessError'; -import { CloudWorkspaceConnectionError } from '../../../../../lib/errors/CloudWorkspaceConnectionError'; import { CloudWorkspaceRegistrationError } from '../../../../../lib/errors/CloudWorkspaceRegistrationError'; import { SystemLogger } from '../../../../../server/lib/logger/system'; -import { settings } from '../../../../settings/server'; import { buildWorkspaceRegistrationData } from '../buildRegistrationData'; import { CloudWorkspaceAccessTokenEmptyError, getWorkspaceAccessToken } from '../getWorkspaceAccessToken'; import { retrieveRegistrationStatus } from '../retrieveRegistrationStatus'; - -const workspaceSyncPayloadSchema = v.object({ - workspaceId: v.string().required(), - publicKey: v.string(), - license: v.string().required(), -}); - -const assertWorkspaceSyncPayload = compile(workspaceSyncPayloadSchema); - -const fetchWorkspaceSyncPayload = async ({ - token, - data, -}: { - token: string; - data: Cloud.WorkspaceSyncRequestPayload; -}): Promise> => { - const workspaceRegistrationClientUri = settings.get('Cloud_Workspace_Registration_Client_Uri'); - const response = await fetch(`${workspaceRegistrationClientUri}/sync`, { - method: 'POST', - headers: { - Authorization: `Bearer ${token}`, - }, - body: data, - }); - - if (!response.ok) { - try { - const { error } = await response.json(); - throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${error}`); - } catch (error) { - throw new CloudWorkspaceConnectionError(`Failed to connect to Rocket.Chat Cloud: ${response.statusText}`); - } - } - - const payload = await response.json(); - - assertWorkspaceSyncPayload(payload); - - return payload; -}; +import { fetchWorkspaceSyncPayload } from './fetchWorkspaceSyncPayload'; export async function syncCloudData() { try { @@ -67,11 +24,17 @@ export async function syncCloudData() { const workspaceRegistrationData = await buildWorkspaceRegistrationData(undefined); - const { license, removeLicense = false } = await fetchWorkspaceSyncPayload({ + const { + license, + removeLicense = false, + cloudSyncAnnouncement, + } = await fetchWorkspaceSyncPayload({ token, data: workspaceRegistrationData, }); + await Settings.updateValueById('Cloud_Sync_Announcement_Payload', JSON.stringify(cloudSyncAnnouncement ?? null)); + if (removeLicense) { await callbacks.run('workspaceLicenseRemoved'); } else { diff --git a/apps/meteor/client/components/Page/PageHeaderNoShadow.tsx b/apps/meteor/client/components/Page/PageHeaderNoShadow.tsx index 51feca504834..afc3891a5714 100644 --- a/apps/meteor/client/components/Page/PageHeaderNoShadow.tsx +++ b/apps/meteor/client/components/Page/PageHeaderNoShadow.tsx @@ -22,7 +22,7 @@ const PageHeaderNoShadow = ({ children = undefined, title, onClickBack, ...props useDocumentTitle(typeof title === 'string' ? title : undefined); return ( - + ({ }; export const useLicense = (params?: LicenseParams) => { - return useLicenseBase({ params, select: (data) => data.license }); + return useLicenseBase({ + params, + select: (data) => data.license, + }); +}; + +export const useLicenseWithCloudAnnouncement = (params?: LicenseParams) => { + return useLicenseBase({ + params, + select: ({ license, cloudSyncAnnouncement }) => ({ + ...license, + cloudSyncAnnouncement, + }), + }); }; export const useHasLicense = (): UseQueryResult => { diff --git a/apps/meteor/client/uikit/hooks/useBannerContextValue.ts b/apps/meteor/client/uikit/hooks/useBannerContextValue.ts index 35b38e8dee63..81cb70af6636 100644 --- a/apps/meteor/client/uikit/hooks/useBannerContextValue.ts +++ b/apps/meteor/client/uikit/hooks/useBannerContextValue.ts @@ -41,6 +41,7 @@ export const useBannerContextValue = ({ view, values }: UseBannerContextValuePar }, updateState: (): void => undefined, appId: view.appId, + viewId: view.viewId, values, }; }; diff --git a/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx b/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx index e6f5beffe7ba..aaf4f984c89e 100644 --- a/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx +++ b/apps/meteor/client/views/admin/subscription/SubscriptionPage.tsx @@ -21,9 +21,12 @@ import PlanCardCommunity from './components/cards/PlanCard/PlanCardCommunity'; import SeatsCard from './components/cards/SeatsCard'; import { useCancelSubscriptionModal } from './hooks/useCancelSubscriptionModal'; import { useWorkspaceSync } from './hooks/useWorkspaceSync'; -import { Page, PageHeader, PageScrollableContentWithShadow } from '../../../components/Page'; +import UiKitSubscriptionLicense from './surface/UiKitSubscriptionLicense'; +import { Page, PageScrollableContentWithShadow } from '../../../components/Page'; +import PageBlockWithBorder from '../../../components/Page/PageBlockWithBorder'; +import PageHeaderNoShadow from '../../../components/Page/PageHeaderNoShadow'; import { useIsEnterprise } from '../../../hooks/useIsEnterprise'; -import { useInvalidateLicense, useLicense } from '../../../hooks/useLicense'; +import { useInvalidateLicense, useLicenseWithCloudAnnouncement } from '../../../hooks/useLicense'; import { useRegistrationStatus } from '../../../hooks/useRegistrationStatus'; function useShowLicense() { @@ -48,7 +51,7 @@ const SubscriptionPage = () => { const router = useRouter(); const { data: enterpriseData } = useIsEnterprise(); const { isRegistered } = useRegistrationStatus(); - const { data: licensesData, isLoading: isLicenseLoading } = useLicense({ loadValues: true }); + const { data: licensesData, isLoading: isLicenseLoading } = useLicenseWithCloudAnnouncement({ loadValues: true }); const syncLicenseUpdate = useWorkspaceSync(); const invalidateLicenseQuery = useInvalidateLicense(); @@ -56,7 +59,7 @@ const SubscriptionPage = () => { const showSubscriptionCallout = useDebouncedValue(subscriptionSuccess || syncLicenseUpdate.isLoading, 10000); - const { license, limits, activeModules = [] } = licensesData || {}; + const { license, limits, activeModules = [], cloudSyncAnnouncement } = licensesData || {}; const { isEnterprise = true } = enterpriseData || {}; const getKeyLimit = (key: 'monthlyActiveContacts' | 'activeUsers') => { @@ -99,7 +102,7 @@ const SubscriptionPage = () => { return ( - + {isRegistered && (