diff --git a/apps/web/test/lib/getSchedule.test.ts b/apps/web/test/lib/getSchedule.test.ts index 02887e65bc..fc3324ede1 100644 --- a/apps/web/test/lib/getSchedule.test.ts +++ b/apps/web/test/lib/getSchedule.test.ts @@ -274,6 +274,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); // As per Google Calendar Availability, only 4PM(4-4:45PM) GMT slot would be available @@ -358,6 +359,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); // getSchedule returns timeslots in GMT @@ -387,6 +389,7 @@ describe("getSchedule", () => { startTime: `${plus2DateString}T18:30:00.000Z`, endTime: `${plus3DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(scheduleForDayWithOneBooking).toHaveTimeSlots( @@ -450,6 +453,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(scheduleForEventWith30Length).toHaveTimeSlots( @@ -483,6 +487,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); // `slotInterval` takes precedence over `length` // 4:30 is utc so it is 10:00 in IST @@ -545,6 +550,7 @@ describe("getSchedule", () => { startTime: `${minus1DateString}T18:30:00.000Z`, endTime: `${todayDateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(scheduleForEventWithBookingNotice13Hrs).toHaveTimeSlots( [ @@ -563,6 +569,7 @@ describe("getSchedule", () => { startTime: `${minus1DateString}T18:30:00.000Z`, endTime: `${todayDateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(scheduleForEventWithBookingNotice10Hrs).toHaveTimeSlots( [ @@ -625,6 +632,7 @@ describe("getSchedule", () => { startTime: `${plus2DateString}T18:30:00.000Z`, endTime: `${plus3DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(scheduleForEventOnADayWithNonCalBooking).toHaveTimeSlots( @@ -697,6 +705,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(scheduleForEventOnADayWithCalBooking).toHaveTimeSlots( @@ -753,6 +762,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(schedule).toHaveTimeSlots( @@ -815,6 +825,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(scheduleForEventOnADayWithDateOverride).toHaveTimeSlots( @@ -899,6 +910,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: false, }); expect(thisUserAvailability).toHaveTimeSlots( @@ -995,6 +1007,7 @@ describe("getSchedule", () => { startTime: `${todayDateString}T18:30:00.000Z`, endTime: `${plus1DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: true, }); expect(scheduleForTeamEventOnADayWithNoBooking).toHaveTimeSlots( @@ -1022,6 +1035,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: true, }); // A user with blocked time in another event, still affects Team Event availability @@ -1128,6 +1142,7 @@ describe("getSchedule", () => { startTime: `${plus1DateString}T18:30:00.000Z`, endTime: `${plus2DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: true, }); // A user with blocked time in another event, still affects Team Event availability expect(scheduleForTeamEventOnADayWithOneBookingForEachUserButOnDifferentTimeslots).toHaveTimeSlots( @@ -1153,6 +1168,7 @@ describe("getSchedule", () => { startTime: `${plus2DateString}T18:30:00.000Z`, endTime: `${plus3DateString}T18:29:59.999Z`, timeZone: Timezones["+5:30"], + isTeamEvent: true, }); // A user with blocked time in another event, still affects Team Event availability expect(scheduleForTeamEventOnADayWithOneBookingForEachUserOnSameTimeSlot).toHaveTimeSlots( diff --git a/packages/features/bookings/Booker/utils/event.ts b/packages/features/bookings/Booker/utils/event.ts index d996ba67fd..34fa277510 100644 --- a/packages/features/bookings/Booker/utils/event.ts +++ b/packages/features/bookings/Booker/utils/event.ts @@ -1,4 +1,4 @@ -import { useSearchParams } from "next/navigation"; +import { useSearchParams, usePathname } from "next/navigation"; import { shallow } from "zustand/shallow"; import { useSchedule } from "@calcom/features/schedules"; @@ -59,8 +59,10 @@ export const useScheduleForEvent = ({ (state) => [state.username, state.eventSlug, state.month, state.selectedDuration], shallow ); - const serachParams = useSearchParams(); - const rescheduleUid = serachParams.get("rescheduleUid"); + const searchParams = useSearchParams(); + const rescheduleUid = searchParams.get("rescheduleUid"); + + const pathname = usePathname(); return useSchedule({ username: usernameFromStore ?? username, @@ -71,5 +73,6 @@ export const useScheduleForEvent = ({ rescheduleUid, month: monthFromStore ?? month, duration: durationFromStore ?? duration, + isTeamEvent: pathname.indexOf("/team/") !== -1, }); }; diff --git a/packages/features/schedules/lib/use-schedule/useSchedule.ts b/packages/features/schedules/lib/use-schedule/useSchedule.ts index c302a81f8e..0b96bcff2c 100644 --- a/packages/features/schedules/lib/use-schedule/useSchedule.ts +++ b/packages/features/schedules/lib/use-schedule/useSchedule.ts @@ -11,6 +11,7 @@ type UseScheduleWithCacheArgs = { prefetchNextMonth?: boolean; duration?: number | null; rescheduleUid?: string | null; + isTeamEvent?: boolean; }; export const useSchedule = ({ @@ -22,6 +23,7 @@ export const useSchedule = ({ prefetchNextMonth, duration, rescheduleUid, + isTeamEvent, }: UseScheduleWithCacheArgs) => { const monthDayjs = month ? dayjs(month) : dayjs(); const nextMonthDayjs = monthDayjs.add(1, "month"); @@ -30,6 +32,7 @@ export const useSchedule = ({ // no satisfy typscript. return trpc.viewer.public.slots.getSchedule.useQuery( { + isTeamEvent, usernameList: getUsernameList(username ?? ""), // Prioritize slug over id, since slug is the first value we get available. // If we have a slug, we don't need to fetch the id. diff --git a/packages/trpc/server/routers/viewer/slots/types.ts b/packages/trpc/server/routers/viewer/slots/types.ts index 4c9696ece5..aed8403143 100644 --- a/packages/trpc/server/routers/viewer/slots/types.ts +++ b/packages/trpc/server/routers/viewer/slots/types.ts @@ -13,7 +13,7 @@ export const getScheduleSchema = z // invitee timezone timeZone: z.string().optional(), // or list of users (for dynamic events) - usernameList: z.union([z.string(), z.array(z.string())]).optional(), + usernameList: z.array(z.string()).min(1).optional(), debug: z.boolean().optional(), // to handle event types with multiple duration options duration: z @@ -21,6 +21,8 @@ export const getScheduleSchema = z .optional() .transform((val) => val && parseInt(val)), rescheduleUid: z.string().optional().nullable(), + // whether to do team event or user event + isTeamEvent: z.boolean().optional().default(false), }) .transform((val) => { // Need this so we can pass a single username in the query string form public API diff --git a/packages/trpc/server/routers/viewer/slots/util.ts b/packages/trpc/server/routers/viewer/slots/util.ts index 378a658578..79f2c86933 100644 --- a/packages/trpc/server/routers/viewer/slots/util.ts +++ b/packages/trpc/server/routers/viewer/slots/util.ts @@ -69,42 +69,53 @@ export const checkIfIsAvailable = ({ }); }; +async function getEventTypeId({ + slug, + eventTypeSlug, + isTeamEvent, +}: { + slug?: string; + eventTypeSlug?: string; + isTeamEvent: boolean; +}) { + if (!eventTypeSlug || !slug) return null; + + let teamId; + let userId; + if (isTeamEvent) { + teamId = await getTeamIdFromSlug(slug); + } else { + userId = await getUserIdFromUsername(slug); + } + const eventType = await prisma.eventType.findFirst({ + where: { + slug: eventTypeSlug, + ...(teamId ? { teamId } : {}), + ...(userId ? { userId } : {}), + }, + select: { + id: true, + }, + }); + if (!eventType) { + throw new TRPCError({ code: "NOT_FOUND" }); + } + return eventType?.id; +} + export async function getEventType(input: TGetScheduleInputSchema) { - const { eventTypeSlug, usernameList } = input; - let eventTypeId = input.eventTypeId; + const { eventTypeSlug, usernameList, isTeamEvent } = input; + const eventTypeId = + input.eventTypeId || + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + (await getEventTypeId({ + slug: usernameList?.[0], + eventTypeSlug: eventTypeSlug, + isTeamEvent, + })); - if (eventTypeId === undefined && eventTypeSlug && usernameList && usernameList.length === 1) { - // If we only have the slug and usernameList, we need to get the id first - const username = usernameList[0]; - const userId = await getUserIdFromUsername(username); - let teamId; - - if (!userId) { - teamId = await getTeamIdFromSlug(username); - if (!teamId) { - throw new TRPCError({ - message: "User or team not found", - code: "NOT_FOUND", - }); - } - } - - const eventType = await prisma.eventType.findFirst({ - where: { - slug: eventTypeSlug, - ...(teamId ? { teamId } : {}), - ...(userId ? { userId } : {}), - }, - select: { - id: true, - }, - }); - - if (!eventType) { - throw new TRPCError({ code: "NOT_FOUND" }); - } - - eventTypeId = eventType.id; + if (!eventTypeId) { + return null; } const eventType = await prisma.eventType.findUnique({ @@ -165,7 +176,7 @@ export async function getEventType(input: TGetScheduleInputSchema) { }, }); if (!eventType) { - return eventType; + return null; } return {