fix: org admin has team member privileges [CAL-2338] (#10809)
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
co-authored by
Omar López
parent
8995dcc82a
commit
fe8b13885c
@@ -562,6 +562,7 @@
|
||||
"leave": "Leave",
|
||||
"profile": "Profile",
|
||||
"my_team_url": "My team URL",
|
||||
"my_teams": "My teams",
|
||||
"team_name": "Team Name",
|
||||
"your_team_name": "Your team name",
|
||||
"team_updated_successfully": "Team updated successfully",
|
||||
@@ -1998,7 +1999,7 @@
|
||||
"kyc_verification": "KYC Verification",
|
||||
"organizations": "Organizations",
|
||||
"org_admin_other_teams": "Other teams",
|
||||
"org_admin_other_teams_description": "Here you can see teams inside your organization but that you are not part of. You can add yourself to them if needed.",
|
||||
"org_admin_other_teams_description": "Here you can see teams inside your organization that you are not part of. You can add yourself to them if needed.",
|
||||
"no_other_teams_found": "No other teams found",
|
||||
"no_other_teams_found_description": "There are no other teams in this organization.",
|
||||
"attendee_first_name_variable": "Attendee first name",
|
||||
@@ -2007,6 +2008,7 @@
|
||||
"attendee_last_name_info": "The person booking's last name",
|
||||
"me": "Me",
|
||||
"verify_team_tooltip": "Verify your team to enable sending messages to attendees",
|
||||
"member_removed": "Member removed",
|
||||
"my_availability": "My Availability",
|
||||
"team_availability": "Team Availability",
|
||||
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useMemo, useState } from "react";
|
||||
import { useState } from "react";
|
||||
|
||||
import SkeletonLoaderTeamList from "@calcom/ee/teams/components/SkeletonloaderTeamList";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
@@ -12,14 +12,12 @@ export function OtherTeamsListing() {
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState("");
|
||||
|
||||
const { data, isLoading } = trpc.viewer.organizations.listOtherTeams.useQuery(undefined, {
|
||||
const { data: teams, isLoading } = trpc.viewer.organizations.listOtherTeams.useQuery(undefined, {
|
||||
onError: (e) => {
|
||||
setErrorMessage(e.message);
|
||||
},
|
||||
});
|
||||
|
||||
const teams = useMemo(() => data?.filter((m) => m.accepted) || [], [data]);
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLoaderTeamList />;
|
||||
}
|
||||
@@ -28,7 +26,7 @@ export function OtherTeamsListing() {
|
||||
<>
|
||||
{!!errorMessage && <Alert severity="error" title={errorMessage} />}
|
||||
|
||||
{teams.length > 0 ? (
|
||||
{teams && teams.length > 0 ? (
|
||||
<OtherTeamList teams={teams} />
|
||||
) : (
|
||||
<EmptyScreen
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
// import { debounce } from "lodash";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useSearchParams, useRouter } from "next/navigation";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
import MemberInvitationModal from "@calcom/ee/teams/components/MemberInvitationModal";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { Meta, showToast } from "@calcom/ui";
|
||||
import { Meta, showToast, Button } from "@calcom/ui";
|
||||
import { Plus } from "@calcom/ui/components/icon";
|
||||
|
||||
import { getLayout } from "../../../../settings/layouts/SettingsLayout";
|
||||
import MakeTeamPrivateSwitch from "../../../teams/components/MakeTeamPrivateSwitch";
|
||||
@@ -51,7 +54,7 @@ const MembersView = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const teamId = Number(searchParams.get("id"));
|
||||
|
||||
const session = useSession();
|
||||
const utils = trpc.useContext();
|
||||
const [offset, setOffset] = useState<number>(1);
|
||||
// const [query, setQuery] = useState<string | undefined>("");
|
||||
@@ -60,6 +63,9 @@ const MembersView = () => {
|
||||
const limit = 100;
|
||||
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState<boolean>(false);
|
||||
const [members, setMembers] = useState<Members>([]);
|
||||
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||
enabled: !!session.data?.user?.organizationId,
|
||||
});
|
||||
const { data: team, isLoading: isTeamLoading } = trpc.viewer.organizations.getOtherTeam.useQuery(
|
||||
{ teamId },
|
||||
{
|
||||
@@ -68,6 +74,16 @@ const MembersView = () => {
|
||||
},
|
||||
}
|
||||
);
|
||||
const { data: orgMembersNotInThisTeam, isLoading: isOrgListLoading } =
|
||||
trpc.viewer.organizations.getMembers.useQuery(
|
||||
{
|
||||
teamIdToExclude: teamId,
|
||||
distinctUser: true,
|
||||
},
|
||||
{
|
||||
enabled: searchParams !== null,
|
||||
}
|
||||
);
|
||||
const { data: membersFetch, isLoading: isLoadingMembers } =
|
||||
trpc.viewer.organizations.listOtherTeamMembers.useQuery(
|
||||
{ teamId, limit, offset: (offset - 1) * limit },
|
||||
@@ -89,38 +105,34 @@ const MembersView = () => {
|
||||
}
|
||||
}, [membersFetch]);
|
||||
|
||||
// useEffect(() => {
|
||||
// if (queryToFetch !== "") {
|
||||
// setMembers(membersFetch || []);
|
||||
// setLoadMore(false);
|
||||
// }
|
||||
// }, [membersFetch, query]);
|
||||
|
||||
const isLoading = isTeamLoading || isLoadingMembers;
|
||||
const isLoading = isTeamLoading || isLoadingMembers || isOrgListLoading;
|
||||
|
||||
const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation();
|
||||
|
||||
// const debouncedFunction = debounce((query) => {
|
||||
// setQueryToFetch(query);
|
||||
// }, 500);
|
||||
const isOrgAdminOrOwner =
|
||||
currentOrg &&
|
||||
(currentOrg.user.role === MembershipRole.OWNER || currentOrg.user.role === MembershipRole.ADMIN);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title={t("team_members")}
|
||||
description={t("members_team_description")}
|
||||
// @TODO: Add this back in when we have the ability to invite members
|
||||
// CTA={
|
||||
// <Button
|
||||
// type="button"
|
||||
// color="primary"
|
||||
// StartIcon={Plus}
|
||||
// className="ml-auto"
|
||||
// onClick={() => setShowMemberInvitationModal(true)}
|
||||
// data-testid="new-member-button">
|
||||
// {t("add")}
|
||||
// </Button>
|
||||
// }
|
||||
CTA={
|
||||
isOrgAdminOrOwner ? (
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
StartIcon={Plus}
|
||||
className="ml-auto"
|
||||
onClick={() => setShowMemberInvitationModal(true)}
|
||||
data-testid="new-member-button">
|
||||
{t("add")}
|
||||
</Button>
|
||||
) : (
|
||||
<></>
|
||||
)
|
||||
}
|
||||
/>
|
||||
{!isLoading && (
|
||||
<>
|
||||
@@ -157,6 +169,7 @@ const MembersView = () => {
|
||||
<MemberInvitationModal
|
||||
isLoading={inviteMemberMutation.isLoading}
|
||||
isOpen={showMemberInvitationModal}
|
||||
orgMembers={orgMembersNotInThisTeam}
|
||||
teamId={team.id}
|
||||
disableCopyLink={true}
|
||||
onExit={() => setShowMemberInvitationModal(false)}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
|
||||
import InviteLinkSettingsModal from "@calcom/features/ee/teams/components/InviteLinkSettingsModal";
|
||||
import MemberInvitationModal from "@calcom/features/ee/teams/components/MemberInvitationModal";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { APP_NAME, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import {
|
||||
@@ -22,10 +23,6 @@ import {
|
||||
} from "@calcom/ui";
|
||||
import { ArrowRight, Plus, Trash2 } from "@calcom/ui/components/icon";
|
||||
|
||||
const querySchema = z.object({
|
||||
id: z.string().transform((val) => parseInt(val)),
|
||||
});
|
||||
|
||||
type TeamMember = RouterOutputs["viewer"]["teams"]["get"]["members"][number];
|
||||
|
||||
type FormValues = {
|
||||
@@ -57,12 +54,22 @@ export const AddNewTeamMembersForm = ({
|
||||
|
||||
const router = useRouter();
|
||||
const utils = trpc.useContext();
|
||||
const orgBranding = useOrgBranding();
|
||||
|
||||
const showDialog = searchParams?.get("inviteModal") === "true";
|
||||
const [memberInviteModal, setMemberInviteModal] = useState(showDialog);
|
||||
const [inviteLinkSettingsModal, setInviteLinkSettingsModal] = useState(false);
|
||||
|
||||
const { data: team, isLoading } = trpc.viewer.teams.get.useQuery({ teamId }, { enabled: !!teamId });
|
||||
const { data: orgMembersNotInThisTeam } = trpc.viewer.organizations.getMembers.useQuery(
|
||||
{
|
||||
teamIdToExclude: teamId,
|
||||
distinctUser: true,
|
||||
},
|
||||
{
|
||||
enabled: orgBranding !== null,
|
||||
}
|
||||
);
|
||||
|
||||
const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation();
|
||||
|
||||
@@ -78,17 +85,19 @@ export const AddNewTeamMembersForm = ({
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
<ul className="border-subtle rounded-md border" data-testid="pending-member-list">
|
||||
{defaultValues.members.map((member, index) => (
|
||||
<PendingMemberItem key={member.email} member={member} index={index} teamId={teamId} />
|
||||
))}
|
||||
</ul>
|
||||
{defaultValues.members.length > 0 && (
|
||||
<ul className="border-subtle rounded-md border" data-testid="pending-member-list">
|
||||
{defaultValues.members.map((member, index) => (
|
||||
<PendingMemberItem key={member.email} member={member} index={index} teamId={teamId} />
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<Button
|
||||
color="secondary"
|
||||
data-testid="new-member-button"
|
||||
StartIcon={Plus}
|
||||
onClick={() => setMemberInviteModal(true)}
|
||||
className="mt-6 w-full justify-center">
|
||||
className={classNames("w-full justify-center", defaultValues.members.length > 0 && "mt-6")}>
|
||||
{t("add_team_member")}
|
||||
</Button>
|
||||
</div>
|
||||
@@ -99,6 +108,7 @@ export const AddNewTeamMembersForm = ({
|
||||
<MemberInvitationModal
|
||||
isLoading={inviteMemberMutation.isLoading}
|
||||
isOpen={memberInviteModal}
|
||||
orgMembers={orgMembersNotInThisTeam}
|
||||
teamId={teamId}
|
||||
token={team?.inviteToken?.token}
|
||||
onExit={() => setMemberInviteModal(false)}
|
||||
@@ -162,14 +172,18 @@ export const AddNewTeamMembersForm = ({
|
||||
)}
|
||||
<hr className="border-subtle my-6" />
|
||||
<Button
|
||||
EndIcon={ArrowRight}
|
||||
EndIcon={!orgBranding ? ArrowRight : undefined}
|
||||
color="primary"
|
||||
className="mt-6 w-full justify-center"
|
||||
className="w-full justify-center"
|
||||
disabled={publishTeamMutation.isLoading}
|
||||
onClick={() => {
|
||||
publishTeamMutation.mutate({ teamId });
|
||||
if (orgBranding) {
|
||||
router.push("/settings/teams");
|
||||
} else {
|
||||
publishTeamMutation.mutate({ teamId });
|
||||
}
|
||||
}}>
|
||||
{t("team_publish")}
|
||||
{t(orgBranding ? "finish" : "team_publish")}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
@@ -198,18 +212,26 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n
|
||||
const { member, index, teamId } = props;
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
const session = useSession();
|
||||
const bookerUrl = useBookerUrl();
|
||||
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||
enabled: !!session.data?.user?.organizationId,
|
||||
});
|
||||
const removeMemberMutation = trpc.viewer.teams.removeMember.useMutation({
|
||||
async onSuccess() {
|
||||
await utils.viewer.teams.get.invalidate();
|
||||
await utils.viewer.eventTypes.invalidate();
|
||||
showToast("Member removed", "success");
|
||||
showToast(t("member_removed"), "success");
|
||||
},
|
||||
async onError(err) {
|
||||
showToast(err.message, "error");
|
||||
},
|
||||
});
|
||||
|
||||
const isOrgAdminOrOwner =
|
||||
currentOrg &&
|
||||
(currentOrg.user.role === MembershipRole.OWNER || currentOrg.user.role === MembershipRole.ADMIN);
|
||||
|
||||
return (
|
||||
<li
|
||||
key={member.email}
|
||||
@@ -229,7 +251,7 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n
|
||||
<div className="flex space-x-1">
|
||||
<p>{member.name || member.email || t("team_member")}</p>
|
||||
{/* Assume that the first member of the team is the creator */}
|
||||
{index === 0 && <Badge variant="green">{t("you")}</Badge>}
|
||||
{member.id === session.data?.user.id && <Badge variant="green">{t("you")}</Badge>}
|
||||
{!member.accepted && <Badge variant="orange">{t("pending")}</Badge>}
|
||||
{member.role === "MEMBER" && <Badge variant="gray">{t("member")}</Badge>}
|
||||
{member.role === "ADMIN" && <Badge variant="default">{t("admin")}</Badge>}
|
||||
@@ -241,7 +263,7 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{member.role !== "OWNER" && (
|
||||
{(member.role !== "OWNER" || isOrgAdminOrOwner) && (
|
||||
<Button
|
||||
data-testid="remove-member-button"
|
||||
StartIcon={Trash2}
|
||||
@@ -249,7 +271,10 @@ const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: n
|
||||
color="secondary"
|
||||
className="h-[36px] w-[36px]"
|
||||
onClick={() => {
|
||||
removeMemberMutation.mutate({ teamId, memberId: member.id });
|
||||
removeMemberMutation.mutate({
|
||||
teamId: teamId,
|
||||
memberId: member.id,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -79,6 +79,9 @@ const MembersView = () => {
|
||||
const showDialog = searchParams?.get("inviteModal") === "true";
|
||||
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState(showDialog);
|
||||
const [showInviteLinkSettingsModal, setInviteLinkSettingsModal] = useState(false);
|
||||
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
||||
enabled: !!session.data?.user?.organizationId,
|
||||
});
|
||||
|
||||
const { data: orgMembersNotInThisTeam, isLoading: isOrgListLoading } =
|
||||
trpc.viewer.organizations.getMembers.useQuery(
|
||||
@@ -109,13 +112,17 @@ const MembersView = () => {
|
||||
const isAdmin =
|
||||
team && (team.membership.role === MembershipRole.OWNER || team.membership.role === MembershipRole.ADMIN);
|
||||
|
||||
const isOrgAdminOrOwner =
|
||||
currentOrg &&
|
||||
(currentOrg.user.role === MembershipRole.OWNER || currentOrg.user.role === MembershipRole.ADMIN);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta
|
||||
title={t("team_members")}
|
||||
description={t("members_team_description")}
|
||||
CTA={
|
||||
isAdmin ? (
|
||||
isAdmin || isOrgAdminOrOwner ? (
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
@@ -152,7 +159,7 @@ const MembersView = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{((team?.isPrivate && isAdmin) || !team?.isPrivate) && (
|
||||
{((team?.isPrivate && isAdmin) || !team?.isPrivate || isOrgAdminOrOwner) && (
|
||||
<>
|
||||
<MembersList team={team} />
|
||||
<hr className="border-subtle my-8" />
|
||||
@@ -167,7 +174,7 @@ const MembersView = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{team && isAdmin && (
|
||||
{team && (isAdmin || isOrgAdminOrOwner) && (
|
||||
<>
|
||||
<hr className="border-subtle my-8" />
|
||||
<MakeTeamPrivateSwitch teamId={team.id} isPrivate={team.isPrivate} disabled={isInviteOpen} />
|
||||
|
||||
@@ -244,7 +244,10 @@ const SettingsSidebarContainer = ({
|
||||
}
|
||||
}, [searchParams?.get("id"), otherTeams]);
|
||||
|
||||
if (currentOrg && currentOrg?.user?.role && ["OWNER", "ADMIN"].includes(currentOrg?.user?.role)) {
|
||||
const isOrgAdminOrOwner =
|
||||
currentOrg && currentOrg?.user?.role && ["OWNER", "ADMIN"].includes(currentOrg?.user?.role);
|
||||
|
||||
if (isOrgAdminOrOwner) {
|
||||
const teamsIndex = tabsWithPermissions.findIndex((tab) => tab.name === "teams");
|
||||
|
||||
tabsWithPermissions.splice(teamsIndex + 1, 0, {
|
||||
@@ -325,7 +328,7 @@ const SettingsSidebarContainer = ({
|
||||
as="p"
|
||||
className="truncate text-sm font-medium leading-5"
|
||||
loadingClassName="ms-3">
|
||||
{t(tab.name)}
|
||||
{t(isOrgAdminOrOwner ? "my_teams" : tab.name)}
|
||||
</Skeleton>
|
||||
</div>
|
||||
</Link>
|
||||
@@ -517,7 +520,7 @@ const SettingsSidebarContainer = ({
|
||||
alt={otherTeam.name || "Team logo"}
|
||||
/>
|
||||
<p className="w-1/2 truncate">{otherTeam.name}</p>
|
||||
{!otherTeam.accepted && (
|
||||
{!otherTeam.accepted && otherTeam.userId === session.data?.user.id && (
|
||||
<Badge className="ms-3" variant="orange">
|
||||
Inv.
|
||||
</Badge>
|
||||
@@ -525,7 +528,8 @@ const SettingsSidebarContainer = ({
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="space-y-0.5">
|
||||
{otherTeam.accepted && (
|
||||
{((otherTeam.accepted && otherTeam.userId === session.data?.user.id) ||
|
||||
isOrgAdminOrOwner) && (
|
||||
<VerticalTabItem
|
||||
name={t("profile")}
|
||||
href={`/settings/organizations/teams/other/${otherTeam.id}/profile`}
|
||||
|
||||
@@ -37,6 +37,7 @@ export const listOtherTeamHandler = async ({ ctx }: ListOptions) => {
|
||||
return teamsInOrgIamNotPartOf.map(({ team, ...membership }) => ({
|
||||
role: membership.role,
|
||||
accepted: membership.accepted,
|
||||
userId: membership.userId,
|
||||
isOrgAdmin: true,
|
||||
...team,
|
||||
}));
|
||||
|
||||
@@ -66,6 +66,7 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
|
||||
|
||||
const createTeam = await prisma.team.create({
|
||||
data: {
|
||||
...(isOrgChildTeam ? { slug } : {}),
|
||||
name,
|
||||
logo,
|
||||
members: {
|
||||
@@ -75,9 +76,11 @@ export const createHandler = async ({ ctx, input }: CreateOptions) => {
|
||||
accepted: true,
|
||||
},
|
||||
},
|
||||
metadata: {
|
||||
requestedSlug: slug,
|
||||
},
|
||||
metadata: !isOrgChildTeam
|
||||
? {
|
||||
requestedSlug: slug,
|
||||
}
|
||||
: undefined,
|
||||
...(isOrgChildTeam && { parentId: user.organizationId }),
|
||||
},
|
||||
});
|
||||
|
||||
@@ -15,7 +15,10 @@ type GetOptions = {
|
||||
};
|
||||
|
||||
export const getHandler = async ({ ctx, input }: GetOptions) => {
|
||||
const team = await getTeamWithMembers({ id: input.teamId, userId: ctx.user.id });
|
||||
const team = await getTeamWithMembers({
|
||||
id: input.teamId,
|
||||
userId: ctx.user.organization?.isOrgAdmin ? undefined : ctx.user.id,
|
||||
});
|
||||
|
||||
if (!team) {
|
||||
throw new TRPCError({ code: "NOT_FOUND", message: "Team not found." });
|
||||
|
||||
@@ -35,7 +35,12 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) =
|
||||
const team = await getTeamOrThrow(input.teamId, input.isOrg);
|
||||
const { autoAcceptEmailDomain, orgVerified } = getIsOrgVerified(input.isOrg, team);
|
||||
|
||||
await checkPermissions({ userId: ctx.user.id, teamId: input.teamId, isOrg: input.isOrg });
|
||||
await checkPermissions({
|
||||
userId: ctx.user.id,
|
||||
teamId:
|
||||
ctx.user.organization.id && ctx.user.organization.isOrgAdmin ? ctx.user.organization.id : input.teamId,
|
||||
isOrg: input.isOrg,
|
||||
});
|
||||
|
||||
const translation = await getTranslation(input.language ?? "en", "common");
|
||||
|
||||
|
||||
@@ -20,12 +20,15 @@ type RemoveMemberOptions = {
|
||||
|
||||
export const removeMemberHandler = async ({ ctx, input }: RemoveMemberOptions) => {
|
||||
const isAdmin = await isTeamAdmin(ctx.user.id, input.teamId);
|
||||
const isOrgAdmin = ctx.user.organizationId
|
||||
? await isTeamAdmin(ctx.user.id, ctx.user.organizationId)
|
||||
: false;
|
||||
if (!isAdmin && ctx.user.id !== input.memberId) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
// Only a team owner can remove another team owner.
|
||||
if ((await isTeamOwner(input.memberId, input.teamId)) && !(await isTeamOwner(ctx.user.id, input.teamId)))
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
if (ctx.user.id === input.memberId && isAdmin)
|
||||
if (ctx.user.id === input.memberId && isAdmin && !isOrgAdmin)
|
||||
throw new TRPCError({
|
||||
code: "FORBIDDEN",
|
||||
message: "You can not remove yourself from a team you own.",
|
||||
|
||||
Reference in New Issue
Block a user