fix: use destination calendar email (#12821)

* fix: use destination calendar email

* fix: success page

* feat: add primary email in destination calendar

* fix: set destination bug

* chore: fix tests

* test: add test for different email

* wip: save progress

* feat: add switch and email in booking

* fix: use userPrimaryEmail

* chore: fix type errr

* chore: update buileder

* fix: type err in advanced tab

* fix: unit tests

* Update apps/web/public/static/locales/en/common.json

Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>

* fix: cancel, request reschedule, paymeny

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2024-02-13 10:05:00 -05:00
1 parent d5b45b757a
commit 7ba1557d1d
30 files changed
+162 -76

No files matched your search

@@ -30,6 +30,7 @@ import {
Label,
SettingsToggle,
showToast,
Switch,
TextField,
Tooltip,
} from "@calcom/ui";
@@ -44,10 +45,12 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const { data: user } = trpc.viewer.me.useQuery();
const formMethods = useFormContext<FormValues>();
const { t } = useLocale();
const [showEventNameTip, setShowEventNameTip] = useState(false);
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
const [useEventTypeDestinationCalendarEmail, setUseEventTypeDestinationCalendarEmail] = useState(
formMethods.getValues("useEventTypeDestinationCalendarEmail")
);
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
const bookingFields: Prisma.JsonObject = {};
@@ -116,6 +119,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
const seatsLocked = shouldLockDisableProps("seatsPerTimeSlotEnabled");
const closeEventNameTip = () => setShowEventNameTip(false);
const displayDestinationCalendarSelector =
!!connectedCalendarsQuery.data?.connectedCalendars.length && !team;
return (
<div className="flex flex-col space-y-4">
@@ -125,7 +130,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
* This will fallback to each user selected destination calendar.
*/}
<div className="border-subtle space-y-6 rounded-lg border p-6">
{!!connectedCalendarsQuery.data?.connectedCalendars.length && !team && (
{displayDestinationCalendarSelector && (
<div className="flex flex-col">
<div className="flex justify-between">
<div>
@@ -171,6 +176,18 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
}
/>
</div>
{displayDestinationCalendarSelector && (
<div className="w-full">
<Switch
label={t("display_add_to_calendar_organizer")}
checked={useEventTypeDestinationCalendarEmail}
onCheckedChange={(val) => {
setUseEventTypeDestinationCalendarEmail(val);
formMethods.setValue("useEventTypeDestinationCalendarEmail", val);
}}
/>
</div>
)}
</div>
<BookerLayoutSelector fallbackToUserSettings isDark={selectedThemeIsDark} isOuterBorder={true} />
@@ -82,6 +82,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
cancellationReason: true,
responses: true,
rejectionReason: true,
userPrimaryEmail: true,
user: {
select: {
id: true,
@@ -203,7 +203,7 @@ export default function Success(props: PageProps) {
duration: calculatedDuration,
organizer: {
name: users[0].name || "Nameless",
email: users[0].email || "Email-less",
email: bookingInfo?.userPrimaryEmail || users[0].email || "Email-less",
timeZone: users[0].timeZone,
},
confirmed: !needsConfirmation,
@@ -450,7 +450,9 @@ export default function Success(props: PageProps) {
</span>
<Badge variant="blue">{t("Host")}</Badge>
</div>
<p className="text-default">{bookingInfo.user.email}</p>
<p className="text-default">
{bookingInfo?.userPrimaryEmail ?? bookingInfo.user.email}
</p>
</div>
)}
{bookingInfo?.attendees.map((attendee) => (
@@ -148,6 +148,7 @@ export type FormValues = {
multipleDurationEnabled: boolean;
users: EventTypeSetup["users"];
assignAllTeamMembers: boolean;
useEventTypeDestinationCalendarEmail: boolean;
};
export type CustomInputParsed = typeof customInputSchema._output;
@@ -298,6 +299,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
hosts: eventType.hosts,
successRedirectUrl: eventType.successRedirectUrl || "",
users: eventType.users,
useEventTypeDestinationCalendarEmail: eventType.useEventTypeDestinationCalendarEmail,
children: eventType.children.map((ch) => ({
...ch,
created: true,
+1 -1
View File
@@ -119,7 +119,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
endTime: booking.endTime.toISOString(),
organizer: {
id: user.id,
email: user.email,
email: booking?.userPrimaryEmail ?? user.email,
name,
timeZone: user.timeZone,
language: { translate: tOrganizer, locale: user.locale ?? "en" },
+1 -1
View File
@@ -230,7 +230,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
organizer: {
email: booking.user?.email || "Email-less",
email: booking?.userPrimaryEmail || booking.user?.email || "Email-less",
name: booking.user?.name || "Nameless",
timeZone: booking.user?.timeZone || "Europe/London",
language: { translate: t, locale: booking?.user?.locale ?? "en" },
@@ -807,6 +807,7 @@
"additional_input_description": "Require scheduler to input additional inputs prior the booking is confirmed",
"label": "Label",
"placeholder": "Placeholder",
"display_add_to_calendar_organizer":"Display 'Add to calendar' email as the organizer",
"type": "Type",
"edit": "Edit",
"add_input": "Add an Input",
@@ -108,6 +108,7 @@ describe("handleChildrenEventTypes", () => {
timeZone,
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
id: 123,
@@ -156,6 +157,7 @@ describe("handleChildrenEventTypes", () => {
scheduleId,
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
metadata: { managedEventConfig: {} },
@@ -250,6 +252,7 @@ describe("handleChildrenEventTypes", () => {
timeZone,
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
id: 123,
@@ -298,6 +301,7 @@ describe("handleChildrenEventTypes", () => {
userId,
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
metadata: { managedEventConfig: {} },
@@ -350,6 +354,7 @@ describe("handleChildrenEventTypes", () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
requiresBookerEmailVerification,
lockTimeZoneToggleOnBookingPage,
useEventTypeDestinationCalendarEmail,
...evType
} = mockFindFirstEventType({
metadata: { managedEventConfig: {} },
@@ -307,16 +307,18 @@ export function expectWebhookToHaveBeenCalledWith(
export function expectWorkflowToBeTriggered({
emails,
organizer,
destinationEmail,
}: {
emails: Fixtures["emails"];
organizer: { email: string; name: string; timeZone: string };
destinationEmail?: string;
}) {
const subjectPattern = /^Reminder: /i;
expect(emails.get()).toEqual(
expect.arrayContaining([
expect.objectContaining({
subject: expect.stringMatching(subjectPattern),
to: organizer.email,
to: destinationEmail ?? organizer.email,
}),
])
);
@@ -370,6 +372,7 @@ export function expectSuccessfulBookingCreationEmails({
recurrence,
bookingTimeRange,
booking,
destinationEmail,
}: {
emails: Fixtures["emails"];
organizer: { email: string; name: string; timeZone: string };
@@ -381,6 +384,7 @@ export function expectSuccessfulBookingCreationEmails({
eventDomain?: string;
bookingTimeRange?: { start: Date; end: Date };
booking: { uid: string; urlOrigin?: string };
destinationEmail?: string;
}) {
const bookingUrlOrigin = booking.urlOrigin || WEBSITE_URL;
expect(emails).toHaveEmail(
@@ -409,7 +413,7 @@ export function expectSuccessfulBookingCreationEmails({
},
}
: null),
to: `${organizer.email}`,
to: `${destinationEmail ?? organizer.email}`,
ics: {
filename: "event.ics",
iCalUID: `${iCalUID}`,
@@ -417,7 +421,7 @@ export function expectSuccessfulBookingCreationEmails({
method: "REQUEST",
},
},
`${organizer.email}`
`${destinationEmail ?? organizer.email}`
);
expect(emails).toHaveEmail(
@@ -245,7 +245,7 @@ async function handler(req: CustomRequest) {
startTime: bookingToDelete?.startTime ? dayjs(bookingToDelete.startTime).format() : "",
endTime: bookingToDelete?.endTime ? dayjs(bookingToDelete.endTime).format() : "",
organizer: {
email: organizer.email,
email: bookingToDelete?.userPrimaryEmail ?? organizer.email,
name: organizer.name ?? "Nameless",
timeZone: organizer.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(organizer.timeFormat),
@@ -524,7 +524,7 @@ async function handler(req: CustomRequest) {
startTime: bookingToDelete.startTime.toISOString(),
endTime: bookingToDelete.endTime.toISOString(),
organizer: {
email: bookingToDelete.user?.email ?? "dev@calendso.com",
email: bookingToDelete?.userPrimaryEmail ?? bookingToDelete.user?.email ?? "dev@calendso.com",
name: bookingToDelete.user?.name ?? "no user",
timeZone: bookingToDelete.user?.timeZone ?? "",
timeFormat: getTimeFormatStringFromUserTimeFormat(organizer.timeFormat),
@@ -342,6 +342,7 @@ export const getEventTypesFromDB = async (eventTypeId: number) => {
durationLimits: true,
assignAllTeamMembers: true,
parentId: true,
useEventTypeDestinationCalendarEmail: true,
owner: {
select: {
hideBranding: true,
@@ -741,6 +742,7 @@ async function createBooking({
const newBookingData: Prisma.BookingCreateInput = {
uid,
userPrimaryEmail: evt.organizer.email,
responses: responses === null || evt.seatsPerTimeSlot ? Prisma.JsonNull : responses,
title: evt.title,
startTime: dayjs.utc(evt.startTime).toDate(),
@@ -1496,6 +1498,7 @@ async function handler(
if (isTeamEventType && eventType.schedulingType === "COLLECTIVE" && user.destinationCalendar) {
teamDestinationCalendars.push(user.destinationCalendar);
}
return {
id: user.id,
email: user.email ?? "",
@@ -1548,6 +1551,12 @@ async function handler(
? await getBookerBaseUrl(eventType.team.parentId)
: await getBookerBaseUrl(organizerOrganizationId ?? null);
const destinationCalendar = eventType.destinationCalendar
? [eventType.destinationCalendar]
: organizerUser.destinationCalendar
? [organizerUser.destinationCalendar]
: null;
let evt: CalendarEvent = {
bookerUrl,
type: eventType.slug,
@@ -1560,7 +1569,10 @@ async function handler(
organizer: {
id: organizerUser.id,
name: organizerUser.name || "Nameless",
email: organizerUser.email || "Email-less",
email:
eventType.useEventTypeDestinationCalendarEmail && destinationCalendar?.[0]?.primaryEmail
? destinationCalendar[0].primaryEmail
: organizerUser.email || "Email-less",
username: organizerUser.username || undefined,
timeZone: organizerUser.timeZone,
language: { translate: tOrganizer, locale: organizerUser.locale ?? "en" },
@@ -1571,11 +1583,7 @@ async function handler(
attendees: attendeesList,
location: bookingLocation, // Will be processed by the EventManager later.
conferenceCredentialId,
destinationCalendar: eventType.destinationCalendar
? [eventType.destinationCalendar]
: organizerUser.destinationCalendar
? [organizerUser.destinationCalendar]
: null,
destinationCalendar,
hideCalendarNotes: eventType.hideCalendarNotes,
requiresConfirmation: !isConfirmedByDefault,
eventTypeId: eventType.id,
@@ -82,6 +82,9 @@ describe("handleNewBooking", () => {
name: "Booker",
});
const organizerOtherEmail = "organizer2@example.com";
const organizerDestinationCalendarEmailOnEventType = "organizerEventTypeEmail@example.com";
const organizer = getOrganizer({
name: "Organizer",
email: "organizer@example.com",
@@ -92,6 +95,7 @@ describe("handleNewBooking", () => {
destinationCalendar: {
integration: "google_calendar",
externalId: "organizer@google-calendar.com",
primaryEmail: organizerOtherEmail,
},
});
@@ -122,6 +126,7 @@ describe("handleNewBooking", () => {
id: 1,
slotInterval: 30,
length: 30,
useEventTypeDestinationCalendarEmail: true,
users: [
{
id: 101,
@@ -130,6 +135,7 @@ describe("handleNewBooking", () => {
destinationCalendar: {
integration: "google_calendar",
externalId: "event-type-1@google-calendar.com",
primaryEmail: organizerDestinationCalendarEmailOnEventType,
},
},
],
@@ -208,7 +214,11 @@ describe("handleNewBooking", () => {
iCalUID: createdBooking.iCalUID,
});
expectWorkflowToBeTriggered({ organizer, emails });
expectWorkflowToBeTriggered({
organizer,
emails,
destinationEmail: organizerDestinationCalendarEmailOnEventType,
});
expectSuccessfulCalendarEventCreationInCalendar(calendarMock, {
calendarId: "event-type-1@google-calendar.com",
videoCallUrl: "http://mock-dailyvideo.example.com/meeting-1",
@@ -225,6 +235,7 @@ describe("handleNewBooking", () => {
organizer,
emails,
iCalUID,
destinationEmail: organizerDestinationCalendarEmailOnEventType,
});
expectBookingCreatedWebhookToHaveBeenFired({
+1
View File
@@ -101,6 +101,7 @@ const commons = {
metadata: EventTypeMetaDataSchema.parse({}),
bookingFields: [],
assignAllTeamMembers: false,
useEventTypeDestinationCalendarEmail: false,
};
const dynamicEvent = {
+1
View File
@@ -112,6 +112,7 @@ export default async function getEventTypeById({
successRedirectUrl: true,
currency: true,
bookingFields: true,
useEventTypeDestinationCalendarEmail: true,
owner: {
select: {
id: true,
+1 -1
View File
@@ -96,7 +96,7 @@ export async function getBooking(bookingId: number) {
bookingFields: booking.eventType?.bookingFields || null,
}),
organizer: {
email: user.email,
email: booking?.userPrimaryEmail ?? user.email,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
name: user.name!,
timeZone: user.timeZone,
+2
View File
@@ -38,6 +38,7 @@ export const buildBooking = (booking?: Partial<Booking>): Booking => {
uid,
userId: null,
eventTypeId: null,
userPrimaryEmail: null,
title: faker.lorem.sentence(),
description: faker.lorem.paragraph(),
customInputs: null,
@@ -82,6 +83,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
userId: null,
teamId: null,
requiresBookerEmailVerification: false,
useEventTypeDestinationCalendarEmail: false,
eventName: faker.lorem.words(),
timeZone: null,
periodType: "UNLIMITED",
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "DestinationCalendar" ADD COLUMN "primaryEmail" TEXT;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Booking" ADD COLUMN "userPrimaryEmail" TEXT;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "useEventTypeDestinationCalendarEmail" BOOLEAN NOT NULL DEFAULT false;
+51 -45
View File
@@ -70,62 +70,63 @@ model EventType {
profileId Int?
profile Profile? @relation(fields: [profileId], references: [id], onDelete: Cascade)
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int?
hashedLink HashedLink?
bookings Booking[]
availability Availability[]
webhooks Webhook[]
destinationCalendar DestinationCalendar?
eventName String?
customInputs EventTypeCustomInput[]
parentId Int?
parent EventType? @relation("managed_eventtype", fields: [parentId], references: [id], onDelete: Cascade)
children EventType[] @relation("managed_eventtype")
team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
teamId Int?
hashedLink HashedLink?
bookings Booking[]
availability Availability[]
webhooks Webhook[]
destinationCalendar DestinationCalendar?
eventName String?
customInputs EventTypeCustomInput[]
parentId Int?
parent EventType? @relation("managed_eventtype", fields: [parentId], references: [id], onDelete: Cascade)
children EventType[] @relation("managed_eventtype")
/// @zod.custom(imports.eventTypeBookingFields)
bookingFields Json?
timeZone String?
periodType PeriodType @default(UNLIMITED)
bookingFields Json?
timeZone String?
periodType PeriodType @default(UNLIMITED)
/// @zod.custom(imports.coerceToDate)
periodStartDate DateTime?
periodStartDate DateTime?
/// @zod.custom(imports.coerceToDate)
periodEndDate DateTime?
periodDays Int?
periodCountCalendarDays Boolean?
lockTimeZoneToggleOnBookingPage Boolean @default(false)
requiresConfirmation Boolean @default(false)
requiresBookerEmailVerification Boolean @default(false)
periodEndDate DateTime?
periodDays Int?
periodCountCalendarDays Boolean?
lockTimeZoneToggleOnBookingPage Boolean @default(false)
requiresConfirmation Boolean @default(false)
requiresBookerEmailVerification Boolean @default(false)
/// @zod.custom(imports.recurringEventType)
recurringEvent Json?
disableGuests Boolean @default(false)
hideCalendarNotes Boolean @default(false)
recurringEvent Json?
disableGuests Boolean @default(false)
hideCalendarNotes Boolean @default(false)
/// @zod.min(0)
minimumBookingNotice Int @default(120)
beforeEventBuffer Int @default(0)
afterEventBuffer Int @default(0)
seatsPerTimeSlot Int?
onlyShowFirstAvailableSlot Boolean @default(false)
seatsShowAttendees Boolean? @default(false)
seatsShowAvailabilityCount Boolean? @default(true)
schedulingType SchedulingType?
schedule Schedule? @relation(fields: [scheduleId], references: [id])
scheduleId Int?
minimumBookingNotice Int @default(120)
beforeEventBuffer Int @default(0)
afterEventBuffer Int @default(0)
seatsPerTimeSlot Int?
onlyShowFirstAvailableSlot Boolean @default(false)
seatsShowAttendees Boolean? @default(false)
seatsShowAvailabilityCount Boolean? @default(true)
schedulingType SchedulingType?
schedule Schedule? @relation(fields: [scheduleId], references: [id])
scheduleId Int?
// price is deprecated. It has now moved to metadata.apps.stripe.price. Plan to drop this column.
price Int @default(0)
price Int @default(0)
// currency is deprecated. It has now moved to metadata.apps.stripe.currency. Plan to drop this column.
currency String @default("usd")
slotInterval Int?
currency String @default("usd")
slotInterval Int?
/// @zod.custom(imports.EventTypeMetaDataSchema)
metadata Json?
metadata Json?
/// @zod.custom(imports.successRedirectUrl)
successRedirectUrl String?
workflows WorkflowsOnEventTypes[]
successRedirectUrl String?
workflows WorkflowsOnEventTypes[]
/// @zod.custom(imports.intervalLimitsType)
bookingLimits Json?
bookingLimits Json?
/// @zod.custom(imports.intervalLimitsType)
durationLimits Json?
isInstantEvent Boolean @default(false)
assignAllTeamMembers Boolean @default(false)
durationLimits Json?
isInstantEvent Boolean @default(false)
assignAllTeamMembers Boolean @default(false)
useEventTypeDestinationCalendarEmail Boolean @default(false)
@@unique([userId, slug])
@@unique([teamId, slug])
@@ -173,6 +174,8 @@ model DestinationCalendar {
id Int @id @default(autoincrement())
integration String
externalId String
/// @zod.email()
primaryEmail String?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int? @unique
booking Booking[]
@@ -459,6 +462,9 @@ model Booking {
uid String @unique
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
userId Int?
// User's email at the time of booking
/// @zod.email()
userPrimaryEmail String?
references BookingReference[]
eventType EventType? @relation(fields: [eventTypeId], references: [id])
eventTypeId Int?
+1
View File
@@ -3,6 +3,7 @@ import { Prisma } from "@prisma/client";
export const bookingMinimalSelect = Prisma.validator<Prisma.BookingSelect>()({
id: true,
title: true,
userPrimaryEmail: true,
description: true,
customInputs: true,
startTime: true,
@@ -39,6 +39,7 @@ export const connectedCalendarsHandler = async ({ ctx, input }: ConnectedCalenda
user.selectedCalendars,
user.destinationCalendar?.externalId
);
let toggledCalendarDetails:
| {
externalId: string;
@@ -59,7 +60,12 @@ export const connectedCalendarsHandler = async ({ ctx, input }: ConnectedCalenda
There are connected calendars, but no destination calendar
So create a default destination calendar with the first primary connected calendar
*/
const { integration = "", externalId = "", credentialId } = connectedCalendars[0].primary ?? {};
const {
integration = "",
externalId = "",
credentialId,
email: primaryEmail,
} = connectedCalendars[0].primary ?? {};
// Select the first calendar matching the primary by default since that will also be the destination calendar
if (onboarding && externalId) {
const calendarIndex = (connectedCalendars[0].calendars || []).findIndex(
@@ -79,6 +85,7 @@ export const connectedCalendarsHandler = async ({ ctx, input }: ConnectedCalenda
integration,
externalId,
credentialId,
primaryEmail,
},
});
} else {
@@ -94,7 +101,7 @@ export const connectedCalendarsHandler = async ({ ctx, input }: ConnectedCalenda
if (!destinationCal) {
// If destinationCalendar is out of date, update it with the first primary connected calendar
const { integration = "", externalId = "" } = connectedCalendars[0].primary ?? {};
const { integration = "", externalId = "", email: primaryEmail } = connectedCalendars[0].primary ?? {};
// Select the first calendar matching the primary by default since that will also be the destination calendar
if (onboarding && externalId) {
const calendarIndex = (connectedCalendars[0].calendars || []).findIndex(
@@ -113,6 +120,7 @@ export const connectedCalendarsHandler = async ({ ctx, input }: ConnectedCalenda
data: {
integration,
externalId,
primaryEmail,
},
});
} else if (onboarding && !destinationCal.isSelected) {
@@ -272,7 +272,7 @@ export const deleteCredentialHandler = async ({ ctx, input }: DeleteCredentialOp
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
organizer: {
email: booking?.user?.email as string,
email: booking?.userPrimaryEmail ?? (booking?.user?.email as string),
name: booking?.user?.name ?? "Nameless",
timeZone: booking?.user?.timeZone as string,
language: { translate: tOrganizer, locale: booking?.user?.locale ?? "en" },
@@ -30,6 +30,8 @@ export const setDestinationCalendarHandler = async ({ ctx, input }: SetDestinati
throw new TRPCError({ code: "BAD_REQUEST", message: `Could not find calendar ${input.externalId}` });
}
const primaryEmail = allCals.find((cal) => cal.primary && cal.credentialId === credentialId)?.email ?? null;
let where;
if (eventTypeId) {
@@ -56,12 +58,14 @@ export const setDestinationCalendarHandler = async ({ ctx, input }: SetDestinati
integration,
externalId,
credentialId,
primaryEmail,
},
create: {
...where,
integration,
externalId,
credentialId,
primaryEmail,
},
});
};
@@ -49,6 +49,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
eventTypeId: true,
responses: true,
metadata: true,
userPrimaryEmail: true,
eventType: {
select: {
id: true,
@@ -168,7 +169,7 @@ export const confirmHandler = async ({ ctx, input }: ConfirmOptions) => {
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
organizer: {
email: user.email,
email: booking.userPrimaryEmail ?? user.email,
name: user.name || "Unnamed",
username: user.username || undefined,
timeZone: user.timeZone,
@@ -74,7 +74,7 @@ export const editLocationHandler = async ({ ctx, input }: EditLocationOptions) =
startTime: booking.startTime ? dayjs(booking.startTime).format() : "",
endTime: booking.endTime ? dayjs(booking.endTime).format() : "",
organizer: {
email: organizer.email,
email: booking?.userPrimaryEmail ?? organizer.email,
name: organizer.name ?? "Nameless",
timeZone: organizer.timeZone,
language: { translate: tOrganizer, locale: organizer.locale ?? "en" },
@@ -53,6 +53,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
startTime: true,
endTime: true,
eventTypeId: true,
userPrimaryEmail: true,
eventType: {
include: {
team: {
@@ -180,6 +181,10 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
const userTranslation = await getTranslation(user.locale ?? "en", "common");
const [userAsPeopleType] = usersToPeopleType([user], userTranslation);
const organizer = {
...userAsPeopleType,
email: bookingToReschedule?.userPrimaryEmail ?? userAsPeopleType.email,
};
const builder = new CalendarEventBuilder();
const eventType = bookingToReschedule.eventType;
@@ -196,7 +201,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
tAttendees
),
organizer: userAsPeopleType,
organizer,
iCalUID: bookingToReschedule.iCalUID,
});
@@ -257,7 +262,7 @@ export const requestRescheduleHandler = async ({ ctx, input }: RequestReschedule
}),
startTime: bookingToReschedule?.startTime ? dayjs(bookingToReschedule.startTime).format() : "",
endTime: bookingToReschedule?.endTime ? dayjs(bookingToReschedule.endTime).format() : "",
organizer: userAsPeopleType,
organizer,
attendees: usersToPeopleType(
// username field doesn't exists on attendee but could be in the future
bookingToReschedule.attendees as unknown as PersonAttendeeCommonFields[],
@@ -67,7 +67,7 @@ export const chargeCardHandler = async ({ ctx, input }: ChargeCardHandlerOptions
startTime: dayjs(booking.startTime).format(),
endTime: dayjs(booking.endTime).format(),
organizer: {
email: booking.user?.email || "",
email: booking?.userPrimaryEmail ?? booking.user?.email ?? "",
name: booking.user?.name || "Nameless",
timeZone: booking.user?.timeZone || "",
language: { translate: tOrganizer, locale: booking.user?.locale ?? "en" },
@@ -169,7 +169,7 @@ export const activateEventTypeHandler = async ({ ctx, input }: ActivateEventType
organizer: booking.user
? {
name: booking.user.name || "",
email: booking.user.email,
email: booking?.userPrimaryEmail ?? booking.user.email,
timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
language: { locale: booking.user.locale || defaultLocale },
@@ -288,7 +288,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
? {
language: { locale: booking.user.locale || defaultLocale },
name: booking.user.name || "",
email: booking.user.email,
email: booking?.userPrimaryEmail ?? booking.user.email,
timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
}
@@ -525,7 +525,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
? {
language: { locale: booking.user.locale || defaultLocale },
name: booking.user.name || "",
email: booking.user.email,
email: booking?.userPrimaryEmail ?? booking.user.email,
timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
}
@@ -669,7 +669,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
organizer: booking.user
? {
name: booking.user.name || "",
email: booking.user.email,
email: booking?.userPrimaryEmail ?? booking.user.email,
timeZone: booking.user.timeZone,
timeFormat: getTimeFormatStringFromUserTimeFormat(booking.user.timeFormat),
language: { locale: booking.user.locale || defaultLocale },