From f9359aaa7b94341942a22be76eeab66a6780c7db Mon Sep 17 00:00:00 2001 From: Pradumn Kumar Date: Fri, 26 Jan 2024 23:07:38 +0530 Subject: [PATCH] feat: added ability to assign event to all members (#13219) * feat: added ability to assign event to all members * fix: types * chore: incorporate assignment for collective and round robin event sas well * fix: update event types query * feat: round robin hosts * fix: update * fix: update * fix: assignAllTeamMembers issue * design fixes --------- Co-authored-by: Peer Richelsen Co-authored-by: CarinaWolli --- apps/api/lib/validations/event-type.ts | 1 + .../web/components/eventtype/EventTeamTab.tsx | 231 ++++++++++++++---- .../views/event-types-single-view.tsx | 5 + apps/web/public/static/locales/en/common.json | 2 + packages/core/getUserAvailability.ts | 1 + .../signup/utils/createOrUpdateMemberships.ts | 2 + .../auth/signup/utils/organization.ts | 3 + .../features/bookings/lib/handleNewBooking.ts | 1 + .../features/eventtypes/lib/getPublicEvent.ts | 2 + packages/lib/defaultEvents.ts | 1 + packages/lib/getEventTypeById.ts | 1 + packages/lib/server/queries/teams/index.ts | 71 +++++- packages/lib/test/builder.ts | 1 + .../migration.sql | 2 + packages/prisma/schema.prisma | 1 + packages/prisma/zod-utils.ts | 1 + .../server/routers/viewer/eventTypes/types.ts | 1 + .../viewer/eventTypes/update.handler.ts | 30 ++- .../trpc/server/routers/viewer/slots/util.ts | 1 + .../viewer/teams/acceptOrLeave.handler.ts | 3 + .../inviteMember/inviteMember.handler.ts | 7 + 21 files changed, 321 insertions(+), 47 deletions(-) create mode 100644 packages/prisma/migrations/20240114195534_assign_all_team_members/migration.sql diff --git a/apps/api/lib/validations/event-type.ts b/apps/api/lib/validations/event-type.ts index cbabad6dc4..f874ac143d 100644 --- a/apps/api/lib/validations/event-type.ts +++ b/apps/api/lib/validations/event-type.ts @@ -62,6 +62,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({ bookingLimits: true, onlyShowFirstAvailableSlot: true, durationLimits: true, + assignAllTeamMembers: true, }) .merge( z.object({ diff --git a/apps/web/components/eventtype/EventTeamTab.tsx b/apps/web/components/eventtype/EventTeamTab.tsx index 5a8e91bcb7..74959c6d58 100644 --- a/apps/web/components/eventtype/EventTeamTab.tsx +++ b/apps/web/components/eventtype/EventTeamTab.tsx @@ -1,8 +1,8 @@ import { Trans } from "next-i18next"; import Link from "next/link"; import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]"; -import { useEffect, useRef } from "react"; -import type { ComponentProps } from "react"; +import { useEffect, useRef, useState } from "react"; +import type { ComponentProps, Dispatch, SetStateAction } from "react"; import { Controller, useFormContext, useWatch } from "react-hook-form"; import type { Options } from "react-select"; @@ -11,7 +11,7 @@ import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTea import ChildrenEventTypeSelect from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { SchedulingType } from "@calcom/prisma/enums"; -import { Label, Select } from "@calcom/ui"; +import { Label, Select, SettingsToggle } from "@calcom/ui"; interface IUserToValue { id: number | null; @@ -97,6 +97,44 @@ const ChildrenEventTypesList = ({ ); }; +const AssignAllTeamMembers = ({ + assignAllTeamMembers, + setAssignAllTeamMembers, + onActive, + onInactive, +}: { + assignAllTeamMembers: boolean; + setAssignAllTeamMembers: Dispatch>; + onActive: () => void; + onInactive?: () => void; +}) => { + const { t } = useLocale(); + const formMethods = useFormContext(); + + return ( + + name="assignAllTeamMembers" + render={() => ( + { + formMethods.setValue("assignAllTeamMembers", active); + setAssignAllTeamMembers(active); + if (active) { + onActive(); + } else if (!!onInactive) { + onInactive(); + } + }} + toggleSwitchAtTheEnd + /> + )} + /> + ); +}; + const CheckedHostField = ({ labelText, placeholder, @@ -107,7 +145,7 @@ const CheckedHostField = ({ helperText, ...rest }: { - labelText: string; + labelText?: string; placeholder: string; isFixed: boolean; value: { isFixed: boolean; userId: number }[]; @@ -118,7 +156,7 @@ const CheckedHostField = ({ return (
- + {labelText ? : <>} !!value.find((host) => host.userId.toString() === option.value)} onChange={(options) => { @@ -165,6 +203,8 @@ const RoundRobinHosts = ({ teamMembers, value, onChange, + assignAllTeamMembers, + setAssignAllTeamMembers, }: { value: { isFixed: boolean; userId: number }[]; onChange: (hosts: { isFixed: boolean; userId: number }[]) => void; @@ -174,51 +214,102 @@ const RoundRobinHosts = ({ avatar: string; email: string; }[]; + assignAllTeamMembers: boolean; + setAssignAllTeamMembers: Dispatch>; }) => { const { t } = useLocale(); + const formMethods = useFormContext(); + return ( - <> - { - onChange([...value.filter(({ isFixed }) => !isFixed), ...changeValue]); - }} - value={value} - placeholder={t("add_fixed_hosts")} - labelText={t("fixed_hosts")} - helperText={FixedHostHelper} - /> - onChange([...value.filter(({ isFixed }) => isFixed), ...changeValue])} - value={value} - isFixed={false} - placeholder={t("add_attendees")} - labelText={t("round_robin_hosts")} - helperText={t("round_robin_helper")} - /> - +
+
+ { + onChange([...value.filter(({ isFixed }) => !isFixed), ...changeValue]); + }} + value={value} + placeholder={t("add_fixed_hosts")} + labelText={t("fixed_hosts")} + helperText={FixedHostHelper} + /> + +
+ +
+ + formMethods.setValue( + "hosts", + teamMembers.map((teamMember) => ({ + isFixed: false, + userId: parseInt(teamMember.value, 10), + })) + ) + } + onInactive={() => formMethods.setValue("hosts", [])} + /> +
+ {assignAllTeamMembers ? ( +
+ ) : ( + + onChange([...value.filter(({ isFixed }) => isFixed), ...changeValue]) + } + value={value} + isFixed={false} + placeholder={t("add_attendees")} + helperText={t("round_robin_helper")} + /> + )} +
+
+
); }; const ChildrenEventTypes = ({ childrenEventTypeOptions, + assignAllTeamMembers, + setAssignAllTeamMembers, }: { childrenEventTypeOptions: ReturnType[]; + assignAllTeamMembers: boolean; + setAssignAllTeamMembers: Dispatch>; }) => { + const formMethods = useFormContext(); return ( - - name="children" - render={({ field: { onChange, value } }) => ( - - )} - /> +
+
+ formMethods.setValue("children", childrenEventTypeOptions)} + /> + {!assignAllTeamMembers ? ( + + name="children" + render={({ field: { onChange, value } }) => ( + + )} + /> + ) : ( + <> + )} +
+
); }; const Hosts = ({ teamMembers, + assignAllTeamMembers, + setAssignAllTeamMembers, }: { teamMembers: { value: string; @@ -226,6 +317,8 @@ const Hosts = ({ avatar: string; email: string; }[]; + assignAllTeamMembers: boolean; + setAssignAllTeamMembers: Dispatch>; }) => { const { t } = useLocale(); const { @@ -255,24 +348,53 @@ const Hosts = ({ }); }, [schedulingType, resetField, getValues, submitCount]); + const formMethods = useFormContext(); return ( name="hosts" render={({ field: { onChange, value } }) => { const schedulingTypeRender = { COLLECTIVE: ( - +
+
+ + formMethods.setValue( + "hosts", + teamMembers.map((teamMember) => ({ + isFixed: true, + userId: parseInt(teamMember.value, 10), + })) + ) + } + onInactive={() => formMethods.setValue("hosts", [])} + /> + {assignAllTeamMembers ? ( + <> + ) : ( + + )} +
+
), ROUND_ROBIN: ( <> - + {/* mapUserToValue(member, t("pending"))); const childrenEventTypeOptions = teamMembers.filter(pendingMembers).map((member) => { - return mapMemberToChildrenOption(member, eventType.slug, t("pending")); + return mapMemberToChildrenOption( + { ...member, eventTypes: member.eventTypes.filter((et) => et !== eventType.slug) }, + eventType.slug, + t("pending") + ); }); const isManagedEventType = eventType.schedulingType === SchedulingType.MANAGED; + const formMethods = useFormContext(); + const [assignAllTeamMembers, setAssignAllTeamMembers] = useState( + formMethods.getValues("assignAllTeamMembers") ?? false + ); + return (
{team && !isManagedEventType && ( @@ -338,16 +469,26 @@ export const EventTeamTab = ({ className="w-full" onChange={(val) => { onChange(val?.value); + formMethods.setValue("assignAllTeamMembers", false); + setAssignAllTeamMembers(false); }} /> )} />
- +
)} {team && isManagedEventType && ( - + )}
); diff --git a/apps/web/modules/event-types/views/event-types-single-view.tsx b/apps/web/modules/event-types/views/event-types-single-view.tsx index 1a99bf6281..35c1a3ed06 100644 --- a/apps/web/modules/event-types/views/event-types-single-view.tsx +++ b/apps/web/modules/event-types/views/event-types-single-view.tsx @@ -145,6 +145,7 @@ export type FormValues = { bookerLayouts: BookerLayoutSettings; multipleDurationEnabled: boolean; users: EventTypeSetup["users"]; + assignAllTeamMembers: boolean; }; export type CustomInputParsed = typeof customInputSchema._output; @@ -195,6 +196,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { created: true, })) ); + formMethods.setValue("assignAllTeamMembers", formMethods.getValues("assignAllTeamMembers") || false); showToast(t("event_type_updated_successfully", { eventTypeTitle: eventType.title }), "success"); }, async onSettled() { @@ -305,6 +307,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { }, })), seatsPerTimeSlotEnabled: eventType.seatsPerTimeSlot, + assignAllTeamMembers: eventType.assignAllTeamMembers, }; }, [eventType, periodDates, metadata]); @@ -463,6 +466,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { metadata, customInputs, children, + assignAllTeamMembers, // We don't need to send send these values to the backend // eslint-disable-next-line @typescript-eslint/no-unused-vars seatsPerTimeSlotEnabled, @@ -532,6 +536,7 @@ const EventTypePage = (props: EventTypeSetupProps) => { metadata, customInputs, children, + assignAllTeamMembers, }); }; diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index 935ed980b4..e0c5815bad 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -2235,6 +2235,8 @@ "add_a_redirect": "Add a redirect", "create_entry": "Create entry", "time_range": "Time range", + "automatically_add_all_team_members": "Automatically add all team members", + "including_people_who_join_in_future": "Including people who join the team in the future", "redirect_to": "Redirect to", "having_trouble_finding_time": "Having trouble finding a time?", "show_more": "Show more", diff --git a/packages/core/getUserAvailability.ts b/packages/core/getUserAvailability.ts index 097189d7e9..34b68ad85e 100644 --- a/packages/core/getUserAvailability.ts +++ b/packages/core/getUserAvailability.ts @@ -57,6 +57,7 @@ const _getEventType = async (id: number) => { seatsPerTimeSlot: true, bookingLimits: true, durationLimits: true, + assignAllTeamMembers: true, timeZone: true, length: true, metadata: true, diff --git a/packages/features/auth/signup/utils/createOrUpdateMemberships.ts b/packages/features/auth/signup/utils/createOrUpdateMemberships.ts index 593fb50ebd..8ba879b3d9 100644 --- a/packages/features/auth/signup/utils/createOrUpdateMemberships.ts +++ b/packages/features/auth/signup/utils/createOrUpdateMemberships.ts @@ -1,5 +1,6 @@ import type z from "zod"; +import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries"; import { prisma } from "@calcom/prisma"; import type { Team, User } from "@calcom/prisma/client"; import { MembershipRole } from "@calcom/prisma/enums"; @@ -56,6 +57,7 @@ export const createOrUpdateMemberships = async ({ }, }); } + await updateNewTeamMemberEventTypes(user.id, team.id); return { membership, orgMembership }; }); }; diff --git a/packages/features/auth/signup/utils/organization.ts b/packages/features/auth/signup/utils/organization.ts index c34474e324..b12a1ac605 100644 --- a/packages/features/auth/signup/utils/organization.ts +++ b/packages/features/auth/signup/utils/organization.ts @@ -1,3 +1,4 @@ +import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries"; import prisma from "@calcom/prisma"; export async function joinOrganization({ @@ -52,4 +53,6 @@ export async function joinAnyChildTeamOnOrgInvite({ userId, orgId }: { userId: n }, }), ]); + + await updateNewTeamMemberEventTypes(userId, orgId); } diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index e89a23412b..06300e0b52 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -333,6 +333,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => { seatsShowAvailabilityCount: true, bookingLimits: true, durationLimits: true, + assignAllTeamMembers: true, parentId: true, owner: { select: { diff --git a/packages/features/eventtypes/lib/getPublicEvent.ts b/packages/features/eventtypes/lib/getPublicEvent.ts index 1c2d1d3462..36ac73b6df 100644 --- a/packages/features/eventtypes/lib/getPublicEvent.ts +++ b/packages/features/eventtypes/lib/getPublicEvent.ts @@ -112,6 +112,7 @@ const publicEventSelect = Prisma.validator()({ }, }, hidden: true, + assignAllTeamMembers: true, }); export const getPublicEvent = async ( @@ -264,6 +265,7 @@ export const getPublicEvent = async ( }, isDynamic: false, isInstantEvent: event.isInstantEvent, + assignAllTeamMembers: event.assignAllTeamMembers, }; }; diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts index 43ddb0edd7..b0da09d6e5 100644 --- a/packages/lib/defaultEvents.ts +++ b/packages/lib/defaultEvents.ts @@ -101,6 +101,7 @@ const commons = { hosts: [], metadata: EventTypeMetaDataSchema.parse({}), bookingFields: [], + assignAllTeamMembers: false, }; const dynamicEvent = { diff --git a/packages/lib/getEventTypeById.ts b/packages/lib/getEventTypeById.ts index 0632aed00d..3aafbb37bc 100644 --- a/packages/lib/getEventTypeById.ts +++ b/packages/lib/getEventTypeById.ts @@ -105,6 +105,7 @@ export default async function getEventTypeById({ bookingLimits: true, onlyShowFirstAvailableSlot: true, durationLimits: true, + assignAllTeamMembers: true, successRedirectUrl: true, currency: true, bookingFields: true, diff --git a/packages/lib/server/queries/teams/index.ts b/packages/lib/server/queries/teams/index.ts index c910c8cea1..7930aad051 100644 --- a/packages/lib/server/queries/teams/index.ts +++ b/packages/lib/server/queries/teams/index.ts @@ -3,7 +3,12 @@ import { Prisma } from "@prisma/client"; import { getAppFromSlug } from "@calcom/app-store/utils"; import prisma, { baseEventTypeSelect } from "@calcom/prisma"; import { SchedulingType } from "@calcom/prisma/enums"; -import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils"; +import { _EventTypeModel } from "@calcom/prisma/zod"; +import { + EventTypeMetaDataSchema, + allManagedEventTypeProps, + unlockedManagedEventTypeProps, +} from "@calcom/prisma/zod-utils"; import { WEBAPP_URL } from "../../../constants"; import { getBookerBaseUrlSync } from "../../../getBookerUrl/client"; @@ -247,3 +252,67 @@ export async function isTeamMember(userId: number, teamId: number) { }, })); } + +export async function updateNewTeamMemberEventTypes(userId: number, teamId: number) { + const eventTypesToAdd = await prisma.eventType.findMany({ + where: { + team: { id: teamId }, + assignAllTeamMembers: true, + }, + select: { + ...allManagedEventTypeProps, + id: true, + schedulingType: true, + }, + }); + + const allManagedEventTypePropsZod = _EventTypeModel.pick(allManagedEventTypeProps); + + eventTypesToAdd.length > 0 && + (await prisma.$transaction( + eventTypesToAdd.map((eventType) => { + if (eventType.schedulingType === "MANAGED") { + const managedEventTypeValues = allManagedEventTypePropsZod + .omit(unlockedManagedEventTypeProps) + .parse(eventType); + + // Define the values for unlocked properties to use on creation, not updation + const unlockedEventTypeValues = allManagedEventTypePropsZod + .pick(unlockedManagedEventTypeProps) + .parse(eventType); + + // Calculate if there are new workflows for which assigned members will get too + const currentWorkflowIds = eventType.workflows?.map((wf) => wf.workflowId); + + return prisma.eventType.create({ + data: { + ...managedEventTypeValues, + ...unlockedEventTypeValues, + bookingLimits: + (managedEventTypeValues.bookingLimits as unknown as Prisma.InputJsonObject) ?? undefined, + recurringEvent: + (managedEventTypeValues.recurringEvent as unknown as Prisma.InputJsonValue) ?? undefined, + metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined, + bookingFields: (managedEventTypeValues.bookingFields as Prisma.InputJsonValue) ?? undefined, + durationLimits: (managedEventTypeValues.durationLimits as Prisma.InputJsonValue) ?? undefined, + onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false, + userId, + users: { + connect: [{ id: userId }], + }, + parentId: eventType.parentId, + hidden: false, + workflows: currentWorkflowIds && { + create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })), + }, + }, + }); + } else { + return prisma.eventType.update({ + where: { id: eventType.id }, + data: { hosts: { create: [{ userId, isFixed: eventType.schedulingType === "COLLECTIVE" }] } }, + }); + } + }) + )); +} diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts index b6238d9349..04c392a2ad 100644 --- a/packages/lib/test/builder.ts +++ b/packages/lib/test/builder.ts @@ -105,6 +105,7 @@ export const buildEventType = (eventType?: Partial): EventType => { scheduleId: null, bookingLimits: null, durationLimits: null, + assignAllTeamMembers: false, price: 0, currency: "usd", slotInterval: null, diff --git a/packages/prisma/migrations/20240114195534_assign_all_team_members/migration.sql b/packages/prisma/migrations/20240114195534_assign_all_team_members/migration.sql new file mode 100644 index 0000000000..3c1a9e6f81 --- /dev/null +++ b/packages/prisma/migrations/20240114195534_assign_all_team_members/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "EventType" ADD COLUMN "assignAllTeamMembers" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 90f8a76cf6..00d41f73d1 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -119,6 +119,7 @@ model EventType { /// @zod.custom(imports.intervalLimitsType) durationLimits Json? isInstantEvent Boolean @default(false) + assignAllTeamMembers Boolean @default(false) @@unique([userId, slug]) @@unique([teamId, slug]) diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index 2cd4232581..da4f327f55 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -611,6 +611,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit { recurringEvent, users, children, + assignAllTeamMembers, hosts, id, hashedLink, @@ -65,9 +66,33 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { }, team: { select: { - name: true, id: true, + name: true, + slug: true, parentId: true, + parent: { + select: { + slug: true, + }, + }, + members: { + select: { + role: true, + accepted: true, + user: { + select: { + name: true, + id: true, + email: true, + eventTypes: { + select: { + slug: true, + }, + }, + }, + }, + }, + }, }, }, }, @@ -290,6 +315,8 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { } } + data.assignAllTeamMembers = assignAllTeamMembers ?? false; + const updatedEventTypeSelect = Prisma.validator()({ slug: true, schedulingType: true, @@ -322,6 +349,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => { children, prisma: ctx.prisma, }); + const res = ctx.res as NextApiResponse; if (typeof res?.revalidate !== "undefined") { try { diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 52e0f6932c..dadaac65de 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -153,6 +153,7 @@ export async function getEventType( afterEventBuffer: true, bookingLimits: true, durationLimits: true, + assignAllTeamMembers: true, schedulingType: true, periodType: true, periodStartDate: true, diff --git a/packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts b/packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts index 9e25f31a02..b82ca1ec84 100644 --- a/packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/acceptOrLeave.handler.ts @@ -1,3 +1,4 @@ +import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries"; import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager"; import { prisma } from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/enums"; @@ -26,6 +27,8 @@ export const acceptOrLeaveHandler = async ({ ctx, input }: AcceptOrLeaveOptions) }, }); + await updateNewTeamMemberEventTypes(ctx.user.id, input.teamId); + closeComUpsertTeamUser(membership.team, ctx.user, membership.role); } else { try { diff --git a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts index 114c9fca37..38cac6d99f 100644 --- a/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts @@ -2,6 +2,7 @@ import { updateQuantitySubscriptionFromStripe } from "@calcom/features/ee/teams/ import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError"; import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants"; import { getTranslation } from "@calcom/lib/server/i18n"; +import { updateNewTeamMemberEventTypes } from "@calcom/lib/server/queries"; import { isOrganisationOwner } from "@calcom/lib/server/queries/organisations"; import { prisma } from "@calcom/prisma"; import { MembershipRole } from "@calcom/prisma/enums"; @@ -131,6 +132,12 @@ export const inviteMemberHandler = async ({ ctx, input }: InviteMemberOptions) = }), }); + await Promise.all( + autoJoinUsers.map(async (userToAutoJoin) => { + await updateNewTeamMemberEventTypes(userToAutoJoin.id, team.id); + }) + ); + await sendTeamInviteEmails({ currentUserName: ctx?.user?.name, currentUserTeamName: team?.name,