diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx
index 875fa641eb..c2b1894322 100644
--- a/apps/web/components/booking/pages/BookingPage.tsx
+++ b/apps/web/components/booking/pages/BookingPage.tsx
@@ -115,14 +115,14 @@ const BookingPage = ({
const mutation = useMutation(createBooking, {
onSuccess: async (responseData) => {
- const { id, attendees, paymentUid } = responseData;
+ const { id, paymentUid } = responseData;
if (paymentUid) {
return await router.push(
createPaymentLink({
paymentUid,
date,
- name: attendees[0].name,
- email: attendees[0].email,
+ name: bookingForm.getValues("name"),
+ email: bookingForm.getValues("email"),
absolute: false,
})
);
@@ -136,8 +136,8 @@ const BookingPage = ({
eventSlug: eventType.slug,
username: profile.slug,
reschedule: !!rescheduleUid,
- name: attendees[0].name,
- email: attendees[0].email,
+ name: bookingForm.getValues("name"),
+ email: bookingForm.getValues("email"),
location: responseData.location,
eventName: profile.eventName || "",
bookingId: id,
@@ -523,7 +523,15 @@ const BookingPage = ({
)}
{!!eventType.seatsPerTimeSlot && (
-
+
= 0.5
+ ? "text-rose-600"
+ : booking && booking.attendees.length / eventType.seatsPerTimeSlot >= 0.33
+ ? "text-yellow-500"
+ : "text-bookinghighlight"
+ }`}
+ />
= 0.5
diff --git a/apps/web/components/v2/eventtype/EventAdvancedTab.tsx b/apps/web/components/v2/eventtype/EventAdvancedTab.tsx
index 85b7641e8e..09f515d94a 100644
--- a/apps/web/components/v2/eventtype/EventAdvancedTab.tsx
+++ b/apps/web/components/v2/eventtype/EventAdvancedTab.tsx
@@ -24,6 +24,7 @@ import {
TextField,
Tooltip,
} from "@calcom/ui/v2";
+import CheckboxField from "@calcom/ui/v2/core/form/Checkbox";
import CustomInputTypeForm from "@components/v2/eventtype/CustomInputTypeForm";
@@ -429,6 +430,13 @@ export const EventAdvancedTab = ({ eventType, team }: Pick
+
+ formMethods.setValue("seatsShowAttendees", e.target.checked)}
+ defaultChecked={!!eventType.seatsShowAttendees}
+ />
+
)}
/>
diff --git a/apps/web/pages/api/email.ts b/apps/web/pages/api/email.ts
index 57c81afa61..5b6b01da87 100644
--- a/apps/web/pages/api/email.ts
+++ b/apps/web/pages/api/email.ts
@@ -32,6 +32,18 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
timeZone: "America/Chihuahua",
language,
},
+ {
+ email: "pro@example.com",
+ name: "pro@example.com",
+ timeZone: "America/Chihuahua",
+ language,
+ },
+ {
+ email: "pro@example.com",
+ name: "pro@example.com",
+ timeZone: "America/Chihuahua",
+ language,
+ },
],
location: "Zoom video",
destinationCalendar: null,
@@ -48,7 +60,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write(
- renderEmail("OrganizerRequestReminderEmail", {
+ renderEmail("AttendeeScheduledEmail", {
attendee: evt.attendees[0],
calEvent: evt,
})
diff --git a/apps/web/pages/event-types/[type]/index.tsx b/apps/web/pages/event-types/[type]/index.tsx
index 810bd62a74..c8eadd7481 100644
--- a/apps/web/pages/event-types/[type]/index.tsx
+++ b/apps/web/pages/event-types/[type]/index.tsx
@@ -69,6 +69,7 @@ export type FormValues = {
periodCountCalendarDays: "1" | "0";
periodDates: { startDate: Date; endDate: Date };
seatsPerTimeSlot: number | null;
+ seatsShowAttendees: boolean | null;
seatsPerTimeSlotEnabled: boolean;
minimumBookingNotice: number;
beforeBufferTime: number;
@@ -232,6 +233,7 @@ const EventTypePage = (props: inferSSRProps) => {
beforeBufferTime,
afterBufferTime,
seatsPerTimeSlot,
+ seatsShowAttendees,
bookingLimits,
recurringEvent,
locations,
@@ -259,6 +261,7 @@ const EventTypePage = (props: inferSSRProps) => {
afterEventBuffer: afterBufferTime,
bookingLimits,
seatsPerTimeSlot,
+ seatsShowAttendees,
metadata,
});
}}>
@@ -391,6 +394,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
price: true,
destinationCalendar: true,
seatsPerTimeSlot: true,
+ seatsShowAttendees: true,
workflows: {
include: {
workflow: {
diff --git a/apps/web/pages/success.tsx b/apps/web/pages/success.tsx
index 907bdc77ce..1d63301bc8 100644
--- a/apps/web/pages/success.tsx
+++ b/apps/web/pages/success.tsx
@@ -141,7 +141,15 @@ type SuccessProps = inferSSRProps;
export default function Success(props: SuccessProps) {
const { t } = useLocale();
const router = useRouter();
- const { location: _location, name, reschedule, listingStatus, status, isSuccessBookingPage } = router.query;
+ const {
+ location: _location,
+ name,
+ email,
+ reschedule,
+ listingStatus,
+ status,
+ isSuccessBookingPage,
+ } = router.query;
const location: ReturnType = Array.isArray(_location)
? _location[0] || ""
: _location || "";
@@ -357,14 +365,23 @@ export default function Success(props: SuccessProps) {
{bookingInfo.user.email}
)}
- {bookingInfo?.attendees.map((attendee, index) => (
-
-
{attendee.name}
-
{attendee.email}
-
- ))}
+ {!!eventType.seatsShowAttendees
+ ? bookingInfo?.attendees
+ .filter((attendee) => attendee.email === email)
+ .map((attendee) => (
+
+
{attendee.name}
+
{attendee.email}
+
+ ))
+ : bookingInfo?.attendees.map((attendee, index) => (
+
+
{attendee.name}
+
{attendee.email}
+
+ ))}
>
)}
@@ -725,6 +742,7 @@ const getEventTypesFromDB = async (id: number) => {
},
},
metadata: true,
+ seatsShowAttendees: true,
},
});
diff --git a/apps/web/playwright/integrations.e2e.ts-snapshots/webhookResponse--calcom-web.txt b/apps/web/playwright/integrations.e2e.ts-snapshots/webhookResponse--calcom-web.txt
index 66acf4008b..4ba223d0b0 100644
--- a/apps/web/playwright/integrations.e2e.ts-snapshots/webhookResponse--calcom-web.txt
+++ b/apps/web/playwright/integrations.e2e.ts-snapshots/webhookResponse--calcom-web.txt
@@ -1 +1 @@
-{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","uid":"[redacted/dynamic]","bookingId":"[redacted/dynamic]","metadata":{},"additionalInformation":"[redacted/dynamic]"}}
+{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","bookingId":"[redacted/dynamic]","metadata":{},"additionalInformation":"[redacted/dynamic]"}}
diff --git a/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt b/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt
index 97234902d2..04144019b5 100644
--- a/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt
+++ b/apps/web/playwright/webhook.e2e.ts-snapshots/webhookResponse--calcom-web.txt
@@ -1 +1 @@
-{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","uid":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
\ No newline at end of file
+{"triggerEvent":"BOOKING_CREATED","createdAt":"[redacted/dynamic]","payload":{"type":"30 min","title":"30 min between PRO and Test Testson","description":"","additionalNotes":"","customInputs":{},"startTime":"[redacted/dynamic]","endTime":"[redacted/dynamic]","organizer":{"name":"PRO","email":"[redacted/dynamic]","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"},"attendees":[{"email":"test@example.com","name":"Test Testson","timeZone":"[redacted/dynamic]","language":"[redacted/dynamic]"}],"location":"[redacted/dynamic]","destinationCalendar":null,"hideCalendarNotes":false,"requiresConfirmation":"[redacted/dynamic]","eventTypeId":"[redacted/dynamic]","seatsShowAttendees":false,"uid":"[redacted/dynamic]","eventTitle":"30 min","eventDescription":null,"price":0,"currency":"usd","length":30,"bookingId":"[redacted/dynamic]","metadata":{},"status":"ACCEPTED","additionalInformation":"[redacted/dynamic]"}}
\ No newline at end of file
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 2556d8be0d..1474809f58 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -1278,6 +1278,7 @@
"limit_booking_frequency_description":"Limit how many times this event can be booked",
"add_limit":"Add Limit",
"team_name_required": "Team name required",
+ "show_attendees": "Share attendee information between guests",
"how_additional_inputs_as_variables": "How to use Additional Inputs as Variables",
"format": "Format",
"uppercase_for_letters": "Use uppercase for all letters",
diff --git a/packages/app-store/closecomothercalendar/test/lib/CalendarService.test.ts b/packages/app-store/closecomothercalendar/test/lib/CalendarService.test.ts
index 04d9781cdc..da4e316a6d 100644
--- a/packages/app-store/closecomothercalendar/test/lib/CalendarService.test.ts
+++ b/packages/app-store/closecomothercalendar/test/lib/CalendarService.test.ts
@@ -55,7 +55,7 @@ test("retrieve contact IDs: all exist", async () => {
const event = {
attendees,
- } as CalendarEvent;
+ } as { attendees: { email: string; name: string | null; id: string }[] };
CloseCom.prototype.contact = {
search: () => ({ data: attendees }),
@@ -225,7 +225,7 @@ test("prepare data to create custom activity type instance: one attendees, with
attendees,
startTime: now.toISOString(),
additionalNotes: "Some comment!",
- } as CalendarEvent;
+ } as any;
CloseCom.prototype.activity = {
type: {
diff --git a/packages/app-store/googlecalendar/lib/CalendarService.ts b/packages/app-store/googlecalendar/lib/CalendarService.ts
index e943c9fcaf..d1ff65166c 100644
--- a/packages/app-store/googlecalendar/lib/CalendarService.ts
+++ b/packages/app-store/googlecalendar/lib/CalendarService.ts
@@ -82,12 +82,22 @@ export default class GoogleCalendarService implements Calendar {
timeZone: calEventRaw.organizer.timeZone,
},
attendees: [
- { ...calEventRaw.organizer, organizer: true, responseStatus: "accepted" },
- ...calEventRaw.attendees.map((attendee) => ({ ...attendee, responseStatus: "accepted" })),
+ {
+ ...calEventRaw.organizer,
+ id: String(calEventRaw.organizer.id),
+ organizer: true,
+ responseStatus: "accepted",
+ },
+ // eslint-disable-next-line
+ ...calEventRaw.attendees.map(({ id, ...rest }) => ({
+ rest,
+ responseStatus: "accepted",
+ })),
],
reminders: {
useDefault: true,
},
+ guestsCanSeeOtherGuests: calEventRaw.seatsShowAttendees,
};
if (calEventRaw.location) {
@@ -160,10 +170,23 @@ export default class GoogleCalendarService implements Calendar {
dateTime: event.endTime,
timeZone: event.organizer.timeZone,
},
- attendees: [{ ...event.organizer, organizer: true, responseStatus: "accepted" }, ...event.attendees],
+ attendees: [
+ {
+ ...event.organizer,
+ id: String(event.organizer.id),
+ organizer: true,
+ responseStatus: "accepted",
+ },
+ // eslint-disable-next-line
+ ...event.attendees.map(({ id, ...rest }) => ({
+ rest,
+ responseStatus: "accepted",
+ })),
+ ],
reminders: {
useDefault: true,
},
+ guestsCanSeeOtherGuests: event.seatsShowAttendees,
};
if (event.location) {
diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts
index 75e4b43844..e112d40932 100644
--- a/packages/emails/email-manager.ts
+++ b/packages/emails/email-manager.ts
@@ -84,17 +84,18 @@ export const sendRescheduledEmails = async (calEvent: CalendarEvent) => {
export const sendScheduledSeatsEmails = async (
calEvent: CalendarEvent,
invitee: Person,
- newSeat: boolean
+ newSeat: boolean,
+ showAttendees: boolean
) => {
const emailsToSend: Promise[] = [];
emailsToSend.push(
new Promise((resolve, reject) => {
try {
- const scheduledEmail = new AttendeeScheduledEmail(calEvent, invitee);
+ const scheduledEmail = new AttendeeScheduledEmail(calEvent, invitee, showAttendees);
resolve(scheduledEmail.sendEmail());
} catch (e) {
- reject(console.error("AttendeeRescheduledEmail.sendEmail failed", e));
+ reject(console.error("AttendeeScheduledEmail.sendEmail failed", e));
}
})
);
diff --git a/packages/emails/templates/attendee-scheduled-email.ts b/packages/emails/templates/attendee-scheduled-email.ts
index 8a86a0393b..c0897daadf 100644
--- a/packages/emails/templates/attendee-scheduled-email.ts
+++ b/packages/emails/templates/attendee-scheduled-email.ts
@@ -12,14 +12,24 @@ import BaseEmail from "./_base-email";
export default class AttendeeScheduledEmail extends BaseEmail {
calEvent: CalendarEvent;
attendee: Person;
+ showAttendees: boolean | undefined;
t: TFunction;
- constructor(calEvent: CalendarEvent, attendee: Person) {
+ constructor(calEvent: CalendarEvent, attendee: Person, showAttendees?: boolean | undefined) {
super();
this.name = "SEND_BOOKING_CONFIRMATION";
this.calEvent = calEvent;
this.attendee = attendee;
+ this.showAttendees = showAttendees;
this.t = attendee.language.translate;
+
+ if (!this.showAttendees) {
+ this.calEvent.attendees = [
+ {
+ ...this.attendee,
+ },
+ ];
+ }
}
protected getiCalEventAsString(): string | undefined {
diff --git a/packages/features/bookings/lib/handleNewBooking.ts b/packages/features/bookings/lib/handleNewBooking.ts
index 4f9d594c47..51dee551c5 100644
--- a/packages/features/bookings/lib/handleNewBooking.ts
+++ b/packages/features/bookings/lib/handleNewBooking.ts
@@ -153,6 +153,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
hideCalendarNotes: true,
seatsPerTimeSlot: true,
recurringEvent: true,
+ seatsShowAttendees: true,
bookingLimits: true,
workflows: {
include: {
@@ -442,6 +443,7 @@ async function handler(req: NextApiRequest & { userId?: number }) {
hideCalendarNotes: eventType.hideCalendarNotes,
requiresConfirmation: eventType.requiresConfirmation ?? false,
eventTypeId: eventType.id,
+ seatsShowAttendees: !!eventType.seatsShowAttendees,
};
// For seats, if the booking already exists then we want to add the new attendee to the existing booking
@@ -502,7 +504,7 @@ async function handler(req: NextApiRequest & { userId?: number }) {
const newSeat = booking.attendees.length !== 0;
- await sendScheduledSeatsEmails(evt, invitee[0], newSeat);
+ await sendScheduledSeatsEmails(evt, invitee[0], newSeat, !!eventType.seatsShowAttendees);
const credentials = await refreshCredentials(organizerUser.credentials);
const eventManager = new EventManager({ ...organizerUser, credentials });
diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts
index 50c086fd8c..1968bc2c4c 100644
--- a/packages/lib/defaultEvents.ts
+++ b/packages/lib/defaultEvents.ts
@@ -76,6 +76,7 @@ const commons = {
currency: "usd",
schedulingType: SchedulingType.COLLECTIVE,
seatsPerTimeSlot: null,
+ seatsShowAttendees: null,
id: 0,
hideCalendarNotes: false,
recurringEvent: null,
diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts
index 8678ab1712..d4daa0dc51 100644
--- a/packages/lib/test/builder.ts
+++ b/packages/lib/test/builder.ts
@@ -19,7 +19,7 @@ export const buildPerson = (person?: Partial): Person => {
email: faker.internet.email(),
timeZone: faker.address.timeZone(),
username: faker.internet.userName(),
- id: faker.datatype.uuid(),
+ id: faker.datatype.number(),
language: {
locale: faker.random.locale(),
translate: (key: string) => key,
@@ -85,6 +85,7 @@ export const buildEventType = (eventType?: Partial): EventType => {
beforeEventBuffer: 0,
afterEventBuffer: 0,
seatsPerTimeSlot: null,
+ seatsShowAttendees: null,
schedulingType: null,
scheduleId: null,
bookingLimits: null,
diff --git a/packages/prisma/migrations/20220929132137_add_seats_hide_attendees/migration.sql b/packages/prisma/migrations/20220929132137_add_seats_hide_attendees/migration.sql
new file mode 100644
index 0000000000..8d53957011
--- /dev/null
+++ b/packages/prisma/migrations/20220929132137_add_seats_hide_attendees/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "EventType" ADD COLUMN "seatsShowAttendees" BOOLEAN;
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index 00b53dc320..cd96601c1c 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -68,6 +68,7 @@ model EventType {
beforeEventBuffer Int @default(0)
afterEventBuffer Int @default(0)
seatsPerTimeSlot Int?
+ seatsShowAttendees Boolean?
schedulingType SchedulingType?
schedule Schedule? @relation(fields: [scheduleId], references: [id])
scheduleId Int?
diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts
index ad7aab9506..5e003f1c48 100644
--- a/packages/types/Calendar.d.ts
+++ b/packages/types/Calendar.d.ts
@@ -22,7 +22,9 @@ export type Person = {
timeZone: string;
language: { translate: TFunction; locale: string };
username?: string;
- id?: string;
+ id?: number;
+ bookingId?: number;
+ locale?: string;
};
export type EventBusyDate = {
@@ -141,6 +143,7 @@ export interface CalendarEvent {
recurrence?: string;
recurringEvent?: RecurringEvent | null;
eventTypeId?: number | null;
+ seatsShowAttendees?: boolean | null;
}
export interface EntryPoint {