From 6cd427bc782468abe88c1d552692c04cdd3c9821 Mon Sep 17 00:00:00 2001
From: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Date: Tue, 3 Sep 2024 19:58:15 +0530
Subject: [PATCH] feat: availability in instant meeting (#16424)
* chore: save progress
* feat: add isAvailable functionality
* fix: type error
* chore: add in builder
* tests: add unit tests
* chore: improvements
* chore: tests
* chore
* chore: remove log
* fix: tets
---------
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
---
apps/web/public/static/locales/en/common.json | 1 +
.../test/lib/handleChildrenEventTypes.test.ts | 6 +
packages/features/bookings/Booker/Booker.tsx | 2 +-
packages/features/bookings/types.ts | 2 +-
.../eventtypes/components/EventType.tsx | 1 +
.../tabs/instant/InstantEventController.tsx | 81 ++++++++++-
.../features/eventtypes/lib/getPublicEvent.ts | 90 ++++++++++++
.../lib/isCurrentlyAvailable.test.ts | 134 ++++++++++++++++++
packages/lib/event-types/getEventTypeById.ts | 1 +
packages/lib/server/eventTypeSelect.ts | 1 +
packages/lib/server/repository/eventType.ts | 6 +
packages/lib/test/builder.ts | 1 +
.../migration.sql | 5 +
packages/prisma/schema.prisma | 17 ++-
.../viewer/eventTypes/duplicate.handler.ts | 1 +
.../server/routers/viewer/eventTypes/types.ts | 1 +
.../viewer/eventTypes/update.handler.ts | 13 ++
17 files changed, 351 insertions(+), 12 deletions(-)
create mode 100644 packages/features/eventtypes/lib/isCurrentlyAvailable.test.ts
create mode 100644 packages/prisma/migrations/20240830084943_add_instant_meeting_schedule/migration.sql
diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json
index 51accb3d745fcb..eee194cf97356e 100644
--- a/apps/web/public/static/locales/en/common.json
+++ b/apps/web/public/static/locales/en/common.json
@@ -2201,6 +2201,7 @@
"member_removed": "Member removed",
"my_availability": "My Availability",
"team_availability": "Team Availability",
+ "instant_meeting_availability": "Instant meeting availability",
"backup_code": "Backup Code",
"backup_codes": "Backup Codes",
"backup_code_instructions": "Each backup code can be used exactly once to grant access without your authenticator.",
diff --git a/apps/web/test/lib/handleChildrenEventTypes.test.ts b/apps/web/test/lib/handleChildrenEventTypes.test.ts
index afc0abe74fbc21..18b8c96001d022 100644
--- a/apps/web/test/lib/handleChildrenEventTypes.test.ts
+++ b/apps/web/test/lib/handleChildrenEventTypes.test.ts
@@ -143,6 +143,7 @@ describe("handleChildrenEventTypes", () => {
users: { connect: [{ id: 4 }] },
lockTimeZoneToggleOnBookingPage: false,
requiresBookerEmailVerification: false,
+ instantMeetingScheduleId: undefined,
bookingLimits: undefined,
durationLimits: undefined,
recurringEvent: undefined,
@@ -199,6 +200,7 @@ describe("handleChildrenEventTypes", () => {
scheduleId: null,
lockTimeZoneToggleOnBookingPage: false,
requiresBookerEmailVerification: false,
+ instantMeetingScheduleId: undefined,
hashedLink: { create: { link: expect.any(String) } },
},
where: {
@@ -304,6 +306,7 @@ describe("handleChildrenEventTypes", () => {
recurringEvent: undefined,
eventTypeColor: undefined,
hashedLink: undefined,
+ instantMeetingScheduleId: undefined,
lockTimeZoneToggleOnBookingPage: false,
requiresBookerEmailVerification: false,
userId: 4,
@@ -357,6 +360,7 @@ describe("handleChildrenEventTypes", () => {
locations: [],
lockTimeZoneToggleOnBookingPage: false,
requiresBookerEmailVerification: false,
+ instantMeetingScheduleId: undefined,
},
where: {
userId_parentId: {
@@ -421,6 +425,7 @@ describe("handleChildrenEventTypes", () => {
recurringEvent: undefined,
eventTypeColor: undefined,
hashedLink: undefined,
+ instantMeetingScheduleId: undefined,
locations: [],
lockTimeZoneToggleOnBookingPage: false,
requiresBookerEmailVerification: false,
@@ -447,6 +452,7 @@ describe("handleChildrenEventTypes", () => {
lockTimeZoneToggleOnBookingPage: false,
requiresBookerEmailVerification: false,
hashedLink: undefined,
+ instantMeetingScheduleId: undefined,
},
where: {
userId_parentId: {
diff --git a/packages/features/bookings/Booker/Booker.tsx b/packages/features/bookings/Booker/Booker.tsx
index f06ef2852e7217..bbc798d44d5805 100644
--- a/packages/features/bookings/Booker/Booker.tsx
+++ b/packages/features/bookings/Booker/Booker.tsx
@@ -439,7 +439,7 @@ const BookerComponent = ({
}}
/>
- {bookerState !== "booking" && event.data?.isInstantEvent && (
+ {bookerState !== "booking" && event.data?.showInstantEventConnectNowModal && (
& { users: BookerEventUser[] } & { profile: BookerEventProfile };
+> & { users: BookerEventUser[]; showInstantEventConnectNowModal: boolean } & { profile: BookerEventProfile };
export type ValidationErrors
= { key: FieldPath; error: ErrorOption }[];
diff --git a/packages/features/eventtypes/components/EventType.tsx b/packages/features/eventtypes/components/EventType.tsx
index 9b2ffe4647354b..9b5180f98538f7 100644
--- a/packages/features/eventtypes/components/EventType.tsx
+++ b/packages/features/eventtypes/components/EventType.tsx
@@ -210,6 +210,7 @@ export const EventType = (props: EventTypeSetupProps & { allActiveWorkflows?: Wo
instantMeetingExpiryTimeOffsetInSeconds: eventType.instantMeetingExpiryTimeOffsetInSeconds,
description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined,
+ instantMeetingSchedule: eventType.instantMeetingSchedule || undefined,
bookingLimits: eventType.bookingLimits || undefined,
onlyShowFirstAvailableSlot: eventType.onlyShowFirstAvailableSlot || undefined,
durationLimits: eventType.durationLimits || undefined,
diff --git a/packages/features/eventtypes/components/tabs/instant/InstantEventController.tsx b/packages/features/eventtypes/components/tabs/instant/InstantEventController.tsx
index d498e7f11815b8..aea168004a389c 100644
--- a/packages/features/eventtypes/components/tabs/instant/InstantEventController.tsx
+++ b/packages/features/eventtypes/components/tabs/instant/InstantEventController.tsx
@@ -2,10 +2,12 @@ import type { Webhook } from "@prisma/client";
import { useSession } from "next-auth/react";
import { useState } from "react";
import { useFormContext, Controller } from "react-hook-form";
+import { components } from "react-select";
+import type { OptionProps, SingleValueProps } from "react-select";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
-import type { EventTypeSetup, FormValues } from "@calcom/features/eventtypes/lib/types";
+import type { EventTypeSetup, FormValues, AvailabilityOption } from "@calcom/features/eventtypes/lib/types";
import { WebhookForm } from "@calcom/features/webhooks/components";
import type { WebhookFormSubmitData } from "@calcom/features/webhooks/components/WebhookForm";
import WebhookListItem from "@calcom/features/webhooks/components/WebhookListItem";
@@ -24,6 +26,8 @@ import {
showToast,
TextField,
Label,
+ Select,
+ Badge,
} from "@calcom/ui";
type InstantEventControllerProps = {
@@ -32,6 +36,36 @@ type InstantEventControllerProps = {
isTeamEvent: boolean;
};
+const Option = ({ ...props }: OptionProps) => {
+ const { label, isDefault } = props.data;
+ const { t } = useLocale();
+ return (
+
+ {label}
+ {isDefault && (
+
+ {t("default")}
+
+ )}
+
+ );
+};
+
+const SingleValue = ({ ...props }: SingleValueProps) => {
+ const { label, isDefault } = props.data;
+ const { t } = useLocale();
+ return (
+
+ {label}
+ {isDefault && (
+
+ {t("default")}
+
+ )}
+
+ );
+};
+
export default function InstantEventController({
eventType,
paymentEnabled,
@@ -42,13 +76,28 @@ export default function InstantEventController({
const [instantEventState, setInstantEventState] = useState(eventType?.isInstantEvent ?? false);
const formMethods = useFormContext();
- const { shouldLockDisableProps } = useLockedFieldsManager({ eventType, translate: t, formMethods });
+ const { shouldLockDisableProps } = useLockedFieldsManager({
+ eventType,
+ translate: t,
+ formMethods,
+ });
const instantLocked = shouldLockDisableProps("isInstantEvent");
const isOrg = !!session.data?.user?.org?.id;
- if (session.status === "loading") return <>>;
+ const { data, isPending } = trpc.viewer.availability.list.useQuery(undefined);
+
+ if (session.status === "loading" || isPending || !data) return <>>;
+
+ const schedules = data.schedules;
+
+ const options = schedules.map((schedule) => ({
+ value: schedule.id,
+ label: schedule.name,
+ isDefault: schedule.isDefault,
+ isManaged: false,
+ }));
return (
@@ -96,6 +145,32 @@ export default function InstantEventController({
{instantEventState && (
+
{
+ const optionValue: AvailabilityOption | undefined = options.find(
+ (option) => option.value === value
+ );
+ return (
+ <>
+
+