fix(server): wrong member count query (#4506)

This commit is contained in:
liuyi
2023-09-26 10:36:08 -05:00
committed by GitHub
parent a633fb6dea
commit 4a03fa65d1
4 changed files with 106 additions and 67 deletions

View File

@@ -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>
); );
}; };

View File

@@ -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',
}); });

View File

@@ -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,

View File

@@ -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."""