Skip to content

Commit

Permalink
feat(core): add CRUD function of workspace member permission
Browse files Browse the repository at this point in the history
  • Loading branch information
JimmFly committed Dec 5, 2024
1 parent fd45731 commit 475c2c5
Show file tree
Hide file tree
Showing 16 changed files with 552 additions and 205 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export interface WorkspaceProfileInfo {
avatar?: string;
name?: string;
isOwner?: boolean;
isAdmin?: boolean;
isTeam?: boolean;
}

/**
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
LocalWorkspaceIcon,
NoNetworkIcon,
SettingsIcon,
TeamWorkspaceIcon,
UnsyncIcon,
} from '@blocksuite/icons/rc';
import {
Expand Down Expand Up @@ -241,6 +242,7 @@ export const WorkspaceCard = forwardRef<
avatarSize?: number;
disable?: boolean;
hideCollaborationIcon?: boolean;
hideTeamWorkspaceIcon?: boolean;
active?: boolean;
onClickOpenSettings?: (workspaceMetadata: WorkspaceMetadata) => void;
onClickEnableCloud?: (workspaceMetadata: WorkspaceMetadata) => void;
Expand All @@ -257,6 +259,7 @@ export const WorkspaceCard = forwardRef<
className,
disable,
hideCollaborationIcon,
hideTeamWorkspaceIcon,
active,
...props
},
Expand Down Expand Up @@ -326,12 +329,9 @@ export const WorkspaceCard = forwardRef<
{hideCollaborationIcon || information?.isOwner ? null : (
<CollaborationIcon className={styles.collaborationIcon} />
)}
{/* {hideTeamWorkspaceIcon || !isTeamWorkspace ? null : (
<Tooltip content={'team Workspace owned by xxx'}>
<TeamWorkspaceIcon className={styles.collaborationIcon} />
</Tooltip>
)} */}
{hideTeamWorkspaceIcon || !information?.isTeam ? null : (
<TeamWorkspaceIcon className={styles.collaborationIcon} />
)}
{onClickOpenSettings && (
<div className={styles.settingButton} onClick={onOpenSettings}>
<SettingsIcon width={16} height={16} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import {
MemberLimitModal,
} from '@affine/component/member-components';
import { SettingRow } from '@affine/component/setting-components';
import { useInviteMember } from '@affine/core/components/hooks/affine/use-invite-member';
import { useRevokeMemberPermission } from '@affine/core/components/hooks/affine/use-revoke-member-permission';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { ServerService, SubscriptionService } from '@affine/core/modules/cloud';
import { WorkspacePermissionService } from '@affine/core/modules/permissions';
import { WorkspaceQuotaService } from '@affine/core/modules/quota';
import { copyTextToClipboard } from '@affine/core/utils/clipboard';
import { UserFriendlyError } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import { track } from '@affine/track';
import { useLiveData, useService, WorkspaceService } from '@toeverything/infra';
import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useState } from 'react';

import type { SettingState } from '../../../types';
Expand All @@ -35,10 +32,10 @@ export const CloudWorkspaceMembersPanel = ({
const hasPaymentFeature = useLiveData(
serverService.server.features$.map(f => f?.payment)
);
const workspace = useService(WorkspaceService).workspace;

const permissionService = useService(WorkspacePermissionService);
const isOwner = useLiveData(permissionService.permission.isOwner$);
const isAdmin = useLiveData(permissionService.permission.isAdmin$);
useEffect(() => {
permissionService.permission.revalidate();
}, [permissionService]);
Expand All @@ -60,21 +57,20 @@ export const CloudWorkspaceMembersPanel = ({
: null;

const t = useI18n();
const { invite, isMutating } = useInviteMember(workspace.id);
const revokeMemberPermission = useRevokeMemberPermission(workspace.id);

const [open, setOpen] = useState(false);
const [isMutating, setIsMutating] = useState(false);

const openModal = useCallback(() => {
setOpen(true);
}, []);

const onInviteConfirm = useCallback<InviteModalProps['onConfirm']>(
async ({ email, permission }) => {
const success = await invite(
setIsMutating(true);
const success = await permissionService.permission.inviteMember(
email,
permission,
// send invite email
true
);
if (success) {
Expand All @@ -84,8 +80,9 @@ export const CloudWorkspaceMembersPanel = ({
});
setOpen(false);
}
setIsMutating(false);
},
[invite, t]
[permissionService.permission, t]
);

const handleUpgradeConfirm = useCallback(() => {
Expand All @@ -98,16 +95,6 @@ export const CloudWorkspaceMembersPanel = ({
});
}, [onChangeSettingState]);

const onRevoke = useAsyncCallback(
async (memberId: string) => {
const res = await revokeMemberPermission(memberId);
if (res?.revoke) {
notify.success({ title: t['Removed successfully']() });
}
},
[revokeMemberPermission, t]
);

const desc = useMemo(() => {
if (!workspaceQuota) return null;

Expand Down Expand Up @@ -188,7 +175,7 @@ export const CloudWorkspaceMembersPanel = ({
</SettingRow>

<div className={styles.membersPanel}>
<MemberList isOwner={!!isOwner} onRevoke={onRevoke} />
<MemberList isOwner={!!isOwner} isAdmin={!!isAdmin} />
</div>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ConfirmModal, Input, notify } from '@affine/component';
import { ConfirmModal, Input } from '@affine/component';
import type { Member } from '@affine/core/modules/permissions';
import { useI18n } from '@affine/i18n';
import { cssVar } from '@toeverything/theme';
Expand All @@ -12,12 +12,14 @@ export const ConfirmAssignModal = ({
inputValue,
setInputValue,
isEquals,
onConfirm,
}: {
open: boolean;
setOpen: (value: boolean) => void;
isEquals: boolean;
member: Member;
inputValue: string;
onConfirm: () => void;
setInputValue: (value: string) => void;
}) => {
const t = useI18n();
Expand All @@ -29,14 +31,7 @@ export const ConfirmAssignModal = ({
onOpenChange={setOpen}
title={t['com.affine.payment.member.team.assign.confirm.title']()}
confirmText={t['com.affine.payment.member.team.assign.confirm.button']()}
onConfirm={() => {
notify.success({
title: t['com.affine.payment.member.team.assign.notify.title'](),
message: t['com.affine.payment.member.team.assign.notify.message']({
name: member.name || member.email || member.id,
}),
});
}}
onConfirm={onConfirm}
confirmButtonOptions={{ disabled: !isEquals, variant: 'error' }}
>
<div className={styles.confirmAssignModalContent}>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { Avatar, IconButton, Loading, Menu } from '@affine/component';
import { Avatar, IconButton, Loading, Menu, notify } from '@affine/component';
import { Pagination } from '@affine/component/member-components';
import { type AuthAccountInfo, AuthService } from '@affine/core/modules/cloud';
import {
type Member,
WorkspaceMembersService,
WorkspacePermissionService,
} from '@affine/core/modules/permissions';
import { Permission, UserFriendlyError } from '@affine/graphql';
import { useI18n } from '@affine/i18n';
import {
Permission,
UserFriendlyError,
WorkspaceMemberStatus,
} from '@affine/graphql';
import { type I18nString, useI18n } from '@affine/i18n';
import { MoreVerticalIcon } from '@blocksuite/icons/rc';
import {
useEnsureLiveData,
Expand All @@ -24,10 +29,10 @@ import * as styles from './styles.css';

export const MemberList = ({
isOwner,
onRevoke,
isAdmin,
}: {
isOwner: boolean;
onRevoke: (memberId: string) => void;
isAdmin: boolean;
}) => {
const membersService = useService(WorkspaceMembersService);
const memberCount = useLiveData(membersService.members.memberCount$);
Expand Down Expand Up @@ -80,7 +85,7 @@ export const MemberList = ({
key={member.id}
member={member}
isOwner={isOwner}
onRevoke={onRevoke}
isAdmin={isAdmin}
/>
))
)}
Expand All @@ -100,30 +105,52 @@ export const MemberList = ({
const MemberItem = ({
member,
isOwner,
isAdmin,
currentAccount,
onRevoke,
}: {
member: Member;
isAdmin: boolean;
isOwner: boolean;
currentAccount: AuthAccountInfo;
onRevoke: (memberId: string) => void;
}) => {
const t = useI18n();
const [open, setOpen] = useState(false);
const [inputValue, setInputValue] = useState('');
const workspace = useService(WorkspaceService).workspace;
const workspaceName = useLiveData(workspace.name$);
const permission = useService(WorkspacePermissionService).permission;
const isEquals = workspaceName === inputValue;

const show = isOwner && currentAccount.id !== member.id;

//TODO(@JimmFly): implement team feature
const underReview = member.accepted === false;

const handleOpenAssignModal = useCallback(() => {
setOpen(true);
}, []);

const confirmAssign = useCallback(() => {
permission
.adjustMemberPermission(member.id, Permission.Owner)
.then(result => {
if (result) {
setOpen(false);
notify.success({
title: t['com.affine.payment.member.team.assign.notify.title'](),
message: t['com.affine.payment.member.team.assign.notify.message']({
name: member.name || member.email || member.id,
}),
});
}
})
.catch(error => {
notify.error({
title: 'Operation failed',
message: error.message,
});
});
}, [permission, member, t]);

const memberStatus = useMemo(() => getMemberStatus(member), [member]);

return (
<div
key={member.id}
Expand All @@ -150,22 +177,15 @@ const MemberItem = ({
pending: !member.accepted,
})}
>
{member.accepted
? member.permission === Permission.Owner
? t.t('Workspace Owner')
: member.permission === Permission.Admin
? t.t('Admin')
: t.t('Collaborator')
: underReview
? t.t('Under-Review')
: t.t('Pending')}
{t.t(memberStatus)}
</div>
<Menu
items={
<MemberOptions
member={member}
onRevoke={onRevoke}
openAssignModal={handleOpenAssignModal}
isAdmin={isAdmin}
isOwner={isOwner}
/>
}
>
Expand All @@ -186,11 +206,33 @@ const MemberItem = ({
inputValue={inputValue}
setInputValue={setInputValue}
isEquals={isEquals}
onConfirm={confirmAssign}
/>
</div>
);
};

const getMemberStatus = (member: Member): I18nString => {
if (member.status === WorkspaceMemberStatus.Pending) {
return 'Pending';
} else if (member.status === WorkspaceMemberStatus.UnderReview) {
return 'Under-Review';
} else if (member.status === WorkspaceMemberStatus.Accepted) {
switch (member.permission) {
case Permission.Owner:
return 'Workspace Owner';
case Permission.Admin:
return 'Admin';
case Permission.Write:
return 'Collaborator';
default:
return 'Member';
}
} else {
return 'Need-More-Seats';
}
};

export const MemberListFallback = ({
memberCount,
}: {
Expand Down
Loading

0 comments on commit 475c2c5

Please sign in to comment.