fix(core): adjust member limit dialog (#9195)

close AF-1945 AF-1989 AF-1981 AF-1998 AF-1950 AF-1951

Adjust the member limit dialog. Now it will determine whether the member limit is reached when clicking Send Invite Email.
Modified the notification after sending the invitation.
This commit is contained in:
JimmFly
2024-12-19 02:23:35 +00:00
parent def4dc854a
commit 70deeaa804
5 changed files with 95 additions and 36 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

After

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 256 KiB

After

Width:  |  Height:  |  Size: 300 KiB

@@ -81,18 +81,15 @@ export const CloudWorkspaceMembersPanel = ({
const plan = useLiveData( const plan = useLiveData(
subscriptionService.subscription.pro$.map(s => s?.plan) subscriptionService.subscription.pro$.map(s => s?.plan)
); );
const isLimited =
workspaceQuota && workspaceQuota.memberLimit
? workspaceQuota.memberCount >= workspaceQuota.memberLimit
: null;
const t = useI18n(); const t = useI18n();
const [open, setOpen] = useState(false); const [openInvite, setOpenInvite] = useState(false);
const [openMemberLimit, setOpenMemberLimit] = useState(false);
const [isMutating, setIsMutating] = useState(false); const [isMutating, setIsMutating] = useState(false);
const openModal = useCallback(() => { const openInviteModal = useCallback(() => {
setOpen(true); setOpenInvite(true);
}, []); }, []);
const onGenerateInviteLink = useCallback( const onGenerateInviteLink = useCallback(
@@ -116,21 +113,51 @@ export const CloudWorkspaceMembersPanel = ({
emails, emails,
}: Parameters<InviteTeamMemberModalProps['onConfirm']>[0]) => { }: Parameters<InviteTeamMemberModalProps['onConfirm']>[0]) => {
setIsMutating(true); setIsMutating(true);
const success = await permissionService.permission.inviteMembers( const uniqueEmails = deduplicateEmails(emails);
emails, if (
!isTeam &&
workspaceQuota &&
uniqueEmails.length >
workspaceQuota.memberLimit - workspaceQuota.memberCount
) {
setOpenMemberLimit(true);
setIsMutating(false);
return;
}
const results = await permissionService.permission.inviteMembers(
uniqueEmails,
true true
); );
if (success) { const unSuccessInvites = results.reduce<string[]>((acc, result) => {
notify.success({ if (!result.sentSuccess) {
title: t['Invitation sent'](), acc.push(result.email);
message: t['Invitation sent hint'](), }
return acc;
}, []);
if (results) {
notify({
title: t['com.affine.payment.member.team.invite.notify.title']({
successCount: (
uniqueEmails.length - unSuccessInvites.length
).toString(),
failedCount: unSuccessInvites.length.toString(),
}),
message: <NotifyMessage unSuccessInvites={unSuccessInvites} />,
}); });
setOpen(false); setOpenInvite(false);
membersService.members.revalidate(); membersService.members.revalidate();
workspaceQuotaService.quota.revalidate();
} }
setIsMutating(false); setIsMutating(false);
}, },
[membersService.members, permissionService.permission, t] [
isTeam,
membersService.members,
permissionService.permission,
t,
workspaceQuota,
workspaceQuotaService.quota,
]
); );
const onImportCSV = useAsyncCallback( const onImportCSV = useAsyncCallback(
@@ -209,29 +236,28 @@ export const CloudWorkspaceMembersPanel = ({
<SettingRow name={title} desc={desc} spreadCol={!!isOwner}> <SettingRow name={title} desc={desc} spreadCol={!!isOwner}>
{isOwner ? ( {isOwner ? (
<> <>
<Button onClick={openModal}>{t['Invite Members']()}</Button> <Button onClick={openInviteModal}>{t['Invite Members']()}</Button>
{isLimited && !isTeam ? ( {!isTeam ? (
<MemberLimitModal <MemberLimitModal
isFreePlan={!plan} isFreePlan={!plan}
open={open} open={openMemberLimit}
plan={workspaceQuota.humanReadable.name ?? ''} plan={workspaceQuota.humanReadable.name ?? ''}
quota={workspaceQuota.humanReadable.memberLimit ?? ''} quota={workspaceQuota.humanReadable.memberLimit ?? ''}
setOpen={setOpen} setOpen={setOpenMemberLimit}
onConfirm={handleUpgradeConfirm} onConfirm={handleUpgradeConfirm}
/> />
) : ( ) : null}
<InviteTeamMemberModal <InviteTeamMemberModal
open={open} open={openInvite}
setOpen={setOpen} setOpen={setOpenInvite}
onConfirm={onInviteBatchConfirm} onConfirm={onInviteBatchConfirm}
isMutating={isMutating} isMutating={isMutating}
copyTextToClipboard={copyTextToClipboard} copyTextToClipboard={copyTextToClipboard}
onGenerateInviteLink={onGenerateInviteLink} onGenerateInviteLink={onGenerateInviteLink}
onRevokeInviteLink={onRevokeInviteLink} onRevokeInviteLink={onRevokeInviteLink}
importCSV={<ImportCSV onImport={onImportCSV} />} importCSV={<ImportCSV onImport={onImportCSV} />}
invitationLink={inviteLink} invitationLink={inviteLink}
/> />
)}
</> </>
) : null} ) : null}
</SettingRow> </SettingRow>
@@ -243,6 +269,27 @@ export const CloudWorkspaceMembersPanel = ({
); );
}; };
const NotifyMessage = ({
unSuccessInvites,
}: {
unSuccessInvites: string[];
}) => {
const t = useI18n();
if (unSuccessInvites.length === 0) {
return t['Invitation sent hint']();
}
return (
<div>
{t['com.affine.payment.member.team.invite.notify.fail-message']()}
{unSuccessInvites.map((email, index) => (
<div key={`${index}:${email}`}>{email}</div>
))}
</div>
);
};
export const MembersPanelFallback = () => { export const MembersPanelFallback = () => {
const t = useI18n(); const t = useI18n();
@@ -298,3 +345,16 @@ const ImportCSV = ({ onImport }: { onImport: (file: File) => void }) => {
</Upload> </Upload>
); );
}; };
function deduplicateEmails(emails: string[]): string[] {
const seenEmails = new Set<string>();
return emails.filter(email => {
const lowerCaseEmail = email.trim().toLowerCase();
if (seenEmails.has(lowerCaseEmail)) {
return false;
} else {
seenEmails.add(lowerCaseEmail);
return true;
}
});
}
@@ -43,7 +43,6 @@ export const storageProgressContainer = style({
}); });
export const storageProgressWrapper = style({ export const storageProgressWrapper = style({
flexGrow: 1, flexGrow: 1,
marginRight: '20px',
}); });
globalStyle(`${storageProgressWrapper} .storage-progress-desc`, { globalStyle(`${storageProgressWrapper} .storage-progress-desc`, {
fontSize: cssVar('fontXs'), fontSize: cssVar('fontXs'),
+3 -3
View File
@@ -898,7 +898,7 @@
"com.affine.payment.cloud.team-workspace.benefit.g1-5": "Multiple admin roles.", "com.affine.payment.cloud.team-workspace.benefit.g1-5": "Multiple admin roles.",
"com.affine.payment.cloud.team-workspace.benefit.g1-6": "Priority customer support.", "com.affine.payment.cloud.team-workspace.benefit.g1-6": "Priority customer support.",
"com.affine.payment.cloud.team-workspace.description": "Best for scalable teams.", "com.affine.payment.cloud.team-workspace.description": "Best for scalable teams.",
"com.affine.payment.cloud.team-workspace.name": "Team Workspace", "com.affine.payment.cloud.team-workspace.name": "Team",
"com.affine.payment.cloud.team-workspace.title.billed-yearly": "annually", "com.affine.payment.cloud.team-workspace.title.billed-yearly": "annually",
"com.affine.payment.cloud.team-workspace.title.price-monthly": "{{price}} per seat/month", "com.affine.payment.cloud.team-workspace.title.price-monthly": "{{price}} per seat/month",
"com.affine.payment.contact-sales": "Contact sales", "com.affine.payment.contact-sales": "Contact sales",
@@ -953,8 +953,8 @@
"com.affine.payment.member.team.invite.generate": "Generate", "com.affine.payment.member.team.invite.generate": "Generate",
"com.affine.payment.member.team.invite.copy": "Copy", "com.affine.payment.member.team.invite.copy": "Copy",
"com.affine.payment.member.team.invite.done": "Done", "com.affine.payment.member.team.invite.done": "Done",
"com.affine.payment.member.team.invite.notify.title": "Invitation sent", "com.affine.payment.member.team.invite.notify.title": "Invitation sent,{{successCount}} successful, {{failedCount}} failed",
"com.affine.payment.member.team.invite.notify.message": "Invited members have been notified with email to join this Workspace.", "com.affine.payment.member.team.invite.notify.fail-message": "These email addresses have already been invited:",
"com.affine.payment.member.team.revoke": "Revoke invitation", "com.affine.payment.member.team.revoke": "Revoke invitation",
"com.affine.payment.member.team.approve": "Approve", "com.affine.payment.member.team.approve": "Approve",
"com.affine.payment.member.team.decline": "Decline", "com.affine.payment.member.team.decline": "Decline",