From 7d886e44a6fbc2c5baed61834acf201dae91b65b Mon Sep 17 00:00:00 2001 From: JimmFly <447268514@qq.com> Date: Wed, 3 Jan 2024 14:57:27 +0000 Subject: [PATCH] feat(core): add cloud workspace member limit (#5500) image image --- .../components/member-components/index.tsx | 1 + .../member-components/member-limit-modal.tsx | 53 ++++++++++++ .../new-workspace-setting-detail/members.tsx | 86 ++++++++++++------- packages/frontend/i18n/src/resources/en.json | 5 ++ 4 files changed, 114 insertions(+), 31 deletions(-) create mode 100644 packages/frontend/component/src/components/member-components/member-limit-modal.tsx diff --git a/packages/frontend/component/src/components/member-components/index.tsx b/packages/frontend/component/src/components/member-components/index.tsx index 5612a4b5dc..eea1cd77c5 100644 --- a/packages/frontend/component/src/components/member-components/index.tsx +++ b/packages/frontend/component/src/components/member-components/index.tsx @@ -1,3 +1,4 @@ export * from './accept-invite-page'; export * from './invite-modal'; +export * from './member-limit-modal'; export * from './pagination'; diff --git a/packages/frontend/component/src/components/member-components/member-limit-modal.tsx b/packages/frontend/component/src/components/member-components/member-limit-modal.tsx new file mode 100644 index 0000000000..66762639f5 --- /dev/null +++ b/packages/frontend/component/src/components/member-components/member-limit-modal.tsx @@ -0,0 +1,53 @@ +import { ConfirmModal } from '@affine/component/ui/modal'; +import { useAFFiNEI18N } from '@affine/i18n/hooks'; +import { useCallback } from 'react'; + +export interface MemberLimitModalProps { + isFreePlan: boolean; + open: boolean; + plan: string; + quota: string; + setOpen: (value: boolean) => void; + onConfirm: () => void; +} + +export const MemberLimitModal = ({ + isFreePlan, + open, + plan, + quota, + setOpen, + onConfirm, +}: MemberLimitModalProps) => { + const t = useAFFiNEI18N(); + const handleConfirm = useCallback(() => { + setOpen(false); + if (isFreePlan) { + onConfirm(); + } + }, [onConfirm, setOpen, isFreePlan]); + + return ( + + ); +}; diff --git a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx index 43150ae02a..afef7cb2e0 100644 --- a/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx +++ b/packages/frontend/core/src/components/affine/setting-modal/workspace-setting/new-workspace-setting-detail/members.tsx @@ -1,6 +1,7 @@ import { InviteModal, type InviteModalProps, + MemberLimitModal, } from '@affine/component/member-components'; import { Pagination, @@ -13,8 +14,18 @@ import { Button, IconButton } from '@affine/component/ui/button'; import { Loading } from '@affine/component/ui/loading'; import { Menu, MenuItem } from '@affine/component/ui/menu'; import { Tooltip } from '@affine/component/ui/tooltip'; +import { openSettingModalAtom } from '@affine/core/atoms'; +import { AffineErrorBoundary } from '@affine/core/components/affine/affine-error-boundary'; +import type { CheckedUser } from '@affine/core/hooks/affine/use-current-user'; +import { useCurrentUser } from '@affine/core/hooks/affine/use-current-user'; +import { useInviteMember } from '@affine/core/hooks/affine/use-invite-member'; +import { useMemberCount } from '@affine/core/hooks/affine/use-member-count'; +import { type Member, useMembers } from '@affine/core/hooks/affine/use-members'; +import { useRevokeMemberPermission } from '@affine/core/hooks/affine/use-revoke-member-permission'; +import { useUserQuota } from '@affine/core/hooks/use-quota'; +import { useUserSubscription } from '@affine/core/hooks/use-subscription'; import { WorkspaceFlavour } from '@affine/env/workspace'; -import { Permission } from '@affine/graphql'; +import { Permission, SubscriptionPlan } from '@affine/graphql'; import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { ArrowRightBigIcon, MoreVerticalIcon } from '@blocksuite/icons'; import clsx from 'clsx'; @@ -29,18 +40,6 @@ import { useState, } from 'react'; -import { openSettingModalAtom } from '../../../../../atoms'; -import type { CheckedUser } from '../../../../../hooks/affine/use-current-user'; -import { useCurrentUser } from '../../../../../hooks/affine/use-current-user'; -import { useInviteMember } from '../../../../../hooks/affine/use-invite-member'; -import { useMemberCount } from '../../../../../hooks/affine/use-member-count'; -import { - type Member, - useMembers, -} from '../../../../../hooks/affine/use-members'; -import { useRevokeMemberPermission } from '../../../../../hooks/affine/use-revoke-member-permission'; -import { useUserQuota } from '../../../../../hooks/use-quota'; -import { AffineErrorBoundary } from '../../../affine-error-boundary'; import * as style from './style.css'; import type { WorkspaceSettingDetailProps } from './types'; @@ -70,6 +69,19 @@ export const CloudWorkspaceMembersPanel = ({ const workspaceId = workspaceMetadata.id; const memberCount = useMemberCount(workspaceId); + const checkMemberCountLimit = useCallback( + (memberCount: number, memberLimit?: number) => { + if (memberLimit === undefined) return false; + return memberCount >= memberLimit; + }, + [] + ); + + const quota = useUserQuota(); + const [subscription] = useUserSubscription(); + const plan = subscription?.plan ?? SubscriptionPlan.Free; + const isLimited = checkMemberCountLimit(memberCount, quota?.memberLimit); + const t = useAFFiNEI18N(); const { invite, isMutating } = useInviteMember(workspaceId); const revokeMemberPermission = useRevokeMemberPermission(workspaceId); @@ -107,6 +119,14 @@ export const CloudWorkspaceMembersPanel = ({ [invite, pushNotification, t] ); + const setSettingModalAtom = useSetAtom(openSettingModalAtom); + const handleUpgradeConfirm = useCallback(() => { + setSettingModalAtom({ + open: true, + activeTab: 'plans', + }); + }, [setSettingModalAtom]); + const listContainerRef = useRef(null); const [memberListHeight, setMemberListHeight] = useState(null); @@ -134,16 +154,6 @@ export const CloudWorkspaceMembersPanel = ({ [pushNotification, revokeMemberPermission, t] ); - const setSettingModalAtom = useSetAtom(openSettingModalAtom); - const handleUpgrade = useCallback(() => { - setSettingModalAtom({ - open: true, - activeTab: 'plans', - }); - }, [setSettingModalAtom]); - - const quota = useUserQuota(); - const desc = useMemo(() => { if (!quota) return null; @@ -157,7 +167,10 @@ export const CloudWorkspaceMembersPanel = ({ {upgradable ? ( <> , -
+
{t['com.affine.payment.member.description.go-upgrade']()} @@ -167,7 +180,7 @@ export const CloudWorkspaceMembersPanel = ({ ) : null} ); - }, [handleUpgrade, quota, t, upgradable]); + }, [handleUpgradeConfirm, quota, t, upgradable]); return ( <> @@ -179,12 +192,23 @@ export const CloudWorkspaceMembersPanel = ({ {isOwner ? ( <> - + {isLimited ? ( + + ) : ( + + )} ) : null} diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index b4baf26319..38da25994c 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -799,6 +799,11 @@ "com.affine.payment.upgrade-success-page.support": "If you have any questions, please contact our <1> customer support.", "com.affine.payment.upgrade-success-page.text": "Congratulations! Your AFFiNE account has been successfully upgraded to a Pro account.", "com.affine.payment.upgrade-success-page.title": "Upgrade Successful!", + "com.affine.payment.member-limit.title": "You have reached the limit", + "com.affine.payment.member-limit.free.description": "Each {{planName}} user can invite up to {{quota}} members to join their workspace. You can upgrade your account to unlock more members.", + "com.affine.payment.member-limit.pro.description": "Each {{planName}} user can invite up to {{quota}} members to join their workspace. If you want to continue adding collaboration members, you can create a new workspace.", + "com.affine.payment.member-limit.free.confirm": "Upgrade", + "com.affine.payment.member-limit.pro.confirm": "Got it", "com.affine.publicLinkDisableModal.button.cancel": "Cancel", "com.affine.publicLinkDisableModal.button.disable": "Disable", "com.affine.publicLinkDisableModal.description": "Disabling this public link will prevent anyone with the link from accessing this page.",