import { useSession } from "next-auth/react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
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 } from "@calcom/lib/constants";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { MembershipRole } from "@calcom/prisma/enums";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import {
Badge,
Button,
showToast,
SkeletonButton,
SkeletonContainer,
SkeletonText,
UserAvatar,
} from "@calcom/ui";
type TeamMember = RouterOutputs["viewer"]["teams"]["get"]["members"][number];
type FormValues = {
members: TeamMember[];
};
const AddNewTeamMembers = ({ isOrg = false }: { isOrg?: boolean }) => {
const searchParams = useCompatSearchParams();
const session = useSession();
const telemetry = useTelemetry();
const teamId = searchParams?.get("id") ? Number(searchParams.get("id")) : -1;
const teamQuery = trpc.viewer.teams.get.useQuery(
{ teamId, isOrg },
{ enabled: session.status === "authenticated" }
);
useEffect(() => {
const event = searchParams?.get("event");
if (event === "team_created") {
telemetry.event(telemetryEventTypes.team_created);
}
}, []);
if (session.status === "loading" || !teamQuery.data) return ;
return (
);
};
export const AddNewTeamMembersForm = ({
defaultValues,
teamId,
isOrg,
}: {
defaultValues: FormValues;
teamId: number;
isOrg?: boolean;
}) => {
const searchParams = useCompatSearchParams();
const { t, i18n } = useLocale();
const router = useRouter();
const utils = trpc.useUtils();
const orgBranding = useOrgBranding();
const showDialog = searchParams?.get("inviteModal") === "true";
const [memberInviteModal, setMemberInviteModal] = useState(showDialog);
const [inviteLinkSettingsModal, setInviteLinkSettingsModal] = useState(false);
const { data: team, isPending } = trpc.viewer.teams.get.useQuery({ teamId, isOrg }, { enabled: !!teamId });
const { data: orgMembersNotInThisTeam } = trpc.viewer.organizations.getMembers.useQuery(
{
teamIdToExclude: teamId,
distinctUser: true,
},
{
enabled: orgBranding !== null,
}
);
const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation();
const publishTeamMutation = trpc.viewer.teams.publish.useMutation({
onSuccess(data) {
router.push(data.url);
},
onError: (error) => {
showToast(error.message, "error");
},
});
return (
<>
{defaultValues.members.length > 0 && (
{defaultValues.members.map((member, index) => (
))}
)}
setMemberInviteModal(true)}
className={classNames("w-full justify-center", defaultValues.members.length > 0 && "mt-6")}>
{isOrg ? t("add_org_members") : t("add_team_member")}
{isPending ? (
) : (
<>
setMemberInviteModal(false)}
onSubmit={(values, resetFields) => {
inviteMemberMutation.mutate(
{
teamId,
language: i18n.language,
role: values.role,
usernameOrEmail: values.emailOrUsername,
},
{
onSuccess: async (data) => {
await utils.viewer.teams.get.invalidate();
setMemberInviteModal(false);
resetFields();
if (Array.isArray(data.usernameOrEmail)) {
showToast(
t("email_invite_team_bulk", {
userCount: data.numUsersInvited,
}),
"success"
);
} else {
showToast(
t("email_invite_team", {
email: data.usernameOrEmail,
}),
"success"
);
}
},
onError: (error) => {
showToast(error.message, "error");
},
}
);
}}
onSettingsOpen={() => {
setMemberInviteModal(false);
setInviteLinkSettingsModal(true);
}}
members={defaultValues.members}
/>
{team?.inviteToken && (
{
setInviteLinkSettingsModal(false);
setMemberInviteModal(true);
}}
/>
)}
>
)}
{
let uri = `/settings/teams/${teamId}/profile`;
if (isOrg) {
uri = `/settings/organizations/${teamId}/add-teams`;
}
router.push(uri);
}}>
{isOrg ? t("continue") : t("finish")}
>
);
};
export default AddNewTeamMembers;
const AddNewTeamMemberSkeleton = () => {
return (
);
};
const PendingMemberItem = (props: { member: TeamMember; index: number; teamId: number; isOrg?: boolean }) => {
const { member, index, teamId } = props;
const { t } = useLocale();
const utils = trpc.useUtils();
const session = useSession();
const orgRole = session?.data?.user.org?.role;
const bookerUrl = member.bookerUrl;
const removeMemberMutation = trpc.viewer.teams.removeMember.useMutation({
async onSuccess() {
await utils.viewer.teams.get.invalidate();
await utils.viewer.eventTypes.invalidate();
showToast(t("member_removed"), "success");
},
async onError(err) {
showToast(err.message, "error");
},
});
const isOrgAdminOrOwner = orgRole === MembershipRole.OWNER || orgRole === MembershipRole.ADMIN;
return (
{member.name || member.email || t("team_member")}
{/* Assume that the first member of the team is the creator */}
{member.id === session.data?.user.id &&
{t("you")} }
{!member.accepted &&
{t("pending")} }
{member.role === MembershipRole.MEMBER &&
{t("member")} }
{member.role === MembershipRole.ADMIN &&
{t("admin")} }
{member.role === MembershipRole.OWNER &&
{t("owner")} }
{member.username ? (
{`${bookerUrl}/${member.username}`}
) : (
{t("not_on_cal", { appName: APP_NAME })}
)}
{(member.role !== "OWNER" || isOrgAdminOrOwner) && member.id !== session.data?.user.id && (
{
removeMemberMutation.mutate({
teamIds: [teamId],
memberIds: [member.id],
isOrg: !!props.isOrg,
});
}}
/>
)}
);
};