fix/google-calendar-removes-attendees-when-seats-5336 (#5366)
* Adding missing code that copied booking attendees to event attendees * Clean up code * Fix types * Fix types * Doing deep clone for sending seats email
This commit is contained in:
@@ -54,7 +54,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
||||
<div className="flex flex-col space-y-8">
|
||||
{/**
|
||||
* Only display calendar selector if user has connected calendars AND if it's not
|
||||
* a team event. Since we don't have logic to handle each attende calendar (for now).
|
||||
* a team event. Since we don't have logic to handle each attendee calendar (for now).
|
||||
* This will fallback to each user selected destination calendar.
|
||||
*/}
|
||||
{!!connectedCalendarsQuery.data?.connectedCalendars.length && !team && (
|
||||
@@ -292,7 +292,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
||||
description={t("offer_seats_description")}
|
||||
checked={value}
|
||||
onCheckedChange={(e) => {
|
||||
// Enabling seats will disable guests and requiring confimation until fully supported
|
||||
// Enabling seats will disable guests and requiring confirmation until fully supported
|
||||
if (e) {
|
||||
formMethods.setValue("disableGuests", true);
|
||||
formMethods.setValue("requiresConfirmation", false);
|
||||
@@ -316,9 +316,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
|
||||
label={t("number_of_seats")}
|
||||
type="number"
|
||||
defaultValue={value || 2}
|
||||
min={1}
|
||||
addOnSuffix={<>{t("seats")}</>}
|
||||
onChange={(e) => {
|
||||
onChange(Number(e.target.value));
|
||||
onChange(Math.abs(Number(e.target.value)));
|
||||
}}
|
||||
/>
|
||||
<div className="mt-2">
|
||||
|
||||
@@ -160,6 +160,10 @@ export default class GoogleCalendarService implements Calendar {
|
||||
async updateEvent(uid: string, event: CalendarEvent, externalCalendarId: string): Promise<any> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const myGoogleAuth = await this.auth.getToken();
|
||||
const eventAttendees = event.attendees.map(({ id, ...rest }) => ({
|
||||
...rest,
|
||||
responseStatus: "accepted",
|
||||
}));
|
||||
const payload: calendar_v3.Schema$Event = {
|
||||
summary: event.title,
|
||||
description: getRichDescription(event),
|
||||
@@ -179,10 +183,7 @@ export default class GoogleCalendarService implements Calendar {
|
||||
responseStatus: "accepted",
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
...event.attendees.map(({ id, ...rest }) => ({
|
||||
...rest,
|
||||
responseStatus: "accepted",
|
||||
})),
|
||||
...eventAttendees,
|
||||
],
|
||||
reminders: {
|
||||
useDefault: true,
|
||||
|
||||
@@ -7,8 +7,14 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi
|
||||
import { getEventLocationTypeFromApp } from "@calcom/app-store/locations";
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { Attendee } from "@calcom/prisma/client";
|
||||
import { createdEventSchema } from "@calcom/prisma/zod-utils";
|
||||
import type { AdditionalInformation, CalendarEvent, NewCalendarEventType } from "@calcom/types/Calendar";
|
||||
import type {
|
||||
AdditionalInformation,
|
||||
CalendarEvent,
|
||||
NewCalendarEventType,
|
||||
Person,
|
||||
} from "@calcom/types/Calendar";
|
||||
import { CredentialPayload, CredentialWithAppName } from "@calcom/types/Credential";
|
||||
import type { Event } from "@calcom/types/Event";
|
||||
import type {
|
||||
@@ -294,6 +300,7 @@ export default class EventManager {
|
||||
}
|
||||
|
||||
public async updateCalendarAttendees(event: CalendarEvent, booking: PartialBooking) {
|
||||
// @NOTE: This function is only used for updating attendees on a calendar event. Can we remove this?
|
||||
await this.updateAllCalendarEvents(event, booking);
|
||||
}
|
||||
|
||||
@@ -430,11 +437,14 @@ export default class EventManager {
|
||||
try {
|
||||
// Bookings should only have one calendar reference
|
||||
calendarReference = booking.references.filter((reference) => reference.type.includes("_calendar"))[0];
|
||||
if (!calendarReference) throw new Error("bookingRef");
|
||||
|
||||
if (!calendarReference) {
|
||||
throw new Error("bookingRef");
|
||||
}
|
||||
const { uid: bookingRefUid, externalCalendarId: bookingExternalCalendarId } = calendarReference;
|
||||
|
||||
if (!bookingExternalCalendarId) throw new Error("externalCalendarId");
|
||||
if (!bookingExternalCalendarId) {
|
||||
throw new Error("externalCalendarId");
|
||||
}
|
||||
|
||||
let result = [];
|
||||
if (calendarReference.credentialId) {
|
||||
@@ -458,13 +468,15 @@ export default class EventManager {
|
||||
.map(async (cred) => {
|
||||
const calendarReference = booking.references.find((ref) => ref.type === cred.type);
|
||||
if (!calendarReference)
|
||||
return {
|
||||
appName: cred.appName,
|
||||
type: cred.type,
|
||||
success: false,
|
||||
uid: "",
|
||||
originalEvent: event,
|
||||
};
|
||||
if (!calendarReference) {
|
||||
return {
|
||||
appName: cred.appName,
|
||||
type: cred.type,
|
||||
success: false,
|
||||
uid: "",
|
||||
originalEvent: event,
|
||||
};
|
||||
}
|
||||
const { externalCalendarId: bookingExternalCalendarId, meetingId: bookingRefUid } =
|
||||
calendarReference;
|
||||
return await updateEvent(cred, event, bookingRefUid ?? null, bookingExternalCalendarId ?? null);
|
||||
@@ -474,7 +486,9 @@ export default class EventManager {
|
||||
return Promise.all(result);
|
||||
} catch (error) {
|
||||
let message = `Tried to 'updateAllCalendarEvents' but there was no '{thing}' for '${credential?.type}', userId: '${credential?.userId}', bookingId: '${booking?.id}'`;
|
||||
if (error instanceof Error) message = message.replace("{thing}", error.message);
|
||||
if (error instanceof Error) {
|
||||
message = message.replace("{thing}", error.message);
|
||||
}
|
||||
console.error(message);
|
||||
return Promise.resolve([
|
||||
{
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
WebhookTriggerEvents,
|
||||
} from "@prisma/client";
|
||||
import async from "async";
|
||||
import { cloneDeep } from "lodash";
|
||||
import type { NextApiRequest } from "next";
|
||||
import { RRule } from "rrule";
|
||||
import short from "short-uuid";
|
||||
@@ -482,8 +483,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
|
||||
// For seats, if the booking already exists then we want to add the new attendee to the existing booking
|
||||
if (reqBody.bookingUid) {
|
||||
if (!eventType.seatsPerTimeSlot)
|
||||
if (!eventType.seatsPerTimeSlot) {
|
||||
throw new HttpError({ statusCode: 404, message: "Event type does not have seats" });
|
||||
}
|
||||
|
||||
const booking = await prisma.booking.findUnique({
|
||||
where: {
|
||||
@@ -505,7 +507,9 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!booking) throw new HttpError({ statusCode: 404, message: "Booking not found" });
|
||||
if (!booking) {
|
||||
throw new HttpError({ statusCode: 404, message: "Booking not found" });
|
||||
}
|
||||
|
||||
// Need to add translation for attendees to pass type checks. Since these values are never written to the db we can just use the new attendee language
|
||||
const bookingAttendees = booking.attendees.map((attendee) => {
|
||||
@@ -514,11 +518,13 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
|
||||
evt = { ...evt, attendees: [...bookingAttendees, invitee[0]] };
|
||||
|
||||
if (eventType.seatsPerTimeSlot <= booking.attendees.length)
|
||||
if (eventType.seatsPerTimeSlot <= booking.attendees.length) {
|
||||
throw new HttpError({ statusCode: 409, message: "Booking seats are full" });
|
||||
}
|
||||
|
||||
if (booking.attendees.some((attendee) => attendee.email === invitee[0].email))
|
||||
if (booking.attendees.find((attendee) => attendee.email === invitee[0].email)) {
|
||||
throw new HttpError({ statusCode: 409, message: "Already signed up for time slot" });
|
||||
}
|
||||
|
||||
await prisma.booking.update({
|
||||
where: {
|
||||
@@ -537,8 +543,13 @@ async function handler(req: NextApiRequest & { userId?: number | undefined }) {
|
||||
});
|
||||
|
||||
const newSeat = booking.attendees.length !== 0;
|
||||
|
||||
await sendScheduledSeatsEmails(evt, invitee[0], newSeat, !!eventType.seatsShowAttendees);
|
||||
/**
|
||||
* Remember objects are passed into functions as references
|
||||
* so if you modify it in a inner function it will be modified in the outer function
|
||||
* deep cloning evt to avoid this
|
||||
*/
|
||||
const copyEvent = cloneDeep(evt);
|
||||
await sendScheduledSeatsEmails(copyEvent, invitee[0], newSeat, !!eventType.seatsShowAttendees);
|
||||
|
||||
const credentials = await refreshCredentials(organizerUser.credentials);
|
||||
const eventManager = new EventManager({ ...organizerUser, credentials });
|
||||
|
||||
@@ -5,9 +5,9 @@ import { FixedSizeList as List } from "react-window";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import Select from "@calcom/ui/form/Select";
|
||||
import TimezoneSelect, { ITimezone } from "@calcom/ui/form/TimezoneSelect";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import DatePicker from "@calcom/ui/v2/core/form/DatePicker";
|
||||
|
||||
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
|
||||
|
||||
@@ -5,9 +5,9 @@ import { FixedSizeList as List } from "react-window";
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import { inferQueryOutput, trpc } from "@calcom/trpc/react";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import Select from "@calcom/ui/form/Select";
|
||||
import TimezoneSelect, { ITimezone } from "@calcom/ui/form/TimezoneSelect";
|
||||
import { Avatar } from "@calcom/ui/components/avatar";
|
||||
import DatePicker from "@calcom/ui/v2/core/form/DatePicker";
|
||||
|
||||
import TeamAvailabilityTimes from "./TeamAvailabilityTimes";
|
||||
|
||||
@@ -6,8 +6,8 @@ import { HttpError } from "@calcom/lib/http-error";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@calcom/ui/Dialog";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { Form } from "@calcom/ui/form/fields";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { Form } from "@calcom/ui/form/fields";
|
||||
import showToast from "@calcom/ui/v2/core/notifications";
|
||||
|
||||
export function NewScheduleButton({ name = "new-schedule" }: { name?: string }) {
|
||||
|
||||
@@ -5,8 +5,8 @@ import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { EmptyScreen, SkeletonText } from "@calcom/ui/v2";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { EmptyScreen, SkeletonText } from "@calcom/ui/v2";
|
||||
import Meta from "@calcom/ui/v2/core/Meta";
|
||||
import { getLayout } from "@calcom/ui/v2/core/layouts/SettingsLayout";
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { App } from "@calcom/types/App";
|
||||
import { Icon } from "@calcom/ui/Icon";
|
||||
import { showToast } from "@calcom/ui/v2";
|
||||
import { Button } from "@calcom/ui/components/button";
|
||||
import { showToast } from "@calcom/ui/v2";
|
||||
|
||||
interface AppCardProps {
|
||||
app: App;
|
||||
|
||||
Reference in New Issue
Block a user