From ad4800fd24b3fd7d7c2e15a1d3d44efb1b2387f9 Mon Sep 17 00:00:00 2001 From: simeng-li Date: Tue, 10 Dec 2024 18:33:02 +0800 Subject: [PATCH] fix(console): fix enterprise tenant current sku always return dev (#6869) * fix(console): fix enterprise tenant current sku always dev fix the enterprise tenant current sku always dev bug * fix(console): fix the lint error fix the lint error --- .../SelectTenantPlanModal/index.tsx | 2 +- .../use-new-subscription-data.ts | 24 +++++++++++--- packages/console/src/hooks/use-logto-skus.ts | 31 +++++++++---------- .../pages/CheckoutSuccessCallback/index.tsx | 15 +++------ packages/console/src/utils/subscription.ts | 22 ++++++++++++- 5 files changed, 59 insertions(+), 35 deletions(-) diff --git a/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx index 594b4ea5a..05020686c 100644 --- a/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx +++ b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx @@ -35,7 +35,7 @@ function SelectTenantPlanModal({ tenantData, onClose }: Props) { const { subscribe } = useSubscribe(); const cloudApi = useCloudApi({ hideErrorToast: true }); - const reservedBasicLogtoSkus = conditional(logtoSkus && pickupFeaturedLogtoSkus(logtoSkus)); + const reservedBasicLogtoSkus = conditional(pickupFeaturedLogtoSkus(logtoSkus)); if (!reservedBasicLogtoSkus || !tenantData) { return null; diff --git a/packages/console/src/contexts/SubscriptionDataProvider/use-new-subscription-data.ts b/packages/console/src/contexts/SubscriptionDataProvider/use-new-subscription-data.ts index ab71e62c2..4ee1923ef 100644 --- a/packages/console/src/contexts/SubscriptionDataProvider/use-new-subscription-data.ts +++ b/packages/console/src/contexts/SubscriptionDataProvider/use-new-subscription-data.ts @@ -1,9 +1,9 @@ -import { cond, pick } from '@silverhand/essentials'; +import { pick } from '@silverhand/essentials'; import { useContext, useEffect, useMemo } from 'react'; import useSWR from 'swr'; import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; -import { type NewSubscriptionUsageResponse } from '@/cloud/types/router'; +import { type LogtoSkuResponse, type NewSubscriptionUsageResponse } from '@/cloud/types/router'; import { defaultLogtoSku, defaultTenantResponse, @@ -12,7 +12,8 @@ import { } from '@/consts'; import { isCloud } from '@/consts/env'; import { TenantsContext } from '@/contexts/TenantsProvider'; -import useLogtoSkus from '@/hooks/use-logto-skus'; +import { LogtoSkuType } from '@/types/skus'; +import { formatLogtoSkusResponses } from '@/utils/subscription'; import useSubscription from '../../hooks/use-subscription'; @@ -22,7 +23,6 @@ const useNewSubscriptionData: () => NewSubscriptionContext & { isLoading: boolea const cloudApi = useCloudApi(); const { currentTenant, currentTenantId, updateTenant } = useContext(TenantsContext); - const { isLoading: isLogtoSkusLoading, data: fetchedLogtoSkus } = useLogtoSkus(); const { data: currentSubscription, @@ -42,7 +42,21 @@ const useNewSubscriptionData: () => NewSubscriptionContext & { isLoading: boolea }) ); - const logtoSkus = useMemo(() => cond(isCloud && fetchedLogtoSkus) ?? [], [fetchedLogtoSkus]); + // Fetch tenant specific available SKUs + // Unlike the `useLogtoSkus` hook, apart from public available SKUs, this hook also fetches tenant specific private SKUs + // For enterprise tenants who have their own private SKUs, and all grandfathered plan tenants, + // this is the only place to retrieve their current SKU data. + const { isLoading: isLogtoSkusLoading, data: fetchedLogtoSkus } = useSWR< + LogtoSkuResponse[], + Error + >(isCloud && currentTenantId && `/api/tenants/${currentTenantId}/available-skus`, async () => + cloudApi.get('/api/tenants/:tenantId/available-skus', { + params: { tenantId: currentTenantId }, + search: { type: LogtoSkuType.Basic }, + }) + ); + + const logtoSkus = useMemo(() => formatLogtoSkusResponses(fetchedLogtoSkus), [fetchedLogtoSkus]); const currentSku = useMemo( () => logtoSkus.find((logtoSku) => logtoSku.id === currentTenant?.planId) ?? defaultLogtoSku, diff --git a/packages/console/src/hooks/use-logto-skus.ts b/packages/console/src/hooks/use-logto-skus.ts index 211ce9b98..f8285d3ea 100644 --- a/packages/console/src/hooks/use-logto-skus.ts +++ b/packages/console/src/hooks/use-logto-skus.ts @@ -5,17 +5,22 @@ import useSWRImmutable from 'swr/immutable'; import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; import { type LogtoSkuResponse } from '@/cloud/types/router'; import { isCloud } from '@/consts/env'; -import { featuredPlanIdOrder } from '@/consts/subscriptions'; // Used in the docs // eslint-disable-next-line unused-imports/no-unused-imports import TenantAccess from '@/containers/TenantAccess'; +// eslint-disable-next-line unused-imports/no-unused-imports -- for jsDoc use +import type { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { LogtoSkuType } from '@/types/skus'; -import { sortBy } from '@/utils/sort'; -import { addSupportQuota } from '@/utils/subscription'; +import { formatLogtoSkusResponses } from '@/utils/subscription'; /** - * Fetch Logto SKUs from the cloud API. - * Note: If you want to retrieve Logto SKUs under the {@link TenantAccess} component, use `SubscriptionDataContext` instead. + * Fetch public Logto SKUs from the cloud API. + * + * @remarks + * Note: This hook is used for retrieving public available Logto SKUs for all the users. + * If you want to retrieve tenant specific available Logto SKUs under the {@link TenantAccess} component, + * e.g. For enterprise tenant who have their own private SKUs, and all grandfathered plan tenants, + * use the logtoSkus from the {@link SubscriptionDataContext} instead. */ const useLogtoSkus = () => { const cloudApi = useCloudApi(); @@ -30,18 +35,10 @@ const useLogtoSkus = () => { const { data: logtoSkuResponse } = useSwrResponse; - const logtoSkus: Optional = useMemo(() => { - if (!logtoSkuResponse) { - return; - } - - return logtoSkuResponse - .map((logtoSku) => addSupportQuota(logtoSku)) - .slice() - .sort(({ id: previousId }, { id: nextId }) => - sortBy(featuredPlanIdOrder)(previousId, nextId) - ); - }, [logtoSkuResponse]); + const logtoSkus: Optional = useMemo( + () => formatLogtoSkusResponses(logtoSkuResponse), + [logtoSkuResponse] + ); return { ...useSwrResponse, diff --git a/packages/console/src/pages/CheckoutSuccessCallback/index.tsx b/packages/console/src/pages/CheckoutSuccessCallback/index.tsx index 0c8a30bd4..9a5b07737 100644 --- a/packages/console/src/pages/CheckoutSuccessCallback/index.tsx +++ b/packages/console/src/pages/CheckoutSuccessCallback/index.tsx @@ -14,7 +14,6 @@ import SkuName from '@/components/SkuName'; import { checkoutStateQueryKey } from '@/consts/subscriptions'; import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider'; import { TenantsContext } from '@/contexts/TenantsProvider'; -import useLogtoSkus from '@/hooks/use-logto-skus'; import useTenantPathname from '@/hooks/use-tenant-pathname'; import { clearLocalCheckoutSession, getLocalCheckoutSession } from '@/utils/checkout'; @@ -32,9 +31,6 @@ function CheckoutSuccessCallback() { const checkoutState = new URLSearchParams(search).get(checkoutStateQueryKey); const { state, sessionId, callbackPage, isDowngrade } = getLocalCheckoutSession() ?? {}; - const { data: logtoSkus, error: fetchLogtoSkusError } = useLogtoSkus(); - const isLoadingLogtoSkus = !logtoSkus && !fetchLogtoSkusError; - // Note: if we can't get the subscription results in 10 seconds, we will redirect to the console home page useTimer({ autoStart: true, @@ -63,7 +59,6 @@ function CheckoutSuccessCallback() { ); const checkoutTenantId = stripeCheckoutSession?.tenantId; - const checkoutPlanId = stripeCheckoutSession?.planId; const checkoutSkuId = stripeCheckoutSession?.skuId; const { data: tenantSubscription } = useSWR( @@ -80,19 +75,18 @@ function CheckoutSuccessCallback() { const isCheckoutSuccessful = checkoutTenantId && stripeCheckoutSession.status === 'complete' && - !isLoadingLogtoSkus && checkoutSkuId === tenantSubscription?.planId; useEffect(() => { if (isCheckoutSuccessful) { clearLocalCheckoutSession(); - const checkoutSku = logtoSkus?.find((sku) => sku.id === checkoutPlanId); - if (checkoutSku) { + // Make the typescript happy checkoutSkuId should not be empty here + if (checkoutSkuId) { toast.success( , + name: , }} > {t(isDowngrade ? 'downgrade_success' : 'upgrade_success')} @@ -124,12 +118,11 @@ function CheckoutSuccessCallback() { } }, [ callbackPage, - checkoutPlanId, + checkoutSkuId, checkoutTenantId, currentTenantId, isCheckoutSuccessful, isDowngrade, - logtoSkus, navigate, navigateTenant, onCurrentSubscriptionUpdated, diff --git a/packages/console/src/utils/subscription.ts b/packages/console/src/utils/subscription.ts index 4d689b8b6..eda27bae5 100644 --- a/packages/console/src/utils/subscription.ts +++ b/packages/console/src/utils/subscription.ts @@ -9,7 +9,7 @@ import { ticketSupportResponseTimeMap } from '@/consts/plan-quotas'; import { featuredPlanIdOrder, featuredPlanIds } from '@/consts/subscriptions'; import { type LogtoSkuQuota } from '@/types/skus'; -export const addSupportQuota = (logtoSkuResponse: LogtoSkuResponse) => { +const addSupportQuota = (logtoSkuResponse: LogtoSkuResponse) => { const { id, quota } = logtoSkuResponse; return { @@ -24,6 +24,26 @@ export const addSupportQuota = (logtoSkuResponse: LogtoSkuResponse) => { }; }; +/** + * Format Logto SKUs responses. + * + * - add support quota to the SKUs. + * - Sort the SKUs by the order of `featuredPlanIdOrder`. + */ +export const formatLogtoSkusResponses = (logtoSkus: LogtoSkuResponse[] | undefined) => { + if (!logtoSkus) { + return []; + } + + return logtoSkus + .map((logtoSku) => addSupportQuota(logtoSku)) + .slice() + .sort( + ({ id: previousId }, { id: nextId }) => + featuredPlanIdOrder.indexOf(previousId) - featuredPlanIdOrder.indexOf(nextId) + ); +}; + const getSubscriptionPlanOrderById = (id: string) => { const index = featuredPlanIdOrder.indexOf(id);