Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add instant meeting expiry input #15555

Merged
merged 7 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 35 additions & 6 deletions apps/web/components/eventtype/InstantEventController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -15,7 +15,16 @@ 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,
} from "@calcom/ui";

type InstantEventControllerProps = {
eventType: EventTypeSetup;
Expand Down Expand Up @@ -85,7 +94,30 @@ export default function InstantEventController({
}
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
{instantEventState && <InstantMeetingWebhooks eventType={eventType} />}
{instantEventState && (
<div className="flex flex-col gap-2">
<Controller
name="instantMeetingExpiryTimeOffset"
render={({ field: { value, onChange } }) => (
<TextField
required
name="instantMeetingExpiryTimeOffset"
label={t("set_expiry_time_offset")}
type="number"
defaultValue={value}
min={10}
containerClassName="max-w-80"
addOnSuffix={<>{t("seconds")}</>}
onChange={(e) => {
onChange(Math.abs(Number(e.target.value)));
}}
data-testid="instant-meeting-expiry-time-offset"
/>
)}
/>
<InstantMeetingWebhooks eventType={eventType} />
</div>
)}
</div>
</SettingsToggle>
</>
Expand Down Expand Up @@ -213,9 +245,6 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
</>
) : (
<>
<p className="text-default mb-4 text-sm font-normal">
{t("warning_payment_instant_meeting_event")}
</p>
<EmptyScreen
Icon="webhook"
headline={t("create_your_first_webhook")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@
destinationCalendar: eventType.destinationCalendar,
recurringEvent: eventType.recurringEvent || null,
isInstantEvent: eventType.isInstantEvent,
instantMeetingExpiryTimeOffset: eventType.instantMeetingExpiryTimeOffset,
description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined,
bookingLimits: eventType.bookingLimits || undefined,
Expand Down Expand Up @@ -430,7 +431,7 @@
return () => {
router.events.off("routeChangeStart", handleRouteChange);
};
}, [router]);

Check warning on line 434 in apps/web/modules/event-types/views/event-types-single-view.tsx

View workflow job for this annotation

GitHub Actions / ESLint Report Analysis

apps/web/modules/event-types/views/event-types-single-view.tsx#L434

[react-hooks/exhaustive-deps] React Hook useEffect has missing dependencies: 'eventType.assignAllTeamMembers', 'eventType.children', 'eventType.hosts', 'eventType.schedulingType', and 'team'. Either include them or remove the dependency array.

const appsMetadata = formMethods.getValues("metadata")?.apps;
const availability = formMethods.watch("availability");
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/static/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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_expiry_time_offset": "Set instant meeting expiry time offset",
Udit-takkar marked this conversation as resolved.
Show resolved Hide resolved
"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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" });
},
});
Expand Down
1 change: 1 addition & 0 deletions packages/features/eventtypes/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export type FormValues = {
eventName: string;
slug: string;
isInstantEvent: boolean;
instantMeetingExpiryTimeOffset: number;
length: number;
offsetStart: number;
description: string;
Expand Down
17 changes: 15 additions & 2 deletions packages/features/instant-meeting/handleInstantMeeting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,25 @@ 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: {
instantMeetingExpiryTimeOffset: true,
},
});

const instantMeetingExpiryTimeOffset = eventTypeWithExpiryTimeOffset?.instantMeetingExpiryTimeOffset ?? 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 * instantMeetingExpiryTimeOffset),
team: {
connect: {
id: eventType.team.id,
Expand Down
1 change: 1 addition & 0 deletions packages/lib/event-types/getEventTypeById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ export const getEventTypeById = async ({
description: true,
length: true,
isInstantEvent: true,
instantMeetingExpiryTimeOffset: true,
aiPhoneCallConfig: true,
offsetStart: true,
hidden: true,
Expand Down
1 change: 1 addition & 0 deletions packages/lib/server/eventTypeSelect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const eventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
slotInterval: true,
successRedirectUrl: true,
isInstantEvent: true,
instantMeetingExpiryTimeOffset: true,
aiPhoneCallConfig: true,
assignAllTeamMembers: true,
recurringEvent: true,
Expand Down
1 change: 1 addition & 0 deletions packages/lib/test/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
description: faker.lorem.paragraph(),
position: 1,
isInstantEvent: false,
instantMeetingExpiryTimeOffset: 90,
locations: null,
length: 15,
offsetStart: 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export type EventType = {
bookingLimits: number | null;
durationLimits: number | null;
isInstantEvent: boolean;
instantMeetingExpiryTimeOffset: number;
assignAllTeamMembers: boolean;
useEventTypeDestinationCalendarEmail: boolean;
};
1 change: 1 addition & 0 deletions packages/platform/sdk/src/endpoints/events/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export type Event = {
eventName: string;
slug: string;
isInstantEvent: boolean;
instantMeetingExpiryTimeOffset: number;
aiPhoneCallConfig: {
eventTypeId: number;
enabled: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "instantMeetingExpiryTimeOffset" INTEGER NOT NULL DEFAULT 90;
1 change: 1 addition & 0 deletions packages/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ model EventType {
/// @zod.custom(imports.intervalLimitsType)
durationLimits Json?
isInstantEvent Boolean @default(false)
instantMeetingExpiryTimeOffset Int @default(90)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick here. Are these seconds? minutes? Can we specify in the column name? I know it's already quite long enough. instantMeetingExpiryTimeOffsetInSeconds

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

assignAllTeamMembers Boolean @default(false)
useEventTypeDestinationCalendarEmail Boolean @default(false)
aiPhoneCallConfig AIPhoneCallConfiguration?
Expand Down
1 change: 1 addition & 0 deletions packages/prisma/zod-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
title: true,
description: true,
isInstantEvent: true,
instantMeetingExpiryTimeOffset: true,
aiPhoneCallConfig: true,
currency: true,
periodDays: true,
Expand Down
1 change: 1 addition & 0 deletions packages/trpc/server/routers/viewer/eventTypes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const EventTypeUpdateInput = _EventTypeModel
/** Optional fields */
.extend({
isInstantEvent: z.boolean().optional(),
instantMeetingExpiryTimeOffset: z.number().optional(),
aiPhoneCallConfig: z
.object({
generalPrompt: z.string(),
Expand Down
Loading