diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 95e4ed2717b089..ee88e04e0014a3 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -15,6 +15,7 @@ import { deleteWebhookScheduledTriggers } from "@calcom/features/webhooks/lib/sc import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import { HttpError } from "@calcom/lib/http-error"; @@ -276,7 +277,18 @@ async function handler(req: CustomRequest) { const teamMembers = await Promise.all(teamMembersPromises); const tOrganizer = await getTranslation(organizer.locale ?? "en", "common"); + const ownerProfile = await prisma.profile.findFirst({ + where: { + userId: bookingToDelete.userId, + }, + }); + + const bookerUrl = await getBookerBaseUrl( + bookingToDelete.eventType?.team?.parentId ?? ownerProfile?.organizationId ?? null + ); + const evt: CalendarEvent = { + bookerUrl, title: bookingToDelete?.title, length: bookingToDelete?.eventType?.length, type: bookingToDelete?.eventType?.slug as string, @@ -371,6 +383,7 @@ async function handler(req: CustomRequest) { evt: { ...evt, metadata: { videoCallUrl: bookingMetadataSchema.parse(bookingToDelete.metadata || {})?.videoCallUrl }, + bookerUrl, ...{ eventType: { slug: bookingToDelete.eventType?.slug, diff --git a/packages/features/bookings/lib/handleConfirmation.ts b/packages/features/bookings/lib/handleConfirmation.ts index 0c15709420145b..91826f80750c6f 100644 --- a/packages/features/bookings/lib/handleConfirmation.ts +++ b/packages/features/bookings/lib/handleConfirmation.ts @@ -15,6 +15,7 @@ import { scheduleTrigger } from "@calcom/features/webhooks/lib/scheduleTrigger"; import sendPayload from "@calcom/features/webhooks/lib/sendOrSchedulePayload"; import type { EventPayloadType, EventTypeInfo } from "@calcom/features/webhooks/lib/sendPayload"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; @@ -283,6 +284,21 @@ export async function handleConfirmation(args: { updatedBookings.push(updatedBooking); } + const teamId = await getTeamIdFromEventType({ + eventType: { + team: { id: eventType?.teamId ?? null }, + parentId: eventType?.parentId ?? null, + }, + }); + + const triggerForUser = !teamId || (teamId && eventType?.parentId); + + const userId = triggerForUser ? booking.userId : null; + + const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); + + const bookerUrl = await getBookerBaseUrl(orgId ?? null); + //Workflows - set reminders for confirmed events try { for (let index = 0; index < updatedBookings.length; index++) { @@ -295,6 +311,7 @@ export async function handleConfirmation(args: { schedulingType: updatedBookings[index].eventType?.schedulingType, hosts: updatedBookings[index].eventType?.hosts, }, + bookerUrl, }; evtOfBooking.startTime = updatedBookings[index].startTime.toISOString(); evtOfBooking.endTime = updatedBookings[index].endTime.toISOString(); @@ -325,19 +342,6 @@ export async function handleConfirmation(args: { } try { - const teamId = await getTeamIdFromEventType({ - eventType: { - team: { id: eventType?.teamId ?? null }, - parentId: eventType?.parentId ?? null, - }, - }); - - const triggerForUser = !teamId || (teamId && eventType?.parentId); - - const userId = triggerForUser ? booking.userId : null; - - const orgId = await getOrgIdFromMemberOrTeamId({ memberId: userId, teamId }); - const subscribersBookingCreated = await getWebhooks({ userId, eventTypeId: booking.eventTypeId, diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 6baa4bda0c5233..67012fa09c27e8 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -987,7 +987,7 @@ async function handler( rescheduleUid, reqBookingUid: reqBody.bookingUid, eventType, - evt, + evt: { ...evt, bookerUrl }, invitee, allCredentials, organizerUser, @@ -1782,6 +1782,7 @@ async function handler( ...evt, metadata, eventType: { slug: eventType.slug, schedulingType: eventType.schedulingType, hosts: eventType.hosts }, + bookerUrl, }; if (!eventType.metadata?.disableStandardEmails?.all?.attendee) { diff --git a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts index 8eb88a60aaf950..14a75509714fcb 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/attendee/attendeeRescheduleSeatedBooking.ts @@ -76,14 +76,14 @@ const attendeeRescheduleSeatedBooking = async ( }), ]); } - // Add the new attendees to the new time slot booking attendees for (const attendee of newTimeSlotBooking.attendees) { - const language = await getTranslation(attendee.locale ?? "en", "common"); + const translate = await getTranslation(attendee.locale ?? "en", "common"); evt.attendees.push({ email: attendee.email, name: attendee.name, - language, + timeZone: attendee.timeZone, + language: { translate, locale: attendee.locale ?? "en" }, }); } diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts index 5d1132f4754302..c38e295bf1aacd 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/combineTwoSeatedBookings.ts @@ -119,7 +119,7 @@ const combineTwoSeatedBookings = async ( evt.attendees = updatedBookingAttendees; - evt = addVideoCallDataToEvent(updatedNewBooking.references, evt); + evt = { ...addVideoCallDataToEvent(updatedNewBooking.references, evt), bookerUrl: evt.bookerUrl }; const copyEvent = cloneDeep(evt); diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts index 524fc243f36e45..7ee29652f6cee1 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/moveSeatedBookingToNewTimeSlot.ts @@ -48,7 +48,7 @@ const moveSeatedBookingToNewTimeSlot = async ( }, }); - evt = addVideoCallDataToEvent(newBooking.references, evt); + evt = { ...addVideoCallDataToEvent(newBooking.references, evt), bookerUrl: evt.bookerUrl }; const copyEvent = cloneDeep(evt); diff --git a/packages/features/bookings/lib/handleSeats/reschedule/owner/ownerRescheduleSeatedBooking.ts b/packages/features/bookings/lib/handleSeats/reschedule/owner/ownerRescheduleSeatedBooking.ts index 03a03a1dc64b60..d5c932fd599824 100644 --- a/packages/features/bookings/lib/handleSeats/reschedule/owner/ownerRescheduleSeatedBooking.ts +++ b/packages/features/bookings/lib/handleSeats/reschedule/owner/ownerRescheduleSeatedBooking.ts @@ -21,14 +21,16 @@ const ownerRescheduleSeatedBooking = async ( ) => { const { originalRescheduledBooking, tAttendees } = rescheduleSeatedBookingObject; const { evt } = rescheduleSeatedBookingObject; - evt.attendees = originalRescheduledBooking?.attendees.map((attendee) => { - return { - name: attendee.name, - email: attendee.email, - timeZone: attendee.timeZone, - language: { translate: tAttendees, locale: attendee.locale ?? "en" }, - }; - }); + + evt.attendees = + originalRescheduledBooking?.attendees.map((attendee) => { + return { + name: attendee.name, + email: attendee.email, + timeZone: attendee.timeZone, + language: { translate: tAttendees, locale: attendee.locale ?? "en" }, + }; + }) ?? []; // If there is no booking during the new time slot then update the current booking to the new date if (!newTimeSlotBooking) { diff --git a/packages/features/bookings/lib/handleSeats/types.d.ts b/packages/features/bookings/lib/handleSeats/types.d.ts index ef639438c62d5f..1e36bd8c62d88b 100644 --- a/packages/features/bookings/lib/handleSeats/types.d.ts +++ b/packages/features/bookings/lib/handleSeats/types.d.ts @@ -2,7 +2,7 @@ import type { Prisma } from "@prisma/client"; import type z from "zod"; import type { Workflow } from "@calcom/features/ee/workflows/lib/types"; -import type { AppsStatus } from "@calcom/types/Calendar"; +import type { AppsStatus, CalendarEvent } from "@calcom/types/Calendar"; import type { Booking, NewBookingEventType, OriginalRescheduledBooking } from "../handleNewBooking/types"; @@ -12,7 +12,9 @@ export type NewSeatedBookingObject = { rescheduleUid: string | undefined; reqBookingUid: string | undefined; eventType: NewBookingEventType; - evt: CalendarEvent; + evt: Omit & { + bookerUrl: string; + }; invitee: Invitee; allCredentials: Awaited>; organizerUser: OrganizerUser; diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index 98f3bc8adbdcc0..d3c2a2105481cf 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -18,6 +18,7 @@ import { import { scheduleWorkflowReminders } from "@calcom/features/ee/workflows/lib/reminders/reminderScheduler"; import { isPrismaObjOrUndefined } from "@calcom/lib"; import { getVideoCallUrlFromCalEvent } from "@calcom/lib/CalEventParser"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import logger from "@calcom/lib/logger"; import { getLuckyUser } from "@calcom/lib/server"; import { getTranslation } from "@calcom/lib/server/i18n"; @@ -50,7 +51,13 @@ const bookingSelect = { references: true, }; -export const roundRobinReassignment = async ({ bookingId }: { bookingId: number }) => { +export const roundRobinReassignment = async ({ + bookingId, + orgId, +}: { + bookingId: number; + orgId: number | null; +}) => { const roundRobinReassignLogger = logger.getSubLogger({ prefix: ["roundRobinReassign", `${bookingId}`], }); @@ -465,6 +472,8 @@ export const roundRobinReassignment = async ({ bookingId }: { bookingId: number const workflowEventMetadata = { videoCallUrl: getVideoCallUrlFromCalEvent(evt) }; + const bookerUrl = await getBookerBaseUrl(orgId); + for (const workflowReminder of workflowReminders) { const workflowStep = workflowReminder?.workflowStep; const workflow = workflowStep?.workflow; @@ -475,6 +484,7 @@ export const roundRobinReassignment = async ({ bookingId }: { bookingId: number ...evt, metadata: workflowEventMetadata, eventType, + bookerUrl, }, action: WorkflowActions.EMAIL_HOST, triggerEvent: workflow.trigger, @@ -538,7 +548,12 @@ export const roundRobinReassignment = async ({ bookingId }: { bookingId: number await scheduleWorkflowReminders({ workflows: newEventWorkflows, smsReminderNumber: null, - calendarEvent: { ...evt, metadata: workflowEventMetadata, eventType: { slug: eventType.slug } }, + calendarEvent: { + ...evt, + metadata: workflowEventMetadata, + eventType: { slug: eventType.slug }, + bookerUrl, + }, hideBranding: !!eventType?.owner?.hideBranding, }); } diff --git a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts index f5a26670f036c9..6b7dca17054345 100644 --- a/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/emailReminderManager.ts @@ -8,6 +8,7 @@ import { v4 as uuidv4 } from "uuid"; import { guessEventLocationType } from "@calcom/app-store/locations"; import dayjs from "@calcom/dayjs"; import { preprocessNameFieldDataWithVariant } from "@calcom/features/form-builder/utils"; +import { WEBSITE_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import prisma from "@calcom/prisma"; import type { TimeUnit } from "@calcom/prisma/enums"; @@ -189,6 +190,8 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => emailSubject, emailBody: `${emailBody}`, }; + const bookerUrl = evt.bookerUrl ?? WEBSITE_URL; + if (emailBody) { const variables: VariablesType = { eventName: evt.title || "", @@ -204,10 +207,10 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => additionalNotes: evt.additionalNotes, responses: evt.responses, meetingUrl: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl, - cancelLink: `${evt.bookerUrl}/booking/${evt.uid}?cancel=true`, - rescheduleLink: `${evt.bookerUrl}/reschedule/${evt.uid}`, - ratingUrl: `${evt.bookerUrl}/booking/${evt.uid}?rating`, - noShowUrl: `${evt.bookerUrl}/booking/${evt.uid}?noShow=true`, + cancelLink: `${bookerUrl}/booking/${evt.uid}?cancel=true`, + rescheduleLink: `${bookerUrl}/reschedule/${evt.uid}`, + ratingUrl: `${bookerUrl}/booking/${evt.uid}?rating`, + noShowUrl: `${bookerUrl}/booking/${evt.uid}?noShow=true`, }; const locale = @@ -247,8 +250,8 @@ export const scheduleEmailReminder = async (args: scheduleEmailReminderArgs) => timeZone, organizer: evt.organizer.name, name, - ratingUrl: `${evt.bookerUrl}/booking/${evt.uid}?rating`, - noShowUrl: `${evt.bookerUrl}/booking/${evt.uid}?noShow=true`, + ratingUrl: `${bookerUrl}/booking/${evt.uid}?rating`, + noShowUrl: `${bookerUrl}/booking/${evt.uid}?noShow=true`, }); } diff --git a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts index 23089618c930f7..7db5f0bd064ec0 100644 --- a/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts +++ b/packages/features/ee/workflows/lib/reminders/reminderScheduler.ts @@ -14,13 +14,14 @@ import type { ScheduleTextReminderAction } from "./smsReminderManager"; import { scheduleSMSReminder } from "./smsReminderManager"; import { scheduleWhatsappReminder } from "./whatsappReminderManager"; -export type ExtendedCalendarEvent = CalendarEvent & { +export type ExtendedCalendarEvent = Omit & { metadata?: { videoCallUrl: string | undefined }; eventType: { slug?: string; schedulingType?: SchedulingType | null; hosts?: { user: { email: string; destinationCalendar?: { primaryEmail: string | null } | null } }[]; }; + bookerUrl: string; }; type ProcessWorkflowStepParams = { diff --git a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts index 08fab335bdcd81..27ebf063de7838 100644 --- a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts @@ -1,5 +1,5 @@ import dayjs from "@calcom/dayjs"; -import { SENDER_ID } from "@calcom/lib/constants"; +import { SENDER_ID, WEBSITE_URL } from "@calcom/lib/constants"; import logger from "@calcom/lib/logger"; import type { TimeFormat } from "@calcom/lib/timeFormat"; import type { PrismaClient } from "@calcom/prisma"; @@ -36,7 +36,7 @@ export type AttendeeInBookingInfo = { export type BookingInfo = { uid?: string | null; - bookerUrl?: string; + bookerUrl: string; attendees: AttendeeInBookingInfo[]; organizer: { language: { locale: string }; @@ -158,8 +158,8 @@ export const scheduleSMSReminder = async (args: ScheduleTextReminderArgs) => { additionalNotes: evt.additionalNotes, responses: evt.responses, meetingUrl: bookingMetadataSchema.parse(evt.metadata || {})?.videoCallUrl, - cancelLink: `${evt.bookerUrl}/booking/${evt.uid}?cancel=true`, - rescheduleLink: `${evt.bookerUrl}/reschedule/${evt.uid}`, + cancelLink: `${evt.bookerUrl ?? WEBSITE_URL}/booking/${evt.uid}?cancel=true`, + rescheduleLink: `${evt.bookerUrl ?? WEBSITE_URL}/reschedule/${evt.uid}`, }; const customMessage = customTemplate(smsMessage, variables, locale, evt.organizer.timeFormat); smsMessage = customMessage.text; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index d07ddcfa561bd1..0fa9000395a797 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -1,4 +1,4 @@ - // This is your Prisma Schema file +// This is your Prisma Schema file // learn more about it in the docs: https://pris.ly/d/prisma-schema datasource db { diff --git a/packages/trpc/server/routers/viewer/teams/roundRobin/roundRobinReassign.handler.ts b/packages/trpc/server/routers/viewer/teams/roundRobin/roundRobinReassign.handler.ts index 3332fb6337ff52..ccf357af22dc26 100644 --- a/packages/trpc/server/routers/viewer/teams/roundRobin/roundRobinReassign.handler.ts +++ b/packages/trpc/server/routers/viewer/teams/roundRobin/roundRobinReassign.handler.ts @@ -23,7 +23,7 @@ export const roundRobinReassignHandler = async ({ ctx, input }: RoundRobinReassi throw new TRPCError({ code: "FORBIDDEN", message: "You do not have permission" }); } - await roundRobinReassignment({ bookingId }); + await roundRobinReassignment({ bookingId, orgId: ctx.user.organizationId }); }; export default roundRobinReassignHandler; diff --git a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts index cbe86d940505f1..999335a5a7b0da 100644 --- a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts @@ -1,6 +1,7 @@ import { scheduleEmailReminder } from "@calcom/features/ee/workflows/lib/reminders/emailReminderManager"; import { scheduleSMSReminder } from "@calcom/features/ee/workflows/lib/reminders/smsReminderManager"; import { scheduleWhatsappReminder } from "@calcom/features/ee/workflows/lib/reminders/whatsappReminderManager"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import { WorkflowRepository } from "@calcom/lib/server/repository/workflow"; import { getTimeFormatStringFromUserTimeFormat } from "@calcom/lib/timeFormat"; import { prisma } from "@calcom/prisma"; @@ -221,10 +222,13 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType }, }); + const bookerUrl = await getBookerBaseUrl(ctx.user.organizationId ?? null); + for (const booking of bookingsForReminders) { const defaultLocale = "en"; const bookingInfo = { uid: booking.uid, + bookerUrl, attendees: booking.attendees.map((attendee) => { return { name: attendee.name, diff --git a/packages/trpc/server/routers/viewer/workflows/util.ts b/packages/trpc/server/routers/viewer/workflows/util.ts index 1da29092dbb5f5..1e1960c4429dde 100644 --- a/packages/trpc/server/routers/viewer/workflows/util.ts +++ b/packages/trpc/server/routers/viewer/workflows/util.ts @@ -14,6 +14,7 @@ import { } from "@calcom/features/bookings/lib/getBookingFields"; import { removeBookingField, upsertBookingField } from "@calcom/features/eventtypes/lib/bookingFieldsManager"; import { SENDER_ID, SENDER_NAME } from "@calcom/lib/constants"; +import { getBookerBaseUrl } from "@calcom/lib/getBookerUrl/server"; import getOrgIdFromMemberOrTeamId from "@calcom/lib/getOrgIdFromMemberOrTeamId"; import { getTeamIdFromEventType } from "@calcom/lib/getTeamIdFromEventType"; import logger from "@calcom/lib/logger"; @@ -481,7 +482,8 @@ export async function scheduleWorkflowNotifications( timeUnit, trigger, userId, - teamId + teamId, + isOrg ); } @@ -587,10 +589,14 @@ export async function scheduleBookingReminders( timeUnit: TimeUnit | null, trigger: WorkflowTriggerEvents, userId: number, - teamId: number | null + teamId: number | null, + isOrg: boolean ) { if (!bookings.length) return; if (trigger !== WorkflowTriggerEvents.BEFORE_EVENT && trigger !== WorkflowTriggerEvents.AFTER_EVENT) return; + + const bookerUrl = await getBookerBaseUrl(isOrg ? teamId : null); + //create reminders for all bookings for each workflow step const promiseSteps = workflowSteps.map(async (step) => { // we do not have attendees phone number (user is notified about that when setting this action) @@ -601,6 +607,7 @@ export async function scheduleBookingReminders( const defaultLocale = "en"; const bookingInfo = { uid: booking.uid, + bookerUrl, attendees: booking.attendees.map((attendee) => { return { name: attendee.name,