mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-15 05:37:32 +00:00
fix(server): wrong member count query (#4506)
This commit is contained in:
@@ -21,7 +21,14 @@ import { Tooltip } from '@toeverything/components/tooltip';
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useSetAtom } from 'jotai';
|
import { useSetAtom } from 'jotai';
|
||||||
import type { ReactElement } from 'react';
|
import type { ReactElement } from 'react';
|
||||||
import { Suspense, useCallback, useMemo, useState } from 'react';
|
import {
|
||||||
|
Suspense,
|
||||||
|
useCallback,
|
||||||
|
useLayoutEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
import { ErrorBoundary } from 'react-error-boundary';
|
import { ErrorBoundary } from 'react-error-boundary';
|
||||||
|
|
||||||
import type { CheckedUser } from '../../../hooks/affine/use-current-user';
|
import type { CheckedUser } from '../../../hooks/affine/use-current-user';
|
||||||
@@ -96,6 +103,20 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
[invite, pushNotification, t]
|
[invite, pushNotification, t]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const listContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [memberListHeight, setMemberListHeight] = useState<number | null>(null);
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (
|
||||||
|
memberCount > COUNT_PER_PAGE &&
|
||||||
|
listContainerRef.current &&
|
||||||
|
memberListHeight === null
|
||||||
|
) {
|
||||||
|
const rect = listContainerRef.current.getBoundingClientRect();
|
||||||
|
setMemberListHeight(rect.height);
|
||||||
|
}
|
||||||
|
}, [listContainerRef, memberCount, memberListHeight]);
|
||||||
|
|
||||||
const onRevoke = useCallback<OnRevoke>(
|
const onRevoke = useCallback<OnRevoke>(
|
||||||
async memberId => {
|
async memberId => {
|
||||||
const res = await revokeMemberPermission(memberId);
|
const res = await revokeMemberPermission(memberId);
|
||||||
@@ -129,7 +150,11 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
) : null}
|
) : null}
|
||||||
</SettingRow>
|
</SettingRow>
|
||||||
|
|
||||||
<div className={style.membersPanel}>
|
<div
|
||||||
|
className={style.membersPanel}
|
||||||
|
ref={listContainerRef}
|
||||||
|
style={memberListHeight ? { height: memberListHeight } : {}}
|
||||||
|
>
|
||||||
<Suspense fallback={<MemberListFallback memberCount={memberCount} />}>
|
<Suspense fallback={<MemberListFallback memberCount={memberCount} />}>
|
||||||
<MemberList
|
<MemberList
|
||||||
workspaceId={workspaceId}
|
workspaceId={workspaceId}
|
||||||
@@ -139,11 +164,13 @@ export const CloudWorkspaceMembersPanel = ({
|
|||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
|
||||||
<Pagination
|
{memberCount > COUNT_PER_PAGE && (
|
||||||
totalCount={memberCount}
|
<Pagination
|
||||||
countPerPage={COUNT_PER_PAGE}
|
totalCount={memberCount}
|
||||||
onPageChange={onPageChange}
|
countPerPage={COUNT_PER_PAGE}
|
||||||
/>
|
onPageChange={onPageChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -186,7 +213,7 @@ const MemberList = ({
|
|||||||
const currentUser = useCurrentUser();
|
const currentUser = useCurrentUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className={style.memberList}>
|
||||||
{members.map(member => (
|
{members.map(member => (
|
||||||
<MemberItem
|
<MemberItem
|
||||||
key={member.id}
|
key={member.id}
|
||||||
@@ -196,7 +223,7 @@ const MemberList = ({
|
|||||||
onRevoke={onRevoke}
|
onRevoke={onRevoke}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -225,54 +252,56 @@ const MemberItem = ({
|
|||||||
}, [currentUser.id, isOwner, member.id, t]);
|
}, [currentUser.id, isOwner, member.id, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
<div key={member.id} className={style.listItem} data-testid="member-item">
|
key={member.id}
|
||||||
<Avatar
|
className={style.memberListItem}
|
||||||
size={36}
|
data-testid="member-item"
|
||||||
url={member.avatarUrl}
|
>
|
||||||
name={(member.emailVerified ? member.name : member.email) as string}
|
<Avatar
|
||||||
/>
|
size={36}
|
||||||
<div className={style.memberContainer}>
|
url={member.avatarUrl}
|
||||||
{member.emailVerified ? (
|
name={(member.emailVerified ? member.name : member.email) as string}
|
||||||
<>
|
/>
|
||||||
<div className={style.memberName}>{member.name}</div>
|
<div className={style.memberContainer}>
|
||||||
<div className={style.memberEmail}>{member.email}</div>
|
{member.emailVerified ? (
|
||||||
</>
|
<>
|
||||||
) : (
|
<div className={style.memberName}>{member.name}</div>
|
||||||
<div className={style.memberName}>{member.email}</div>
|
<div className={style.memberEmail}>{member.email}</div>
|
||||||
)}
|
</>
|
||||||
</div>
|
) : (
|
||||||
<div
|
<div className={style.memberName}>{member.email}</div>
|
||||||
className={clsx(style.roleOrStatus, {
|
)}
|
||||||
pending: !member.accepted,
|
|
||||||
})}
|
|
||||||
>
|
|
||||||
{member.accepted
|
|
||||||
? member.permission === Permission.Owner
|
|
||||||
? 'Workspace Owner'
|
|
||||||
: 'Member'
|
|
||||||
: 'Pending'}
|
|
||||||
</div>
|
|
||||||
<Menu
|
|
||||||
items={
|
|
||||||
<MenuItem data-member-id={member.id} onClick={handleRevoke}>
|
|
||||||
{operationButtonInfo.leaveOrRevokeText}
|
|
||||||
</MenuItem>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<IconButton
|
|
||||||
disabled={!operationButtonInfo.show}
|
|
||||||
type="plain"
|
|
||||||
style={{
|
|
||||||
visibility: operationButtonInfo.show ? 'visible' : 'hidden',
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<MoreVerticalIcon />
|
|
||||||
</IconButton>
|
|
||||||
</Menu>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
<div
|
||||||
|
className={clsx(style.roleOrStatus, {
|
||||||
|
pending: !member.accepted,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{member.accepted
|
||||||
|
? member.permission === Permission.Owner
|
||||||
|
? 'Workspace Owner'
|
||||||
|
: 'Member'
|
||||||
|
: 'Pending'}
|
||||||
|
</div>
|
||||||
|
<Menu
|
||||||
|
items={
|
||||||
|
<MenuItem data-member-id={member.id} onClick={handleRevoke}>
|
||||||
|
{operationButtonInfo.leaveOrRevokeText}
|
||||||
|
</MenuItem>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<IconButton
|
||||||
|
disabled={!operationButtonInfo.show}
|
||||||
|
type="plain"
|
||||||
|
style={{
|
||||||
|
visibility: operationButtonInfo.show ? 'visible' : 'hidden',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MoreVerticalIcon />
|
||||||
|
</IconButton>
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -93,14 +93,17 @@ export const membersFallback = style({
|
|||||||
color: 'var(--affine-primary-color)',
|
color: 'var(--affine-primary-color)',
|
||||||
});
|
});
|
||||||
export const membersPanel = style({
|
export const membersPanel = style({
|
||||||
marginTop: '24px',
|
|
||||||
padding: '4px',
|
padding: '4px',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
background: 'var(--affine-background-primary-color)',
|
background: 'var(--affine-background-primary-color)',
|
||||||
border: '1px solid var(--affine-border-color)',
|
border: '1px solid var(--affine-border-color)',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
justifyContent: 'space-between',
|
||||||
});
|
});
|
||||||
|
|
||||||
export const listItem = style({
|
export const memberList = style({});
|
||||||
|
export const memberListItem = style({
|
||||||
padding: '0 4px 0 16px',
|
padding: '0 4px 0 16px',
|
||||||
height: '58px',
|
height: '58px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@@ -155,7 +158,7 @@ export const memberEmail = style({
|
|||||||
});
|
});
|
||||||
export const iconButton = style({});
|
export const iconButton = style({});
|
||||||
|
|
||||||
globalStyle(`${listItem}:hover ${iconButton}`, {
|
globalStyle(`${memberListItem}:hover ${iconButton}`, {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
pointerEvents: 'all',
|
pointerEvents: 'all',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -174,6 +174,9 @@ export class WorkspaceResolver {
|
|||||||
return this.prisma.userWorkspacePermission.count({
|
return this.prisma.userWorkspacePermission.count({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
|
userId: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -214,6 +217,9 @@ export class WorkspaceResolver {
|
|||||||
const data = await this.prisma.userWorkspacePermission.findMany({
|
const data = await this.prisma.userWorkspacePermission.findMany({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
|
userId: {
|
||||||
|
not: null,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
skip,
|
skip,
|
||||||
take: take || 8,
|
take: take || 8,
|
||||||
|
|||||||
@@ -173,6 +173,15 @@ type Query {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
signUp(name: String!, email: String!, password: String!): UserType!
|
||||||
|
signIn(email: String!, password: String!): UserType!
|
||||||
|
changePassword(token: String!, newPassword: String!): UserType!
|
||||||
|
changeEmail(token: String!): UserType!
|
||||||
|
sendChangePasswordEmail(email: String!, callbackUrl: String!): Boolean!
|
||||||
|
sendSetPasswordEmail(email: String!, callbackUrl: String!): Boolean!
|
||||||
|
sendChangeEmail(email: String!, callbackUrl: String!): Boolean!
|
||||||
|
sendVerifyChangeEmail(token: String!, email: String!, callbackUrl: String!): Boolean!
|
||||||
|
|
||||||
"""Create a new workspace"""
|
"""Create a new workspace"""
|
||||||
createWorkspace(init: Upload!): WorkspaceType!
|
createWorkspace(init: Upload!): WorkspaceType!
|
||||||
|
|
||||||
@@ -196,14 +205,6 @@ type Mutation {
|
|||||||
removeAvatar: RemoveAvatar!
|
removeAvatar: RemoveAvatar!
|
||||||
deleteAccount: DeleteAccount!
|
deleteAccount: DeleteAccount!
|
||||||
addToNewFeaturesWaitingList(type: NewFeaturesKind!, email: String!): AddToNewFeaturesWaitingList!
|
addToNewFeaturesWaitingList(type: NewFeaturesKind!, email: String!): AddToNewFeaturesWaitingList!
|
||||||
signUp(name: String!, email: String!, password: String!): UserType!
|
|
||||||
signIn(email: String!, password: String!): UserType!
|
|
||||||
changePassword(token: String!, newPassword: String!): UserType!
|
|
||||||
changeEmail(token: String!): UserType!
|
|
||||||
sendChangePasswordEmail(email: String!, callbackUrl: String!): Boolean!
|
|
||||||
sendSetPasswordEmail(email: String!, callbackUrl: String!): Boolean!
|
|
||||||
sendChangeEmail(email: String!, callbackUrl: String!): Boolean!
|
|
||||||
sendVerifyChangeEmail(token: String!, email: String!, callbackUrl: String!): Boolean!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"""The `Upload` scalar type represents a file upload."""
|
"""The `Upload` scalar type represents a file upload."""
|
||||||
|
|||||||
Reference in New Issue
Block a user