diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx
index 2b237f9cf1..6f3f03abe7 100644
--- a/apps/web/components/booking/BookingListItem.tsx
+++ b/apps/web/components/booking/BookingListItem.tsx
@@ -13,11 +13,12 @@ import dayjs from "dayjs";
import { useRouter } from "next/router";
import { useState } from "react";
import { useMutation } from "react-query";
-import { Frequency as RRuleFrequency } from "rrule";
+import { parseRecurringEvent } from "@calcom/lib";
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
+import { Frequency } from "@calcom/prisma/zod-utils";
import Button from "@calcom/ui/Button";
import { Dialog, DialogClose, DialogContent, DialogFooter, DialogHeader } from "@calcom/ui/Dialog";
import { Tooltip } from "@calcom/ui/Tooltip";
@@ -196,7 +197,7 @@ function BookingListItem(booking: BookingItemProps) {
[recurringStrings] = parseRecurringDates(
{
startDate: booking.startTime,
- recurringEvent: booking.eventType.recurringEvent,
+ recurringEvent: parseRecurringEvent(booking.eventType.recurringEvent),
recurringCount: booking.recurringCount,
},
i18n
@@ -307,14 +308,10 @@ function BookingListItem(booking: BookingItemProps) {
{`${t("every_for_freq", {
freq: t(
- `${RRuleFrequency[booking.eventType.recurringEvent.freq]
- .toString()
- .toLowerCase()}`
+ `${Frequency[booking.eventType.recurringEvent.freq].toString().toLowerCase()}`
),
})} ${booking.recurringCount} ${t(
- `${RRuleFrequency[booking.eventType.recurringEvent.freq]
- .toString()
- .toLowerCase()}`,
+ `${Frequency[booking.eventType.recurringEvent.freq].toString().toLowerCase()}`,
{ count: booking.recurringCount }
)}`}
diff --git a/apps/web/components/booking/pages/AvailabilityPage.tsx b/apps/web/components/booking/pages/AvailabilityPage.tsx
index d0ea17ba51..92d926ea5c 100644
--- a/apps/web/components/booking/pages/AvailabilityPage.tsx
+++ b/apps/web/components/booking/pages/AvailabilityPage.tsx
@@ -21,7 +21,6 @@ import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { useCallback, useEffect, useMemo, useState } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
-import { Frequency as RRuleFrequency } from "rrule";
import { AppStoreLocationType, LocationObject, LocationType } from "@calcom/app-store/locations";
import {
@@ -35,6 +34,7 @@ import classNames from "@calcom/lib/classNames";
import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { localStorage } from "@calcom/lib/webstorage";
+import { Frequency } from "@calcom/prisma/zod-utils";
import { asStringOrNull } from "@lib/asStringOrNull";
import { timeZone } from "@lib/clock";
@@ -302,7 +302,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
{t("every_for_freq", {
freq: t(
- `${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
+ `${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
),
})}
@@ -317,12 +317,9 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
}}
/>
- {t(
- `${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`,
- {
- count: recurringEventCount,
- }
- )}
+ {t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
+ count: recurringEventCount,
+ })}
)}
@@ -344,7 +341,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
{t("every_for_freq", {
freq: t(
- `${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
+ `${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
),
})}
@@ -359,12 +356,9 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
}}
/>
- {t(
- `${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`,
- {
- count: recurringEventCount,
- }
- )}
+ {t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
+ count: recurringEventCount,
+ })}
)}
@@ -471,9 +465,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
{t("every_for_freq", {
- freq: t(
- `${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
- ),
+ freq: t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`),
})}
- {t(`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
+ {t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
count: recurringEventCount,
})}
diff --git a/apps/web/components/booking/pages/BookingPage.tsx b/apps/web/components/booking/pages/BookingPage.tsx
index 545a0e9b16..3862480656 100644
--- a/apps/web/components/booking/pages/BookingPage.tsx
+++ b/apps/web/components/booking/pages/BookingPage.tsx
@@ -20,7 +20,6 @@ import { Controller, useForm, useWatch } from "react-hook-form";
import { FormattedNumber, IntlProvider } from "react-intl";
import { ReactMultiEmail } from "react-multi-email";
import { useMutation } from "react-query";
-import { Frequency as RRuleFrequency } from "rrule";
import { v4 as uuidv4 } from "uuid";
import { z } from "zod";
@@ -32,6 +31,7 @@ import {
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
+import { Frequency } from "@calcom/prisma/zod-utils";
import { createPaymentLink } from "@calcom/stripe/client";
import { Button } from "@calcom/ui/Button";
import { Tooltip } from "@calcom/ui/Tooltip";
@@ -518,9 +518,9 @@ const BookingPage = ({
{`${t("every_for_freq", {
- freq: t(`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`),
+ freq: t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`),
})} ${recurringEventCount} ${t(
- `${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`,
+ `${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`,
{ count: parseInt(recurringEventCount.toString()) }
)}`}
@@ -529,12 +529,12 @@ const BookingPage = ({
- {(rescheduleUid || !eventType.recurringEvent.freq) &&
+ {(rescheduleUid || !eventType.recurringEvent?.freq) &&
parseDate(dayjs(date).tz(timeZone()), i18n)}
{!rescheduleUid &&
- eventType.recurringEvent.freq &&
+ eventType.recurringEvent?.freq &&
recurringStrings.slice(0, 5).map((aDate, key) =>
{aDate}
)}
- {!rescheduleUid && eventType.recurringEvent.freq && recurringStrings.length > 5 && (
+ {!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
(
diff --git a/apps/web/components/eventtype/EventTypeDescription.tsx b/apps/web/components/eventtype/EventTypeDescription.tsx
index 2a06a0e1d0..dea616a9aa 100644
--- a/apps/web/components/eventtype/EventTypeDescription.tsx
+++ b/apps/web/components/eventtype/EventTypeDescription.tsx
@@ -10,9 +10,9 @@ import { Prisma, SchedulingType } from "@prisma/client";
import { useMemo } from "react";
import { FormattedNumber, IntlProvider } from "react-intl";
+import { parseRecurringEvent } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { baseEventTypeSelect } from "@calcom/prisma/selects";
-import { RecurringEvent } from "@calcom/types/Calendar";
import classNames from "@lib/classNames";
@@ -30,8 +30,8 @@ export type EventTypeDescriptionProps = {
export const EventTypeDescription = ({ eventType, className }: EventTypeDescriptionProps) => {
const { t } = useLocale();
- const recurringEvent: RecurringEvent = useMemo(
- () => (eventType.recurringEvent as RecurringEvent) || [],
+ const recurringEvent = useMemo(
+ () => parseRecurringEvent(eventType.recurringEvent),
[eventType.recurringEvent]
);
diff --git a/apps/web/components/eventtype/RecurringEventController.tsx b/apps/web/components/eventtype/RecurringEventController.tsx
index bd81513ea7..32c9f64bff 100644
--- a/apps/web/components/eventtype/RecurringEventController.tsx
+++ b/apps/web/components/eventtype/RecurringEventController.tsx
@@ -1,17 +1,16 @@
-import { Collapsible, CollapsibleContent } from "@radix-ui/react-collapsible";
import type { FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { UseFormReturn } from "react-hook-form";
-import { Frequency as RRuleFrequency } from "rrule";
import { useLocale } from "@calcom/lib/hooks/useLocale";
+import { Frequency } from "@calcom/prisma/zod-utils";
import { RecurringEvent } from "@calcom/types/Calendar";
import { Alert } from "@calcom/ui/Alert";
import Select from "@components/ui/form/Select";
type RecurringEventControllerProps = {
- recurringEvent: RecurringEvent;
+ recurringEvent: RecurringEvent | null;
formMethods: UseFormReturn;
paymentEnabled: boolean;
onRecurringEventDefined: (value: boolean) => void;
@@ -24,20 +23,13 @@ export default function RecurringEventController({
onRecurringEventDefined,
}: RecurringEventControllerProps) {
const { t } = useLocale();
-
- const [recurringEventDefined, setRecurringEventDefined] = useState(recurringEvent?.count !== undefined);
-
- const [recurringEventInterval, setRecurringEventInterval] = useState(recurringEvent?.interval || 1);
- const [recurringEventFrequency, setRecurringEventFrequency] = useState(
- recurringEvent?.freq || RRuleFrequency.WEEKLY
- );
- const [recurringEventCount, setRecurringEventCount] = useState(recurringEvent?.count || 12);
+ const [recurringEventState, setRecurringEventState] = useState(recurringEvent);
/* Just yearly-0, monthly-1 and weekly-2 */
- const recurringEventFreqOptions = Object.entries(RRuleFrequency)
+ const recurringEventFreqOptions = Object.entries(Frequency)
.filter(([key, value]) => isNaN(Number(key)) && Number(value) < 3)
.map(([key, value]) => ({
- label: t(`${key.toString().toLowerCase()}`, { count: recurringEventInterval }),
+ label: t(`${key.toString().toLowerCase()}`, { count: recurringEventState?.interval }),
value: value.toString(),
}));
@@ -55,27 +47,23 @@ export default function RecurringEventController({
{
- setRecurringEventDefined(event?.target.checked);
onRecurringEventDefined(event?.target.checked);
if (!event?.target.checked) {
- formMethods.setValue("recurringEvent", {});
+ formMethods.setValue("recurringEvent", null);
+ setRecurringEventState(null);
} else {
- formMethods.setValue(
- "recurringEvent",
- recurringEventDefined
- ? recurringEvent
- : {
- interval: 1,
- count: 12,
- freq: RRuleFrequency.WEEKLY,
- }
- );
+ const newVal = recurringEvent || {
+ interval: 1,
+ count: 12,
+ freq: Frequency.WEEKLY,
+ };
+ formMethods.setValue("recurringEvent", newVal);
+ setRecurringEventState(newVal);
}
- recurringEvent = formMethods.getValues("recurringEvent");
}}
type="checkbox"
className="text-primary-600 h-4 w-4 rounded border-gray-300"
- defaultChecked={recurringEventDefined}
+ defaultChecked={recurringEventState !== null}
data-testid="recurring-event-check"
id="recurringEvent"
/>
@@ -86,11 +74,8 @@ export default function RecurringEventController({
-
setRecurringEventDefined(!recurringEventDefined)}>
-
+ {recurringEventState && (
+
@@ -126,21 +115,24 @@ export default function RecurringEventController({
min="1"
max="20"
className="block w-16 rounded-sm border-gray-300 shadow-sm [appearance:textfield] ltr:mr-2 rtl:ml-2 sm:text-sm"
- defaultValue={recurringEvent?.count || 12}
+ defaultValue={recurringEventState.count}
onChange={(event) => {
- setRecurringEventCount(parseInt(event?.target.value));
- recurringEvent.count = parseInt(event?.target.value);
- formMethods.setValue("recurringEvent", recurringEvent);
+ const newVal = {
+ ...recurringEventState,
+ count: parseInt(event?.target.value),
+ };
+ formMethods.setValue("recurringEvent", newVal);
+ setRecurringEventState(newVal);
}}
/>
- {t(`${RRuleFrequency[recurringEventFrequency].toString().toLowerCase()}`, {
- count: recurringEventCount,
+ {t(`${Frequency[recurringEventState.freq].toString().toLowerCase()}`, {
+ count: recurringEventState.count,
})}
-
-
+
+ )}
>
)}
diff --git a/apps/web/ee/pages/api/integrations/stripepayment/webhook.ts b/apps/web/ee/pages/api/integrations/stripepayment/webhook.ts
index 02a39ca8d9..dfdb8badaa 100644
--- a/apps/web/ee/pages/api/integrations/stripepayment/webhook.ts
+++ b/apps/web/ee/pages/api/integrations/stripepayment/webhook.ts
@@ -5,11 +5,11 @@ import Stripe from "stripe";
import EventManager from "@calcom/core/EventManager";
import { sendScheduledEmails } from "@calcom/emails";
-import { isPrismaObjOrUndefined } from "@calcom/lib";
+import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import stripe from "@calcom/stripe/server";
-import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
+import { CalendarEvent } from "@calcom/types/Calendar";
import { IS_PRODUCTION } from "@lib/config/constants";
import { HttpError as HttpCode } from "@lib/core/http/error";
@@ -80,10 +80,6 @@ async function handlePaymentSuccess(event: Stripe.Event) {
});
}
- const eventType = {
- recurringEvent: (eventTypeRaw?.recurringEvent || {}) as RecurringEvent,
- };
-
const { user } = booking;
if (!user) throw new Error("No user found");
@@ -119,6 +115,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
attendees: attendeesList,
uid: booking.uid,
destinationCalendar: booking.destinationCalendar || user.destinationCalendar,
+ recurringEvent: parseRecurringEvent(eventTypeRaw?.recurringEvent),
};
if (booking.location) evt.location = booking.location;
@@ -153,7 +150,7 @@ async function handlePaymentSuccess(event: Stripe.Event) {
await prisma.$transaction([paymentUpdate, bookingUpdate]);
- await sendScheduledEmails({ ...evt }, eventType.recurringEvent);
+ await sendScheduledEmails({ ...evt });
throw new HttpCode({
statusCode: 200,
diff --git a/apps/web/lib/parseDate.ts b/apps/web/lib/parseDate.ts
index 19de2750c1..2769ca1891 100644
--- a/apps/web/lib/parseDate.ts
+++ b/apps/web/lib/parseDate.ts
@@ -25,11 +25,11 @@ export const parseRecurringDates = (
startDate,
recurringEvent,
recurringCount,
- }: { startDate: string | null | Dayjs; recurringEvent: RecurringEvent; recurringCount: number },
+ }: { startDate: string | null | Dayjs; recurringEvent: RecurringEvent | null; recurringCount: number },
i18n: I18n
): [string[], Date[]] => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- const { count, ...restRecurringEvent } = recurringEvent;
+ const { count, ...restRecurringEvent } = recurringEvent || {};
const rule = new RRule({
...restRecurringEvent,
count: recurringCount,
diff --git a/apps/web/pages/[user]/[type].tsx b/apps/web/pages/[user]/[type].tsx
index 879ee702c6..8c9f8be239 100644
--- a/apps/web/pages/[user]/[type].tsx
+++ b/apps/web/pages/[user]/[type].tsx
@@ -1,12 +1,11 @@
-import { Prisma } from "@prisma/client";
-import { UserPlan } from "@prisma/client";
+import { Prisma, UserPlan } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { locationHiddenFilter, LocationObject } from "@calcom/app-store/locations";
+import { parseRecurringEvent } from "@calcom/lib";
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
import { useLocale } from "@calcom/lib/hooks/useLocale";
-import { RecurringEvent } from "@calcom/types/Calendar";
import { asStringOrNull } from "@lib/asStringOrNull";
import { getWorkingHours } from "@lib/availability";
@@ -262,7 +261,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
metadata: (eventType.metadata || {}) as JSONObject,
periodStartDate: eventType.periodStartDate?.toString() ?? null,
periodEndDate: eventType.periodEndDate?.toString() ?? null,
- recurringEvent: (eventType.recurringEvent || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(eventType.recurringEvent),
locations: locationHiddenFilter(locations),
});
diff --git a/apps/web/pages/[user]/book.tsx b/apps/web/pages/[user]/book.tsx
index 9891e1db04..6aadd417f5 100644
--- a/apps/web/pages/[user]/book.tsx
+++ b/apps/web/pages/[user]/book.tsx
@@ -5,6 +5,7 @@ import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { getLocationLabels } from "@calcom/app-store/utils";
+import { parseRecurringEvent } from "@calcom/lib";
import {
getDefaultEvent,
getDynamicEventName,
@@ -12,9 +13,8 @@ import {
getUsernameList,
} from "@calcom/lib/defaultEvents";
import { useLocale } from "@calcom/lib/hooks/useLocale";
-import { RecurringEvent } from "@calcom/types/Calendar";
-import { asStringOrThrow, asStringOrNull } from "@lib/asStringOrNull";
+import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
import getBooking, { GetBookingType } from "@lib/getBooking";
import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -162,7 +162,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const eventType = {
...eventTypeRaw,
metadata: (eventTypeRaw.metadata || {}) as JSONObject,
- recurringEvent: (eventTypeRaw.recurringEvent || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
isWeb3Active:
web3Credentials && web3Credentials.key
? (((web3Credentials.key as JSONObject).isWeb3Active || false) as boolean)
diff --git a/apps/web/pages/api/book/confirm.ts b/apps/web/pages/api/book/confirm.ts
index 22a02ebc87..d40a5b2680 100644
--- a/apps/web/pages/api/book/confirm.ts
+++ b/apps/web/pages/api/book/confirm.ts
@@ -4,10 +4,10 @@ import { z } from "zod";
import EventManager from "@calcom/core/EventManager";
import { sendDeclinedEmails, sendScheduledEmails } from "@calcom/emails";
-import { isPrismaObjOrUndefined } from "@calcom/lib";
+import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import logger from "@calcom/lib/logger";
import prisma from "@calcom/prisma";
-import type { AdditionalInformation, CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
+import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
import { refund } from "@ee/lib/stripe/server";
import { getSession } from "@lib/auth";
@@ -179,8 +179,7 @@ async function patchHandler(req: NextApiRequest) {
destinationCalendar: booking?.destinationCalendar || currentUser.destinationCalendar,
};
- const recurringEvent = booking.eventType?.recurringEvent as RecurringEvent;
-
+ const recurringEvent = parseRecurringEvent(booking.eventType?.recurringEvent);
if (recurringEventId && recurringEvent) {
const groupedRecurringBookings = await prisma.booking.groupBy({
where: {
@@ -192,6 +191,8 @@ async function patchHandler(req: NextApiRequest) {
// Overriding the recurring event configuration count to be the actual number of events booked for
// the recurring event (equal or less than recurring event configuration count)
recurringEvent.count = groupedRecurringBookings[0]._count;
+ // count changed, parsing again to get the new value in
+ evt.recurringEvent = parseRecurringEvent(recurringEvent);
}
if (confirmed) {
@@ -217,10 +218,7 @@ async function patchHandler(req: NextApiRequest) {
metadata.entryPoints = results[0].createdEvent?.entryPoints;
}
try {
- await sendScheduledEmails(
- { ...evt, additionalInformation: metadata },
- recurringEventId ? recurringEvent : {} // Send email with recurring event info only on recurring event context
- );
+ await sendScheduledEmails({ ...evt, additionalInformation: metadata });
} catch (error) {
log.error(error);
}
@@ -267,23 +265,16 @@ async function patchHandler(req: NextApiRequest) {
evt.rejectionReason = rejectionReason;
if (recurringEventId) {
// The booking to reject is a recurring event and comes from /booking/upcoming, proceeding to mark all related
- // bookings as rejected. Prisma updateMany does not support relations, so doing this in two steps for now.
- const unconfirmedRecurringBookings = await prisma.booking.findMany({
+ // bookings as rejected.
+ await prisma.booking.updateMany({
where: {
recurringEventId,
status: BookingStatus.PENDING,
},
- });
- unconfirmedRecurringBookings.map(async (recurringBooking) => {
- await prisma.booking.update({
- where: {
- id: recurringBooking.id,
- },
- data: {
- status: BookingStatus.REJECTED,
- rejectionReason,
- },
- });
+ data: {
+ status: BookingStatus.REJECTED,
+ rejectionReason,
+ },
});
} else {
await refund(booking, evt); // No payment integration for recurring events for v1
@@ -298,7 +289,7 @@ async function patchHandler(req: NextApiRequest) {
});
}
- await sendDeclinedEmails(evt, recurringEventId ? recurringEvent : {}); // Send email with recurring event info only on recurring event context
+ await sendDeclinedEmails(evt);
}
req.statusCode = 204;
diff --git a/apps/web/pages/api/book/event.ts b/apps/web/pages/api/book/event.ts
index e934681cf6..774d040f53 100644
--- a/apps/web/pages/api/book/event.ts
+++ b/apps/web/pages/api/book/event.ts
@@ -17,12 +17,12 @@ import {
sendRescheduledEmails,
sendScheduledEmails,
} from "@calcom/emails";
-import { isPrismaObjOrUndefined } from "@calcom/lib";
+import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import { getDefaultEvent, getGroupName, getUsernameList } from "@calcom/lib/defaultEvents";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import logger from "@calcom/lib/logger";
import type { BufferedBusyTime } from "@calcom/types/BufferedBusyTime";
-import type { AdditionalInformation, CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
+import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
import type { EventResult, PartialReference } from "@calcom/types/EventManager";
import { handlePayment } from "@ee/lib/stripe/server";
@@ -196,7 +196,7 @@ const getEventTypesFromDB = async (eventTypeId: number) => {
return {
...eventType,
- recurringEvent: (eventType.recurringEvent || undefined) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(eventType.recurringEvent),
};
};
@@ -606,10 +606,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
let isAvailableToBeBooked = true;
try {
if (eventType.recurringEvent) {
- const allBookingDates = new rrule({
- dtstart: new Date(reqBody.start),
- ...eventType.recurringEvent,
- }).all();
+ const recurringEvent = parseRecurringEvent(eventType.recurringEvent);
+ const allBookingDates = new rrule({ dtstart: new Date(reqBody.start), ...recurringEvent }).all();
// Go through each date for the recurring event and check if each one's availability
isAvailableToBeBooked = allBookingDates
.map((aDate) => isAvailable(bufferedBusyTimes, aDate, eventType.length)) // <-- array of booleans
@@ -721,15 +719,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
if (noEmail !== true) {
- await sendRescheduledEmails(
- {
- ...evt,
- additionalInformation: metadata,
- additionalNotes, // Resets back to the additionalNote input and not the override value
- cancellationReason: reqBody.rescheduleReason,
- },
- reqBody.recurringEventId ? (eventType.recurringEvent as RecurringEvent) : {}
- );
+ await sendRescheduledEmails({
+ ...evt,
+ additionalInformation: metadata,
+ additionalNotes, // Resets back to the additionalNote input and not the override value
+ cancellationReason: reqBody.rescheduleReason,
+ });
}
}
// If it's not a reschedule, doesn't require confirmation and there's no price,
@@ -761,29 +756,19 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
metadata.entryPoints = results[0].createdEvent?.entryPoints;
}
if (noEmail !== true) {
- await sendScheduledEmails(
- {
- ...evt,
- additionalInformation: metadata,
- additionalNotes,
- customInputs,
- },
- reqBody.recurringEventId ? (eventType.recurringEvent as RecurringEvent) : {}
- );
+ await sendScheduledEmails({
+ ...evt,
+ additionalInformation: metadata,
+ additionalNotes,
+ customInputs,
+ });
}
}
}
if (eventType.requiresConfirmation && !rescheduleUid && noEmail !== true) {
- await sendOrganizerRequestEmail(
- { ...evt, additionalNotes },
- reqBody.recurringEventId ? (eventType.recurringEvent as RecurringEvent) : {}
- );
- await sendAttendeeRequestEmail(
- { ...evt, additionalNotes },
- attendeesList[0],
- reqBody.recurringEventId ? (eventType.recurringEvent as RecurringEvent) : {}
- );
+ await sendOrganizerRequestEmail({ ...evt, additionalNotes });
+ await sendAttendeeRequestEmail({ ...evt, additionalNotes }, attendeesList[0]);
}
if (
diff --git a/apps/web/pages/api/cancel.ts b/apps/web/pages/api/cancel.ts
index 85f18a349e..428184ba1a 100644
--- a/apps/web/pages/api/cancel.ts
+++ b/apps/web/pages/api/cancel.ts
@@ -7,7 +7,7 @@ import { FAKE_DAILY_CREDENTIAL } from "@calcom/app-store/dailyvideo/lib/VideoApi
import { getCalendar } from "@calcom/core/CalendarManager";
import { deleteMeeting } from "@calcom/core/videoClient";
import { sendCancelledEmails } from "@calcom/emails";
-import { isPrismaObjOrUndefined } from "@calcom/lib";
+import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
import { refund } from "@ee/lib/stripe/server";
@@ -58,6 +58,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
paid: true,
eventType: {
select: {
+ recurringEvent: true,
title: true,
},
},
@@ -122,6 +123,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
attendees: attendeesList,
uid: bookingToDelete?.uid,
+ recurringEvent: parseRecurringEvent(bookingToDelete.eventType?.recurringEvent),
location: bookingToDelete?.location,
destinationCalendar: bookingToDelete?.destinationCalendar || bookingToDelete?.user.destinationCalendar,
cancellationReason: cancellationReason,
@@ -175,6 +177,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
});
+ // Avoiding taking care of recurrence for now as Payments are not supported with Recurring Events at the moment
if (bookingToDelete && bookingToDelete.paid) {
const evt: CalendarEvent = {
type: bookingToDelete?.eventType?.title as string,
diff --git a/apps/web/pages/api/cron/bookingReminder.ts b/apps/web/pages/api/cron/bookingReminder.ts
index e747b9dba1..b90ff94403 100644
--- a/apps/web/pages/api/cron/bookingReminder.ts
+++ b/apps/web/pages/api/cron/bookingReminder.ts
@@ -3,7 +3,7 @@ import dayjs from "dayjs";
import type { NextApiRequest, NextApiResponse } from "next";
import { sendOrganizerRequestReminderEmail } from "@calcom/emails";
-import { isPrismaObjOrUndefined } from "@calcom/lib";
+import { isPrismaObjOrUndefined, parseRecurringEvent } from "@calcom/lib";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
@@ -45,6 +45,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
destinationCalendar: true,
},
},
+ eventType: {
+ select: {
+ recurringEvent: true,
+ },
+ },
uid: true,
destinationCalendar: true,
},
@@ -102,6 +107,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
attendees: attendeesList,
uid: booking.uid,
+ recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
destinationCalendar: booking.destinationCalendar || user.destinationCalendar,
};
diff --git a/apps/web/pages/api/email.ts b/apps/web/pages/api/email.ts
index a8fdca5b29..193978b31d 100644
--- a/apps/web/pages/api/email.ts
+++ b/apps/web/pages/api/email.ts
@@ -39,6 +39,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
metadata: {},
cancellationReason: "It got late",
paymentInfo: { id: "pi_12312", link: "https://cal.com", reason: "no reason" },
+ recurringEvent: null,
};
req.statusCode = 200;
@@ -49,7 +50,6 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
renderEmail("OrganizerRequestReminderEmail", {
attendee: evt.attendees[0],
calEvent: evt,
- recurringEvent: {},
})
);
res.end();
diff --git a/apps/web/pages/d/[link]/[slug].tsx b/apps/web/pages/d/[link]/[slug].tsx
index 38ca1c2677..2edb2925be 100644
--- a/apps/web/pages/d/[link]/[slug].tsx
+++ b/apps/web/pages/d/[link]/[slug].tsx
@@ -2,7 +2,7 @@ import { Prisma } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
-import { RecurringEvent } from "@calcom/types/Calendar";
+import { parseRecurringEvent } from "@calcom/lib";
import { asStringOrNull } from "@lib/asStringOrNull";
import { getWorkingHours } from "@lib/availability";
@@ -142,7 +142,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const [user] = users;
const eventTypeObject = Object.assign({}, hashedLink.eventType, {
metadata: {} as JSONObject,
- recurringEvent: (eventTypeSelect.recurringEvent || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(hashedLink.eventType.recurringEvent),
periodStartDate: hashedLink.eventType.periodStartDate?.toString() ?? null,
periodEndDate: hashedLink.eventType.periodEndDate?.toString() ?? null,
slug,
diff --git a/apps/web/pages/d/[link]/book.tsx b/apps/web/pages/d/[link]/book.tsx
index ba2e528cb4..a0bea8b3ce 100644
--- a/apps/web/pages/d/[link]/book.tsx
+++ b/apps/web/pages/d/[link]/book.tsx
@@ -6,10 +6,10 @@ import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { getLocationLabels } from "@calcom/app-store/utils";
+import { parseRecurringEvent } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
-import { RecurringEvent } from "@calcom/types/Calendar";
-import { asStringOrThrow, asStringOrNull } from "@lib/asStringOrNull";
+import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -128,7 +128,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const eventType = {
...eventTypeRaw,
metadata: (eventTypeRaw.metadata || {}) as JSONObject,
- recurringEvent: (eventTypeRaw.recurringEvent || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
isWeb3Active:
web3Credentials && web3Credentials.key
? (((web3Credentials.key as JSONObject).isWeb3Active || false) as boolean)
@@ -159,7 +159,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
recurringEventCountQuery &&
(parseInt(recurringEventCountQuery) <= eventTypeObject.recurringEvent.count
? recurringEventCountQuery
- : eventType.recurringEvent.count)) ||
+ : eventType.recurringEvent?.count)) ||
null;
return {
diff --git a/apps/web/pages/event-types/[type].tsx b/apps/web/pages/event-types/[type].tsx
index 00ef66b301..cc97c3864d 100644
--- a/apps/web/pages/event-types/[type].tsx
+++ b/apps/web/pages/event-types/[type].tsx
@@ -34,9 +34,11 @@ import { z } from "zod";
import { SelectGifInput } from "@calcom/app-store/giphy/components";
import getApps, { getLocationOptions } from "@calcom/app-store/utils";
+import { parseRecurringEvent } from "@calcom/lib";
import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
+import prisma from "@calcom/prisma";
import { StripeData } from "@calcom/stripe/server";
import { RecurringEvent } from "@calcom/types/Calendar";
import { Alert } from "@calcom/ui/Alert";
@@ -52,7 +54,6 @@ import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error";
import { isSuccessRedirectAvailable } from "@lib/isSuccessRedirectAvailable";
import { LocationObject, LocationType } from "@lib/location";
-import prisma from "@lib/prisma";
import { slugify } from "@lib/slugify";
import { trpc } from "@lib/trpc";
import { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -112,7 +113,7 @@ export type FormValues = {
description: string;
disableGuests: boolean;
requiresConfirmation: boolean;
- recurringEvent: RecurringEvent;
+ recurringEvent: RecurringEvent | null;
schedulingType: SchedulingType | null;
price: number;
currency: string;
@@ -477,7 +478,7 @@ const EventTypePage = (props: inferSSRProps) => {
const formMethods = useForm({
defaultValues: {
locations: eventType.locations || [],
- recurringEvent: eventType.recurringEvent || {},
+ recurringEvent: eventType.recurringEvent || null,
schedule: eventType.schedule?.id,
periodDates: {
startDate: periodDates.startDate,
@@ -2249,7 +2250,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const { locations, metadata, ...restEventType } = rawEventType;
const eventType = {
...restEventType,
- recurringEvent: (restEventType.recurringEvent || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(restEventType.recurringEvent),
locations: locations as unknown as LocationObject[],
metadata: (metadata || {}) as JSONObject,
isWeb3Active:
diff --git a/apps/web/pages/success.tsx b/apps/web/pages/success.tsx
index c3ecab48e9..a0d1910989 100644
--- a/apps/web/pages/success.tsx
+++ b/apps/web/pages/success.tsx
@@ -23,11 +23,11 @@ import {
useIsBackgroundTransparent,
useIsEmbed,
} from "@calcom/embed-core/embed-iframe";
+import { parseRecurringEvent } from "@calcom/lib";
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { localStorage } from "@calcom/lib/webstorage";
import { Prisma } from "@calcom/prisma/client";
-import { RecurringEvent } from "@calcom/types/Calendar";
import Button from "@calcom/ui/Button";
import { EmailInput } from "@calcom/ui/form/fields";
@@ -813,7 +813,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const eventType = {
...eventTypeRaw,
- recurringEvent: (eventTypeRaw.recurringEvent || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
};
const profile = {
diff --git a/apps/web/pages/team/[slug]/[type].tsx b/apps/web/pages/team/[slug]/[type].tsx
index 4d7dd6a637..6ab35f2d01 100644
--- a/apps/web/pages/team/[slug]/[type].tsx
+++ b/apps/web/pages/team/[slug]/[type].tsx
@@ -2,7 +2,7 @@ import { UserPlan } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
-import { RecurringEvent } from "@calcom/types/Calendar";
+import { parseRecurringEvent } from "@calcom/lib";
import { asStringOrNull } from "@lib/asStringOrNull";
import { getWorkingHours } from "@lib/availability";
@@ -115,7 +115,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
metadata: (eventType.metadata || {}) as JSONObject,
periodStartDate: eventType.periodStartDate?.toString() ?? null,
periodEndDate: eventType.periodEndDate?.toString() ?? null,
- recurringEvent: (eventType.recurringEvent || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(eventType.recurringEvent),
locations: locationHiddenFilter(locations),
});
diff --git a/apps/web/pages/team/[slug]/book.tsx b/apps/web/pages/team/[slug]/book.tsx
index 69368e23b2..09b1d95a50 100644
--- a/apps/web/pages/team/[slug]/book.tsx
+++ b/apps/web/pages/team/[slug]/book.tsx
@@ -2,8 +2,8 @@ import { GetServerSidePropsContext } from "next";
import { JSONObject } from "superjson/dist/types";
import { getLocationLabels } from "@calcom/app-store/utils";
+import { parseRecurringEvent } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
-import { RecurringEvent } from "@calcom/types/Calendar";
import { asStringOrNull, asStringOrThrow } from "@lib/asStringOrNull";
import getBooking, { GetBookingType } from "@lib/getBooking";
@@ -75,7 +75,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
const eventType = {
...eventTypeRaw,
- recurringEvent: (eventTypeRaw.recurringEvent || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
};
const eventTypeObject = [eventType].map((e) => {
diff --git a/apps/web/playwright/event-types.test.ts b/apps/web/playwright/event-types.test.ts
index ac61eda820..bfd39ab2c1 100644
--- a/apps/web/playwright/event-types.test.ts
+++ b/apps/web/playwright/event-types.test.ts
@@ -68,9 +68,9 @@ test.describe("Event Types tests", () => {
});
await page.click("[data-testid=show-advanced-settings]");
- await expect(page.locator("[data-testid=recurring-event-collapsible] > *")).not.toBeVisible();
+ await expect(page.locator("[data-testid=recurring-event-collapsible]")).not.toBeVisible();
await page.click("[data-testid=recurring-event-check]");
- isCreated = page.locator("[data-testid=recurring-event-collapsible] > *");
+ isCreated = page.locator("[data-testid=recurring-event-collapsible]");
await expect(isCreated).toBeVisible();
expect(
diff --git a/apps/web/server/routers/viewer.tsx b/apps/web/server/routers/viewer.tsx
index c040648b3e..89564388bd 100644
--- a/apps/web/server/routers/viewer.tsx
+++ b/apps/web/server/routers/viewer.tsx
@@ -8,8 +8,8 @@ import getApps, { getLocationOptions } from "@calcom/app-store/utils";
import { getCalendarCredentials, getConnectedCalendars } from "@calcom/core/CalendarManager";
import { checkPremiumUsername } from "@calcom/ee/lib/core/checkPremiumUsername";
import { sendFeedbackEmail } from "@calcom/emails";
+import { parseRecurringEvent } from "@calcom/lib";
import { baseEventTypeSelect, bookingMinimalSelect } from "@calcom/prisma";
-import { RecurringEvent } from "@calcom/types/Calendar";
import { checkRegularUsername } from "@lib/core/checkRegularUsername";
import jackson from "@lib/jackson";
@@ -429,7 +429,7 @@ const loggedInViewerRouter = createProtectedRouter()
...booking,
eventType: {
...booking.eventType,
- recurringEvent: ((booking.eventType && booking.eventType.recurringEvent) || {}) as RecurringEvent,
+ recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
},
startTime: booking.startTime.toISOString(),
endTime: booking.endTime.toISOString(),
diff --git a/apps/web/server/routers/viewer/bookings.tsx b/apps/web/server/routers/viewer/bookings.tsx
index 88548aae82..fdade79293 100644
--- a/apps/web/server/routers/viewer/bookings.tsx
+++ b/apps/web/server/routers/viewer/bookings.tsx
@@ -4,6 +4,7 @@ import { z } from "zod";
import EventManager from "@calcom/core/EventManager";
import { sendLocationChangeEmails } from "@calcom/emails";
+import { parseRecurringEvent } from "@calcom/lib";
import logger from "@calcom/lib/logger";
import { getTranslation } from "@calcom/lib/server/i18n";
import type { AdditionalInformation, CalendarEvent } from "@calcom/types/Calendar";
@@ -118,6 +119,7 @@ export const bookingsRouter = createProtectedRouter()
},
attendees: attendeesList,
uid: booking.uid,
+ recurringEvent: parseRecurringEvent(booking.eventType?.recurringEvent),
location,
destinationCalendar: booking?.destinationCalendar || booking?.user?.destinationCalendar,
};
diff --git a/apps/web/server/routers/viewer/eventTypes.tsx b/apps/web/server/routers/viewer/eventTypes.tsx
index 4fb76c6a4e..2b4fbba90e 100644
--- a/apps/web/server/routers/viewer/eventTypes.tsx
+++ b/apps/web/server/routers/viewer/eventTypes.tsx
@@ -283,6 +283,8 @@ export const eventTypesRouter = createProtectedRouter()
until: recurringEvent.until as unknown as Prisma.InputJsonObject,
tzid: recurringEvent.tzid,
};
+ } else {
+ data.recurringEvent = Prisma.DbNull;
}
if (destinationCalendar) {
diff --git a/apps/web/test/lib/team-event-types.test.ts b/apps/web/test/lib/team-event-types.test.ts
index 90f6000203..241574113f 100644
--- a/apps/web/test/lib/team-event-types.test.ts
+++ b/apps/web/test/lib/team-event-types.test.ts
@@ -1,28 +1,46 @@
+import { UserPlan } from "@prisma/client";
+
import { getLuckyUsers } from "../../pages/api/book/event";
+const baseUser = {
+ id: 0,
+ username: "test",
+ name: "Test User",
+ credentials: [],
+ timeZone: "GMT",
+ bufferTime: 0,
+ email: "test@example.com",
+ destinationCalendar: null,
+ locale: "en",
+ theme: null,
+ brandColor: "#292929",
+ darkBrandColor: "#fafafa",
+ availability: [],
+ selectedCalendars: [],
+ startTime: 0,
+ endTime: 0,
+ schedules: [],
+ defaultScheduleId: null,
+ plan: UserPlan.PRO,
+ avatar: "",
+ hideBranding: true,
+};
+
it("can find lucky users", async () => {
const users = [
{
+ ...baseUser,
id: 1,
username: "test",
name: "Test User",
- credentials: [],
- timeZone: "GMT",
- bufferTime: 0,
email: "test@example.com",
- destinationCalendar: null,
- locale: "en",
},
{
+ ...baseUser,
id: 2,
username: "test2",
name: "Test 2 User",
- credentials: [],
- timeZone: "GMT",
- bufferTime: 0,
email: "test2@example.com",
- destinationCalendar: null,
- locale: "en",
},
];
expect(
diff --git a/packages/emails/email-manager.ts b/packages/emails/email-manager.ts
index f195ac4c9f..12d264805a 100644
--- a/packages/emails/email-manager.ts
+++ b/packages/emails/email-manager.ts
@@ -1,4 +1,4 @@
-import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
+import type { CalendarEvent, Person } from "@calcom/types/Calendar";
import AttendeeAwaitingPaymentEmail from "./templates/attendee-awaiting-payment-email";
import AttendeeCancelledEmail from "./templates/attendee-cancelled-email";
@@ -20,14 +20,14 @@ import OrganizerRescheduledEmail from "./templates/organizer-rescheduled-email";
import OrganizerScheduledEmail from "./templates/organizer-scheduled-email";
import TeamInviteEmail, { TeamInvite } from "./templates/team-invite-email";
-export const sendScheduledEmails = async (calEvent: CalendarEvent, recurringEvent: RecurringEvent = {}) => {
+export const sendScheduledEmails = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise[] = [];
emailsToSend.push(
...calEvent.attendees.map((attendee) => {
return new Promise((resolve, reject) => {
try {
- const scheduledEmail = new AttendeeScheduledEmail(calEvent, attendee, recurringEvent);
+ const scheduledEmail = new AttendeeScheduledEmail(calEvent, attendee);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("AttendeeRescheduledEmail.sendEmail failed", e));
@@ -39,7 +39,7 @@ export const sendScheduledEmails = async (calEvent: CalendarEvent, recurringEven
emailsToSend.push(
new Promise((resolve, reject) => {
try {
- const scheduledEmail = new OrganizerScheduledEmail(calEvent, recurringEvent);
+ const scheduledEmail = new OrganizerScheduledEmail(calEvent);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerScheduledEmail.sendEmail failed", e));
@@ -50,14 +50,14 @@ export const sendScheduledEmails = async (calEvent: CalendarEvent, recurringEven
await Promise.all(emailsToSend);
};
-export const sendRescheduledEmails = async (calEvent: CalendarEvent, recurringEvent: RecurringEvent = {}) => {
+export const sendRescheduledEmails = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise[] = [];
emailsToSend.push(
...calEvent.attendees.map((attendee) => {
return new Promise((resolve, reject) => {
try {
- const scheduledEmail = new AttendeeRescheduledEmail(calEvent, attendee, recurringEvent);
+ const scheduledEmail = new AttendeeRescheduledEmail(calEvent, attendee);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("AttendeeRescheduledEmail.sendEmail failed", e));
@@ -69,7 +69,7 @@ export const sendRescheduledEmails = async (calEvent: CalendarEvent, recurringEv
emailsToSend.push(
new Promise((resolve, reject) => {
try {
- const scheduledEmail = new OrganizerRescheduledEmail(calEvent, recurringEvent);
+ const scheduledEmail = new OrganizerRescheduledEmail(calEvent);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerScheduledEmail.sendEmail failed", e));
@@ -80,13 +80,10 @@ export const sendRescheduledEmails = async (calEvent: CalendarEvent, recurringEv
await Promise.all(emailsToSend);
};
-export const sendOrganizerRequestEmail = async (
- calEvent: CalendarEvent,
- recurringEvent: RecurringEvent = {}
-) => {
+export const sendOrganizerRequestEmail = async (calEvent: CalendarEvent) => {
await new Promise((resolve, reject) => {
try {
- const organizerRequestEmail = new OrganizerRequestEmail(calEvent, recurringEvent);
+ const organizerRequestEmail = new OrganizerRequestEmail(calEvent);
resolve(organizerRequestEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerRequestEmail.sendEmail failed", e));
@@ -94,14 +91,10 @@ export const sendOrganizerRequestEmail = async (
});
};
-export const sendAttendeeRequestEmail = async (
- calEvent: CalendarEvent,
- attendee: Person,
- recurringEvent: RecurringEvent = {}
-) => {
+export const sendAttendeeRequestEmail = async (calEvent: CalendarEvent, attendee: Person) => {
await new Promise((resolve, reject) => {
try {
- const attendeeRequestEmail = new AttendeeRequestEmail(calEvent, attendee, recurringEvent);
+ const attendeeRequestEmail = new AttendeeRequestEmail(calEvent, attendee);
resolve(attendeeRequestEmail.sendEmail());
} catch (e) {
reject(console.error("AttendRequestEmail.sendEmail failed", e));
@@ -109,14 +102,14 @@ export const sendAttendeeRequestEmail = async (
});
};
-export const sendDeclinedEmails = async (calEvent: CalendarEvent, recurringEvent: RecurringEvent = {}) => {
+export const sendDeclinedEmails = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise[] = [];
emailsToSend.push(
...calEvent.attendees.map((attendee) => {
return new Promise((resolve, reject) => {
try {
- const declinedEmail = new AttendeeDeclinedEmail(calEvent, attendee, recurringEvent);
+ const declinedEmail = new AttendeeDeclinedEmail(calEvent, attendee);
resolve(declinedEmail.sendEmail());
} catch (e) {
reject(console.error("AttendeeRescheduledEmail.sendEmail failed", e));
@@ -128,14 +121,14 @@ export const sendDeclinedEmails = async (calEvent: CalendarEvent, recurringEvent
await Promise.all(emailsToSend);
};
-export const sendCancelledEmails = async (calEvent: CalendarEvent, recurringEvent: RecurringEvent = {}) => {
+export const sendCancelledEmails = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise[] = [];
emailsToSend.push(
...calEvent.attendees.map((attendee) => {
return new Promise((resolve, reject) => {
try {
- const scheduledEmail = new AttendeeCancelledEmail(calEvent, attendee, recurringEvent);
+ const scheduledEmail = new AttendeeCancelledEmail(calEvent, attendee);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("AttendeeCancelledEmail.sendEmail failed", e));
@@ -147,7 +140,7 @@ export const sendCancelledEmails = async (calEvent: CalendarEvent, recurringEven
emailsToSend.push(
new Promise((resolve, reject) => {
try {
- const scheduledEmail = new OrganizerCancelledEmail(calEvent, recurringEvent);
+ const scheduledEmail = new OrganizerCancelledEmail(calEvent);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerCancelledEmail.sendEmail failed", e));
@@ -158,13 +151,10 @@ export const sendCancelledEmails = async (calEvent: CalendarEvent, recurringEven
await Promise.all(emailsToSend);
};
-export const sendOrganizerRequestReminderEmail = async (
- calEvent: CalendarEvent,
- recurringEvent: RecurringEvent = {}
-) => {
+export const sendOrganizerRequestReminderEmail = async (calEvent: CalendarEvent) => {
await new Promise((resolve, reject) => {
try {
- const organizerRequestReminderEmail = new OrganizerRequestReminderEmail(calEvent, recurringEvent);
+ const organizerRequestReminderEmail = new OrganizerRequestReminderEmail(calEvent);
resolve(organizerRequestReminderEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerRequestReminderEmail.sendEmail failed", e));
@@ -172,17 +162,14 @@ export const sendOrganizerRequestReminderEmail = async (
});
};
-export const sendAwaitingPaymentEmail = async (
- calEvent: CalendarEvent,
- recurringEvent: RecurringEvent = {}
-) => {
+export const sendAwaitingPaymentEmail = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise[] = [];
emailsToSend.push(
...calEvent.attendees.map((attendee) => {
return new Promise((resolve, reject) => {
try {
- const paymentEmail = new AttendeeAwaitingPaymentEmail(calEvent, attendee, recurringEvent);
+ const paymentEmail = new AttendeeAwaitingPaymentEmail(calEvent, attendee);
resolve(paymentEmail.sendEmail());
} catch (e) {
reject(console.error("AttendeeAwaitingPaymentEmail.sendEmail failed", e));
@@ -194,13 +181,10 @@ export const sendAwaitingPaymentEmail = async (
await Promise.all(emailsToSend);
};
-export const sendOrganizerPaymentRefundFailedEmail = async (
- calEvent: CalendarEvent,
- recurringEvent: RecurringEvent = {}
-) => {
+export const sendOrganizerPaymentRefundFailedEmail = async (calEvent: CalendarEvent) => {
await new Promise((resolve, reject) => {
try {
- const paymentRefundFailedEmail = new OrganizerPaymentRefundFailedEmail(calEvent, recurringEvent);
+ const paymentRefundFailedEmail = new OrganizerPaymentRefundFailedEmail(calEvent);
resolve(paymentRefundFailedEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerPaymentRefundFailedEmail.sendEmail failed", e));
@@ -232,19 +216,14 @@ export const sendTeamInviteEmail = async (teamInviteEvent: TeamInvite) => {
export const sendRequestRescheduleEmail = async (
calEvent: CalendarEvent,
- metadata: { rescheduleLink: string },
- recurringEvent: RecurringEvent = {}
+ metadata: { rescheduleLink: string }
) => {
const emailsToSend: Promise[] = [];
emailsToSend.push(
new Promise((resolve, reject) => {
try {
- const requestRescheduleEmail = new AttendeeRequestRescheduledEmail(
- calEvent,
- metadata,
- recurringEvent
- );
+ const requestRescheduleEmail = new AttendeeRequestRescheduledEmail(calEvent, metadata);
resolve(requestRescheduleEmail.sendEmail());
} catch (e) {
reject(console.error("AttendeeRequestRescheduledEmail.sendEmail failed", e));
@@ -255,11 +234,7 @@ export const sendRequestRescheduleEmail = async (
emailsToSend.push(
new Promise((resolve, reject) => {
try {
- const requestRescheduleEmail = new OrganizerRequestRescheduleEmail(
- calEvent,
- metadata,
- recurringEvent
- );
+ const requestRescheduleEmail = new OrganizerRequestRescheduleEmail(calEvent, metadata);
resolve(requestRescheduleEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerRequestRescheduledEmail.sendEmail failed", e));
@@ -270,17 +245,14 @@ export const sendRequestRescheduleEmail = async (
await Promise.all(emailsToSend);
};
-export const sendLocationChangeEmails = async (
- calEvent: CalendarEvent,
- recurringEvent: RecurringEvent = {}
-) => {
+export const sendLocationChangeEmails = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise[] = [];
emailsToSend.push(
...calEvent.attendees.map((attendee) => {
return new Promise((resolve, reject) => {
try {
- const scheduledEmail = new AttendeeLocationChangeEmail(calEvent, attendee, recurringEvent);
+ const scheduledEmail = new AttendeeLocationChangeEmail(calEvent, attendee);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("AttendeeLocationChangeEmail.sendEmail failed", e));
@@ -292,7 +264,7 @@ export const sendLocationChangeEmails = async (
emailsToSend.push(
new Promise((resolve, reject) => {
try {
- const scheduledEmail = new OrganizerLocationChangeEmail(calEvent, recurringEvent);
+ const scheduledEmail = new OrganizerLocationChangeEmail(calEvent);
resolve(scheduledEmail.sendEmail());
} catch (e) {
reject(console.error("OrganizerLocationChangeEmail.sendEmail failed", e));
diff --git a/packages/emails/src/components/WhenInfo.tsx b/packages/emails/src/components/WhenInfo.tsx
index 533fb01fa3..c31808656e 100644
--- a/packages/emails/src/components/WhenInfo.tsx
+++ b/packages/emails/src/components/WhenInfo.tsx
@@ -3,31 +3,26 @@ import timezone from "dayjs/plugin/timezone";
import { TFunction } from "next-i18next";
import rrule from "rrule";
-import type { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
+import type { CalendarEvent } from "@calcom/types/Calendar";
import { Info } from "./Info";
dayjs.extend(timezone);
-function getRecurringWhen(props: { calEvent: CalendarEvent; recurringEvent: RecurringEvent }) {
+function getRecurringWhen(props: { calEvent: CalendarEvent }) {
const t = props.calEvent.attendees[0].language.translate;
- return props.recurringEvent?.count && props.recurringEvent?.freq
+ const { calEvent: { recurringEvent } = {} } = props;
+ return recurringEvent?.count && recurringEvent?.freq
? ` - ${t("every_for_freq", {
- freq: t(`${rrule.FREQUENCIES[props.recurringEvent.freq].toString().toLowerCase()}`),
- })} ${props.recurringEvent.count} ${t(
- `${rrule.FREQUENCIES[props.recurringEvent.freq].toString().toLowerCase()}`,
- { count: props.recurringEvent.count }
- )}`
+ freq: t(`${rrule.FREQUENCIES[recurringEvent.freq].toString().toLowerCase()}`),
+ })} ${recurringEvent.count} ${t(`${rrule.FREQUENCIES[recurringEvent.freq].toString().toLowerCase()}`, {
+ count: recurringEvent.count,
+ })}`
: "";
}
-export function WhenInfo(props: {
- calEvent: CalendarEvent;
- recurringEvent: RecurringEvent;
- timeZone: string;
- t: TFunction;
-}) {
- const { timeZone, t } = props;
+export function WhenInfo(props: { calEvent: CalendarEvent; timeZone: string; t: TFunction }) {
+ const { timeZone, t, calEvent: { recurringEvent } = {} } = props;
function getRecipientStart(format: string) {
return dayjs(props.calEvent.startTime).tz(timeZone).format(format);
@@ -44,7 +39,7 @@ export function WhenInfo(props: {
lineThrough={!!props.calEvent.cancellationReason}
description={
<>
- {props.recurringEvent?.count ? `${t("starting")} ` : ""}
+ {recurringEvent?.count ? `${t("starting")} ` : ""}
{t(getRecipientStart("dddd").toLowerCase())}, {t(getRecipientStart("MMMM").toLowerCase())}{" "}
{getRecipientStart("D, YYYY | h:mma")} - {getRecipientEnd("h:mma")}{" "}
({timeZone})
diff --git a/packages/emails/src/templates/AttendeeDeclinedEmail.tsx b/packages/emails/src/templates/AttendeeDeclinedEmail.tsx
index 4c66c83688..73a5a07afc 100644
--- a/packages/emails/src/templates/AttendeeDeclinedEmail.tsx
+++ b/packages/emails/src/templates/AttendeeDeclinedEmail.tsx
@@ -2,7 +2,9 @@ import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
export const AttendeeDeclinedEmail = (props: React.ComponentProps) => (
) => (
>
) => {
return (
diff --git a/packages/emails/src/templates/BaseScheduledEmail.tsx b/packages/emails/src/templates/BaseScheduledEmail.tsx
index bf55356c2b..2a15f83e29 100644
--- a/packages/emails/src/templates/BaseScheduledEmail.tsx
+++ b/packages/emails/src/templates/BaseScheduledEmail.tsx
@@ -5,7 +5,7 @@ import toArray from "dayjs/plugin/toArray";
import utc from "dayjs/plugin/utc";
import type { TFunction } from "next-i18next";
-import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
+import type { CalendarEvent, Person } from "@calcom/types/Calendar";
import {
BaseEmailHtml,
@@ -26,7 +26,6 @@ export const BaseScheduledEmail = (
props: {
calEvent: CalendarEvent;
attendee: Person;
- recurringEvent: RecurringEvent;
timeZone: string;
t: TFunction;
} & Partial>
@@ -56,7 +55,7 @@ export const BaseScheduledEmail = (
title={t(
props.title
? props.title
- : props.recurringEvent?.count
+ : props.calEvent.recurringEvent?.count
? "your_event_has_been_scheduled_recurring"
: "your_event_has_been_scheduled"
)}
@@ -69,7 +68,7 @@ export const BaseScheduledEmail = (
-
+
diff --git a/packages/emails/src/templates/OrganizerRequestEmail.tsx b/packages/emails/src/templates/OrganizerRequestEmail.tsx
index 27027838a8..4013ecdc68 100644
--- a/packages/emails/src/templates/OrganizerRequestEmail.tsx
+++ b/packages/emails/src/templates/OrganizerRequestEmail.tsx
@@ -4,7 +4,7 @@ import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
export const OrganizerRequestEmail = (props: React.ComponentProps) => (
>
) => {
const t = props.calEvent.organizer.language.translate;
@@ -15,7 +14,9 @@ export const OrganizerScheduledEmail = (
timeZone={props.calEvent.organizer.timeZone}
t={t}
subject={t("confirmed_event_type_subject")}
- title={t(props.recurringEvent?.count ? "new_event_scheduled_recurring" : "new_event_scheduled")}
+ title={t(
+ props.calEvent.recurringEvent?.count ? "new_event_scheduled_recurring" : "new_event_scheduled"
+ )}
{...props}
/>
);
diff --git a/packages/emails/templates/attendee-awaiting-payment-email.ts b/packages/emails/templates/attendee-awaiting-payment-email.ts
index 66f6d38c01..8c012190e5 100644
--- a/packages/emails/templates/attendee-awaiting-payment-email.ts
+++ b/packages/emails/templates/attendee-awaiting-payment-email.ts
@@ -15,7 +15,6 @@ export default class AttendeeAwaitingPaymentEmail extends AttendeeScheduledEmail
html: renderEmail("AttendeeAwaitingPaymentEmail", {
calEvent: this.calEvent,
attendee: this.attendee,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("meeting_awaiting_payment"),
};
diff --git a/packages/emails/templates/attendee-cancelled-email.ts b/packages/emails/templates/attendee-cancelled-email.ts
index 71531b549e..13d6f12e37 100644
--- a/packages/emails/templates/attendee-cancelled-email.ts
+++ b/packages/emails/templates/attendee-cancelled-email.ts
@@ -15,7 +15,6 @@ export default class AttendeeCancelledEmail extends AttendeeScheduledEmail {
html: renderEmail("AttendeeCancelledEmail", {
calEvent: this.calEvent,
attendee: this.attendee,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("event_request_cancelled", "emailed_you_and_any_other_attendees"),
};
diff --git a/packages/emails/templates/attendee-declined-email.ts b/packages/emails/templates/attendee-declined-email.ts
index e183e75a0e..e86806d832 100644
--- a/packages/emails/templates/attendee-declined-email.ts
+++ b/packages/emails/templates/attendee-declined-email.ts
@@ -15,10 +15,9 @@ export default class AttendeeDeclinedEmail extends AttendeeScheduledEmail {
html: renderEmail("AttendeeDeclinedEmail", {
calEvent: this.calEvent,
attendee: this.attendee,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody(
- this.recurringEvent?.count ? "event_request_declined_recurring" : "event_request_declined"
+ this.calEvent.recurringEvent?.count ? "event_request_declined_recurring" : "event_request_declined"
),
};
}
diff --git a/packages/emails/templates/attendee-location-change-email.ts b/packages/emails/templates/attendee-location-change-email.ts
index fa47ac824b..925ba0806d 100644
--- a/packages/emails/templates/attendee-location-change-email.ts
+++ b/packages/emails/templates/attendee-location-change-email.ts
@@ -19,7 +19,6 @@ export default class AttendeeLocationChangeEmail extends AttendeeScheduledEmail
html: renderEmail("AttendeeLocationChangeEmail", {
calEvent: this.calEvent,
attendee: this.attendee,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("event_location_changed"),
};
diff --git a/packages/emails/templates/attendee-request-email.ts b/packages/emails/templates/attendee-request-email.ts
index f82143ccc3..027a004c10 100644
--- a/packages/emails/templates/attendee-request-email.ts
+++ b/packages/emails/templates/attendee-request-email.ts
@@ -24,7 +24,6 @@ export default class AttendeeRequestEmail extends AttendeeScheduledEmail {
html: renderEmail("AttendeeRequestEmail", {
calEvent: this.calEvent,
attendee: this.attendee,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody(
this.calEvent.attendees[0].language.translate("booking_submitted", {
diff --git a/packages/emails/templates/attendee-request-reschedule-email.ts b/packages/emails/templates/attendee-request-reschedule-email.ts
index 721f6def8a..02c73a0a06 100644
--- a/packages/emails/templates/attendee-request-reschedule-email.ts
+++ b/packages/emails/templates/attendee-request-reschedule-email.ts
@@ -5,10 +5,10 @@ import toArray from "dayjs/plugin/toArray";
import utc from "dayjs/plugin/utc";
import { createEvent, DateArray, Person } from "ics";
-import { renderEmail } from "../";
import { getCancelLink } from "@calcom/lib/CalEventParser";
-import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
+import { CalendarEvent } from "@calcom/types/Calendar";
+import { renderEmail } from "../";
import OrganizerScheduledEmail from "./organizer-scheduled-email";
dayjs.extend(utc);
@@ -18,8 +18,8 @@ dayjs.extend(toArray);
export default class AttendeeRequestRescheduledEmail extends OrganizerScheduledEmail {
private metadata: { rescheduleLink: string };
- constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }, recurringEvent: RecurringEvent) {
- super(calEvent, recurringEvent);
+ constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) {
+ super(calEvent);
this.metadata = metadata;
}
protected getNodeMailerPayload(): Record {
@@ -40,7 +40,6 @@ export default class AttendeeRequestRescheduledEmail extends OrganizerScheduledE
calEvent: this.calEvent,
attendee: this.calEvent.organizer,
metadata: this.metadata,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody(),
};
diff --git a/packages/emails/templates/attendee-rescheduled-email.ts b/packages/emails/templates/attendee-rescheduled-email.ts
index 10f65ce3f5..9413735325 100644
--- a/packages/emails/templates/attendee-rescheduled-email.ts
+++ b/packages/emails/templates/attendee-rescheduled-email.ts
@@ -19,7 +19,6 @@ export default class AttendeeRescheduledEmail extends AttendeeScheduledEmail {
html: renderEmail("AttendeeCancelledEmail", {
calEvent: this.calEvent,
attendee: this.attendee,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("event_has_been_rescheduled", "emailed_you_and_any_other_attendees"),
};
diff --git a/packages/emails/templates/attendee-scheduled-email.ts b/packages/emails/templates/attendee-scheduled-email.ts
index 6b53bed1fd..7cb3f7a5c4 100644
--- a/packages/emails/templates/attendee-scheduled-email.ts
+++ b/packages/emails/templates/attendee-scheduled-email.ts
@@ -8,7 +8,7 @@ import { TFunction } from "next-i18next";
import rrule from "rrule";
import { getRichDescription } from "@calcom/lib/CalEventParser";
-import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
+import type { CalendarEvent, Person } from "@calcom/types/Calendar";
import { renderEmail } from "../";
import BaseEmail from "./_base-email";
@@ -21,23 +21,22 @@ dayjs.extend(toArray);
export default class AttendeeScheduledEmail extends BaseEmail {
calEvent: CalendarEvent;
attendee: Person;
- recurringEvent: RecurringEvent;
t: TFunction;
- constructor(calEvent: CalendarEvent, attendee: Person, recurringEvent: RecurringEvent) {
+ constructor(calEvent: CalendarEvent, attendee: Person) {
super();
this.name = "SEND_BOOKING_CONFIRMATION";
this.calEvent = calEvent;
this.attendee = attendee;
this.t = attendee.language.translate;
- this.recurringEvent = recurringEvent;
}
protected getiCalEventAsString(): string | undefined {
// Taking care of recurrence rule beforehand
let recurrenceRule: string | undefined = undefined;
- if (this.recurringEvent?.count) {
- recurrenceRule = new rrule(this.recurringEvent).toString().replace("RRULE:", "");
+ if (this.calEvent.recurringEvent?.count) {
+ // ics appends "RRULE:" already, so removing it from RRule generated string
+ recurrenceRule = new rrule(this.calEvent.recurringEvent).toString().replace("RRULE:", "");
}
const icsEvent = createEvent({
start: dayjs(this.calEvent.startTime)
@@ -84,7 +83,6 @@ export default class AttendeeScheduledEmail extends BaseEmail {
html: renderEmail("AttendeeScheduledEmail", {
calEvent: this.calEvent,
attendee: this.attendee,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody(),
};
@@ -93,7 +91,7 @@ export default class AttendeeScheduledEmail extends BaseEmail {
protected getTextBody(title = "", subtitle = "emailed_you_and_any_other_attendees"): string {
return `
${this.t(
- title || this.recurringEvent?.count
+ title || this.calEvent.recurringEvent?.count
? "your_event_has_been_scheduled_recurring"
: "your_event_has_been_scheduled"
)}
diff --git a/packages/emails/templates/organizer-cancelled-email.ts b/packages/emails/templates/organizer-cancelled-email.ts
index b931b8c500..ef13237402 100644
--- a/packages/emails/templates/organizer-cancelled-email.ts
+++ b/packages/emails/templates/organizer-cancelled-email.ts
@@ -24,7 +24,6 @@ export default class OrganizerCancelledEmail extends OrganizerScheduledEmail {
html: renderEmail("OrganizerCancelledEmail", {
attendee: this.calEvent.organizer,
calEvent: this.calEvent,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("event_request_cancelled"),
};
diff --git a/packages/emails/templates/organizer-location-change-email.ts b/packages/emails/templates/organizer-location-change-email.ts
index 565d9ff6f7..043b85630b 100644
--- a/packages/emails/templates/organizer-location-change-email.ts
+++ b/packages/emails/templates/organizer-location-change-email.ts
@@ -28,7 +28,6 @@ export default class OrganizerLocationChangeEmail extends OrganizerScheduledEmai
html: renderEmail("OrganizerLocationChangeEmail", {
attendee: this.calEvent.organizer,
calEvent: this.calEvent,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("event_location_changed"),
};
diff --git a/packages/emails/templates/organizer-payment-refund-failed-email.ts b/packages/emails/templates/organizer-payment-refund-failed-email.ts
index 1632670d74..59142da771 100644
--- a/packages/emails/templates/organizer-payment-refund-failed-email.ts
+++ b/packages/emails/templates/organizer-payment-refund-failed-email.ts
@@ -1,5 +1,4 @@
import { renderEmail } from "../";
-
import OrganizerScheduledEmail from "./organizer-scheduled-email";
export default class OrganizerPaymentRefundFailedEmail extends OrganizerScheduledEmail {
@@ -25,7 +24,6 @@ export default class OrganizerPaymentRefundFailedEmail extends OrganizerSchedule
html: renderEmail("OrganizerPaymentRefundFailedEmail", {
calEvent: this.calEvent,
attendee: this.calEvent.organizer,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody(
"a_refund_failed",
diff --git a/packages/emails/templates/organizer-request-email.ts b/packages/emails/templates/organizer-request-email.ts
index 2278535a13..13a5bddf11 100644
--- a/packages/emails/templates/organizer-request-email.ts
+++ b/packages/emails/templates/organizer-request-email.ts
@@ -24,7 +24,6 @@ export default class OrganizerRequestEmail extends OrganizerScheduledEmail {
html: renderEmail("OrganizerRequestEmail", {
calEvent: this.calEvent,
attendee: this.calEvent.organizer,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("event_awaiting_approval"),
};
diff --git a/packages/emails/templates/organizer-request-reminder-email.ts b/packages/emails/templates/organizer-request-reminder-email.ts
index e2e4f1b68c..d87ed752c1 100644
--- a/packages/emails/templates/organizer-request-reminder-email.ts
+++ b/packages/emails/templates/organizer-request-reminder-email.ts
@@ -24,7 +24,6 @@ export default class OrganizerRequestReminderEmail extends OrganizerRequestEmail
html: renderEmail("OrganizerRequestReminderEmail", {
calEvent: this.calEvent,
attendee: this.calEvent.organizer,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("event_still_awaiting_approval"),
};
diff --git a/packages/emails/templates/organizer-request-reschedule-email.ts b/packages/emails/templates/organizer-request-reschedule-email.ts
index f01da57495..fc69d04f9e 100644
--- a/packages/emails/templates/organizer-request-reschedule-email.ts
+++ b/packages/emails/templates/organizer-request-reschedule-email.ts
@@ -5,9 +5,9 @@ import toArray from "dayjs/plugin/toArray";
import utc from "dayjs/plugin/utc";
import { createEvent, DateArray, Person } from "ics";
-import { renderEmail } from "../";
-import { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
+import { CalendarEvent } from "@calcom/types/Calendar";
+import { renderEmail } from "../";
import OrganizerScheduledEmail from "./organizer-scheduled-email";
dayjs.extend(utc);
@@ -17,8 +17,8 @@ dayjs.extend(toArray);
export default class OrganizerRequestRescheduledEmail extends OrganizerScheduledEmail {
private metadata: { rescheduleLink: string };
- constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }, recurringEvent: RecurringEvent) {
- super(calEvent, recurringEvent);
+ constructor(calEvent: CalendarEvent, metadata: { rescheduleLink: string }) {
+ super(calEvent);
this.metadata = metadata;
}
protected getNodeMailerPayload(): Record {
@@ -39,7 +39,6 @@ export default class OrganizerRequestRescheduledEmail extends OrganizerScheduled
html: renderEmail("OrganizerScheduledEmail", {
calEvent: this.calEvent,
attendee: this.calEvent.organizer,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody(
this.t("request_reschedule_title_organizer", {
diff --git a/packages/emails/templates/organizer-rescheduled-email.ts b/packages/emails/templates/organizer-rescheduled-email.ts
index d86a5c9a3b..551a555773 100644
--- a/packages/emails/templates/organizer-rescheduled-email.ts
+++ b/packages/emails/templates/organizer-rescheduled-email.ts
@@ -34,7 +34,6 @@ export default class OrganizerRescheduledEmail extends OrganizerScheduledEmail {
html: renderEmail("OrganizerRescheduledEmail", {
calEvent: this.calEvent,
attendee: this.calEvent.organizer,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody("event_has_been_rescheduled"),
};
diff --git a/packages/emails/templates/organizer-scheduled-email.ts b/packages/emails/templates/organizer-scheduled-email.ts
index e08f8a17be..cacbd0d51b 100644
--- a/packages/emails/templates/organizer-scheduled-email.ts
+++ b/packages/emails/templates/organizer-scheduled-email.ts
@@ -8,7 +8,7 @@ import { TFunction } from "next-i18next";
import rrule from "rrule";
import { getRichDescription } from "@calcom/lib/CalEventParser";
-import type { CalendarEvent, RecurringEvent } from "@calcom/types/Calendar";
+import type { CalendarEvent } from "@calcom/types/Calendar";
import { renderEmail } from "../";
import BaseEmail from "./_base-email";
@@ -20,22 +20,22 @@ dayjs.extend(toArray);
export default class OrganizerScheduledEmail extends BaseEmail {
calEvent: CalendarEvent;
- recurringEvent: RecurringEvent;
+
t: TFunction;
- constructor(calEvent: CalendarEvent, recurringEvent: RecurringEvent) {
+ constructor(calEvent: CalendarEvent) {
super();
this.name = "SEND_BOOKING_CONFIRMATION";
this.calEvent = calEvent;
- this.recurringEvent = recurringEvent;
this.t = this.calEvent.organizer.language.translate;
}
protected getiCalEventAsString(): string | undefined {
// Taking care of recurrence rule beforehand
let recurrenceRule: string | undefined = undefined;
- if (this.recurringEvent?.count) {
- recurrenceRule = new rrule(this.recurringEvent).toString().replace("RRULE:", "");
+ if (this.calEvent.recurringEvent?.count) {
+ // ics appends "RRULE:" already, so removing it from RRule generated string
+ recurrenceRule = new rrule(this.calEvent.recurringEvent).toString().replace("RRULE:", "");
}
const icsEvent = createEvent({
start: dayjs(this.calEvent.startTime)
@@ -91,7 +91,6 @@ export default class OrganizerScheduledEmail extends BaseEmail {
html: renderEmail("OrganizerScheduledEmail", {
calEvent: this.calEvent,
attendee: this.calEvent.organizer,
- recurringEvent: this.recurringEvent,
}),
text: this.getTextBody(),
};
@@ -104,7 +103,9 @@ export default class OrganizerScheduledEmail extends BaseEmail {
callToAction = ""
): string {
return `
-${this.t(title || this.recurringEvent?.count ? "new_event_scheduled_recurring" : "new_event_scheduled")}
+${this.t(
+ title || this.calEvent.recurringEvent?.count ? "new_event_scheduled_recurring" : "new_event_scheduled"
+)}
${this.t(subtitle)}
${extraInfo}
${getRichDescription(this.calEvent)}
diff --git a/packages/lib/defaultEvents.ts b/packages/lib/defaultEvents.ts
index e2afe80f0a..f6809cac8b 100644
--- a/packages/lib/defaultEvents.ts
+++ b/packages/lib/defaultEvents.ts
@@ -58,7 +58,7 @@ const commons = {
},
isWeb3Active: false,
hideCalendarNotes: false,
- recurringEvent: {},
+ recurringEvent: null,
destinationCalendar: null,
team: null,
requiresConfirmation: false,
diff --git a/packages/lib/index.ts b/packages/lib/index.ts
index df087d9d5c..78836ecdd1 100644
--- a/packages/lib/index.ts
+++ b/packages/lib/index.ts
@@ -1 +1,2 @@
export { default as isPrismaObj, isPrismaObjOrUndefined } from "./isPrismaObj";
+export * from "./isRecurringEvent";
diff --git a/packages/lib/isRecurringEvent.ts b/packages/lib/isRecurringEvent.ts
new file mode 100644
index 0000000000..869825f1d1
--- /dev/null
+++ b/packages/lib/isRecurringEvent.ts
@@ -0,0 +1,13 @@
+import { recurringEventType as recurringEventSchema } from "@calcom/prisma/zod-utils";
+import type { RecurringEvent } from "@calcom/types/Calendar";
+
+export function isRecurringEvent(obj: unknown): obj is RecurringEvent {
+ const parsedRecuEvt = recurringEventSchema.safeParse(obj);
+ return parsedRecuEvt.success;
+}
+
+export function parseRecurringEvent(obj: unknown): RecurringEvent | null {
+ let recurringEvent: RecurringEvent | null = null;
+ if (isRecurringEvent(obj)) recurringEvent = obj;
+ return recurringEvent;
+}
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index 2723607d1a..f97e0e1dc5 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -59,7 +59,7 @@ model EventType {
periodDays Int?
periodCountCalendarDays Boolean?
requiresConfirmation Boolean @default(false)
- /// @zod.custom(imports.recurringEvent)
+ /// @zod.custom(imports.recurringEventType)
recurringEvent Json?
disableGuests Boolean @default(false)
hideCalendarNotes Boolean @default(false)
diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts
index d926fdecb7..9ac22ed62e 100644
--- a/packages/prisma/zod-utils.ts
+++ b/packages/prisma/zod-utils.ts
@@ -1,9 +1,20 @@
-import { Frequency as RRuleFrequency } from "rrule";
+import dayjs from "dayjs";
import { z } from "zod";
import { LocationType } from "@calcom/core/location";
import { slugify } from "@calcom/lib/slugify";
+// Let's not import 118kb just to get an enum
+export enum Frequency {
+ YEARLY = 0,
+ MONTHLY = 1,
+ WEEKLY = 2,
+ DAILY = 3,
+ HOURLY = 4,
+ MINUTELY = 5,
+ SECONDLY = 6,
+}
+
export const eventTypeLocations = z.array(
z.object({
type: z.nativeEnum(LocationType),
@@ -15,15 +26,33 @@ export const eventTypeLocations = z.array(
);
// Matching RRule.Options: rrule/dist/esm/src/types.d.ts
-export const recurringEvent = z.object({
- dtstart: z.date().optional(),
- interval: z.number().optional(),
- count: z.number().optional(),
- freq: z.nativeEnum(RRuleFrequency).optional(),
- until: z.date().optional(),
- tzid: z.string().optional(),
-});
+export const recurringEventType = z
+ .object({
+ dtstart: z.date().optional(),
+ interval: z.number(),
+ count: z.number(),
+ freq: z.nativeEnum(Frequency),
+ until: z.date().optional(),
+ tzid: z.string().optional(),
+ })
+ .nullable();
export const eventTypeSlug = z.string().transform((val) => slugify(val.trim()));
+
export const stringToDate = z.string().transform((a) => new Date(a));
-export const stringOrNumber = z.union([z.string().transform((v) => parseInt(v, 10)), z.number().int()]);
+
+export const stringOrNumber = z.union([
+ z.string().transform((v, ctx) => {
+ const parsed = parseInt(v);
+ if (isNaN(parsed)) {
+ ctx.addIssue({
+ code: z.ZodIssueCode.custom,
+ message: "Not a number",
+ });
+ }
+ return parsed;
+ }),
+ z.number().int(),
+]);
+
+export const stringToDayjs = z.string().transform((val) => dayjs(val));
diff --git a/packages/types/Calendar.d.ts b/packages/types/Calendar.d.ts
index 73b8b46a46..b867a11756 100644
--- a/packages/types/Calendar.d.ts
+++ b/packages/types/Calendar.d.ts
@@ -3,7 +3,8 @@ import type { Dayjs } from "dayjs";
import type { calendar_v3 } from "googleapis";
import type { Time } from "ical.js";
import type { TFunction } from "next-i18next";
-import type { Frequency as RRuleFrequency } from "rrule";
+
+import type { Frequency } from "@calcom/prisma/zod-utils";
import type { Event } from "./Event";
import type { Ensure } from "./utils";
@@ -87,9 +88,9 @@ export interface AdditionalInformation {
export interface RecurringEvent {
dtstart?: Date | undefined;
- interval?: number;
- count?: number;
- freq?: RRuleFrequency;
+ interval: number;
+ count: number;
+ freq: Frequency;
until?: Date | undefined;
tzid?: string | undefined;
}
@@ -120,6 +121,7 @@ export interface CalendarEvent {
rejectionReason?: string | null;
hideCalendarNotes?: boolean;
recurrence?: string;
+ recurringEvent?: RecurringEvent | null;
}
export interface EntryPoint {