diff --git a/packages/features/ee/organizations/pages/components/MemberListItem.tsx b/packages/features/ee/organizations/pages/components/MemberListItem.tsx
index 5a4437ec79..33586140d8 100644
--- a/packages/features/ee/organizations/pages/components/MemberListItem.tsx
+++ b/packages/features/ee/organizations/pages/components/MemberListItem.tsx
@@ -18,7 +18,7 @@ import {
import { ExternalLink, MoreHorizontal } from "@calcom/ui/components/icon";
interface Props {
- member: RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"][number];
+ member: RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"]["rows"][number];
}
export default function MemberListItem(props: Props) {
diff --git a/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx b/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx
index 1e37ceaba3..a9506fc6aa 100644
--- a/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx
+++ b/packages/features/ee/organizations/pages/settings/other-team-members-view.tsx
@@ -1,7 +1,7 @@
// import { debounce } from "lodash";
import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
-import { useState, useEffect } from "react";
+import { useState } from "react";
import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@@ -16,20 +16,21 @@ import { getLayout } from "../../../../settings/layouts/SettingsLayout";
import MakeTeamPrivateSwitch from "../../../teams/components/MakeTeamPrivateSwitch";
import MemberListItem from "../components/MemberListItem";
-type Members = RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"];
+type Members = RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"]["rows"];
type Team = RouterOutputs["viewer"]["organizations"]["getOtherTeam"];
interface MembersListProps {
members: Members | undefined;
team: Team | undefined;
- offset: number;
- setOffset: (offset: number) => void;
- displayLoadMore: boolean;
+ fetchNextPage: () => void;
+ hasNextPage: boolean | undefined;
+ isFetchingNextPage: boolean | undefined;
}
function MembersList(props: MembersListProps) {
const { t } = useLocale();
- const { displayLoadMore, members, team } = props;
+ const { hasNextPage, members = [], team, fetchNextPage, isFetchingNextPage } = props;
+
return (
{members?.length && team ? (
@@ -44,13 +45,15 @@ function MembersList(props: MembersListProps) {
{t("no_members_found")}
)}
- {displayLoadMore && (
-
- )}
+
+
+
);
}
@@ -62,11 +65,9 @@ const MembersView = () => {
const teamId = Number(params.id);
const session = useSession();
const utils = trpc.useContext();
- const [offset, setOffset] = useState(1);
// const [query, setQuery] = useState("");
// const [queryToFetch, setQueryToFetch] = useState("");
- const [loadMore, setLoadMore] = useState(true);
- const limit = 100;
+ const limit = 20;
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState(false);
const [members, setMembers] = useState([]);
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
@@ -91,26 +92,25 @@ const MembersView = () => {
enabled: !Number.isNaN(teamId),
}
);
- const { data: membersFetch, isLoading: isLoadingMembers } =
- trpc.viewer.organizations.listOtherTeamMembers.useQuery(
- { teamId, limit, offset: (offset - 1) * limit },
+
+ const { fetchNextPage, isFetchingNextPage, hasNextPage } =
+ trpc.viewer.organizations.listOtherTeamMembers.useInfiniteQuery(
+ { teamId, limit },
{
+ onSuccess: (data) => {
+ const flatData = data?.pages?.flatMap((page) => page.rows) as Members;
+ setMembers(flatData);
+ },
enabled: !Number.isNaN(teamId),
onError: () => {
router.push("/settings");
},
+ getNextPageParam: (lastPage) => lastPage.nextCursor,
+ keepPreviousData: true,
}
);
- useEffect(() => {
- if (membersFetch) {
- setLoadMore(membersFetch.length >= limit);
- setMembers((m) => m.concat(membersFetch));
- }
- }, [membersFetch]);
-
- const isLoading = isTeamLoading || isLoadingMembers || isOrgListLoading;
-
+ const isLoading = isTeamLoading || isOrgListLoading;
const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation({
onSuccess: () => {
utils.viewer.organizations.listOtherTeams.invalidate();
@@ -162,9 +162,9 @@ const MembersView = () => {
>
diff --git a/packages/features/ee/teams/components/MemberListItem.tsx b/packages/features/ee/teams/components/MemberListItem.tsx
index 2f2bacfa32..d0d4cb873f 100644
--- a/packages/features/ee/teams/components/MemberListItem.tsx
+++ b/packages/features/ee/teams/components/MemberListItem.tsx
@@ -67,6 +67,7 @@ export default function MemberListItem(props: Props) {
await utils.viewer.teams.get.invalidate();
await utils.viewer.eventTypes.invalidate();
await utils.viewer.organizations.listMembers.invalidate();
+ await utils.viewer.organizations.getMembers.invalidate();
showToast(t("success"), "success");
},
async onError(err) {
diff --git a/packages/features/ee/teams/pages/team-members-view.tsx b/packages/features/ee/teams/pages/team-members-view.tsx
index 553a8a449c..b6374df16b 100644
--- a/packages/features/ee/teams/pages/team-members-view.tsx
+++ b/packages/features/ee/teams/pages/team-members-view.tsx
@@ -206,6 +206,7 @@ const MembersView = () => {
{
onSuccess: async (data) => {
await utils.viewer.teams.get.invalidate();
+ await utils.viewer.organizations.getMembers.invalidate();
setShowMemberInvitationModal(false);
if (Array.isArray(data.usernameOrEmail)) {
diff --git a/packages/trpc/server/routers/viewer/organizations/getMembers.handler.ts b/packages/trpc/server/routers/viewer/organizations/getMembers.handler.ts
index 1225ffdabf..2a29b001d0 100644
--- a/packages/trpc/server/routers/viewer/organizations/getMembers.handler.ts
+++ b/packages/trpc/server/routers/viewer/organizations/getMembers.handler.ts
@@ -50,6 +50,24 @@ export const getMembersHandler = async ({ input, ctx }: CreateOptions) => {
},
},
});
+
+ if (teamIdToExclude && teamQuery?.members) {
+ const excludedteamUsers = await prisma.team.findUnique({
+ where: {
+ id: teamIdToExclude,
+ },
+ select: {
+ members: {
+ select: {
+ userId: true,
+ },
+ },
+ },
+ });
+ const excludedUserIds = excludedteamUsers?.members.map((item) => item.userId) ?? [];
+ teamQuery.members = teamQuery?.members.filter((member) => !excludedUserIds.includes(member.userId));
+ }
+
return teamQuery?.members || [];
};
diff --git a/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts b/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts
index 1c4d332e62..d9634a908f 100644
--- a/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts
+++ b/packages/trpc/server/routers/viewer/organizations/listOtherTeamMembers.handler.ts
@@ -8,8 +8,9 @@ import type { TrpcSessionUser } from "../../../trpc";
export const ZListOtherTeamMembersSchema = z.object({
teamId: z.number(),
query: z.string().optional(),
- limit: z.number().optional(),
+ limit: z.number(),
offset: z.number().optional(),
+ cursor: z.number().nullish(), // <-- "cursor" needs to exist when using useInfiniteQuery, but can be any type
});
export type TListOtherTeamMembersSchema = z.infer;
@@ -25,11 +26,12 @@ export const listOtherTeamMembers = async ({ input }: ListOptions) => {
const whereConditional: Prisma.MembershipWhereInput = {
teamId: input.teamId,
};
- const { limit = 20 } = input;
- let { offset = 0 } = input;
+ // const { limit = 20 } = input;
+ // let { offset = 0 } = input;
+
+ const { cursor, limit } = input;
if (input.query) {
- offset = 0;
whereConditional.user = {
OR: [
{
@@ -73,11 +75,19 @@ export const listOtherTeamMembers = async ({ input }: ListOptions) => {
},
distinct: ["userId"],
orderBy: { role: "desc" },
- take: limit,
- skip: offset,
+ cursor: cursor ? { id: cursor } : undefined,
+ take: limit + 1, // We take +1 as itll be used for the next cursor
});
+ let nextCursor: typeof cursor | undefined = undefined;
+ if (members && members.length > limit) {
+ const nextItem = members.pop();
+ nextCursor = nextItem?.id || null;
+ }
- return members;
+ return {
+ rows: members || [],
+ nextCursor,
+ };
};
export default listOtherTeamMembers;