From 75eaed1c4d069db25017de7781eb11ccc28c8153 Mon Sep 17 00:00:00 2001 From: Somay Chauhan Date: Thu, 7 Dec 2023 15:09:23 +0530 Subject: [PATCH] fix: adding team members from organization tab that alredy exist (#11689) * fix: adding team members from organization tab that alredy exist * changed organizations.listOtherTeamMembers from useQuery to useInfiniteQuery * undo yarn.lock * fix: invalidate the organizations.getMembers query on removeMember and inviteMember Mutation --------- Co-authored-by: Peer Richelsen Co-authored-by: Hariom --- .../pages/components/MemberListItem.tsx | 2 +- .../settings/other-team-members-view.tsx | 62 +++++++++---------- .../ee/teams/components/MemberListItem.tsx | 1 + .../ee/teams/pages/team-members-view.tsx | 1 + .../organizations/getMembers.handler.ts | 18 ++++++ .../listOtherTeamMembers.handler.ts | 24 ++++--- 6 files changed, 69 insertions(+), 39 deletions(-) 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;