mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-12 04:18:54 +00:00
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:
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(
|
||||
subscriptionService.subscription.pro$.map(s => s?.plan)
|
||||
);
|
||||
const isLimited =
|
||||
workspaceQuota && workspaceQuota.memberLimit
|
||||
? workspaceQuota.memberCount >= workspaceQuota.memberLimit
|
||||
: null;
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
const [openInvite, setOpenInvite] = useState(false);
|
||||
const [openMemberLimit, setOpenMemberLimit] = useState(false);
|
||||
const [isMutating, setIsMutating] = useState(false);
|
||||
|
||||
const openModal = useCallback(() => {
|
||||
setOpen(true);
|
||||
const openInviteModal = useCallback(() => {
|
||||
setOpenInvite(true);
|
||||
}, []);
|
||||
|
||||
const onGenerateInviteLink = useCallback(
|
||||
@@ -116,21 +113,51 @@ export const CloudWorkspaceMembersPanel = ({
|
||||
emails,
|
||||
}: Parameters<InviteTeamMemberModalProps['onConfirm']>[0]) => {
|
||||
setIsMutating(true);
|
||||
const success = await permissionService.permission.inviteMembers(
|
||||
emails,
|
||||
const uniqueEmails = deduplicateEmails(emails);
|
||||
if (
|
||||
!isTeam &&
|
||||
workspaceQuota &&
|
||||
uniqueEmails.length >
|
||||
workspaceQuota.memberLimit - workspaceQuota.memberCount
|
||||
) {
|
||||
setOpenMemberLimit(true);
|
||||
setIsMutating(false);
|
||||
return;
|
||||
}
|
||||
const results = await permissionService.permission.inviteMembers(
|
||||
uniqueEmails,
|
||||
true
|
||||
);
|
||||
if (success) {
|
||||
notify.success({
|
||||
title: t['Invitation sent'](),
|
||||
message: t['Invitation sent hint'](),
|
||||
const unSuccessInvites = results.reduce<string[]>((acc, result) => {
|
||||
if (!result.sentSuccess) {
|
||||
acc.push(result.email);
|
||||
}
|
||||
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();
|
||||
workspaceQuotaService.quota.revalidate();
|
||||
}
|
||||
setIsMutating(false);
|
||||
},
|
||||
[membersService.members, permissionService.permission, t]
|
||||
[
|
||||
isTeam,
|
||||
membersService.members,
|
||||
permissionService.permission,
|
||||
t,
|
||||
workspaceQuota,
|
||||
workspaceQuotaService.quota,
|
||||
]
|
||||
);
|
||||
|
||||
const onImportCSV = useAsyncCallback(
|
||||
@@ -209,29 +236,28 @@ export const CloudWorkspaceMembersPanel = ({
|
||||
<SettingRow name={title} desc={desc} spreadCol={!!isOwner}>
|
||||
{isOwner ? (
|
||||
<>
|
||||
<Button onClick={openModal}>{t['Invite Members']()}</Button>
|
||||
{isLimited && !isTeam ? (
|
||||
<Button onClick={openInviteModal}>{t['Invite Members']()}</Button>
|
||||
{!isTeam ? (
|
||||
<MemberLimitModal
|
||||
isFreePlan={!plan}
|
||||
open={open}
|
||||
open={openMemberLimit}
|
||||
plan={workspaceQuota.humanReadable.name ?? ''}
|
||||
quota={workspaceQuota.humanReadable.memberLimit ?? ''}
|
||||
setOpen={setOpen}
|
||||
setOpen={setOpenMemberLimit}
|
||||
onConfirm={handleUpgradeConfirm}
|
||||
/>
|
||||
) : (
|
||||
<InviteTeamMemberModal
|
||||
open={open}
|
||||
setOpen={setOpen}
|
||||
onConfirm={onInviteBatchConfirm}
|
||||
isMutating={isMutating}
|
||||
copyTextToClipboard={copyTextToClipboard}
|
||||
onGenerateInviteLink={onGenerateInviteLink}
|
||||
onRevokeInviteLink={onRevokeInviteLink}
|
||||
importCSV={<ImportCSV onImport={onImportCSV} />}
|
||||
invitationLink={inviteLink}
|
||||
/>
|
||||
)}
|
||||
) : null}
|
||||
<InviteTeamMemberModal
|
||||
open={openInvite}
|
||||
setOpen={setOpenInvite}
|
||||
onConfirm={onInviteBatchConfirm}
|
||||
isMutating={isMutating}
|
||||
copyTextToClipboard={copyTextToClipboard}
|
||||
onGenerateInviteLink={onGenerateInviteLink}
|
||||
onRevokeInviteLink={onRevokeInviteLink}
|
||||
importCSV={<ImportCSV onImport={onImportCSV} />}
|
||||
invitationLink={inviteLink}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</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 = () => {
|
||||
const t = useI18n();
|
||||
|
||||
@@ -298,3 +345,16 @@ const ImportCSV = ({ onImport }: { onImport: (file: File) => void }) => {
|
||||
</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({
|
||||
flexGrow: 1,
|
||||
marginRight: '20px',
|
||||
});
|
||||
globalStyle(`${storageProgressWrapper} .storage-progress-desc`, {
|
||||
fontSize: cssVar('fontXs'),
|
||||
|
||||
@@ -898,7 +898,7 @@
|
||||
"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.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.price-monthly": "{{price}} per seat/month",
|
||||
"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.copy": "Copy",
|
||||
"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.message": "Invited members have been notified with email to join this Workspace.",
|
||||
"com.affine.payment.member.team.invite.notify.title": "Invitation sent,{{successCount}} successful, {{failedCount}} failed",
|
||||
"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.approve": "Approve",
|
||||
"com.affine.payment.member.team.decline": "Decline",
|
||||
|
||||
Reference in New Issue
Block a user