feat: add instant meeting parameter (#17647)

* feat: add instant meeting parameter

* feat: show modal only when parameter present

* chore: remove logs

* fix: type err

* fix: UI

* fix: remove useFieldArray
This commit is contained in:
Udit Takkar authored and GitHub committed 2024-11-18 19:29:10 +05:30
1 parent e6633050ba
commit 0f3773d724
16 files changed
+97 -16

No files matched your search

@@ -2218,6 +2218,8 @@
"add_times_to_your_email": "Select a few available times and embed them in your Email",
"select_time": "Select Time",
"select_date": "Select Date",
"only_show_if_parameter_set": "Only show if a parameter is set",
"add_parameter": "Add parameter",
"connecting_you_to_someone": "We are connecting you to someone from {{orgName}}.",
"please_do_not_close_this_tab": "Please do not close or refresh this page",
"see_all_available_times": "See all available times",
+25 -15
View File
@@ -1,5 +1,6 @@
import { AnimatePresence, LazyMotion, m } from "framer-motion";
import dynamic from "next/dynamic";
import { useSearchParams } from "next/navigation";
import { useEffect, useMemo, useRef } from "react";
import { Toaster } from "react-hot-toast";
import StickyBox from "react-sticky-box";
@@ -68,6 +69,13 @@ const BookerComponent = ({
customClassNames,
}: BookerProps & WrappedBookerProps) => {
const { t } = useLocale();
const searchParams = useSearchParams();
const areInstantMeetingParametersSet = !!event.data?.instantMeetingParameters
? searchParams &&
event.data.instantMeetingParameters.every((param) => Array.from(searchParams.values()).includes(param))
: true;
const [bookerState, setBookerState] = useBookerStore((state) => [state.state, state.setState], shallow);
const selectedDate = useBookerStore((state) => state.selectedDate);
const {
@@ -441,21 +449,23 @@ const BookerComponent = ({
}}
/>
{bookerState !== "booking" && event.data?.showInstantEventConnectNowModal && (
<div
className={classNames(
"animate-fade-in-up z-40 my-2 opacity-0",
layout === BookerLayouts.MONTH_VIEW && isEmbed ? "" : "fixed bottom-2"
)}
style={{ animationDelay: "1s" }}>
<InstantBooking
event={event.data}
onConnectNow={() => {
onConnectNowInstantMeeting();
}}
/>
</div>
)}
{bookerState !== "booking" &&
event.data?.showInstantEventConnectNowModal &&
areInstantMeetingParametersSet && (
<div
className={classNames(
"animate-fade-in-up z-40 my-2 opacity-0",
layout === BookerLayouts.MONTH_VIEW && isEmbed ? "" : "fixed bottom-2"
)}
style={{ animationDelay: "1s" }}>
<InstantBooking
event={event.data}
onConnectNow={() => {
onConnectNowInstantMeeting();
}}
/>
</div>
)}
{!hideBranding && !isPlatform && (
<m.span
key="logo"
+1
View File
@@ -51,6 +51,7 @@ export type BookerEvent = Pick<
| "bookingFields"
| "seatsShowAvailabilityCount"
| "isInstantEvent"
| "instantMeetingParameters"
| "fieldTranslations"
| "autoTranslateDescriptionEnabled"
> & { users: BookerEventUser[]; showInstantEventConnectNowModal: boolean } & { profile: BookerEventProfile };
@@ -76,6 +76,10 @@ export default function InstantEventController({
const [instantEventState, setInstantEventState] = useState<boolean>(eventType?.isInstantEvent ?? false);
const formMethods = useFormContext<FormValues>();
const [parameters, setParameters] = useState<string[]>(
formMethods.getValues("instantMeetingParameters") || []
);
const { shouldLockDisableProps } = useLockedFieldsManager({
eventType,
translate: t,
@@ -171,6 +175,56 @@ export default function InstantEventController({
);
}}
/>
<div>
<Label>{t("only_show_if_parameter_set")}</Label>
<div className="space-y-2">
{parameters.map((parameter, index) => (
<div key={index} className="flex gap-2">
<TextField
required
name={`parameter-${index}`}
labelSrOnly
type="text"
value={parameter}
containerClassName="flex-1 max-w-80"
onChange={(e) => {
const newParameters = [...parameters];
newParameters[index] = e.target.value;
setParameters(newParameters);
formMethods.setValue("instantMeetingParameters", newParameters, {
shouldDirty: true,
});
}}
/>
<Button
type="button"
color="destructive"
variant="icon"
StartIcon="trash"
onClick={() => {
const newParameters = parameters.filter((_, i) => i !== index);
setParameters(newParameters);
formMethods.setValue("instantMeetingParameters", newParameters, {
shouldDirty: true,
});
}}
/>
</div>
))}
</div>
<Button
color="minimal"
StartIcon="plus"
onClick={() => {
const newParameters = [...parameters, ""];
setParameters(newParameters);
formMethods.setValue("instantMeetingParameters", newParameters, {
shouldDirty: true,
});
}}>
{t("add_parameter")}
</Button>
</div>
<Controller
name="instantMeetingExpiryTimeOffsetInSeconds"
render={({ field: { value, onChange } }) => (
@@ -57,6 +57,7 @@ const publicEventSelect = Prisma.validator<Prisma.EventTypeSelect>()({
eventName: true,
slug: true,
isInstantEvent: true,
instantMeetingParameters: true,
aiPhoneCallConfig: true,
schedulingType: true,
length: true,
@@ -299,6 +300,7 @@ export const getPublicEvent = async (
logoUrl: null,
},
isInstantEvent: false,
instantMeetingParameters: [],
showInstantEventConnectNowModal: false,
autoTranslateDescriptionEnabled: false,
fieldTranslations: [],
@@ -455,7 +457,6 @@ export const getPublicEvent = async (
if (event.team?.isPrivate && !isTeamAdminOrOwner && !isOrgAdminOrOwner) {
users = [];
}
return {
...eventWithUserProfiles,
bookerLayouts: bookerLayoutsSchema.parse(eventMetaData?.bookerLayouts || null),
@@ -495,6 +496,7 @@ export const getPublicEvent = async (
isDynamic: false,
isInstantEvent: eventWithUserProfiles.isInstantEvent,
showInstantEventConnectNowModal,
instantMeetingParameters: eventWithUserProfiles.instantMeetingParameters,
aiPhoneCallConfig: eventWithUserProfiles.aiPhoneCallConfig,
assignAllTeamMembers: event.assignAllTeamMembers,
};
@@ -71,6 +71,7 @@ export type FormValues = {
eventName: string;
slug: string;
isInstantEvent: boolean;
instantMeetingParameters: string[];
instantMeetingExpiryTimeOffsetInSeconds: number;
length: number;
offsetStart: number;
+1
View File
@@ -44,6 +44,7 @@ export const eventTypeSelect = Prisma.validator<Prisma.EventTypeSelect>()({
successRedirectUrl: true,
isInstantEvent: true,
instantMeetingExpiryTimeOffsetInSeconds: true,
instantMeetingParameters: true,
aiPhoneCallConfig: true,
assignAllTeamMembers: true,
isRRWeightsEnabled: true,
@@ -428,6 +428,7 @@ export class EventTypeRepository {
length: true,
isInstantEvent: true,
instantMeetingExpiryTimeOffsetInSeconds: true,
instantMeetingParameters: true,
aiPhoneCallConfig: true,
offsetStart: true,
hidden: true,
+1
View File
@@ -88,6 +88,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
description: faker.lorem.paragraph(),
position: 1,
isInstantEvent: false,
instantMeetingParameters: [],
instantMeetingExpiryTimeOffsetInSeconds: 90,
instantMeetingScheduleId: null,
locations: null,
@@ -63,6 +63,7 @@ export const useEventTypeForm = ({
destinationCalendar: eventType.destinationCalendar,
recurringEvent: eventType.recurringEvent || null,
isInstantEvent: eventType.isInstantEvent,
instantMeetingParameters: eventType.instantMeetingParameters || [],
instantMeetingExpiryTimeOffsetInSeconds: eventType.instantMeetingExpiryTimeOffsetInSeconds,
description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined,
@@ -59,6 +59,7 @@ export type EventType = {
bookingLimits: number | null;
durationLimits: number | null;
isInstantEvent: boolean;
instantMeetingParameters: string[];
instantMeetingExpiryTimeOffsetInSeconds: number;
assignAllTeamMembers: boolean;
useEventTypeDestinationCalendarEmail: boolean;
@@ -45,6 +45,7 @@ export type Event = {
eventName: string;
slug: string;
isInstantEvent: boolean;
instantMeetingParameters: string[];
instantMeetingExpiryTimeOffsetInSeconds: number;
aiPhoneCallConfig: {
eventTypeId: number;
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "instantMeetingParameters" TEXT[];
+1
View File
@@ -146,6 +146,7 @@ model EventType {
instantMeetingExpiryTimeOffsetInSeconds Int @default(90)
instantMeetingScheduleId Int?
instantMeetingSchedule Schedule? @relation("InstantMeetingSchedule", fields: [instantMeetingScheduleId], references: [id])
instantMeetingParameters String[]
assignAllTeamMembers Boolean @default(false)
useEventTypeDestinationCalendarEmail Boolean @default(false)
aiPhoneCallConfig AIPhoneCallConfiguration?
+1
View File
@@ -649,6 +649,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
title: true,
description: true,
isInstantEvent: true,
instantMeetingParameters: true,
instantMeetingExpiryTimeOffsetInSeconds: true,
aiPhoneCallConfig: true,
currency: true,
@@ -42,6 +42,7 @@ const childSchema = z.object({
const BaseEventTypeUpdateInput = _EventTypeModel
.extend({
isInstantEvent: z.boolean(),
instantMeetingParameters: z.array(z.string()),
instantMeetingExpiryTimeOffsetInSeconds: z.number(),
aiPhoneCallConfig,
calAiPhoneScript: z.string(),