diff --git a/web/app/components/billing/billing-page/index.tsx b/web/app/components/billing/billing-page/index.tsx index 43e80f4bc4..adb676cde1 100644 --- a/web/app/components/billing/billing-page/index.tsx +++ b/web/app/components/billing/billing-page/index.tsx @@ -2,36 +2,61 @@ import type { FC } from 'react' import React from 'react' import { useTranslation } from 'react-i18next' -import useSWR from 'swr' import { RiArrowRightUpLine, } from '@remixicon/react' import PlanComp from '../plan' -import Divider from '@/app/components/base/divider' -import { fetchBillingUrl } from '@/service/billing' import { useAppContext } from '@/context/app-context' import { useProviderContext } from '@/context/provider-context' +import { useBillingUrl } from '@/service/use-billing' const Billing: FC = () => { const { t } = useTranslation() const { isCurrentWorkspaceManager } = useAppContext() const { enableBilling } = useProviderContext() - const { data: billingUrl } = useSWR( - (!enableBilling || !isCurrentWorkspaceManager) ? null : ['/billing/invoices'], - () => fetchBillingUrl().then(data => data.url), - ) + const { data: billingUrl, isFetching, refetch } = useBillingUrl(enableBilling && isCurrentWorkspaceManager) + + const handleOpenBilling = async () => { + // Open synchronously to preserve user gesture for popup blockers + if (billingUrl) { + window.open(billingUrl, '_blank', 'noopener,noreferrer') + return + } + + const newWindow = window.open('', '_blank', 'noopener,noreferrer') + try { + const url = (await refetch()).data + if (url && newWindow) { + newWindow.location.href = url + return + } + } + catch (err) { + console.error('Failed to fetch billing url', err) + } + // Close the placeholder window if we failed to fetch the URL + newWindow?.close() + } return (
- {enableBilling && isCurrentWorkspaceManager && billingUrl && ( - <> - - - {t('billing.viewBilling')} + {enableBilling && isCurrentWorkspaceManager && ( + )}
) diff --git a/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx b/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx index 5d7b11c7e0..396dd4a1b0 100644 --- a/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx +++ b/web/app/components/billing/pricing/plans/cloud-plan-item/index.tsx @@ -8,7 +8,7 @@ import { ALL_PLANS } from '../../../config' import Toast from '../../../../base/toast' import { PlanRange } from '../../plan-switcher/plan-range-switcher' import { useAppContext } from '@/context/app-context' -import { fetchSubscriptionUrls } from '@/service/billing' +import { fetchBillingUrl, fetchSubscriptionUrls } from '@/service/billing' import List from './list' import Button from './button' import { Professional, Sandbox, Team } from '../../assets' @@ -39,7 +39,8 @@ const CloudPlanItem: FC = ({ const planInfo = ALL_PLANS[plan] const isYear = planRange === PlanRange.yearly const isCurrent = plan === currentPlan - const isPlanDisabled = planInfo.level <= ALL_PLANS[currentPlan].level + const isCurrentPaidPlan = isCurrent && !isFreePlan + const isPlanDisabled = isCurrentPaidPlan ? false : planInfo.level <= ALL_PLANS[currentPlan].level const { isCurrentWorkspaceManager } = useAppContext() const btnText = useMemo(() => { @@ -60,10 +61,6 @@ const CloudPlanItem: FC = ({ if (isPlanDisabled) return - if (isFreePlan) - return - - // Only workspace manager can buy plan if (!isCurrentWorkspaceManager) { Toast.notify({ type: 'error', @@ -74,6 +71,15 @@ const CloudPlanItem: FC = ({ } setLoading(true) try { + if (isCurrentPaidPlan) { + const res = await fetchBillingUrl() + window.open(res.url, '_blank') + return + } + + if (isFreePlan) + return + const res = await fetchSubscriptionUrls(plan, isYear ? 'year' : 'month') // Adb Block additional tracking block the gtag, so we need to redirect directly window.location.href = res.url diff --git a/web/i18n/en-US/billing.ts b/web/i18n/en-US/billing.ts index 7272214f41..2531e5831a 100644 --- a/web/i18n/en-US/billing.ts +++ b/web/i18n/en-US/billing.ts @@ -25,6 +25,9 @@ const translation = { encourageShort: 'Upgrade', }, viewBilling: 'Manage billing and subscriptions', + viewBillingTitle: 'Billing and Subscriptions', + viewBillingDescription: 'Manage payment methods, invoices, and subscription changes', + viewBillingAction: 'Manage', buyPermissionDeniedTip: 'Please contact your enterprise administrator to subscribe', plansCommon: { title: { diff --git a/web/i18n/ja-JP/billing.ts b/web/i18n/ja-JP/billing.ts index 57ccef5491..561b37a825 100644 --- a/web/i18n/ja-JP/billing.ts +++ b/web/i18n/ja-JP/billing.ts @@ -24,6 +24,9 @@ const translation = { encourageShort: 'アップグレード', }, viewBilling: '請求とサブスクリプションの管理', + viewBillingTitle: '請求とサブスクリプション', + viewBillingDescription: '支払い方法、請求書、サブスクリプションの変更の管理。', + viewBillingAction: '管理', buyPermissionDeniedTip: 'サブスクリプションするには、エンタープライズ管理者に連絡してください', plansCommon: { title: { diff --git a/web/i18n/zh-Hans/billing.ts b/web/i18n/zh-Hans/billing.ts index ee8f7af64d..a6237bef2e 100644 --- a/web/i18n/zh-Hans/billing.ts +++ b/web/i18n/zh-Hans/billing.ts @@ -24,6 +24,9 @@ const translation = { encourageShort: '升级', }, viewBilling: '管理账单及订阅', + viewBillingTitle: '账单与订阅', + viewBillingDescription: '管理支付方式、发票和订阅变更。', + viewBillingAction: '管理', buyPermissionDeniedTip: '请联系企业管理员订阅', plansCommon: { title: { diff --git a/web/service/billing.ts b/web/service/billing.ts index 7dfe5ac0a7..979a888582 100644 --- a/web/service/billing.ts +++ b/web/service/billing.ts @@ -1,4 +1,4 @@ -import { get } from './base' +import { get, put } from './base' import type { CurrentPlanInfoBackend, SubscriptionUrlsBackend } from '@/app/components/billing/type' export const fetchCurrentPlanInfo = () => { @@ -12,3 +12,13 @@ export const fetchSubscriptionUrls = (plan: string, interval: string) => { export const fetchBillingUrl = () => { return get<{ url: string }>('/billing/invoices') } + +export const bindPartnerStackInfo = (partnerKey: string, clickId: string) => { + return put(`/billing/partners/${partnerKey}/tenants`, { + body: { + click_id: clickId, + }, + }, { + silent: true, + }) +} diff --git a/web/service/use-billing.ts b/web/service/use-billing.ts index b48a75eab0..2701861bc0 100644 --- a/web/service/use-billing.ts +++ b/web/service/use-billing.ts @@ -1,19 +1,22 @@ -import { useMutation } from '@tanstack/react-query' -import { put } from './base' +import { useMutation, useQuery } from '@tanstack/react-query' +import { bindPartnerStackInfo, fetchBillingUrl } from '@/service/billing' const NAME_SPACE = 'billing' export const useBindPartnerStackInfo = () => { return useMutation({ mutationKey: [NAME_SPACE, 'bind-partner-stack'], - mutationFn: (data: { partnerKey: string; clickId: string }) => { - return put(`/billing/partners/${data.partnerKey}/tenants`, { - body: { - click_id: data.clickId, - }, - }, { - silent: true, - }) + mutationFn: (data: { partnerKey: string; clickId: string }) => bindPartnerStackInfo(data.partnerKey, data.clickId), + }) +} + +export const useBillingUrl = (enabled: boolean) => { + return useQuery({ + queryKey: [NAME_SPACE, 'url'], + enabled, + queryFn: async () => { + const res = await fetchBillingUrl() + return res.url }, }) }