feat(core): guard service (#9816)
This commit is contained in:
@@ -22,9 +22,11 @@ const getRoleName = (role: DocRole, t: ReturnType<typeof useI18n>) => {
|
||||
export const MembersPermission = ({
|
||||
openPaywallModal,
|
||||
hittingPaywall,
|
||||
disabled,
|
||||
}: {
|
||||
hittingPaywall: boolean;
|
||||
openPaywallModal?: () => void;
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const [docRole, setDocRole] = useState<DocRole>(DocRole.Manager);
|
||||
@@ -63,38 +65,40 @@ export const MembersPermission = ({
|
||||
align: 'end',
|
||||
}}
|
||||
items={
|
||||
<>
|
||||
<MenuItem
|
||||
onSelect={selectManage}
|
||||
selected={docRole === DocRole.Manager}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
{t['com.affine.share-menu.option.permission.can-manage']()}
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onSelect={selectEdit}
|
||||
selected={docRole === DocRole.Editor}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
<div className={styles.tagContainerStyle}>
|
||||
{t['com.affine.share-menu.option.permission.can-edit']()}
|
||||
<PlanTag />
|
||||
disabled ? null : (
|
||||
<>
|
||||
<MenuItem
|
||||
onSelect={selectManage}
|
||||
selected={docRole === DocRole.Manager}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
{t['com.affine.share-menu.option.permission.can-manage']()}
|
||||
</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onSelect={selectRead}
|
||||
selected={docRole === DocRole.Reader}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
<div className={styles.tagContainerStyle}>
|
||||
{t['com.affine.share-menu.option.permission.can-read']()}
|
||||
<PlanTag />
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onSelect={selectEdit}
|
||||
selected={docRole === DocRole.Editor}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
<div className={styles.tagContainerStyle}>
|
||||
{t['com.affine.share-menu.option.permission.can-edit']()}
|
||||
<PlanTag />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
</>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onSelect={selectRead}
|
||||
selected={docRole === DocRole.Reader}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
<div className={styles.tagContainerStyle}>
|
||||
{t['com.affine.share-menu.option.permission.can-read']()}
|
||||
<PlanTag />
|
||||
</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuTrigger
|
||||
@@ -103,6 +107,7 @@ export const MembersPermission = ({
|
||||
contentStyle={{
|
||||
width: '100%',
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
{currentRoleName}
|
||||
</MenuTrigger>
|
||||
|
||||
@@ -15,7 +15,7 @@ import { useEffect } from 'react';
|
||||
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export const PublicDoc = () => {
|
||||
export const PublicDoc = ({ disabled }: { disabled?: boolean }) => {
|
||||
const t = useI18n();
|
||||
const shareInfoService = useService(ShareInfoService);
|
||||
const isSharedPage = useLiveData(shareInfoService.shareInfo.isShared$);
|
||||
@@ -98,27 +98,31 @@ export const PublicDoc = () => {
|
||||
align: 'end',
|
||||
}}
|
||||
items={
|
||||
<>
|
||||
<MenuItem
|
||||
prefixIcon={<LockIcon />}
|
||||
onSelect={onDisablePublic}
|
||||
selected={!isSharedPage}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
<div>{t['com.affine.share-menu.option.link.no-access']()}</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
prefixIcon={<ViewIcon />}
|
||||
onSelect={onClickAnyoneReadOnlyShare}
|
||||
data-testid="share-link-menu-enable-share"
|
||||
selected={!!isSharedPage}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
<div>{t['com.affine.share-menu.option.link.readonly']()}</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
</>
|
||||
disabled ? null : (
|
||||
<>
|
||||
<MenuItem
|
||||
prefixIcon={<LockIcon />}
|
||||
onSelect={onDisablePublic}
|
||||
selected={!isSharedPage}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
<div>
|
||||
{t['com.affine.share-menu.option.link.no-access']()}
|
||||
</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
prefixIcon={<ViewIcon />}
|
||||
onSelect={onClickAnyoneReadOnlyShare}
|
||||
data-testid="share-link-menu-enable-share"
|
||||
selected={!!isSharedPage}
|
||||
>
|
||||
<div className={styles.publicItemRowStyle}>
|
||||
<div>{t['com.affine.share-menu.option.link.readonly']()}</div>
|
||||
</div>
|
||||
</MenuItem>
|
||||
</>
|
||||
)
|
||||
}
|
||||
>
|
||||
<MenuTrigger
|
||||
@@ -128,6 +132,7 @@ export const PublicDoc = () => {
|
||||
contentStyle={{
|
||||
width: '100%',
|
||||
}}
|
||||
disabled={disabled}
|
||||
>
|
||||
{isSharedPage
|
||||
? t['com.affine.share-menu.option.link.readonly']()
|
||||
|
||||
@@ -8,13 +8,15 @@ import {
|
||||
Tooltip,
|
||||
} from '@affine/component';
|
||||
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
DocGrantedUsersService,
|
||||
type GrantedUser,
|
||||
GuardService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { DocRole, UserFriendlyError } from '@affine/graphql';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useService } from '@toeverything/infra';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { PlanTag } from '../plan-tag';
|
||||
@@ -77,7 +79,6 @@ export const MemberItem = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TODO(@eyhn): add guard here */}
|
||||
<Menu
|
||||
items={
|
||||
<Options
|
||||
@@ -118,6 +119,15 @@ const Options = ({
|
||||
}) => {
|
||||
const t = useI18n();
|
||||
const docGrantedUsersService = useService(DocGrantedUsersService);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const canTransferOwner = useLiveData(
|
||||
guardService.can$('Doc_TransferOwner', docService.doc.id)
|
||||
);
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
|
||||
const changeToManager = useAsyncCallback(async () => {
|
||||
try {
|
||||
@@ -188,20 +198,17 @@ const Options = ({
|
||||
label: t['com.affine.share-menu.option.permission.can-manage'](),
|
||||
onClick: changeToManager,
|
||||
role: DocRole.Manager,
|
||||
show: true, // TODO(@eyhn): add guard here
|
||||
},
|
||||
{
|
||||
label: t['com.affine.share-menu.option.permission.can-edit'](),
|
||||
onClick: changeToEditor,
|
||||
role: DocRole.Editor,
|
||||
show: true, // TODO(@eyhn): add guard here
|
||||
showPlanTag: true,
|
||||
},
|
||||
{
|
||||
label: t['com.affine.share-menu.option.permission.can-read'](),
|
||||
onClick: changeToReader,
|
||||
role: DocRole.Reader,
|
||||
show: true, // TODO(@eyhn): add guard here
|
||||
showPlanTag: true,
|
||||
},
|
||||
];
|
||||
@@ -209,26 +216,28 @@ const Options = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
{operationButtonInfo.map(item =>
|
||||
item.show ? (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
onSelect={item.onClick}
|
||||
selected={memberRole === item.role}
|
||||
>
|
||||
<div className={styles.planTagContainer}>
|
||||
{item.label} {item.showPlanTag ? <PlanTag /> : null}
|
||||
</div>
|
||||
</MenuItem>
|
||||
) : null
|
||||
)}
|
||||
{/* TODO(@eyhn): add guard here */}
|
||||
<MenuItem onSelect={changeToOwner}>
|
||||
{operationButtonInfo.map(item => (
|
||||
<MenuItem
|
||||
key={item.label}
|
||||
onSelect={item.onClick}
|
||||
selected={memberRole === item.role}
|
||||
disabled={!canManageUsers}
|
||||
>
|
||||
<div className={styles.planTagContainer}>
|
||||
{item.label} {item.showPlanTag ? <PlanTag /> : null}
|
||||
</div>
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem onSelect={changeToOwner} disabled={!canTransferOwner}>
|
||||
{t['com.affine.share-menu.member-management.set-as-owner']()}
|
||||
</MenuItem>
|
||||
{/* TODO(@eyhn): add guard here */}
|
||||
<MenuSeparator />
|
||||
<MenuItem onSelect={removeMember} type="danger" className={styles.remove}>
|
||||
<MenuItem
|
||||
onSelect={removeMember}
|
||||
type="danger"
|
||||
className={styles.remove}
|
||||
disabled={!canManageUsers}
|
||||
>
|
||||
{t['com.affine.share-menu.member-management.remove']()}
|
||||
</MenuItem>
|
||||
</>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Skeleton } from '@affine/component';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import {
|
||||
DocGrantedUsersService,
|
||||
type GrantedUser,
|
||||
GuardService,
|
||||
} from '@affine/core/modules/permissions';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { ArrowLeftBigIcon } from '@blocksuite/icons/rc';
|
||||
@@ -30,6 +32,12 @@ export const MemberManagement = ({
|
||||
const grantedUserCount = useLiveData(
|
||||
docGrantedUsersService.grantedUserCount$
|
||||
);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
@@ -62,11 +70,15 @@ export const MemberManagement = ({
|
||||
) : (
|
||||
<Skeleton className={styles.scrollableRootStyle} />
|
||||
)}
|
||||
{/* TODO(@eyhn): add guard here */}
|
||||
<div className={styles.footerStyle}>
|
||||
<span className={styles.addCollaboratorsStyle} onClick={onClickInvite}>
|
||||
{t['com.affine.share-menu.member-management.add-collaborators']()}
|
||||
</span>
|
||||
{canManageUsers ? (
|
||||
<span
|
||||
className={styles.addCollaboratorsStyle}
|
||||
onClick={onClickInvite}
|
||||
>
|
||||
{t['com.affine.share-menu.member-management.add-collaborators']()}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Skeleton } from '@affine/component';
|
||||
import { Button } from '@affine/component/ui/button';
|
||||
import { ServerService } from '@affine/core/modules/cloud';
|
||||
import { DocService } from '@affine/core/modules/doc';
|
||||
import { GuardService } from '@affine/core/modules/permissions';
|
||||
import { ShareInfoService } from '@affine/core/modules/share-doc';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import { useLiveData, useService } from '@toeverything/infra';
|
||||
@@ -61,6 +63,16 @@ export const AFFiNESharePage = (
|
||||
} = props;
|
||||
const shareInfoService = useService(ShareInfoService);
|
||||
const serverService = useService(ServerService);
|
||||
const docService = useService(DocService);
|
||||
const guardService = useService(GuardService);
|
||||
|
||||
const canManageUsers = useLiveData(
|
||||
guardService.can$('Doc_Users_Manage', docService.doc.id)
|
||||
);
|
||||
|
||||
const canPublish = useLiveData(
|
||||
guardService.can$('Doc_Publish', docService.doc.id)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
shareInfoService.shareInfo.revalidate();
|
||||
@@ -85,7 +97,7 @@ export const AFFiNESharePage = (
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
<div className={styles.columnContainerStyle}>
|
||||
<InviteInput onFocus={props.onClickInvite} />
|
||||
{canManageUsers && <InviteInput onFocus={props.onClickInvite} />}
|
||||
<MembersRow onClick={props.onClickMembers} />
|
||||
<div className={styles.generalAccessStyle}>
|
||||
{t['com.affine.share-menu.generalAccess']()}
|
||||
@@ -93,8 +105,9 @@ export const AFFiNESharePage = (
|
||||
<MembersPermission
|
||||
openPaywallModal={props.openPaywallModal}
|
||||
hittingPaywall={!!props.hittingPaywall}
|
||||
disabled={!canManageUsers}
|
||||
/>
|
||||
<PublicDoc />
|
||||
<PublicDoc disabled={!canPublish} />
|
||||
</div>
|
||||
<CopyLinkButton workspaceId={workspaceId} />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user