diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 5051a76f6e..0c22af01cb 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -909,7 +909,7 @@ "impersonate": "Impersonate", "user_impersonation_heading": "User Impersonation", "user_impersonation_description": "Allows our support team to temporarily sign in as you to help us quickly resolve any issues you report to us.", - "team_impersonation_description": "Allows your team members to temporarily sign in as you.", + "team_impersonation_description": "Allows your team Owners/Admins to temporarily sign in as you.", "allow_booker_to_select_duration": "Allow booker to select duration", "impersonate_user_tip": "All uses of this feature is audited.", "impersonating_user_warning": "Impersonating username \"{{user}}\".", diff --git a/packages/features/ee/impersonation/lib/ImpersonationProvider.ts b/packages/features/ee/impersonation/lib/ImpersonationProvider.ts index b452eed9d1..11d5c753aa 100644 --- a/packages/features/ee/impersonation/lib/ImpersonationProvider.ts +++ b/packages/features/ee/impersonation/lib/ImpersonationProvider.ts @@ -6,7 +6,7 @@ import { z } from "zod"; import prisma from "@calcom/prisma"; const teamIdschema = z.object({ - teamId: z.number(), + teamId: z.preprocess((a) => parseInt(z.string().parse(a), 10), z.number().positive()), }); const auditAndReturnNextUser = async ( @@ -54,7 +54,7 @@ const ImpersonationProvider = CredentialsProvider({ // @ts-ignore need to figure out how to correctly type this const session = await getSession({ req }); // If teamId is present -> parse the teamId and throw error itn ot number. If not present teamId is set to undefined - const teamId = creds?.teamId ? teamIdschema.parse(creds).teamId : undefined; + const teamId = creds?.teamId ? teamIdschema.parse({ teamId: creds.teamId }).teamId : undefined; if (session?.user.username === creds?.username) { throw new Error("You cannot impersonate yourself."); diff --git a/packages/features/ee/teams/components/DisableTeamImpersonation.tsx b/packages/features/ee/teams/components/DisableTeamImpersonation.tsx index 3b13b07ff9..77aa579f91 100644 --- a/packages/features/ee/teams/components/DisableTeamImpersonation.tsx +++ b/packages/features/ee/teams/components/DisableTeamImpersonation.tsx @@ -49,9 +49,9 @@ const DisableTeamImpersonation = ({
{ - mutation.mutate({ teamId, memberId, disableImpersonation: isChecked }); + mutation.mutate({ teamId, memberId, disableImpersonation: !isChecked }); }} />
diff --git a/packages/features/ee/teams/components/MemberListItem.tsx b/packages/features/ee/teams/components/MemberListItem.tsx index 4ed7e39b47..866a7f3af8 100644 --- a/packages/features/ee/teams/components/MemberListItem.tsx +++ b/packages/features/ee/teams/components/MemberListItem.tsx @@ -1,5 +1,6 @@ import { MembershipRole } from "@prisma/client"; import classNames from "classnames"; +import { signIn } from "next-auth/react"; import { useState } from "react"; import { WEBAPP_URL } from "@calcom/lib/constants"; @@ -12,9 +13,9 @@ import { ButtonGroup, ConfirmationDialogContent, Dialog, + DialogClose, DialogContent, DialogFooter, - DialogTrigger, Dropdown, DropdownItem, DropdownMenuContent, @@ -48,6 +49,8 @@ export default function MemberListItem(props: Props) { const utils = trpc.useContext(); const [showChangeMemberRoleModal, setShowChangeMemberRoleModal] = useState(false); const [showTeamAvailabilityModal, setShowTeamAvailabilityModal] = useState(false); + const [showImpersonateModal, setShowImpersonateModal] = useState(false); + const [showDeleteModal, setShowDeleteModal] = useState(false); const removeMemberMutation = trpc.viewer.teams.removeMember.useMutation({ async onSuccess() { @@ -83,6 +86,11 @@ export default function MemberListItem(props: Props) { ownersInTeam() > 1 || props.member.id !== currentUserId)) || (props.team.membership.role === MembershipRole.ADMIN && props.member.role !== MembershipRole.OWNER); + const impersonationMode = + editMode && + !props.member.disableImpersonation && + props.member.accepted && + process.env.NEXT_PUBLIC_TEAM_IMPERSONATION === "true"; return (
  • @@ -161,28 +169,28 @@ export default function MemberListItem(props: Props) { {t("edit") as string} - - - - + {impersonationMode && ( + <> + - - - {t("remove_member_confirmation_message")} - - + + + + )} + + setShowDeleteModal(true)} + color="destructive" + StartIcon={Icon.FiTrash}> + {t("delete")} + @@ -216,30 +224,14 @@ export default function MemberListItem(props: Props) { {t("edit") as string} - - - - - - - - - {t("remove_member_confirmation_message")} - - + setShowDeleteModal(true)} + StartIcon={Icon.FiTrash}> + {t("edit") as string} + )} @@ -249,6 +241,42 @@ export default function MemberListItem(props: Props) { )} + + {editMode && ( + setShowDeleteModal(false)}> + + {t("remove_member_confirmation_message")} + + + )} + + {showImpersonateModal && props.member.username && ( + setShowImpersonateModal(false)}> + +
    { + e.preventDefault(); + await signIn("impersonation-auth", { + username: props.member.username, + teamId: props.team.id, + }); + setShowImpersonateModal(false); + }}> + + {t("cancel")} + + +
    +
    +
    + )} + {showChangeMemberRoleModal && (