diff --git a/packages/console/package.json b/packages/console/package.json index a89b0ec2d..c9ca45557 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -26,7 +26,7 @@ "@fontsource/roboto-mono": "^5.0.0", "@jest/types": "^29.5.0", "@logto/app-insights": "workspace:^1.3.1", - "@logto/cloud": "0.2.5-4d5e389", + "@logto/cloud": "0.2.5-2087c06", "@logto/connector-kit": "workspace:^1.1.1", "@logto/core-kit": "workspace:^2.0.1", "@logto/language-kit": "workspace:^1.0.0", diff --git a/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx index ffdc2c5f7..9b733ea85 100644 --- a/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx +++ b/packages/console/src/components/CreateTenantModal/SelectTenantPlanModal/index.tsx @@ -8,6 +8,7 @@ import { ReservedPlanId } from '@/consts/subscriptions'; import DangerousRaw from '@/ds-components/DangerousRaw'; import ModalLayout from '@/ds-components/ModalLayout'; import TextLink from '@/ds-components/TextLink'; +import useSubscribe from '@/hooks/use-subscribe'; import useSubscriptionPlans from '@/hooks/use-subscription-plans'; import * as modalStyles from '@/scss/modal.module.scss'; import { type SubscriptionPlan } from '@/types/subscriptions'; @@ -25,6 +26,7 @@ type Props = { function SelectTenantPlanModal({ tenantData, onClose }: Props) { const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); const { data: subscriptionPlans } = useSubscriptionPlans(); + const { subscribe } = useSubscribe(); const cloudApi = useCloudApi(); if (!subscriptionPlans || !tenantData) { return null; @@ -42,7 +44,8 @@ function SelectTenantPlanModal({ tenantData, onClose }: Props) { toast.error(error instanceof Error ? error.message : String(error)); } } - // Todo @xiaoyijun implement checkout + + void subscribe({ planId, tenantData }); }; return ( diff --git a/packages/console/src/consts/pages.ts b/packages/console/src/consts/pages.ts new file mode 100644 index 000000000..b15d3b842 --- /dev/null +++ b/packages/console/src/consts/pages.ts @@ -0,0 +1,3 @@ +import { TenantSettingsTabs } from '.'; + +export const subscriptionPage = `/tenant-settings/${TenantSettingsTabs.Subscription}`; diff --git a/packages/console/src/consts/storage.ts b/packages/console/src/consts/storage.ts index 22917d99e..29a18a776 100644 --- a/packages/console/src/consts/storage.ts +++ b/packages/console/src/consts/storage.ts @@ -2,7 +2,7 @@ export type CamelCase = T extends `${infer A}_${infer B}` ? `${A}${Capitalize>}` : T; -export type StorageType = 'appearance_mode' | 'linking_social_connector'; +export type StorageType = 'appearance_mode' | 'linking_social_connector' | 'checkout_session'; export const getStorageKey = (forType: T) => `logto:admin_console:${forType}` as const; @@ -10,4 +10,5 @@ export const getStorageKey = (forType: T) => export const storageKeys = Object.freeze({ appearanceMode: getStorageKey('appearance_mode'), linkingSocialConnector: getStorageKey('linking_social_connector'), + checkoutSession: getStorageKey('checkout_session'), } satisfies Record, string>); diff --git a/packages/console/src/consts/subscriptions.ts b/packages/console/src/consts/subscriptions.ts index fd84079e9..c97e5607f 100644 --- a/packages/console/src/consts/subscriptions.ts +++ b/packages/console/src/consts/subscriptions.ts @@ -84,3 +84,7 @@ export const planTableGroupKeyMap: SubscriptionPlanTableGroupKeyMap = Object.fre [SubscriptionPlanTableGroupKey.hooks]: ['hooksLimit'], [SubscriptionPlanTableGroupKey.support]: ['communitySupportEnabled', 'ticketSupportResponseTime'], }) satisfies SubscriptionPlanTableGroupKeyMap; + +export const checkoutStateQueryKey = 'checkout-state'; + +export const checkoutSuccessCallbackPath = 'checkout-success'; diff --git a/packages/console/src/hooks/use-subscribe.ts b/packages/console/src/hooks/use-subscribe.ts new file mode 100644 index 000000000..41176ae1e --- /dev/null +++ b/packages/console/src/hooks/use-subscribe.ts @@ -0,0 +1,81 @@ +import { appendPath } from '@silverhand/essentials'; +import { nanoid } from 'nanoid'; +import { toast } from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; + +import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; +import { type CreateTenantData } from '@/components/CreateTenantModal/type'; +import { getBasename } from '@/consts'; +import { checkoutStateQueryKey, checkoutSuccessCallbackPath } from '@/consts/subscriptions'; +import { createLocalCheckoutSession } from '@/utils/checkout'; + +type SubscribeProps = { + planId: string; + callbackPage?: string; + tenantId?: string; + tenantData?: CreateTenantData; + isDowngrade?: boolean; +}; + +const useSubscribe = () => { + const cloudApi = useCloudApi(); + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' }); + + const subscribe = async ({ + planId, + callbackPage, + tenantId, + tenantData, + isDowngrade = false, + }: SubscribeProps) => { + const state = nanoid(6); + + const successSearchParam = new URLSearchParams({ + [checkoutStateQueryKey]: state, + }); + + const successCallbackUrl = appendPath( + new URL(getBasename(), window.location.origin), + `${checkoutSuccessCallbackPath}?${successSearchParam.toString()}` + ).href; + + const { redirectUri, sessionId } = await cloudApi.post('/api/checkout-session', { + body: { + planId, + successCallbackUrl, + tenantId, + tenantName: tenantData?.name, + tenantTag: tenantData?.tag, + }, + }); + + if (!redirectUri) { + toast.error(t('general.unknown_error')); + return; + } + + createLocalCheckoutSession({ + state, + sessionId, + callbackPage, + isDowngrade, + }); + + window.location.assign(redirectUri); + }; + + const cancelSubscription = async (tenantId: string) => { + await cloudApi.delete('/api/tenants/:tenantId/subscription', { + params: { + tenantId, + }, + }); + }; + + return { + subscribe, + cancelSubscription, + }; +}; + +export default useSubscribe; diff --git a/packages/console/src/pages/CheckoutSuccessCallback/index.tsx b/packages/console/src/pages/CheckoutSuccessCallback/index.tsx new file mode 100644 index 000000000..8a57f4bef --- /dev/null +++ b/packages/console/src/pages/CheckoutSuccessCallback/index.tsx @@ -0,0 +1,121 @@ +import { conditional, conditionalString } from '@silverhand/essentials'; +import dayjs from 'dayjs'; +import { useContext, useEffect } from 'react'; +import { toast } from 'react-hot-toast'; +import { Trans, useTranslation } from 'react-i18next'; +import { Navigate, useLocation, useNavigate } from 'react-router-dom'; +import { useTimer } from 'react-timer-hook'; +import useSWR from 'swr'; + +import { useCloudApi } from '@/cloud/hooks/use-cloud-api'; +import AppLoading from '@/components/AppLoading'; +import PlanName from '@/components/PlanName'; +import { checkoutStateQueryKey } from '@/consts/subscriptions'; +import { TenantsContext } from '@/contexts/TenantsProvider'; +import useSubscriptionPlans from '@/hooks/use-subscription-plans'; +import { clearLocalCheckoutSession, getLocalCheckoutSession } from '@/utils/checkout'; + +const consoleHomePage = '/'; +const subscriptionCheckingInterval = 1000; +const subscriptionCheckingTimeout = 10 * 1000; + +function CheckoutSuccessCallback() { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.subscription' }); + const navigate = useNavigate(); + const cloudApi = useCloudApi(); + const { currentTenantId, navigateTenant } = useContext(TenantsContext); + const { search } = useLocation(); + const checkoutState = new URLSearchParams(search).get(checkoutStateQueryKey); + const { state, sessionId, callbackPage, isDowngrade } = getLocalCheckoutSession() ?? {}; + const { data: subscriptionPlans, error: fetchPlansError } = useSubscriptionPlans(); + const isLoadingPlans = !subscriptionPlans && !fetchPlansError; + + // Note: if we can't get the subscription results in 10 seconds, we will redirect to the console home page + useTimer({ + autoStart: true, + expiryTimestamp: dayjs().add(subscriptionCheckingTimeout, 'millisecond').toDate(), + onExpire: () => { + toast.error(t('subscription_check_timeout')); + clearLocalCheckoutSession(); + navigate(consoleHomePage, { replace: true }); + }, + }); + + // Note: only handle the callback comes from the stripe success callback url + const isValidSession = state && state === checkoutState; + + const { data: stripeCheckoutSession } = useSWR( + isValidSession && sessionId && `/api/checkout-session/${sessionId}`, + async () => + cloudApi.get('/api/checkout-session/:id', { + params: { + id: conditionalString(sessionId), + }, + }), + { + refreshInterval: subscriptionCheckingInterval, + } + ); + + const checkoutTenantId = stripeCheckoutSession?.tenantId; + const checkoutPlanId = stripeCheckoutSession?.planId; + + const { data: tenantSubscription } = useSWR( + checkoutTenantId && `/api/tenants/${checkoutTenantId}/subscription`, + async () => + cloudApi.get('/api/tenants/:tenantId/subscription', { + params: { + tenantId: conditionalString(checkoutTenantId), + }, + }), + { refreshInterval: subscriptionCheckingInterval } + ); + + const isCheckoutSuccessful = + !isLoadingPlans && + checkoutTenantId && + stripeCheckoutSession.status === 'complete' && + checkoutPlanId === tenantSubscription?.planId; + + useEffect(() => { + if (isCheckoutSuccessful) { + clearLocalCheckoutSession(); + + const checkoutPlan = subscriptionPlans?.find((plan) => plan.id === checkoutPlanId); + if (checkoutPlan) { + toast.success( + }}> + {t(isDowngrade ? 'downgrade_success' : 'upgrade_success')} + + ); + } + + if (checkoutTenantId === currentTenantId) { + navigate(conditional(callbackPage) ?? consoleHomePage, { replace: true }); + return; + } + + // Note: the tenant is created after checkout. + navigateTenant(checkoutTenantId); + } + }, [ + callbackPage, + checkoutPlanId, + checkoutTenantId, + currentTenantId, + isCheckoutSuccessful, + isDowngrade, + navigate, + navigateTenant, + subscriptionPlans, + t, + ]); + + if (!isValidSession && !isLoadingPlans) { + return ; + } + + return ; +} + +export default CheckoutSuccessCallback; diff --git a/packages/console/src/pages/ConsoleRoutes/index.tsx b/packages/console/src/pages/ConsoleRoutes/index.tsx index 50323f87f..39b1781ab 100644 --- a/packages/console/src/pages/ConsoleRoutes/index.tsx +++ b/packages/console/src/pages/ConsoleRoutes/index.tsx @@ -3,6 +3,8 @@ import { TrackOnce } from '@logto/app-insights/react'; import { Outlet, Route, Routes } from 'react-router-dom'; import { SWRConfig } from 'swr'; +import { isCloud, isProduction } from '@/consts/env'; +import { checkoutSuccessCallbackPath } from '@/consts/subscriptions'; import AppBoundary from '@/containers/AppBoundary'; import AppContent from '@/containers/AppContent'; import ConsoleContent from '@/containers/ConsoleContent'; @@ -13,6 +15,7 @@ import useSwrOptions from '@/hooks/use-swr-options'; import Callback from '@/pages/Callback'; import Welcome from '@/pages/Welcome'; +import CheckoutSuccessCallback from '../CheckoutSuccessCallback'; import HandleSocialCallback from '../Profile/containers/HandleSocialCallback'; function Layout() { @@ -38,6 +41,9 @@ export function ConsoleRoutes() { }> } /> }> + {!isProduction && isCloud && ( + } /> + )} }> } /> diff --git a/packages/console/src/pages/TenantSettings/Subscription/SwitchPlanActionBar/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/SwitchPlanActionBar/index.tsx index 90a8f5c4f..bf2f15336 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/SwitchPlanActionBar/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/SwitchPlanActionBar/index.tsx @@ -1,7 +1,16 @@ +import { useContext } from 'react'; +import { toast } from 'react-hot-toast'; +import { Trans, useTranslation } from 'react-i18next'; + +import PlanName from '@/components/PlanName'; import { contactEmailLink } from '@/consts'; +import { subscriptionPage } from '@/consts/pages'; +import { ReservedPlanId } from '@/consts/subscriptions'; +import { TenantsContext } from '@/contexts/TenantsProvider'; import Button from '@/ds-components/Button'; import Spacer from '@/ds-components/Spacer'; import { useConfirmModal } from '@/hooks/use-confirm-modal'; +import useSubscribe from '@/hooks/use-subscribe'; import { type SubscriptionPlan } from '@/types/subscriptions'; import { isDowngradePlan } from '@/utils/subscription'; @@ -13,19 +22,48 @@ import * as styles from './index.module.scss'; type Props = { currentSubscriptionPlanId: string; subscriptionPlans: SubscriptionPlan[]; + onSubscriptionUpdated: () => void; }; -function SwitchPlanActionBar({ currentSubscriptionPlanId, subscriptionPlans }: Props) { +function SwitchPlanActionBar({ + currentSubscriptionPlanId, + subscriptionPlans, + onSubscriptionUpdated, +}: Props) { + const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.subscription' }); + const { currentTenantId } = useContext(TenantsContext); + const { subscribe, cancelSubscription } = useSubscribe(); const { show } = useConfirmModal(); const handleDownGrade = async (targetPlan: SubscriptionPlan) => { - // Todo @xiaoyijun handle downgrade - await show({ - ModalContent: () => , - title: 'subscription.downgrade_modal.not_eligible', - confirmButtonText: 'general.got_it', - confirmButtonType: 'primary', - }); + const { id: planId, name } = targetPlan; + try { + if (planId === ReservedPlanId.free) { + await cancelSubscription(currentTenantId); + onSubscriptionUpdated(); + toast.success( + }}>{t('downgrade_success')} + ); + return; + } + + await subscribe({ + tenantId: currentTenantId, + planId, + isDowngrade: true, + callbackPage: subscriptionPage, + }); + } catch (error: unknown) { + // Todo @xiaoyijun check if the error is not eligible downgrade error or not + await show({ + ModalContent: () => , + title: 'subscription.downgrade_modal.not_eligible', + confirmButtonText: 'general.got_it', + confirmButtonType: 'primary', + }); + + toast.error(error instanceof Error ? error.message : String(error)); + } }; const onDowngradeClick = async (targetPlanId: string) => { @@ -68,13 +106,17 @@ function SwitchPlanActionBar({ currentSubscriptionPlanId, subscriptionPlans }: P } type={isDowngrade ? 'default' : 'primary'} disabled={isCurrentPlan} - onClick={async () => { + onClick={() => { if (isDowngrade) { - await onDowngradeClick(planId); - // eslint-disable-next-line no-useless-return + void onDowngradeClick(planId); + return; } - // Todo @xiaoyijun handle buy plan + void subscribe({ + tenantId: currentTenantId, + planId, + callbackPage: subscriptionPage, + }); }} /> diff --git a/packages/console/src/pages/TenantSettings/Subscription/index.tsx b/packages/console/src/pages/TenantSettings/Subscription/index.tsx index ba3acee6c..742022eb1 100644 --- a/packages/console/src/pages/TenantSettings/Subscription/index.tsx +++ b/packages/console/src/pages/TenantSettings/Subscription/index.tsx @@ -14,7 +14,11 @@ import * as styles from './index.module.scss'; function Subscription() { const { data: subscriptionPlans, error: fetchPlansError } = useSubscriptionPlans(); - const { data: currentSubscription, error: fetchSubscriptionError } = useCurrentSubscription(); + const { + data: currentSubscription, + error: fetchSubscriptionError, + mutate: mutateSubscription, + } = useCurrentSubscription(); const { data: subscriptionUsage, error: fetchSubscriptionUsageError } = useCurrentSubscriptionUsage(); @@ -50,6 +54,7 @@ function Subscription() { ); diff --git a/packages/console/src/types/subscriptions.ts b/packages/console/src/types/subscriptions.ts index dd741ae63..617bd071d 100644 --- a/packages/console/src/types/subscriptions.ts +++ b/packages/console/src/types/subscriptions.ts @@ -1,3 +1,5 @@ +import { z } from 'zod'; + import { type SubscriptionPlanResponse } from '@/cloud/types/router'; export enum ReservedPlanName { @@ -48,3 +50,12 @@ type SubscriptionPlanTableValue = SubscriptionPlanTable[keyof SubscriptionPlanTa export type SubscriptionPlanTableRow = Record & { quotaKey: keyof SubscriptionPlanTable; }; + +export const localCheckoutSessionGuard = z.object({ + state: z.string(), + sessionId: z.string(), + callbackPage: z.string().optional(), + isDowngrade: z.boolean().optional(), +}); + +export type LocalCheckoutSession = z.infer; diff --git a/packages/console/src/utils/checkout.ts b/packages/console/src/utils/checkout.ts new file mode 100644 index 000000000..8bf7c97bc --- /dev/null +++ b/packages/console/src/utils/checkout.ts @@ -0,0 +1,33 @@ +import { type Optional } from '@silverhand/essentials'; + +import { storageKeys } from '@/consts'; +import { type LocalCheckoutSession, localCheckoutSessionGuard } from '@/types/subscriptions'; + +import { safeParseJson } from './json'; + +export const createLocalCheckoutSession = (session: LocalCheckoutSession) => { + sessionStorage.setItem(storageKeys.checkoutSession, JSON.stringify(session)); +}; + +export const getLocalCheckoutSession = (): Optional => { + const sessionValue = sessionStorage.getItem(storageKeys.checkoutSession); + if (!sessionValue) { + return; + } + + const sessionJson = safeParseJson(sessionValue); + if (!sessionJson.success) { + return; + } + + const parsedSession = localCheckoutSessionGuard.safeParse(sessionJson.data); + if (!parsedSession.success) { + return; + } + + return parsedSession.data; +}; + +export const clearLocalCheckoutSession = () => { + sessionStorage.removeItem(storageKeys.checkoutSession); +}; diff --git a/packages/phrases/src/locales/de/translation/admin-console/general.ts b/packages/phrases/src/locales/de/translation/admin-console/general.ts index 4580c3336..6eed212eb 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/general.ts @@ -54,6 +54,7 @@ const general = { unnamed: 'Unbenannt', view: 'Anzeigen', hide: 'Verbergen', + unknown_error: 'Unbekannter Fehler, bitte versuchen Sie es später erneut.', }; export default general; diff --git a/packages/phrases/src/locales/de/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/de/translation/admin-console/subscription/index.ts index 564bf083d..a76beb686 100644 --- a/packages/phrases/src/locales/de/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/de/translation/admin-console/subscription/index.ts @@ -52,6 +52,7 @@ const subscription = { }, upgrade_success: 'Erfolgreich auf hochgestuft', downgrade_success: 'Erfolgreich auf herabgestuft', + subscription_check_timeout: 'Abo-Überprüfung ist abgelaufen. Bitte später aktualisieren.', }; export default subscription; diff --git a/packages/phrases/src/locales/en/translation/admin-console/general.ts b/packages/phrases/src/locales/en/translation/admin-console/general.ts index 98664f9e9..9190aa97f 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: 'Unnamed', view: 'View', hide: 'Hide', + unknown_error: 'Unknown error, please try again later.', }; export default general; diff --git a/packages/phrases/src/locales/en/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/en/translation/admin-console/subscription/index.ts index 348883292..f47ace001 100644 --- a/packages/phrases/src/locales/en/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/en/translation/admin-console/subscription/index.ts @@ -52,6 +52,7 @@ const subscription = { }, upgrade_success: 'Successfully upgraded to ', downgrade_success: 'Successfully downgraded to ', + subscription_check_timeout: 'Subscription check timed out. Please refresh later.', }; export default subscription; diff --git a/packages/phrases/src/locales/es/translation/admin-console/general.ts b/packages/phrases/src/locales/es/translation/admin-console/general.ts index 7ea240e7a..b99de2a34 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/general.ts @@ -54,6 +54,7 @@ const general = { unnamed: 'Sin nombre', view: 'Ver', hide: 'Ocultar', + unknown_error: 'Error desconocido, por favor inténtalo de nuevo más tarde.', }; export default general; diff --git a/packages/phrases/src/locales/es/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/es/translation/admin-console/subscription/index.ts index cdef20186..f15644173 100644 --- a/packages/phrases/src/locales/es/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/es/translation/admin-console/subscription/index.ts @@ -54,6 +54,8 @@ const subscription = { }, upgrade_success: 'Actualizado con éxito a ', downgrade_success: 'Degradado con éxito a ', + subscription_check_timeout: + 'La comprobación de suscripción expiró. Por favor, actualiza más tarde.', }; export default subscription; diff --git a/packages/phrases/src/locales/fr/translation/admin-console/general.ts b/packages/phrases/src/locales/fr/translation/admin-console/general.ts index 178364061..0b46a7a4b 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/general.ts @@ -54,6 +54,7 @@ const general = { unnamed: 'Sans nom', view: 'Vue', hide: 'Cacher', + unknown_error: 'Erreur inconnue, veuillez réessayer ultérieurement.', }; export default general; diff --git a/packages/phrases/src/locales/fr/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/fr/translation/admin-console/subscription/index.ts index 96e9dc56c..410d124a7 100644 --- a/packages/phrases/src/locales/fr/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/fr/translation/admin-console/subscription/index.ts @@ -56,6 +56,8 @@ const subscription = { }, upgrade_success: 'Passé avec succès à ', downgrade_success: 'Rétrogradé avec succès à ', + subscription_check_timeout: + "La vérification d'abonnement a expiré. Veuillez actualiser ultérieurement.", }; export default subscription; diff --git a/packages/phrases/src/locales/it/translation/admin-console/general.ts b/packages/phrases/src/locales/it/translation/admin-console/general.ts index b29299805..165a5025e 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/general.ts @@ -54,6 +54,7 @@ const general = { unnamed: 'Senza nome', view: 'Vista', hide: 'Nascondi', + unknown_error: 'Errore sconosciuto, riprova più tardi.', }; export default general; diff --git a/packages/phrases/src/locales/it/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/it/translation/admin-console/subscription/index.ts index 1bcec3f2d..555796826 100644 --- a/packages/phrases/src/locales/it/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/it/translation/admin-console/subscription/index.ts @@ -53,6 +53,8 @@ const subscription = { }, upgrade_success: 'Aggiornamento effettuato con successo a ', downgrade_success: 'Degrado effettuato con successo a ', + subscription_check_timeout: + "Il controllo dell'abbonamento è scaduto. Si prega di riprovare più tardi.", }; export default subscription; diff --git a/packages/phrases/src/locales/ja/translation/admin-console/general.ts b/packages/phrases/src/locales/ja/translation/admin-console/general.ts index 0b54e44b2..dcd49ce52 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: '名前がありません', view: '表示', hide: '非表示', + unknown_error: '不明なエラーが発生しました。後で再試行してください。', }; export default general; diff --git a/packages/phrases/src/locales/ja/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/ja/translation/admin-console/subscription/index.ts index 0d60f1b1f..24f1b9738 100644 --- a/packages/phrases/src/locales/ja/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/ja/translation/admin-console/subscription/index.ts @@ -53,6 +53,8 @@ const subscription = { }, upgrade_success: '正常ににアップグレードされました', downgrade_success: '正常ににダウングレードされました', + subscription_check_timeout: + 'サブスクリプションのチェックがタイムアウトしました。後でもう一度更新してください。', }; export default subscription; diff --git a/packages/phrases/src/locales/ko/translation/admin-console/general.ts b/packages/phrases/src/locales/ko/translation/admin-console/general.ts index 2ba3d4508..8d2eb2f37 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: '이름없음', view: '보기', hide: '숨기기', + unknown_error: '알 수 없는 오류가 발생했습니다. 나중에 다시 시도해주세요.', }; export default general; diff --git a/packages/phrases/src/locales/ko/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/ko/translation/admin-console/subscription/index.ts index b9ae55732..1f76eedb3 100644 --- a/packages/phrases/src/locales/ko/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/ko/translation/admin-console/subscription/index.ts @@ -52,6 +52,7 @@ const subscription = { }, upgrade_success: '성공적으로 으로 업그레이드되었습니다.', downgrade_success: '성공적으로 으로 다운그레이드되었습니다.', + subscription_check_timeout: '구독 확인이 타임아웃되었습니다. 나중에 다시 확인해주세요.', }; export default subscription; diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/general.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/general.ts index 69ae91f84..ffd27b9bb 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: 'Bez nazwy', view: 'Pokaż', hide: 'Ukryj', + unknown_error: 'Nieznany błąd, spróbuj ponownie później.', }; export default general; diff --git a/packages/phrases/src/locales/pl-pl/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/pl-pl/translation/admin-console/subscription/index.ts index 579f26d53..63761b17d 100644 --- a/packages/phrases/src/locales/pl-pl/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/pl-pl/translation/admin-console/subscription/index.ts @@ -53,6 +53,7 @@ const subscription = { }, upgrade_success: 'Pomyślnie uaktualniono do ', downgrade_success: 'Pomyślnie zdegradowano do ', + subscription_check_timeout: 'Czas sprawdzenia subskrypcji wygasł. Proszę odświeżyć później.', }; export default subscription; diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/general.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/general.ts index 727aee386..abd79ace4 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/general.ts @@ -54,6 +54,7 @@ const general = { unnamed: 'Sem nome', view: 'Visualizar', hide: 'Ocultar', + unknown_error: 'Erro desconhecido, por favor tente novamente mais tarde.', }; export default general; diff --git a/packages/phrases/src/locales/pt-br/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/pt-br/translation/admin-console/subscription/index.ts index 7b6b19f7b..b3a00dd67 100644 --- a/packages/phrases/src/locales/pt-br/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/pt-br/translation/admin-console/subscription/index.ts @@ -54,6 +54,8 @@ const subscription = { }, upgrade_success: 'Atualizado com sucesso para ', downgrade_success: 'Downgrade realizado com sucesso para ', + subscription_check_timeout: + 'A verificação de assinatura expirou. Por favor, atualize mais tarde.', }; export default subscription; diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/general.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/general.ts index 4fbbfd028..cf24a0de7 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: 'Sem nome', view: 'Ver', hide: 'Esconder', + unknown_error: 'Erro desconhecido, por favor tente novamente mais tarde.', }; export default general; diff --git a/packages/phrases/src/locales/pt-pt/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/pt-pt/translation/admin-console/subscription/index.ts index de3670f9b..e6a1c3169 100644 --- a/packages/phrases/src/locales/pt-pt/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/pt-pt/translation/admin-console/subscription/index.ts @@ -53,6 +53,8 @@ const subscription = { }, upgrade_success: 'Atualizou com sucesso para ', downgrade_success: 'Downgrade concluído com sucesso para ', + subscription_check_timeout: + 'A verificação de subscrição expirou. Por favor, atualize mais tarde.', }; export default subscription; diff --git a/packages/phrases/src/locales/ru/translation/admin-console/general.ts b/packages/phrases/src/locales/ru/translation/admin-console/general.ts index 8644e1a33..a2d2681b7 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: 'Без имени', view: 'Просмотр', hide: 'Скрыть', + unknown_error: 'Неизвестная ошибка, пожалуйста, попробуйте позже.', }; export default general; diff --git a/packages/phrases/src/locales/ru/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/ru/translation/admin-console/subscription/index.ts index df695c15f..5a94507eb 100644 --- a/packages/phrases/src/locales/ru/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/ru/translation/admin-console/subscription/index.ts @@ -52,6 +52,7 @@ const subscription = { }, upgrade_success: 'Успешно повышен до ', downgrade_success: 'Успешно понижен до ', + subscription_check_timeout: 'Время проверки подписки истекло. Пожалуйста, обновите позже.', }; export default subscription; diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/general.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/general.ts index 8ab217897..024e65abe 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/general.ts @@ -54,6 +54,7 @@ const general = { unnamed: 'İsimsiz', view: 'Görünüm', hide: 'Gizle', + unknown_error: 'Bilinmeyen hata, lütfen daha sonra tekrar deneyin.', }; export default general; diff --git a/packages/phrases/src/locales/tr-tr/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/tr-tr/translation/admin-console/subscription/index.ts index e57bf1fb8..ab18320a6 100644 --- a/packages/phrases/src/locales/tr-tr/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/tr-tr/translation/admin-console/subscription/index.ts @@ -53,6 +53,8 @@ const subscription = { }, upgrade_success: 'Successfully upgraded to ', downgrade_success: 'Successfully downgraded to ', + subscription_check_timeout: + 'Abonelik kontrolü zaman aşımına uğradı. Lütfen daha sonra yenileyin.', }; export default subscription; diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/general.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/general.ts index 1941383e6..03a6f6671 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: '未命名', view: '查看', hide: '隐藏', + unknown_error: '未知错误,请稍后重试。', }; export default general; diff --git a/packages/phrases/src/locales/zh-cn/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/zh-cn/translation/admin-console/subscription/index.ts index d576b0530..04f4dcf67 100644 --- a/packages/phrases/src/locales/zh-cn/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/zh-cn/translation/admin-console/subscription/index.ts @@ -50,6 +50,7 @@ const subscription = { }, upgrade_success: '成功升级到 ', downgrade_success: '成功降级到 ', + subscription_check_timeout: '订阅检查超时,请稍后刷新。', }; export default subscription; diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/general.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/general.ts index 3a2e7f0bc..3f4e31c0c 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: '未命名', view: '查看', hide: '隱藏', + unknown_error: '未知錯誤,請稍後重試。', }; export default general; diff --git a/packages/phrases/src/locales/zh-hk/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/zh-hk/translation/admin-console/subscription/index.ts index a190cd679..468c70bf8 100644 --- a/packages/phrases/src/locales/zh-hk/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/zh-hk/translation/admin-console/subscription/index.ts @@ -49,6 +49,7 @@ const subscription = { }, upgrade_success: '升級成功至', downgrade_success: '成功降級至', + subscription_check_timeout: '訂閱檢查已逾時,請稍後重新刷新。', }; export default subscription; diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/general.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/general.ts index 8bf84b82a..542bf349c 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/general.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/general.ts @@ -53,6 +53,7 @@ const general = { unnamed: '未命名', view: '查看', hide: '隱藏', + unknown_error: '未知錯誤,請稍後重試。', }; export default general; diff --git a/packages/phrases/src/locales/zh-tw/translation/admin-console/subscription/index.ts b/packages/phrases/src/locales/zh-tw/translation/admin-console/subscription/index.ts index 5ecaf40aa..509d80328 100644 --- a/packages/phrases/src/locales/zh-tw/translation/admin-console/subscription/index.ts +++ b/packages/phrases/src/locales/zh-tw/translation/admin-console/subscription/index.ts @@ -49,6 +49,7 @@ const subscription = { }, upgrade_success: '已成功升級到 ', downgrade_success: '已成功降級到 ', + subscription_check_timeout: '訂閱檢查已逾時,請稍後重新刷新。', }; export default subscription; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23a482ec4..d58d9bc30 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2755,8 +2755,8 @@ importers: specifier: workspace:^1.3.1 version: link:../app-insights '@logto/cloud': - specifier: 0.2.5-4d5e389 - version: 0.2.5-4d5e389(zod@3.20.2) + specifier: 0.2.5-2087c06 + version: 0.2.5-2087c06(zod@3.20.2) '@logto/connector-kit': specifier: workspace:^1.1.1 version: link:../toolkit/connector-kit @@ -7210,8 +7210,8 @@ packages: jose: 4.14.4 dev: true - /@logto/cloud@0.2.5-4d5e389(zod@3.20.2): - resolution: {integrity: sha512-vRJZGc0WvjE1rFJ0DNLaOHkhpe4TMdui/pvcTwGb/bDKzw/NM+4HtUoZj1a1DZ8Qqn24ex1WkTjxY8XGd3EruQ==} + /@logto/cloud@0.2.5-2087c06(zod@3.20.2): + resolution: {integrity: sha512-v/zisil/t8XrlFxlVic+0O6T2J3FUzB5FDC1w3OYNzhXNbSpmbqlt5vyN9RIwXUGgAKjdhKQjoACpV7HpPFZcQ==} engines: {node: ^18.12.0} dependencies: '@silverhand/essentials': 2.7.0 @@ -7220,8 +7220,8 @@ packages: - zod dev: true - /@logto/cloud@0.2.5-4f1d80b(zod@3.20.2): - resolution: {integrity: sha512-AF0YnJiXDMS3HQ2ugZcwJRBkspvNtlXk992IwTNFZxbZdinpPoVmPnDnHuekACcQY5bFRgHjMB2/o/GKkdLpWg==} + /@logto/cloud@0.2.5-4d5e389(zod@3.20.2): + resolution: {integrity: sha512-vRJZGc0WvjE1rFJ0DNLaOHkhpe4TMdui/pvcTwGb/bDKzw/NM+4HtUoZj1a1DZ8Qqn24ex1WkTjxY8XGd3EruQ==} engines: {node: ^18.12.0} dependencies: '@silverhand/essentials': 2.7.0