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 <peeroke@gmail.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
This commit is contained in:
Somay Chauhan 2023-12-07 15:09:23 +05:30 committed by GitHub
parent e1ac6f5454
commit 75eaed1c4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 69 additions and 39 deletions

View File

@ -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) {

View File

@ -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 (
<div className="flex flex-col gap-y-3">
{members?.length && team ? (
@ -44,13 +45,15 @@ function MembersList(props: MembersListProps) {
<p className="text-default text-sm font-bold">{t("no_members_found")}</p>
</div>
)}
{displayLoadMore && (
<button
className="text-primary-500 hover:text-primary-600"
onClick={() => props.setOffset(props.offset + 1)}>
{t("load_more")}
</button>
)}
<div className="text-default p-4 text-center">
<Button
color="minimal"
loading={isFetchingNextPage}
disabled={!hasNextPage}
onClick={() => fetchNextPage()}>
{hasNextPage ? t("load_more_results") : t("no_more_results")}
</Button>
</div>
</div>
);
}
@ -62,11 +65,9 @@ const MembersView = () => {
const teamId = Number(params.id);
const session = useSession();
const utils = trpc.useContext();
const [offset, setOffset] = useState<number>(1);
// const [query, setQuery] = useState<string | undefined>("");
// const [queryToFetch, setQueryToFetch] = useState<string | undefined>("");
const [loadMore, setLoadMore] = useState<boolean>(true);
const limit = 100;
const limit = 20;
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState<boolean>(false);
const [members, setMembers] = useState<Members>([]);
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 = () => {
<MembersList
members={members}
team={team}
setOffset={setOffset}
offset={offset}
displayLoadMore={loadMore}
fetchNextPage={fetchNextPage}
hasNextPage={hasNextPage}
isFetchingNextPage={isFetchingNextPage}
/>
</>

View File

@ -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) {

View File

@ -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)) {

View File

@ -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 || [];
};

View File

@ -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<typeof ZListOtherTeamMembersSchema>;
@ -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;