import classNames from "classnames"; import { signIn } from "next-auth/react"; import { useState } from "react"; import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { MembershipRole } from "@calcom/prisma/enums"; import { teamMetadataSchema } from "@calcom/prisma/zod-utils"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery"; import { Avatar, Button, ButtonGroup, ConfirmationDialogContent, Dialog, DialogClose, DialogContent, DialogFooter, Dropdown, DropdownItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, showToast, Tooltip, } from "@calcom/ui"; import { ExternalLink, MoreHorizontal, Edit2, Lock, UserX } from "@calcom/ui/components/icon"; import MemberChangeRoleModal from "./MemberChangeRoleModal"; import TeamAvailabilityModal from "./TeamAvailabilityModal"; import TeamPill, { TeamRole } from "./TeamPill"; interface Props { team: RouterOutputs["viewer"]["teams"]["get"]; member: RouterOutputs["viewer"]["teams"]["get"]["members"][number]; } /** TODO: Migrate the one in apps/web to tRPC package */ const useCurrentUserId = () => { const query = useMeQuery(); const user = query.data; return user?.id; }; const checkIsOrg = (team: Props["team"]) => { const metadata = teamMetadataSchema.safeParse(team.metadata); if (metadata.success && metadata.data?.isOrganization) return true; return false; }; export default function MemberListItem(props: Props) { const { t } = useLocale(); 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() { await utils.viewer.teams.get.invalidate(); await utils.viewer.eventTypes.invalidate(); await utils.viewer.organizations.listMembers.invalidate(); showToast(t("success"), "success"); }, async onError(err) { showToast(err.message, "error"); }, }); const ownersInTeam = () => { const { members } = props.team; const owners = members.filter((member) => member["role"] === MembershipRole.OWNER && member["accepted"]); return owners.length; }; const currentUserId = useCurrentUserId(); const name = props.member.name || (() => { const emailName = props.member.email.split("@")[0]; return emailName.charAt(0).toUpperCase() + emailName.slice(1); })(); const removeMember = () => removeMemberMutation.mutate({ teamId: props.team?.id, memberId: props.member.id, isOrg: checkIsOrg(props.team), }); const editMode = (props.team.membership?.role === MembershipRole.OWNER && (props.member.role !== MembershipRole.OWNER || 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"; const bookerUrl = useBookerUrl(); const bookerUrlWithoutProtocol = bookerUrl.replace(/^https?:\/\//, ""); const bookingLink = !!props.member.username && `${bookerUrlWithoutProtocol}/${props.member.username}`; const isAdmin = props.team && ["ADMIN", "OWNER"].includes(props.team.membership?.role); const appList = props.member.connectedApps.map(({ logo, name, externalId }) => { return logo ? ( externalId ? (