diff --git a/apps/web/components/eventtype/InstantEventController.tsx b/apps/web/components/eventtype/InstantEventController.tsx
index a3b2d49008..83ff094983 100644
--- a/apps/web/components/eventtype/InstantEventController.tsx
+++ b/apps/web/components/eventtype/InstantEventController.tsx
@@ -2,7 +2,7 @@ import type { Webhook } from "@prisma/client";
import { useSession } from "next-auth/react";
import type { EventTypeSetup } from "pages/event-types/[type]";
import { useState } from "react";
-import { useFormContext } from "react-hook-form";
+import { useFormContext, Controller } from "react-hook-form";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
@@ -15,7 +15,17 @@ import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
-import { Alert, Button, EmptyScreen, SettingsToggle, Dialog, DialogContent, showToast } from "@calcom/ui";
+import {
+ Alert,
+ Button,
+ EmptyScreen,
+ SettingsToggle,
+ Dialog,
+ DialogContent,
+ showToast,
+ TextField,
+ Label,
+} from "@calcom/ui";
type InstantEventControllerProps = {
eventType: EventTypeSetup;
@@ -85,7 +95,33 @@ export default function InstantEventController({
}
}}>
- {instantEventState &&
}
+ {instantEventState && (
+
+ (
+ <>
+
+ {t("seconds")}>}
+ onChange={(e) => {
+ onChange(Math.abs(Number(e.target.value)));
+ }}
+ data-testid="instant-meeting-expiry-time-offset"
+ />
+ >
+ )}
+ />
+
+
+ )}
>
@@ -213,9 +249,6 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
>
) : (
<>
-
- {t("warning_payment_instant_meeting_event")}
-
{
destinationCalendar: eventType.destinationCalendar,
recurringEvent: eventType.recurringEvent || null,
isInstantEvent: eventType.isInstantEvent,
+ instantMeetingExpiryTimeOffsetInSeconds: eventType.instantMeetingExpiryTimeOffsetInSeconds,
description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined,
bookingLimits: eventType.bookingLimits || undefined,
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 5ac48d34e0..4751ce8779 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -1043,6 +1043,7 @@
"seats_nearly_full": "Seats almost full",
"seats_half_full": "Seats filling fast",
"number_of_seats": "Number of seats per booking",
+ "set_instant_meeting_expiry_time_offset_description": "Set meeting join window (seconds): The time frame in seconds within which host can join and start the meeting. After this period, the meeting join url will expire.",
"enter_number_of_seats": "Enter number of seats",
"you_can_manage_your_schedules": "You can manage your schedules on the Availability page.",
"booking_full": "No more seats available",
diff --git a/packages/features/bookings/Booker/components/hooks/useBookings.ts b/packages/features/bookings/Booker/components/hooks/useBookings.ts
index 59fb8b5442..4ae75c003e 100644
--- a/packages/features/bookings/Booker/components/hooks/useBookings.ts
+++ b/packages/features/bookings/Booker/components/hooks/useBookings.ts
@@ -204,11 +204,6 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb
});
},
onError: (err, _, ctx) => {
- // TODO:
- // const vercelId = ctx?.meta?.headers?.get("x-vercel-id");
- // if (vercelId) {
- // setResponseVercelIdHeader(vercelId);
- // }
bookerFormErrorRef && bookerFormErrorRef.current?.scrollIntoView({ behavior: "smooth" });
},
});
diff --git a/packages/features/eventtypes/lib/types.ts b/packages/features/eventtypes/lib/types.ts
index 4f7495cec6..0363b86891 100644
--- a/packages/features/eventtypes/lib/types.ts
+++ b/packages/features/eventtypes/lib/types.ts
@@ -34,6 +34,7 @@ export type FormValues = {
eventName: string;
slug: string;
isInstantEvent: boolean;
+ instantMeetingExpiryTimeOffsetInSeconds: number;
length: number;
offsetStart: number;
description: string;
diff --git a/packages/features/instant-meeting/handleInstantMeeting.ts b/packages/features/instant-meeting/handleInstantMeeting.ts
index 5385e93789..82c4ab4364 100644
--- a/packages/features/instant-meeting/handleInstantMeeting.ts
+++ b/packages/features/instant-meeting/handleInstantMeeting.ts
@@ -194,12 +194,26 @@ async function handler(req: NextApiRequest) {
const newBooking = await prisma.booking.create(createBookingObj);
// Create Instant Meeting Token
+
const token = randomBytes(32).toString("hex");
+
+ const eventTypeWithExpiryTimeOffset = await prisma.eventType.findUniqueOrThrow({
+ where: {
+ id: req.body.eventTypeId,
+ },
+ select: {
+ instantMeetingExpiryTimeOffsetInSeconds: true,
+ },
+ });
+
+ const instantMeetingExpiryTimeOffsetInSeconds =
+ eventTypeWithExpiryTimeOffset?.instantMeetingExpiryTimeOffsetInSeconds ?? 90;
+
const instantMeetingToken = await prisma.instantMeetingToken.create({
data: {
token,
- // 90 Seconds
- expires: new Date(new Date().getTime() + 1000 * 90),
+ // current time + offset Seconds
+ expires: new Date(new Date().getTime() + 1000 * instantMeetingExpiryTimeOffsetInSeconds),
team: {
connect: {
id: eventType.team.id,
diff --git a/packages/lib/event-types/getEventTypeById.ts b/packages/lib/event-types/getEventTypeById.ts
index 13678b02f5..baf36fdfc8 100644
--- a/packages/lib/event-types/getEventTypeById.ts
+++ b/packages/lib/event-types/getEventTypeById.ts
@@ -84,6 +84,7 @@ export const getEventTypeById = async ({
description: true,
length: true,
isInstantEvent: true,
+ instantMeetingExpiryTimeOffsetInSeconds: true,
aiPhoneCallConfig: true,
offsetStart: true,
hidden: true,
diff --git a/packages/lib/server/eventTypeSelect.ts b/packages/lib/server/eventTypeSelect.ts
index 75dc5f6a4a..a0174cab0d 100644
--- a/packages/lib/server/eventTypeSelect.ts
+++ b/packages/lib/server/eventTypeSelect.ts
@@ -40,6 +40,7 @@ export const eventTypeSelect = Prisma.validator()({
slotInterval: true,
successRedirectUrl: true,
isInstantEvent: true,
+ instantMeetingExpiryTimeOffsetInSeconds: true,
aiPhoneCallConfig: true,
assignAllTeamMembers: true,
recurringEvent: true,
diff --git a/packages/lib/test/builder.ts b/packages/lib/test/builder.ts
index d3083672c8..dd79891e4b 100644
--- a/packages/lib/test/builder.ts
+++ b/packages/lib/test/builder.ts
@@ -82,6 +82,7 @@ export const buildEventType = (eventType?: Partial): EventType => {
description: faker.lorem.paragraph(),
position: 1,
isInstantEvent: false,
+ instantMeetingExpiryTimeOffsetInSeconds: 90,
locations: null,
length: 15,
offsetStart: 0,
diff --git a/packages/platform/sdk/src/endpoints/events/event-types/types.ts b/packages/platform/sdk/src/endpoints/events/event-types/types.ts
index 2586a9acf9..9d88567fb7 100644
--- a/packages/platform/sdk/src/endpoints/events/event-types/types.ts
+++ b/packages/platform/sdk/src/endpoints/events/event-types/types.ts
@@ -59,6 +59,7 @@ export type EventType = {
bookingLimits: number | null;
durationLimits: number | null;
isInstantEvent: boolean;
+ instantMeetingExpiryTimeOffsetInSeconds: number;
assignAllTeamMembers: boolean;
useEventTypeDestinationCalendarEmail: boolean;
};
diff --git a/packages/platform/sdk/src/endpoints/events/types.ts b/packages/platform/sdk/src/endpoints/events/types.ts
index 87edf50321..af7541f4d4 100644
--- a/packages/platform/sdk/src/endpoints/events/types.ts
+++ b/packages/platform/sdk/src/endpoints/events/types.ts
@@ -45,6 +45,7 @@ export type Event = {
eventName: string;
slug: string;
isInstantEvent: boolean;
+ instantMeetingExpiryTimeOffsetInSeconds: number;
aiPhoneCallConfig: {
eventTypeId: number;
enabled: boolean;
diff --git a/packages/prisma/migrations/20240624195855_add_instant_meeting_expiry_offset/migration.sql b/packages/prisma/migrations/20240624195855_add_instant_meeting_expiry_offset/migration.sql
new file mode 100644
index 0000000000..f9f0ed7ca0
--- /dev/null
+++ b/packages/prisma/migrations/20240624195855_add_instant_meeting_expiry_offset/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "EventType" ADD COLUMN "instantMeetingExpiryTimeOffsetInSeconds" INTEGER NOT NULL DEFAULT 90;
diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma
index b0a3e60d78..9df2e32a9b 100644
--- a/packages/prisma/schema.prisma
+++ b/packages/prisma/schema.prisma
@@ -77,65 +77,66 @@ model EventType {
profileId Int?
profile Profile? @relation(fields: [profileId], references: [id], onDelete: Cascade)
- team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
- teamId Int?
- hashedLink HashedLink?
- bookings Booking[]
- availability Availability[]
- webhooks Webhook[]
- destinationCalendar DestinationCalendar?
- eventName String?
- customInputs EventTypeCustomInput[]
- parentId Int?
- parent EventType? @relation("managed_eventtype", fields: [parentId], references: [id], onDelete: Cascade)
- children EventType[] @relation("managed_eventtype")
+ team Team? @relation(fields: [teamId], references: [id], onDelete: Cascade)
+ teamId Int?
+ hashedLink HashedLink?
+ bookings Booking[]
+ availability Availability[]
+ webhooks Webhook[]
+ destinationCalendar DestinationCalendar?
+ eventName String?
+ customInputs EventTypeCustomInput[]
+ parentId Int?
+ parent EventType? @relation("managed_eventtype", fields: [parentId], references: [id], onDelete: Cascade)
+ children EventType[] @relation("managed_eventtype")
/// @zod.custom(imports.eventTypeBookingFields)
- bookingFields Json?
- timeZone String?
- periodType PeriodType @default(UNLIMITED)
+ bookingFields Json?
+ timeZone String?
+ periodType PeriodType @default(UNLIMITED)
/// @zod.custom(imports.coerceToDate)
- periodStartDate DateTime?
+ periodStartDate DateTime?
/// @zod.custom(imports.coerceToDate)
- periodEndDate DateTime?
- periodDays Int?
- periodCountCalendarDays Boolean?
- lockTimeZoneToggleOnBookingPage Boolean @default(false)
- requiresConfirmation Boolean @default(false)
- requiresBookerEmailVerification Boolean @default(false)
+ periodEndDate DateTime?
+ periodDays Int?
+ periodCountCalendarDays Boolean?
+ lockTimeZoneToggleOnBookingPage Boolean @default(false)
+ requiresConfirmation Boolean @default(false)
+ requiresBookerEmailVerification Boolean @default(false)
/// @zod.custom(imports.recurringEventType)
- recurringEvent Json?
- disableGuests Boolean @default(false)
- hideCalendarNotes Boolean @default(false)
+ recurringEvent Json?
+ disableGuests Boolean @default(false)
+ hideCalendarNotes Boolean @default(false)
/// @zod.min(0)
- minimumBookingNotice Int @default(120)
- beforeEventBuffer Int @default(0)
- afterEventBuffer Int @default(0)
- seatsPerTimeSlot Int?
- onlyShowFirstAvailableSlot Boolean @default(false)
- seatsShowAttendees Boolean? @default(false)
- seatsShowAvailabilityCount Boolean? @default(true)
- schedulingType SchedulingType?
- schedule Schedule? @relation(fields: [scheduleId], references: [id])
- scheduleId Int?
+ minimumBookingNotice Int @default(120)
+ beforeEventBuffer Int @default(0)
+ afterEventBuffer Int @default(0)
+ seatsPerTimeSlot Int?
+ onlyShowFirstAvailableSlot Boolean @default(false)
+ seatsShowAttendees Boolean? @default(false)
+ seatsShowAvailabilityCount Boolean? @default(true)
+ schedulingType SchedulingType?
+ schedule Schedule? @relation(fields: [scheduleId], references: [id])
+ scheduleId Int?
// price is deprecated. It has now moved to metadata.apps.stripe.price. Plan to drop this column.
- price Int @default(0)
+ price Int @default(0)
// currency is deprecated. It has now moved to metadata.apps.stripe.currency. Plan to drop this column.
- currency String @default("usd")
- slotInterval Int?
+ currency String @default("usd")
+ slotInterval Int?
/// @zod.custom(imports.EventTypeMetaDataSchema)
- metadata Json?
+ metadata Json?
/// @zod.custom(imports.successRedirectUrl)
- successRedirectUrl String?
- forwardParamsSuccessRedirect Boolean? @default(true)
- workflows WorkflowsOnEventTypes[]
+ successRedirectUrl String?
+ forwardParamsSuccessRedirect Boolean? @default(true)
+ workflows WorkflowsOnEventTypes[]
/// @zod.custom(imports.intervalLimitsType)
- bookingLimits Json?
+ bookingLimits Json?
/// @zod.custom(imports.intervalLimitsType)
- durationLimits Json?
- isInstantEvent Boolean @default(false)
- assignAllTeamMembers Boolean @default(false)
- useEventTypeDestinationCalendarEmail Boolean @default(false)
- aiPhoneCallConfig AIPhoneCallConfiguration?
+ durationLimits Json?
+ isInstantEvent Boolean @default(false)
+ instantMeetingExpiryTimeOffsetInSeconds Int @default(90)
+ assignAllTeamMembers Boolean @default(false)
+ useEventTypeDestinationCalendarEmail Boolean @default(false)
+ aiPhoneCallConfig AIPhoneCallConfiguration?
secondaryEmailId Int?
secondaryEmail SecondaryEmail? @relation(fields: [secondaryEmailId], references: [id], onDelete: Cascade)
diff --git a/packages/prisma/zod-utils.ts b/packages/prisma/zod-utils.ts
index 0ef548f7c0..e97238d929 100644
--- a/packages/prisma/zod-utils.ts
+++ b/packages/prisma/zod-utils.ts
@@ -608,6 +608,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit