mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
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
This commit is contained in:
parent
ba84b1aed8
commit
ad4800fd24
5 changed files with 59 additions and 35 deletions
|
@ -35,7 +35,7 @@ function SelectTenantPlanModal({ tenantData, onClose }: Props) {
|
||||||
const { subscribe } = useSubscribe();
|
const { subscribe } = useSubscribe();
|
||||||
const cloudApi = useCloudApi({ hideErrorToast: true });
|
const cloudApi = useCloudApi({ hideErrorToast: true });
|
||||||
|
|
||||||
const reservedBasicLogtoSkus = conditional(logtoSkus && pickupFeaturedLogtoSkus(logtoSkus));
|
const reservedBasicLogtoSkus = conditional(pickupFeaturedLogtoSkus(logtoSkus));
|
||||||
|
|
||||||
if (!reservedBasicLogtoSkus || !tenantData) {
|
if (!reservedBasicLogtoSkus || !tenantData) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import { cond, pick } from '@silverhand/essentials';
|
import { pick } from '@silverhand/essentials';
|
||||||
import { useContext, useEffect, useMemo } from 'react';
|
import { useContext, useEffect, useMemo } from 'react';
|
||||||
import useSWR from 'swr';
|
import useSWR from 'swr';
|
||||||
|
|
||||||
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
|
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 {
|
import {
|
||||||
defaultLogtoSku,
|
defaultLogtoSku,
|
||||||
defaultTenantResponse,
|
defaultTenantResponse,
|
||||||
|
@ -12,7 +12,8 @@ import {
|
||||||
} from '@/consts';
|
} from '@/consts';
|
||||||
import { isCloud } from '@/consts/env';
|
import { isCloud } from '@/consts/env';
|
||||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
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';
|
import useSubscription from '../../hooks/use-subscription';
|
||||||
|
|
||||||
|
@ -22,7 +23,6 @@ const useNewSubscriptionData: () => NewSubscriptionContext & { isLoading: boolea
|
||||||
const cloudApi = useCloudApi();
|
const cloudApi = useCloudApi();
|
||||||
|
|
||||||
const { currentTenant, currentTenantId, updateTenant } = useContext(TenantsContext);
|
const { currentTenant, currentTenantId, updateTenant } = useContext(TenantsContext);
|
||||||
const { isLoading: isLogtoSkusLoading, data: fetchedLogtoSkus } = useLogtoSkus();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: currentSubscription,
|
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(
|
const currentSku = useMemo(
|
||||||
() => logtoSkus.find((logtoSku) => logtoSku.id === currentTenant?.planId) ?? defaultLogtoSku,
|
() => logtoSkus.find((logtoSku) => logtoSku.id === currentTenant?.planId) ?? defaultLogtoSku,
|
||||||
|
|
|
@ -5,17 +5,22 @@ import useSWRImmutable from 'swr/immutable';
|
||||||
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
|
import { useCloudApi } from '@/cloud/hooks/use-cloud-api';
|
||||||
import { type LogtoSkuResponse } from '@/cloud/types/router';
|
import { type LogtoSkuResponse } from '@/cloud/types/router';
|
||||||
import { isCloud } from '@/consts/env';
|
import { isCloud } from '@/consts/env';
|
||||||
import { featuredPlanIdOrder } from '@/consts/subscriptions';
|
|
||||||
// Used in the docs
|
// Used in the docs
|
||||||
// eslint-disable-next-line unused-imports/no-unused-imports
|
// eslint-disable-next-line unused-imports/no-unused-imports
|
||||||
import TenantAccess from '@/containers/TenantAccess';
|
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 { LogtoSkuType } from '@/types/skus';
|
||||||
import { sortBy } from '@/utils/sort';
|
import { formatLogtoSkusResponses } from '@/utils/subscription';
|
||||||
import { addSupportQuota } from '@/utils/subscription';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch Logto SKUs from the cloud API.
|
* Fetch public Logto SKUs from the cloud API.
|
||||||
* Note: If you want to retrieve Logto SKUs under the {@link TenantAccess} component, use `SubscriptionDataContext` instead.
|
*
|
||||||
|
* @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 useLogtoSkus = () => {
|
||||||
const cloudApi = useCloudApi();
|
const cloudApi = useCloudApi();
|
||||||
|
@ -30,18 +35,10 @@ const useLogtoSkus = () => {
|
||||||
|
|
||||||
const { data: logtoSkuResponse } = useSwrResponse;
|
const { data: logtoSkuResponse } = useSwrResponse;
|
||||||
|
|
||||||
const logtoSkus: Optional<LogtoSkuResponse[]> = useMemo(() => {
|
const logtoSkus: Optional<LogtoSkuResponse[]> = useMemo(
|
||||||
if (!logtoSkuResponse) {
|
() => formatLogtoSkusResponses(logtoSkuResponse),
|
||||||
return;
|
[logtoSkuResponse]
|
||||||
}
|
|
||||||
|
|
||||||
return logtoSkuResponse
|
|
||||||
.map((logtoSku) => addSupportQuota(logtoSku))
|
|
||||||
.slice()
|
|
||||||
.sort(({ id: previousId }, { id: nextId }) =>
|
|
||||||
sortBy(featuredPlanIdOrder)(previousId, nextId)
|
|
||||||
);
|
);
|
||||||
}, [logtoSkuResponse]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...useSwrResponse,
|
...useSwrResponse,
|
||||||
|
|
|
@ -14,7 +14,6 @@ import SkuName from '@/components/SkuName';
|
||||||
import { checkoutStateQueryKey } from '@/consts/subscriptions';
|
import { checkoutStateQueryKey } from '@/consts/subscriptions';
|
||||||
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
|
import { SubscriptionDataContext } from '@/contexts/SubscriptionDataProvider';
|
||||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||||
import useLogtoSkus from '@/hooks/use-logto-skus';
|
|
||||||
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
import useTenantPathname from '@/hooks/use-tenant-pathname';
|
||||||
import { clearLocalCheckoutSession, getLocalCheckoutSession } from '@/utils/checkout';
|
import { clearLocalCheckoutSession, getLocalCheckoutSession } from '@/utils/checkout';
|
||||||
|
|
||||||
|
@ -32,9 +31,6 @@ function CheckoutSuccessCallback() {
|
||||||
const checkoutState = new URLSearchParams(search).get(checkoutStateQueryKey);
|
const checkoutState = new URLSearchParams(search).get(checkoutStateQueryKey);
|
||||||
const { state, sessionId, callbackPage, isDowngrade } = getLocalCheckoutSession() ?? {};
|
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
|
// Note: if we can't get the subscription results in 10 seconds, we will redirect to the console home page
|
||||||
useTimer({
|
useTimer({
|
||||||
autoStart: true,
|
autoStart: true,
|
||||||
|
@ -63,7 +59,6 @@ function CheckoutSuccessCallback() {
|
||||||
);
|
);
|
||||||
|
|
||||||
const checkoutTenantId = stripeCheckoutSession?.tenantId;
|
const checkoutTenantId = stripeCheckoutSession?.tenantId;
|
||||||
const checkoutPlanId = stripeCheckoutSession?.planId;
|
|
||||||
const checkoutSkuId = stripeCheckoutSession?.skuId;
|
const checkoutSkuId = stripeCheckoutSession?.skuId;
|
||||||
|
|
||||||
const { data: tenantSubscription } = useSWR(
|
const { data: tenantSubscription } = useSWR(
|
||||||
|
@ -80,19 +75,18 @@ function CheckoutSuccessCallback() {
|
||||||
const isCheckoutSuccessful =
|
const isCheckoutSuccessful =
|
||||||
checkoutTenantId &&
|
checkoutTenantId &&
|
||||||
stripeCheckoutSession.status === 'complete' &&
|
stripeCheckoutSession.status === 'complete' &&
|
||||||
!isLoadingLogtoSkus &&
|
|
||||||
checkoutSkuId === tenantSubscription?.planId;
|
checkoutSkuId === tenantSubscription?.planId;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isCheckoutSuccessful) {
|
if (isCheckoutSuccessful) {
|
||||||
clearLocalCheckoutSession();
|
clearLocalCheckoutSession();
|
||||||
|
|
||||||
const checkoutSku = logtoSkus?.find((sku) => sku.id === checkoutPlanId);
|
// Make the typescript happy checkoutSkuId should not be empty here
|
||||||
if (checkoutSku) {
|
if (checkoutSkuId) {
|
||||||
toast.success(
|
toast.success(
|
||||||
<Trans
|
<Trans
|
||||||
components={{
|
components={{
|
||||||
name: <SkuName skuId={checkoutSku.id} />,
|
name: <SkuName skuId={checkoutSkuId} />,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t(isDowngrade ? 'downgrade_success' : 'upgrade_success')}
|
{t(isDowngrade ? 'downgrade_success' : 'upgrade_success')}
|
||||||
|
@ -124,12 +118,11 @@ function CheckoutSuccessCallback() {
|
||||||
}
|
}
|
||||||
}, [
|
}, [
|
||||||
callbackPage,
|
callbackPage,
|
||||||
checkoutPlanId,
|
checkoutSkuId,
|
||||||
checkoutTenantId,
|
checkoutTenantId,
|
||||||
currentTenantId,
|
currentTenantId,
|
||||||
isCheckoutSuccessful,
|
isCheckoutSuccessful,
|
||||||
isDowngrade,
|
isDowngrade,
|
||||||
logtoSkus,
|
|
||||||
navigate,
|
navigate,
|
||||||
navigateTenant,
|
navigateTenant,
|
||||||
onCurrentSubscriptionUpdated,
|
onCurrentSubscriptionUpdated,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { ticketSupportResponseTimeMap } from '@/consts/plan-quotas';
|
||||||
import { featuredPlanIdOrder, featuredPlanIds } from '@/consts/subscriptions';
|
import { featuredPlanIdOrder, featuredPlanIds } from '@/consts/subscriptions';
|
||||||
import { type LogtoSkuQuota } from '@/types/skus';
|
import { type LogtoSkuQuota } from '@/types/skus';
|
||||||
|
|
||||||
export const addSupportQuota = (logtoSkuResponse: LogtoSkuResponse) => {
|
const addSupportQuota = (logtoSkuResponse: LogtoSkuResponse) => {
|
||||||
const { id, quota } = logtoSkuResponse;
|
const { id, quota } = logtoSkuResponse;
|
||||||
|
|
||||||
return {
|
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 getSubscriptionPlanOrderById = (id: string) => {
|
||||||
const index = featuredPlanIdOrder.indexOf(id);
|
const index = featuredPlanIdOrder.indexOf(id);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue