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 ( + <> + +