From 9f07584a0040157a0e859a179e7fe019db0cb685 Mon Sep 17 00:00:00 2001 From: NFish Date: Wed, 23 Apr 2025 00:23:38 +0800 Subject: [PATCH] Feat/e license limit (#18436) Co-authored-by: Garfield Dai --- .github/workflows/build-push.yml | 2 +- web/app/components/billing/type.ts | 4 +++ .../members-page/invite-modal/index.tsx | 30 ++++++++++++++++--- web/context/provider-context.tsx | 21 +++++++++++++ web/i18n/en-US/common.ts | 1 + web/i18n/zh-Hans/common.ts | 1 + 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 6d02713c14..ffefe94fdb 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -6,7 +6,7 @@ on: - "main" - "deploy/dev" - "deploy/enterprise" - - "e-0154" + - "e-0156" release: types: [published] diff --git a/web/app/components/billing/type.ts b/web/app/components/billing/type.ts index 924aabcdfc..986ec3ff3f 100644 --- a/web/app/components/billing/type.ts +++ b/web/app/components/billing/type.ts @@ -68,6 +68,10 @@ export type CurrentPlanInfoBackend = { model_load_balancing_enabled: boolean dataset_operator_enabled: boolean webapp_copyright_enabled: boolean + workspace_members: { + size: number + limit: number + } } export type SubscriptionItem = { diff --git a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx index 197e3ee867..6e7fe0092c 100644 --- a/web/app/components/header/account-setting/members-page/invite-modal/index.tsx +++ b/web/app/components/header/account-setting/members-page/invite-modal/index.tsx @@ -1,5 +1,5 @@ 'use client' -import { useCallback, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useContext } from 'use-context-selector' import { XMarkIcon } from '@heroicons/react/24/outline' import { useTranslation } from 'react-i18next' @@ -17,6 +17,7 @@ import type { InvitationResult } from '@/models/common' import I18n from '@/context/i18n' import 'react-multi-email/dist/style.css' +import { useProviderContextSelector } from '@/context/provider-context' type IInviteModalProps = { isEmailSetup: boolean onCancel: () => void @@ -29,13 +30,26 @@ const InviteModal = ({ onSend, }: IInviteModalProps) => { const { t } = useTranslation() + const licenseLimit = useProviderContextSelector(s => s.licenseLimit) const [emails, setEmails] = useState([]) const { notify } = useContext(ToastContext) + const [isLimited, setIsLimited] = useState(false) + const [isLimitExceeded, setIsLimitExceeded] = useState(false) + const [usedSize, setUsedSize] = useState(licenseLimit.workspace_members.size ?? 0) + useEffect(() => { + const limited = licenseLimit.workspace_members.limit > 0 + const used = emails.length + licenseLimit.workspace_members.size + setIsLimited(limited) + setUsedSize(used) + setIsLimitExceeded(limited && (used > licenseLimit.workspace_members.limit)) + }, [licenseLimit, emails]) const { locale } = useContext(I18n) const [role, setRole] = useState('normal') const handleSend = useCallback(async () => { + if (isLimitExceeded) + return if (emails.map((email: string) => emailRegex.test(email)).every(Boolean)) { try { const { result, invitation_results } = await inviteMember({ @@ -53,7 +67,7 @@ const InviteModal = ({ else { notify({ type: 'error', message: t('common.members.emailInvalid') }) } - }, [role, emails, notify, onCancel, onSend, t]) + }, [isLimitExceeded, emails, role, locale, onCancel, onSend, notify, t]) return (
@@ -81,7 +95,7 @@ const InviteModal = ({
{t('common.members.email')}
-
+
+
licenseLimit.workspace_members.limit) ? 'text-text-destructive' : '')} + > + {usedSize} + / + {isLimited ? licenseLimit.workspace_members.limit : t('common.license.unlimited')} +
@@ -109,7 +131,7 @@ const InviteModal = ({ tabIndex={0} className='w-full' onClick={handleSend} - disabled={!emails.length} + disabled={!emails.length || isLimitExceeded} variant='primary' > {t('common.members.sendInvite')} diff --git a/web/context/provider-context.tsx b/web/context/provider-context.tsx index 812d90b777..f51193d46e 100644 --- a/web/context/provider-context.tsx +++ b/web/context/provider-context.tsx @@ -36,6 +36,12 @@ type ProviderContextState = { modelLoadBalancingEnabled: boolean datasetOperatorEnabled: boolean webappCopyrightEnabled: boolean + licenseLimit: { + workspace_members: { + size: number + limit: number + } + } } const ProviderContext = createContext({ modelProviders: [], @@ -66,6 +72,12 @@ const ProviderContext = createContext({ modelLoadBalancingEnabled: false, datasetOperatorEnabled: false, webappCopyrightEnabled: false, + licenseLimit: { + workspace_members: { + size: 0, + limit: 0, + }, + }, }) export const useProviderContext = () => useContext(ProviderContext) @@ -94,6 +106,12 @@ export const ProviderContextProvider = ({ const [modelLoadBalancingEnabled, setModelLoadBalancingEnabled] = useState(false) const [datasetOperatorEnabled, setDatasetOperatorEnabled] = useState(false) const [webappCopyrightEnabled, setWebappCopyrightEnabled] = useState(false) + const [licenseLimit, setLicenseLimit] = useState({ + workspace_members: { + size: 0, + limit: 0, + }, + }) const fetchPlan = async () => { const data = await fetchCurrentPlanInfo() @@ -110,6 +128,8 @@ export const ProviderContextProvider = ({ setDatasetOperatorEnabled(true) if (data.webapp_copyright_enabled) setWebappCopyrightEnabled(true) + if (data.workspace_members) + setLicenseLimit({ workspace_members: data.workspace_members }) } useEffect(() => { fetchPlan() @@ -129,6 +149,7 @@ export const ProviderContextProvider = ({ modelLoadBalancingEnabled, datasetOperatorEnabled, webappCopyrightEnabled, + licenseLimit, }}> {children} diff --git a/web/i18n/en-US/common.ts b/web/i18n/en-US/common.ts index 144b32f74e..032a669823 100644 --- a/web/i18n/en-US/common.ts +++ b/web/i18n/en-US/common.ts @@ -617,6 +617,7 @@ const translation = { license: { expiring: 'Expiring in one day', expiring_plural: 'Expiring in {{count}} days', + unlimited: 'Unlimited', }, pagination: { perPage: 'Items per page', diff --git a/web/i18n/zh-Hans/common.ts b/web/i18n/zh-Hans/common.ts index b4d80736ec..d18f88de3d 100644 --- a/web/i18n/zh-Hans/common.ts +++ b/web/i18n/zh-Hans/common.ts @@ -617,6 +617,7 @@ const translation = { license: { expiring: '许可证还有 1 天到期', expiring_plural: '许可证还有 {{count}} 天到期', + unlimited: '无限制', }, pagination: { perPage: '每页显示',