Skip to content

Commit

Permalink
feat(core): add cloud workspace member limit (#5500)
Browse files Browse the repository at this point in the history
  • Loading branch information
JimmFly committed Jan 3, 2024
1 parent 760d900 commit 7d886e4
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 31 deletions.
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './accept-invite-page';
export * from './invite-modal';
export * from './member-limit-modal';
export * from './pagination';
Original file line number Diff line number Diff line change
@@ -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 (
<ConfirmModal
open={open}
onOpenChange={setOpen}
title={t['com.affine.payment.member-limit.title']()}
description={t[
isFreePlan
? 'com.affine.payment.member-limit.free.description'
: 'com.affine.payment.member-limit.pro.description'
]({ planName: plan, quota: quota })}
cancelButtonOptions={{ style: { display: isFreePlan ? '' : 'none' } }}
confirmButtonOptions={{
type: 'primary',
children:
t[
isFreePlan
? 'com.affine.payment.member-limit.free.confirm'
: 'com.affine.payment.member-limit.pro.confirm'
](),
}}
onConfirm={handleConfirm}
></ConfirmModal>
);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
InviteModal,
type InviteModalProps,
MemberLimitModal,
} from '@affine/component/member-components';
import {
Pagination,
Expand All @@ -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';
Expand All @@ -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';

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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<HTMLDivElement | null>(null);
const [memberListHeight, setMemberListHeight] = useState<number | null>(null);

Expand Down Expand Up @@ -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;

Expand All @@ -157,7 +167,10 @@ export const CloudWorkspaceMembersPanel = ({
{upgradable ? (
<>
,
<div className={style.goUpgradeWrapper} onClick={handleUpgrade}>
<div
className={style.goUpgradeWrapper}
onClick={handleUpgradeConfirm}
>
<span className={style.goUpgrade}>
{t['com.affine.payment.member.description.go-upgrade']()}
</span>
Expand All @@ -167,7 +180,7 @@ export const CloudWorkspaceMembersPanel = ({
) : null}
</span>
);
}, [handleUpgrade, quota, t, upgradable]);
}, [handleUpgradeConfirm, quota, t, upgradable]);

return (
<>
Expand All @@ -179,12 +192,23 @@ export const CloudWorkspaceMembersPanel = ({
{isOwner ? (
<>
<Button onClick={openModal}>{t['Invite Members']()}</Button>
<InviteModal
open={open}
setOpen={setOpen}
onConfirm={onInviteConfirm}
isMutating={isMutating}
/>
{isLimited ? (
<MemberLimitModal
isFreePlan={plan === SubscriptionPlan.Free}
open={open}
plan={quota?.humanReadable.name ?? ''}
quota={quota?.humanReadable.memberLimit ?? ''}
setOpen={setOpen}
onConfirm={handleUpgradeConfirm}
/>
) : (
<InviteModal
open={open}
setOpen={setOpen}
onConfirm={onInviteConfirm}
isMutating={isMutating}
/>
)}
</>
) : null}
</SettingRow>
Expand Down
5 changes: 5 additions & 0 deletions packages/frontend/i18n/src/resources/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -799,6 +799,11 @@
"com.affine.payment.upgrade-success-page.support": "If you have any questions, please contact our <1> customer support</1>.",
"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.",
Expand Down

0 comments on commit 7d886e4

Please sign in to comment.