Recurring event fixes (#3030)
* Incorporates recurringEvent into calEvent * Recurrent event fixes * Update event.ts * type fixes * Update zod-utils.ts * Adding more recurringEvent to calEvent and other fixes * Moving away from heavy 3rd party enum * Fixing test * Renaming as suggested and fixes Co-authored-by: Leo Giovanetti <hello@leog.me>
This commit is contained in:
parent
8cd420173c
commit
b11398f551
|
@ -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) {
|
|||
<RefreshIcon className="mr-1 -mt-1 inline-block h-4 w-4 text-gray-400" />
|
||||
{`${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 }
|
||||
)}`}
|
||||
</p>
|
||||
|
|
|
@ -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
|
|||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
||||
{t("every_for_freq", {
|
||||
freq: t(
|
||||
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
|
||||
`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
|
@ -317,12 +317,9 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
|
|||
}}
|
||||
/>
|
||||
<p className="inline text-gray-600 dark:text-white">
|
||||
{t(
|
||||
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`,
|
||||
{
|
||||
count: recurringEventCount,
|
||||
}
|
||||
)}
|
||||
{t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
|
||||
count: recurringEventCount,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
@ -344,7 +341,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
|
|||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
||||
{t("every_for_freq", {
|
||||
freq: t(
|
||||
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
|
||||
`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
|
||||
),
|
||||
})}
|
||||
</p>
|
||||
|
@ -359,12 +356,9 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
|
|||
}}
|
||||
/>
|
||||
<p className="inline text-gray-600 dark:text-white">
|
||||
{t(
|
||||
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`,
|
||||
{
|
||||
count: recurringEventCount,
|
||||
}
|
||||
)}
|
||||
{t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
|
||||
count: recurringEventCount,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
@ -471,9 +465,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
|
|||
<RefreshIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
|
||||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
||||
{t("every_for_freq", {
|
||||
freq: t(
|
||||
`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`
|
||||
),
|
||||
freq: t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`),
|
||||
})}
|
||||
</p>
|
||||
<input
|
||||
|
@ -487,7 +479,7 @@ const AvailabilityPage = ({ profile, plan, eventType, workingHours, previousPage
|
|||
}}
|
||||
/>
|
||||
<p className="inline text-gray-600 dark:text-white">
|
||||
{t(`${RRuleFrequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
|
||||
{t(`${Frequency[eventType.recurringEvent.freq].toString().toLowerCase()}`, {
|
||||
count: recurringEventCount,
|
||||
})}
|
||||
</p>
|
||||
|
|
|
@ -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 = ({
|
|||
<RefreshIcon className="mr-[10px] -mt-1 ml-[2px] inline-block h-4 w-4 text-gray-400" />
|
||||
<p className="mb-1 -ml-2 inline px-2 py-1">
|
||||
{`${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()) }
|
||||
)}`}
|
||||
</p>
|
||||
|
@ -529,12 +529,12 @@ const BookingPage = ({
|
|||
<div className="text-bookinghighlight mb-4 flex">
|
||||
<CalendarIcon className="mr-[10px] ml-[2px] inline-block h-4 w-4" />
|
||||
<div className="-mt-1">
|
||||
{(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) => <p key={key}>{aDate}</p>)}
|
||||
{!rescheduleUid && eventType.recurringEvent.freq && recurringStrings.length > 5 && (
|
||||
{!rescheduleUid && eventType.recurringEvent?.freq && recurringStrings.length > 5 && (
|
||||
<div className="flex">
|
||||
<Tooltip
|
||||
content={recurringStrings.slice(5).map((aDate, key) => (
|
||||
|
|
|
@ -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]
|
||||
);
|
||||
|
||||
|
|
|
@ -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<FormValues>;
|
||||
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 | null>(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({
|
|||
<div className="flex h-5 items-center">
|
||||
<input
|
||||
onChange={(event) => {
|
||||
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({
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<Collapsible
|
||||
open={recurringEventDefined}
|
||||
data-testid="recurring-event-collapsible"
|
||||
onOpenChange={() => setRecurringEventDefined(!recurringEventDefined)}>
|
||||
<CollapsibleContent className="mt-4 text-sm">
|
||||
{recurringEventState && (
|
||||
<div data-testid="recurring-event-collapsible" className="mt-4 text-sm">
|
||||
<div className="flex items-center">
|
||||
<p className="mr-2 text-neutral-900">{t("repeats_every")}</p>
|
||||
<input
|
||||
|
@ -98,24 +83,28 @@ 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?.interval || 1}
|
||||
defaultValue={recurringEventState.interval}
|
||||
onChange={(event) => {
|
||||
setRecurringEventInterval(parseInt(event?.target.value));
|
||||
recurringEvent.interval = parseInt(event?.target.value);
|
||||
formMethods.setValue("recurringEvent", recurringEvent);
|
||||
const newVal = {
|
||||
...recurringEventState,
|
||||
interval: parseInt(event?.target.value),
|
||||
};
|
||||
formMethods.setValue("recurringEvent", newVal);
|
||||
setRecurringEventState(newVal);
|
||||
}}
|
||||
/>
|
||||
<Select
|
||||
options={recurringEventFreqOptions}
|
||||
value={recurringEventFreqOptions[recurringEventFrequency]}
|
||||
value={recurringEventFreqOptions[recurringEventState.freq]}
|
||||
isSearchable={false}
|
||||
className="w-18 block min-w-0 rounded-sm sm:text-sm"
|
||||
onChange={(e) => {
|
||||
if (e?.value) {
|
||||
setRecurringEventFrequency(parseInt(e?.value));
|
||||
recurringEvent.freq = parseInt(e?.value);
|
||||
formMethods.setValue("recurringEvent", recurringEvent);
|
||||
}
|
||||
onChange={(event) => {
|
||||
const newVal = {
|
||||
...recurringEventState,
|
||||
freq: parseInt(event?.value || `${Frequency.WEEKLY}`),
|
||||
};
|
||||
formMethods.setValue("recurringEvent", newVal);
|
||||
setRecurringEventState(newVal);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -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);
|
||||
}}
|
||||
/>
|
||||
<p className="mr-2 text-neutral-900">
|
||||
{t(`${RRuleFrequency[recurringEventFrequency].toString().toLowerCase()}`, {
|
||||
count: recurringEventCount,
|
||||
{t(`${Frequency[recurringEventState.freq].toString().toLowerCase()}`, {
|
||||
count: recurringEventState.count,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<typeof getServerSideProps>) => {
|
|||
const formMethods = useForm<FormValues>({
|
||||
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:
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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<unknown>[] = [];
|
||||
|
||||
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<unknown>[] = [];
|
||||
|
||||
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<unknown>[] = [];
|
||||
|
||||
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<unknown>[] = [];
|
||||
|
||||
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<unknown>[] = [];
|
||||
|
||||
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<unknown>[] = [];
|
||||
|
||||
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<unknown>[] = [];
|
||||
|
||||
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));
|
||||
|
|
|
@ -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")}{" "}
|
||||
<span style={{ color: "#888888" }}>({timeZone})</span>
|
||||
|
|
|
@ -2,7 +2,9 @@ import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
|||
|
||||
export const AttendeeDeclinedEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => (
|
||||
<AttendeeScheduledEmail
|
||||
title={props.recurringEvent?.count ? "event_request_declined_recurring" : "event_request_declined"}
|
||||
title={
|
||||
props.calEvent.recurringEvent?.count ? "event_request_declined_recurring" : "event_request_declined"
|
||||
}
|
||||
headerType="xCircle"
|
||||
subject="event_declined_subject"
|
||||
callToAction={null}
|
||||
|
|
|
@ -3,10 +3,10 @@ import { AttendeeScheduledEmail } from "./AttendeeScheduledEmail";
|
|||
export const AttendeeRequestEmail = (props: React.ComponentProps<typeof AttendeeScheduledEmail>) => (
|
||||
<AttendeeScheduledEmail
|
||||
title={props.calEvent.organizer.language.translate(
|
||||
props.recurringEvent?.count ? "booking_submitted_recurring" : "booking_submitted"
|
||||
props.calEvent.recurringEvent?.count ? "booking_submitted_recurring" : "booking_submitted"
|
||||
)}
|
||||
subtitle={props.calEvent.organizer.language.translate(
|
||||
props.recurringEvent.count
|
||||
props.calEvent.recurringEvent?.count
|
||||
? "user_needs_to_confirm_or_reject_booking_recurring"
|
||||
: "user_needs_to_confirm_or_reject_booking",
|
||||
{ user: props.calEvent.organizer.name }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { BaseScheduledEmail } from "./BaseScheduledEmail";
|
||||
|
||||
|
@ -6,7 +6,6 @@ export const AttendeeScheduledEmail = (
|
|||
props: {
|
||||
calEvent: CalendarEvent;
|
||||
attendee: Person;
|
||||
recurringEvent: RecurringEvent;
|
||||
} & Partial<React.ComponentProps<typeof BaseScheduledEmail>>
|
||||
) => {
|
||||
return (
|
||||
|
|
|
@ -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<React.ComponentProps<typeof BaseEmailHtml>>
|
||||
|
@ -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 = (
|
|||
<Info label={t("cancellation_reason")} description={props.calEvent.cancellationReason} withSpacer />
|
||||
<Info label={t("rejection_reason")} description={props.calEvent.rejectionReason} withSpacer />
|
||||
<Info label={t("what")} description={props.calEvent.type} withSpacer />
|
||||
<WhenInfo calEvent={props.calEvent} recurringEvent={props.recurringEvent} t={t} timeZone={timeZone} />
|
||||
<WhenInfo calEvent={props.calEvent} t={t} timeZone={timeZone} />
|
||||
<WhoInfo calEvent={props.calEvent} t={t} />
|
||||
<LocationInfo calEvent={props.calEvent} t={t} />
|
||||
<Info label={t("description")} description={props.calEvent.description} withSpacer />
|
||||
|
|
|
@ -4,7 +4,7 @@ import { OrganizerScheduledEmail } from "./OrganizerScheduledEmail";
|
|||
export const OrganizerRequestEmail = (props: React.ComponentProps<typeof OrganizerScheduledEmail>) => (
|
||||
<OrganizerScheduledEmail
|
||||
title={
|
||||
props.title || props.recurringEvent?.count
|
||||
props.title || props.calEvent.recurringEvent?.count
|
||||
? "event_awaiting_approval_recurring"
|
||||
: "event_awaiting_approval"
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { CalendarEvent, Person, RecurringEvent } from "@calcom/types/Calendar";
|
||||
import type { CalendarEvent, Person } from "@calcom/types/Calendar";
|
||||
|
||||
import { BaseScheduledEmail } from "./BaseScheduledEmail";
|
||||
|
||||
|
@ -6,7 +6,6 @@ export const OrganizerScheduledEmail = (
|
|||
props: {
|
||||
calEvent: CalendarEvent;
|
||||
attendee: Person;
|
||||
recurringEvent: RecurringEvent;
|
||||
} & Partial<React.ComponentProps<typeof BaseScheduledEmail>>
|
||||
) => {
|
||||
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}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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", {
|
||||
|
|
|
@ -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<string, unknown> {
|
||||
|
@ -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(),
|
||||
};
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
)}
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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<string, unknown> {
|
||||
|
@ -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", {
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -58,7 +58,7 @@ const commons = {
|
|||
},
|
||||
isWeb3Active: false,
|
||||
hideCalendarNotes: false,
|
||||
recurringEvent: {},
|
||||
recurringEvent: null,
|
||||
destinationCalendar: null,
|
||||
team: null,
|
||||
requiresConfirmation: false,
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
export { default as isPrismaObj, isPrismaObjOrUndefined } from "./isPrismaObj";
|
||||
export * from "./isRecurringEvent";
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue
Block a user