From 513f54f4fe179d9b88542d4b4ff20f6931f3e608 Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Thu, 24 Apr 2025 01:28:29 +0530 Subject: [PATCH] feat: ability to hide organizer email (#20782) * feat: hide orgainzer email * fix type error * update * update * Update schema.prisma --- apps/web/lib/booking.ts | 1 + ...ookings-single-view.getServerSideProps.tsx | 1 + .../bookings/views/bookings-single-view.tsx | 8 +-- apps/web/public/static/locales/en/common.json | 2 + packages/app-store/vital/lib/reschedule.ts | 1 + .../wipemycalother/lib/reschedule.ts | 2 + packages/emails/lib/generateIcsString.test.ts | 54 +++++++++++++++++-- packages/emails/lib/generateIcsString.ts | 6 ++- packages/emails/src/components/WhoInfo.tsx | 2 +- packages/features/CalendarEventBuilder.ts | 2 + .../bookings/lib/getBookingToDelete.ts | 1 + .../features/bookings/lib/getUserBooking.ts | 1 + .../bookings/lib/handleCancelBooking.ts | 1 + .../features/bookings/lib/handleNewBooking.ts | 1 + .../handleNewBooking/getEventTypesFromDB.ts | 1 + .../credentials/handleDeleteCredential.ts | 2 + .../roundRobinManualReassignment.ts | 1 + .../ee/round-robin/roundRobinReassignment.ts | 1 + .../lib/reminders/smsReminderManager.ts | 1 + .../tabs/advanced/EventAdvancedTab.tsx | 22 ++++++++ packages/lib/CalEventParser.ts | 6 ++- packages/lib/buildCalEventFromBooking.ts | 2 + packages/lib/builders/CalendarEvent/class.ts | 1 + packages/lib/defaultEvents.ts | 1 + packages/lib/payment/getBooking.ts | 2 + packages/lib/server/eventTypeSelect.ts | 1 + packages/lib/server/repository/booking.ts | 1 + packages/lib/server/repository/eventType.ts | 2 + packages/lib/test/builder.ts | 1 + .../event-types/hooks/useEventTypeForm.ts | 1 + .../migration.sql | 2 + packages/prisma/schema.prisma | 1 + packages/prisma/zod-utils.ts | 1 + .../loggedInViewer/connectAndJoin.handler.ts | 2 + .../viewer/bookings/addGuests.handler.ts | 1 + .../viewer/bookings/confirm.handler.ts | 2 + .../routers/viewer/bookings/get.handler.ts | 1 + .../bookings/requestReschedule.handler.ts | 1 + .../viewer/payments/chargeCard.handler.ts | 1 + .../workflows/activateEventType.handler.ts | 2 + .../server/routers/viewer/workflows/util.ts | 2 + packages/types/Calendar.d.ts | 1 + 42 files changed, 136 insertions(+), 10 deletions(-) create mode 100644 packages/prisma/migrations/20250420171801_hide_organizer_email/migration.sql diff --git a/apps/web/lib/booking.ts b/apps/web/lib/booking.ts index d138fa1a64..608660aff0 100644 --- a/apps/web/lib/booking.ts +++ b/apps/web/lib/booking.ts @@ -39,6 +39,7 @@ export const getEventTypesFromDB = async (id: number) => { currency: true, bookingFields: true, allowReschedulingPastBookings: true, + hideOrganizerEmail: true, disableCancelling: true, disableRescheduling: true, disableGuests: true, diff --git a/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx index 0997b4655b..c338799f80 100644 --- a/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx +++ b/apps/web/modules/bookings/views/bookings-single-view.getServerSideProps.tsx @@ -132,6 +132,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) { metadata: eventTypeMetaDataSchemaWithTypedApps.parse(eventTypeRaw.metadata), recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent), customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs), + hideOrganizerEmail: eventTypeRaw.hideOrganizerEmail, bookingFields: eventTypeRaw.bookingFields.map((field) => { return { ...field, diff --git a/apps/web/modules/bookings/views/bookings-single-view.tsx b/apps/web/modules/bookings/views/bookings-single-view.tsx index ffd6b55fae..59a9c60406 100644 --- a/apps/web/modules/bookings/views/bookings-single-view.tsx +++ b/apps/web/modules/bookings/views/bookings-single-view.tsx @@ -602,9 +602,11 @@ export default function Success(props: PageProps) { {t("Host")} -

- {bookingInfo?.userPrimaryEmail ?? bookingInfo.user.email} -

+ {!bookingInfo.eventType?.hideOrganizerEmail && ( +

+ {bookingInfo?.userPrimaryEmail ?? bookingInfo.user.email} +

+ )} )} {bookingInfo?.attendees.map((attendee) => ( diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index b2c178942f..1949e5c654 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -3140,6 +3140,8 @@ "meeting_satisfaction_question": "How satisfied were you with your recent meeting?", "meeting_not_joined_question": "didn't join the meeting?", "reschedule_cta_short": "Reschedule here.", + "hide_organizer_email": "Hide organizer's email", + "hide_organizer_email_description": "Hide organizer's email address from the booking screen, email notifications, and calendar events", "you_and_conjunction": "You &", "select_verified_email": "Select a verified email", "custom_reply_to_email_title": "Custom 'Reply-To' email", diff --git a/packages/app-store/vital/lib/reschedule.ts b/packages/app-store/vital/lib/reschedule.ts index 285ff44afa..4de1595efe 100644 --- a/packages/app-store/vital/lib/reschedule.ts +++ b/packages/app-store/vital/lib/reschedule.ts @@ -118,6 +118,7 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => { tAttendees ), organizer: userOwnerAsPeopleType, + hideOrganizerEmail: bookingToReschedule.eventType?.hideOrganizerEmail, team: !!bookingToReschedule.eventType?.team ? { name: bookingToReschedule.eventType.team.name, diff --git a/packages/app-store/wipemycalother/lib/reschedule.ts b/packages/app-store/wipemycalother/lib/reschedule.ts index 8974608ee7..301061c0bf 100644 --- a/packages/app-store/wipemycalother/lib/reschedule.ts +++ b/packages/app-store/wipemycalother/lib/reschedule.ts @@ -35,6 +35,7 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => { eventType: { select: { metadata: true, + hideOrganizerEmail: true, team: { select: { id: true, @@ -120,6 +121,7 @@ const Reschedule = async (bookingUid: string, cancellationReason: string) => { tAttendees ), organizer: userOwnerAsPeopleType, + hideOrganizerEmail: bookingToReschedule.eventType?.hideOrganizerEmail, team: !!bookingToReschedule.eventType?.team ? { name: bookingToReschedule.eventType.team.name, diff --git a/packages/emails/lib/generateIcsString.test.ts b/packages/emails/lib/generateIcsString.test.ts index d271de9e5c..5e1538f075 100644 --- a/packages/emails/lib/generateIcsString.test.ts +++ b/packages/emails/lib/generateIcsString.test.ts @@ -31,9 +31,14 @@ const testIcsStringContains = ({ // Sometimes the deeply equal stringMatching error appears. Don't want to add flakey tests // expect(icsString).toEqual(expect.stringContaining(`SUMMARY:${event.title}`)); expect(icsString).toEqual(expect.stringContaining(`DTSTART:${DTSTART}`)); - expect(icsString).toEqual( - expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`) - ); + if (event.hideOrganizerEmail) { + expect(icsString).toEqual(expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}`)); + expect(icsString).not.toEqual(expect.stringContaining(`mailto:${event.organizer.email}`)); + } else { + expect(icsString).toEqual( + expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`) + ); + } expect(icsString).toEqual(expect.stringContaining(`DTEND:${DTEND}`)); expect(icsString).toEqual(expect.stringContaining(`STATUS:${status}`)); // Getting an error expected icsString to deeply equal stringMatching @@ -158,4 +163,47 @@ describe("generateIcsString", () => { expect(icsString).toEqual(expect.stringContaining(`LOCATION:${event.location}`)); }); }); + + describe("hidden organizer email", () => { + test("when hideOrganizerEmail is true, organizer email should be omitted", () => { + const event = buildCalendarEvent({ + iCalSequence: 0, + attendees: [buildPerson()], + hideOrganizerEmail: true, + }); + const status = "CONFIRMED"; + + const icsString = generateIcsString({ + event: event, + status, + }); + + const assertedIcsString = assertHasIcsString(icsString); + + expect(assertedIcsString).toEqual(expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}`)); + expect(assertedIcsString).not.toEqual( + expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:`) + ); + }); + + test("when hideOrganizerEmail is false, organizer email should be included", () => { + const event = buildCalendarEvent({ + iCalSequence: 0, + attendees: [buildPerson()], + hideOrganizerEmail: false, + }); + const status = "CONFIRMED"; + + const icsString = generateIcsString({ + event: event, + status, + }); + + const assertedIcsString = assertHasIcsString(icsString); + + expect(assertedIcsString).toEqual( + expect.stringContaining(`ORGANIZER;CN=${event.organizer.name}:mailto:${event.organizer.email}`) + ); + }); + }); }); diff --git a/packages/emails/lib/generateIcsString.ts b/packages/emails/lib/generateIcsString.ts index 1eca2818ff..1541734bec 100644 --- a/packages/emails/lib/generateIcsString.ts +++ b/packages/emails/lib/generateIcsString.ts @@ -30,6 +30,7 @@ export type ICSCalendarEvent = Pick< | "team" | "type" | "hideCalendarEventDetails" + | "hideOrganizerEmail" >; const toICalDateArray = (date: string): DateArray => { @@ -73,7 +74,10 @@ const generateIcsString = ({ productId: "calcom/ics", title: event.title, description: getRichDescription(event, t), - organizer: { name: event.organizer.name, email: event.organizer.email }, + organizer: { + name: event.organizer.name, + ...(event.hideOrganizerEmail ? {} : { email: event.organizer.email }), + }, ...{ recurrenceRule }, attendees: [ ...event.attendees.map((attendee: Person) => ({ diff --git a/packages/emails/src/components/WhoInfo.tsx b/packages/emails/src/components/WhoInfo.tsx index 27341a1933..5ebde366d9 100644 --- a/packages/emails/src/components/WhoInfo.tsx +++ b/packages/emails/src/components/WhoInfo.tsx @@ -33,7 +33,7 @@ export function WhoInfo(props: { calEvent: CalendarEvent; t: TFunction }) { {props.calEvent.team?.members.map((member) => ( diff --git a/packages/features/CalendarEventBuilder.ts b/packages/features/CalendarEventBuilder.ts index 447664775f..a4d9bae742 100644 --- a/packages/features/CalendarEventBuilder.ts +++ b/packages/features/CalendarEventBuilder.ts @@ -47,6 +47,7 @@ export class CalendarEventBuilder { id: number; hideCalendarNotes?: boolean; hideCalendarEventDetails?: boolean; + hideOrganizerEmail?: boolean; schedulingType?: SchedulingType | null; seatsPerTimeSlot?: number | null; seatsShowAttendees?: boolean | null; @@ -60,6 +61,7 @@ export class CalendarEventBuilder { eventTypeId: eventType.id, hideCalendarNotes: eventType.hideCalendarNotes, hideCalendarEventDetails: eventType.hideCalendarEventDetails, + hideOrganizerEmail: eventType.hideOrganizerEmail, schedulingType: eventType.schedulingType, seatsPerTimeSlot: eventType.seatsPerTimeSlot, // if seats are not enabled we should default true diff --git a/packages/features/bookings/lib/getBookingToDelete.ts b/packages/features/bookings/lib/getBookingToDelete.ts index ea5d1c1422..590d445075 100644 --- a/packages/features/bookings/lib/getBookingToDelete.ts +++ b/packages/features/bookings/lib/getBookingToDelete.ts @@ -73,6 +73,7 @@ export async function getBookingToDelete(id: number | undefined, uid: string | u bookingFields: true, seatsShowAttendees: true, metadata: true, + hideOrganizerEmail: true, schedulingType: true, customReplyToEmail: true, hosts: { diff --git a/packages/features/bookings/lib/getUserBooking.ts b/packages/features/bookings/lib/getUserBooking.ts index 703486b85e..edb1962a4f 100644 --- a/packages/features/bookings/lib/getUserBooking.ts +++ b/packages/features/bookings/lib/getUserBooking.ts @@ -50,6 +50,7 @@ const getUserBooking = async (uid: string) => { slug: true, timeZone: true, schedulingType: true, + hideOrganizerEmail: true, }, }, seatsReferences: { diff --git a/packages/features/bookings/lib/handleCancelBooking.ts b/packages/features/bookings/lib/handleCancelBooking.ts index 69fb31c7c1..fd953d6c4d 100644 --- a/packages/features/bookings/lib/handleCancelBooking.ts +++ b/packages/features/bookings/lib/handleCancelBooking.ts @@ -265,6 +265,7 @@ async function handler(input: CancelBookingInput) { platformClientId, platformRescheduleUrl, platformCancelUrl, + hideOrganizerEmail: bookingToDelete.eventType?.hideOrganizerEmail, platformBookingUrl, customReplyToEmail: bookingToDelete.eventType?.customReplyToEmail, }; diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts index 819b0ba679..47c45c312e 100644 --- a/packages/features/bookings/lib/handleNewBooking.ts +++ b/packages/features/bookings/lib/handleNewBooking.ts @@ -1019,6 +1019,7 @@ async function handler( id: eventType.id, hideCalendarNotes: eventType.hideCalendarNotes, hideCalendarEventDetails: eventType.hideCalendarEventDetails, + hideOrganizerEmail: eventType.hideOrganizerEmail, schedulingType: eventType.schedulingType, seatsPerTimeSlot: eventType.seatsPerTimeSlot, // if seats are not enabled we should default true diff --git a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts index 642a4e1600..277fc2451f 100644 --- a/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts +++ b/packages/features/bookings/lib/handleNewBooking/getEventTypesFromDB.ts @@ -70,6 +70,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => { destinationCalendar: true, hideCalendarNotes: true, hideCalendarEventDetails: true, + hideOrganizerEmail: true, seatsPerTimeSlot: true, recurringEvent: true, seatsShowAttendees: true, diff --git a/packages/features/credentials/handleDeleteCredential.ts b/packages/features/credentials/handleDeleteCredential.ts index 40a24ccf4c..b8ced4e222 100644 --- a/packages/features/credentials/handleDeleteCredential.ts +++ b/packages/features/credentials/handleDeleteCredential.ts @@ -251,6 +251,7 @@ const handleDeleteCredential = async ({ seatsPerTimeSlot: true, seatsShowAttendees: true, eventName: true, + hideOrganizerEmail: true, team: { select: { id: true, @@ -346,6 +347,7 @@ const handleDeleteCredential = async ({ cancellationReason: "Payment method removed by organizer", seatsPerTimeSlot: booking.eventType?.seatsPerTimeSlot, seatsShowAttendees: booking.eventType?.seatsShowAttendees, + hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, team: !!booking.eventType?.team ? { name: booking.eventType.team.name, diff --git a/packages/features/ee/round-robin/roundRobinManualReassignment.ts b/packages/features/ee/round-robin/roundRobinManualReassignment.ts index 381837e21d..6e9761db0a 100644 --- a/packages/features/ee/round-robin/roundRobinManualReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinManualReassignment.ts @@ -266,6 +266,7 @@ export const roundRobinManualReassignment = async ({ name: eventType.team?.name || "", id: eventType.team?.id || 0, }, + hideOrganizerEmail: eventType.hideOrganizerEmail, customInputs: isPrismaObjOrUndefined(booking.customInputs), ...getCalEventResponses({ bookingFields: eventType.bookingFields ?? null, diff --git a/packages/features/ee/round-robin/roundRobinReassignment.ts b/packages/features/ee/round-robin/roundRobinReassignment.ts index c581baa22d..ae748558dd 100644 --- a/packages/features/ee/round-robin/roundRobinReassignment.ts +++ b/packages/features/ee/round-robin/roundRobinReassignment.ts @@ -318,6 +318,7 @@ export const roundRobinReassignment = async ({ bookingFields: eventType?.bookingFields ?? null, booking, }), + hideOrganizerEmail: eventType.hideOrganizerEmail, customReplyToEmail: eventType?.customReplyToEmail, location: bookingLocation, ...(platformClientParams ? platformClientParams : {}), diff --git a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts index 5598894fcf..2bf1b18153 100644 --- a/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts +++ b/packages/features/ee/workflows/lib/reminders/smsReminderManager.ts @@ -64,6 +64,7 @@ export type BookingInfo = { metadata?: Prisma.JsonValue; cancellationReason?: string | null; rescheduleReason?: string | null; + hideOrganizerEmail?: boolean; }; export type ScheduleTextReminderAction = Extract< diff --git a/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx b/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx index 0663bd525c..be706ca4a0 100644 --- a/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx +++ b/packages/features/eventtypes/components/tabs/advanced/EventAdvancedTab.tsx @@ -95,6 +95,7 @@ export type EventAdvancedTabCustomClassNames = { seatsInput: InputClassNames; }; timezoneLock?: SettingsToggleClassNames; + hideOrganizerEmail?: SettingsToggleClassNames; eventTypeColors?: SettingsToggleClassNames & { warningText?: string; }; @@ -491,6 +492,7 @@ export const EventAdvancedTab = ({ const lockTimeZoneToggleOnBookingPageLocked = shouldLockDisableProps("lockTimeZoneToggleOnBookingPage"); const multiplePrivateLinksLocked = shouldLockDisableProps("multiplePrivateLinks"); const reschedulingPastBookingsLocked = shouldLockDisableProps("allowReschedulingPastBookings"); + const hideOrganizerEmailLocked = shouldLockDisableProps("hideOrganizerEmail"); const customReplyToEmailLocked = shouldLockDisableProps("customReplyToEmail"); const disableCancellingLocked = shouldLockDisableProps("disableCancelling"); @@ -971,6 +973,26 @@ export const EventAdvancedTab = ({ )} /> + ( + onChange(e)} + data-testid="hide-organizer-email" + /> + )} + /> ( diff --git a/packages/lib/CalEventParser.ts b/packages/lib/CalEventParser.ts index 1578be3410..d4eac36f92 100644 --- a/packages/lib/CalEventParser.ts +++ b/packages/lib/CalEventParser.ts @@ -32,7 +32,7 @@ export const getWhen = ( export const getWho = ( calEvent: Pick< CalendarEvent, - "attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team" + "attendees" | "seatsPerTimeSlot" | "seatsShowAttendees" | "organizer" | "team" | "hideOrganizerEmail" >, t: TFunction ) => { @@ -49,7 +49,9 @@ export const getWho = ( ) .join("\n"); - const organizer = `${calEvent.organizer.name} - ${t("organizer")}\n${calEvent.organizer.email}`; + const organizer = calEvent.hideOrganizerEmail + ? `${calEvent.organizer.name} - ${t("organizer")}` + : `${calEvent.organizer.name} - ${t("organizer")}\n${calEvent.organizer.email}`; const teamMembers = calEvent.team?.members ? calEvent.team.members diff --git a/packages/lib/buildCalEventFromBooking.ts b/packages/lib/buildCalEventFromBooking.ts index 87f4154868..631353ce15 100644 --- a/packages/lib/buildCalEventFromBooking.ts +++ b/packages/lib/buildCalEventFromBooking.ts @@ -36,6 +36,7 @@ type EventType = { recurringEvent: Prisma.JsonValue | null; seatsPerTimeSlot: number | null; seatsShowAttendees: boolean | null; + hideOrganizerEmail: boolean; customReplyToEmail: string | null; }; @@ -94,6 +95,7 @@ export const buildCalEventFromBooking = async ({ language: { translate: tOrganizer, locale: organizer.locale ?? "en" }, }, attendees: attendeesList, + hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, uid: booking.uid, recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent), location, diff --git a/packages/lib/builders/CalendarEvent/class.ts b/packages/lib/builders/CalendarEvent/class.ts index a4bd03c949..1ef147a3ad 100644 --- a/packages/lib/builders/CalendarEvent/class.ts +++ b/packages/lib/builders/CalendarEvent/class.ts @@ -31,6 +31,7 @@ class CalendarEventClass implements CalendarEvent { rejectionReason?: string | null; hideCalendarNotes?: boolean; hideCalendarEventDetails?: boolean; + hideOrganizerEmail?: boolean; additionalNotes?: string | null | undefined; recurrence?: string; iCalUID?: string | null; diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts index 6e41bee897..2201f054fe 100644 --- a/packages/lib/defaultEvents.ts +++ b/packages/lib/defaultEvents.ts @@ -92,6 +92,7 @@ const commons = { disableRescheduling: false, onlyShowFirstAvailableSlot: false, allowReschedulingPastBookings: false, + hideOrganizerEmail: false, id: 0, hideCalendarNotes: false, hideCalendarEventDetails: false, diff --git a/packages/lib/payment/getBooking.ts b/packages/lib/payment/getBooking.ts index 92fad9676f..c50444cb9e 100644 --- a/packages/lib/payment/getBooking.ts +++ b/packages/lib/payment/getBooking.ts @@ -41,6 +41,7 @@ export async function getBooking(bookingId: number) { length: true, price: true, requiresConfirmation: true, + hideOrganizerEmail: true, metadata: true, customReplyToEmail: true, title: true, @@ -159,6 +160,7 @@ export async function getBooking(bookingId: number) { language: { translate: t, locale: user.locale ?? "en" }, id: user.id, }, + hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, team: !!booking.eventType?.team ? { name: booking.eventType.team.name, diff --git a/packages/lib/server/eventTypeSelect.ts b/packages/lib/server/eventTypeSelect.ts index 23dc40c4ef..53ca0cfad2 100644 --- a/packages/lib/server/eventTypeSelect.ts +++ b/packages/lib/server/eventTypeSelect.ts @@ -39,6 +39,7 @@ export const eventTypeSelect = Prisma.validator()({ seatsPerTimeSlot: true, onlyShowFirstAvailableSlot: true, allowReschedulingPastBookings: true, + hideOrganizerEmail: true, seatsShowAttendees: true, seatsShowAvailabilityCount: true, scheduleId: true, diff --git a/packages/lib/server/repository/booking.ts b/packages/lib/server/repository/booking.ts index 1ebd0e95c1..4265a42110 100644 --- a/packages/lib/server/repository/booking.ts +++ b/packages/lib/server/repository/booking.ts @@ -212,6 +212,7 @@ export class BookingRepository { requiresConfirmationWillBlockSlot: true, requiresConfirmation: true, allowReschedulingPastBookings: true, + hideOrganizerEmail: true, }, }, ...(seatedEvent && { diff --git a/packages/lib/server/repository/eventType.ts b/packages/lib/server/repository/eventType.ts index e47914fb9f..120574d138 100644 --- a/packages/lib/server/repository/eventType.ts +++ b/packages/lib/server/repository/eventType.ts @@ -517,6 +517,7 @@ export class EventTypeRepository { durationLimits: true, assignAllTeamMembers: true, allowReschedulingPastBookings: true, + hideOrganizerEmail: true, assignRRMembersUsingSegment: true, rrSegmentQueryValue: true, isRRWeightsEnabled: true, @@ -803,6 +804,7 @@ export class EventTypeRepository { periodEndDate: true, onlyShowFirstAvailableSlot: true, allowReschedulingPastBookings: true, + hideOrganizerEmail: true, periodCountCalendarDays: true, rescheduleWithSameRoundRobinHost: true, periodDays: true, diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts index 6de5be6615..7640d0eba6 100644 --- a/packages/lib/test/builder.ts +++ b/packages/lib/test/builder.ts @@ -150,6 +150,7 @@ export const buildEventType = (eventType?: Partial): EventType => { autoTranslateDescriptionEnabled: false, useEventLevelSelectedCalendars: false, allowReschedulingPastBookings: false, + hideOrganizerEmail: false, customReplyToEmail: null, ...eventType, }; diff --git a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts index 402ac35e7d..a8fe9f9669 100644 --- a/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts +++ b/packages/platform/atoms/event-types/hooks/useEventTypeForm.ts @@ -87,6 +87,7 @@ export const useEventTypeForm = ({ slotInterval: eventType.slotInterval, minimumBookingNotice: eventType.minimumBookingNotice, allowReschedulingPastBookings: eventType.allowReschedulingPastBookings, + hideOrganizerEmail: eventType.hideOrganizerEmail, metadata: eventType.metadata, hosts: eventType.hosts.sort((a, b) => sortHosts(a, b, eventType.isRRWeightsEnabled)), successRedirectUrl: eventType.successRedirectUrl || "", diff --git a/packages/prisma/migrations/20250420171801_hide_organizer_email/migration.sql b/packages/prisma/migrations/20250420171801_hide_organizer_email/migration.sql new file mode 100644 index 0000000000..20ad5b0285 --- /dev/null +++ b/packages/prisma/migrations/20250420171801_hide_organizer_email/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "EventType" ADD COLUMN "hideOrganizerEmail" BOOLEAN NOT NULL DEFAULT false; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 858f419681..22ec5085d8 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -165,6 +165,7 @@ model EventType { maxLeadThreshold Int? selectedCalendars SelectedCalendar[] allowReschedulingPastBookings Boolean @default(false) + hideOrganizerEmail Boolean @default(false) /// @zod.custom(imports.emailSchema) customReplyToEmail String? diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts index faeca3b1ee..6fbaafa8dc 100644 --- a/packages/prisma/zod-utils.ts +++ b/packages/prisma/zod-utils.ts @@ -665,6 +665,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit { bookingFields: true, disableGuests: true, metadata: true, + hideOrganizerEmail: true, customInputs: true, parentId: true, customReplyToEmail: true, @@ -203,6 +204,7 @@ export const Handler = async ({ ctx, input }: Options) => { timeFormat: getTimeFormatStringFromUserTimeFormat(user.timeFormat), language: { translate: tOrganizer, locale: user.locale ?? "en" }, }, + hideOrganizerEmail: updatedBooking.eventType?.hideOrganizerEmail, attendees: attendeesList, location: updatedBooking.location ?? "", uid: updatedBooking.uid, diff --git a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts index 6a902425b2..fc8419e51a 100644 --- a/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/addGuests.handler.ts @@ -133,6 +133,7 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => { timeZone: organizer.timeZone, language: { translate: tOrganizer, locale: organizer.locale ?? "en" }, }, + hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, attendees: attendeesList, uid: booking.uid, recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent), diff --git a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts index b3ee8293bd..e05aa51615 100644 --- a/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/confirm.handler.ts @@ -80,6 +80,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { description: true, price: true, bookingFields: true, + hideOrganizerEmail: true, disableGuests: true, customReplyToEmail: true, metadata: true, @@ -226,6 +227,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => { ? [booking.user?.destinationCalendar] : [], requiresConfirmation: booking?.eventType?.requiresConfirmation ?? false, + hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, eventTypeId: booking.eventType?.id, customReplyToEmail: booking.eventType?.customReplyToEmail, team: !!booking.eventType?.team diff --git a/packages/trpc/server/routers/viewer/bookings/get.handler.ts b/packages/trpc/server/routers/viewer/bookings/get.handler.ts index 603808cc56..2bec284781 100644 --- a/packages/trpc/server/routers/viewer/bookings/get.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/get.handler.ts @@ -103,6 +103,7 @@ export async function getBookings({ eventTypeColor: true, customReplyToEmail: true, allowReschedulingPastBookings: true, + hideOrganizerEmail: true, disableCancelling: true, disableRescheduling: true, schedulingType: true, diff --git a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts index f7b7ed05df..eb1fb4c43c 100644 --- a/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts +++ b/packages/trpc/server/routers/viewer/bookings/requestReschedule.handler.ts @@ -195,6 +195,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule type: event && event.slug ? event.slug : bookingToReschedule.title, startTime: bookingToReschedule.startTime.toISOString(), endTime: bookingToReschedule.endTime.toISOString(), + hideOrganizerEmail: eventType?.hideOrganizerEmail, attendees: usersToPeopleType( // username field doesn't exists on attendee but could be in the future bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[], diff --git a/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts b/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts index cc9caf6c36..2ae45b5b07 100644 --- a/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts +++ b/packages/trpc/server/routers/viewer/payments/chargeCard.handler.ts @@ -74,6 +74,7 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions language: { translate: tOrganizer, locale: booking.user?.locale ?? "en" }, }, attendees: attendeesList, + hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, paymentInfo: { amount: booking.payment[0].amount, currency: booking.payment[0].currency, diff --git a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts index fc433c7483..29fc9b2bf0 100644 --- a/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts +++ b/packages/trpc/server/routers/viewer/workflows/activateEventType.handler.ts @@ -220,6 +220,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType }, }, }, + hideOrganizerEmail: true, }, }, user: true, @@ -254,6 +255,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType endTime: booking.endTime.toISOString(), title: booking.title, language: { locale: booking?.user?.locale || defaultLocale }, + hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, eventType: { slug: booking.eventType?.slug || "", schedulingType: booking.eventType?.schedulingType, diff --git a/packages/trpc/server/routers/viewer/workflows/util.ts b/packages/trpc/server/routers/viewer/workflows/util.ts index b0a3438f80..1b6e32aa03 100644 --- a/packages/trpc/server/routers/viewer/workflows/util.ts +++ b/packages/trpc/server/routers/viewer/workflows/util.ts @@ -57,6 +57,7 @@ export const bookingSelect = { slug: true, id: true, schedulingType: true, + hideOrganizerEmail: true, customReplyToEmail: true, hosts: { select: { @@ -643,6 +644,7 @@ export async function scheduleBookingReminders( endTime: booking.endTime?.toISOString(), title: booking.title, language: { locale: booking?.user?.locale || defaultLocale }, + hideOrganizerEmail: booking.eventType?.hideOrganizerEmail, eventType: { slug: booking.eventType?.slug || "", schedulingType: booking.eventType?.schedulingType, diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts index 9853dd0c20..a9a5244070 100644 --- a/packages/types/Calendar.d.ts +++ b/packages/types/Calendar.d.ts @@ -205,6 +205,7 @@ export interface CalendarEvent { schedulingType?: SchedulingType | null; iCalUID?: string | null; iCalSequence?: number | null; + hideOrganizerEmail?: boolean; // It has responses to all the fields(system + user) responses?: CalEventResponses | null;