diff --git a/packages/frontend/component/src/components/affine-other-page-layout/assets/dot-bg.dark.png b/packages/frontend/component/src/components/affine-other-page-layout/assets/dot-bg.dark.png index b2245574b5..9f1745e870 100644 Binary files a/packages/frontend/component/src/components/affine-other-page-layout/assets/dot-bg.dark.png and b/packages/frontend/component/src/components/affine-other-page-layout/assets/dot-bg.dark.png differ diff --git a/packages/frontend/component/src/components/affine-other-page-layout/assets/dot-bg.light.png b/packages/frontend/component/src/components/affine-other-page-layout/assets/dot-bg.light.png index 744015987e..8b74bea2d4 100644 Binary files a/packages/frontend/component/src/components/affine-other-page-layout/assets/dot-bg.light.png and b/packages/frontend/component/src/components/affine-other-page-layout/assets/dot-bg.light.png differ diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx index 1291b2993c..9f28db5a48 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/members/cloud-members-panel.tsx @@ -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[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((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: , }); - 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 = ({ {isOwner ? ( <> - - {isLimited && !isTeam ? ( + + {!isTeam ? ( - ) : ( - } - invitationLink={inviteLink} - /> - )} + ) : null} + } + invitationLink={inviteLink} + /> ) : null} @@ -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 ( +
+ {t['com.affine.payment.member.team.invite.notify.fail-message']()} + {unSuccessInvites.map((email, index) => ( +
{email}
+ ))} +
+ ); +}; + export const MembersPanelFallback = () => { const t = useI18n(); @@ -298,3 +345,16 @@ const ImportCSV = ({ onImport }: { onImport: (file: File) => void }) => { ); }; + +function deduplicateEmails(emails: string[]): string[] { + const seenEmails = new Set(); + return emails.filter(email => { + const lowerCaseEmail = email.trim().toLowerCase(); + if (seenEmails.has(lowerCaseEmail)) { + return false; + } else { + seenEmails.add(lowerCaseEmail); + return true; + } + }); +} diff --git a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/style.css.ts b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/style.css.ts index e0231f400f..e18dc8603a 100644 --- a/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/style.css.ts +++ b/packages/frontend/core/src/desktop/dialogs/setting/workspace-setting/new-workspace-setting-detail/style.css.ts @@ -43,7 +43,6 @@ export const storageProgressContainer = style({ }); export const storageProgressWrapper = style({ flexGrow: 1, - marginRight: '20px', }); globalStyle(`${storageProgressWrapper} .storage-progress-desc`, { fontSize: cssVar('fontXs'), diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index d80f585bc4..7231930b62 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -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",