fix: common schedule toggling incorrectly (#17517)

* fix: common schedule toggling incorrectly

* type fixes to util.ts

* fix another type error
This commit is contained in:
Alex van Andel
2024-11-06 20:21:25 +00:00
committed by GitHub
parent 4b38d73120
commit 993bf23986
5 changed files with 130 additions and 128 deletions
@@ -54,7 +54,6 @@ type HostSchedulesQueryType =
type EventTypeTeamScheduleProps = {
hostSchedulesQuery: HostSchedulesQueryType;
hosts?: Host[];
};
type TeamMember = Pick<TeamMembers[number], "avatar" | "name" | "id">;
@@ -392,13 +391,13 @@ const TeamMemberSchedule = ({
};
const TeamAvailability = ({
hosts,
teamMembers,
hostSchedulesQuery,
}: EventTypeTeamScheduleProps & { teamMembers: TeamMember[] }) => {
const { t } = useLocale();
const { watch } = useFormContext<FormValues>();
const [animationRef] = useAutoAnimate<HTMLUListElement>();
const hosts = watch("hosts");
return (
<>
<div className="border-subtle flex flex-col rounded-md">
@@ -439,42 +438,41 @@ const TeamAvailability = ({
);
};
const useCommonScheduleState = (initialScheduleId: number | null) => {
const { setValue } = useFormContext<FormValues>();
const [useHostSchedulesForTeamEvent, setUseHostSchedulesForTeamEvent] = useState(!initialScheduleId);
// Reset the main schedule
const clearMainSchedule = () => {
setValue("schedule", null, { shouldDirty: Boolean(initialScheduleId) });
};
// Toggle function
const toggleScheduleState = (checked: boolean) => {
const useHostSchedulesForTeamEvent = !checked;
setUseHostSchedulesForTeamEvent(useHostSchedulesForTeamEvent);
if (useHostSchedulesForTeamEvent) clearMainSchedule();
};
return {
useHostSchedulesForTeamEvent,
toggleScheduleState,
};
};
const UseCommonScheduleSettingsToggle = ({ eventType, ...rest }: EventTypeScheduleProps) => {
const { t } = useLocale();
const { setValue, resetField, getValues, watch } = useFormContext<FormValues>();
const [useHostSchedulesForTeamEvent, setUseHostSchedulesForTeamEvent] = useState(
!Boolean(eventType.schedule)
);
const watchHosts = watch("hosts");
const { useHostSchedulesForTeamEvent, toggleScheduleState } = useCommonScheduleState(eventType.schedule);
return (
<>
<SettingsToggle
checked={!useHostSchedulesForTeamEvent}
onCheckedChange={(checked) => {
setUseHostSchedulesForTeamEvent(!checked);
if (checked) {
if (Boolean(eventType.schedule)) resetField("schedule");
getValues("hosts").map((_, index) => {
setValue(`hosts.${index}.scheduleId`, null, { shouldDirty: true });
});
} else {
setValue("schedule", null, { shouldDirty: Boolean(eventType.schedule) });
}
}}
onCheckedChange={toggleScheduleState}
title={t("choose_common_schedule_team_event")}
description={t("choose_common_schedule_team_event_description")}>
{/* handles the state for which 'schedule' ID is set, as it's unknown until the Select dropdown is loaded */}
<EventTypeSchedule eventType={eventType} {...rest} />
</SettingsToggle>
{useHostSchedulesForTeamEvent && (
<div className="lg:ml-14">
<TeamAvailability
hosts={watchHosts}
teamMembers={rest.teamMembers}
hostSchedulesQuery={rest.hostSchedulesQuery}
/>
<TeamAvailability teamMembers={rest.teamMembers} hostSchedulesQuery={rest.hostSchedulesQuery} />
</div>
)}
</>
+29 -25
View File
@@ -37,6 +37,33 @@ export type TeamMember = {
defaultScheduleId: number | null;
};
type EventLocation = {
type: EventLocationType["type"];
address?: string;
attendeeAddress?: string;
somewhereElse?: string;
link?: string;
hostPhoneNumber?: string;
displayLocationPublicly?: boolean;
phone?: string;
hostDefault?: string;
credentialId?: number;
teamName?: string;
};
type PhoneCallConfig = {
generalPrompt: string;
enabled: boolean;
beginMessage: string;
yourPhoneNumber: string;
numberToCall: string;
guestName?: string;
guestEmail?: string;
guestCompany?: string;
templateType: string;
schedulerName?: string;
};
export type FormValues = {
id: number;
title: string;
@@ -59,31 +86,8 @@ export type FormValues = {
hideCalendarNotes: boolean;
multiplePrivateLinks: string[] | undefined;
eventTypeColor: z.infer<typeof eventTypeColor>;
locations: {
type: EventLocationType["type"];
address?: string;
attendeeAddress?: string;
somewhereElse?: string;
link?: string;
hostPhoneNumber?: string;
displayLocationPublicly?: boolean;
phone?: string;
hostDefault?: string;
credentialId?: number;
teamName?: string;
}[];
aiPhoneCallConfig: {
generalPrompt: string;
enabled: boolean;
beginMessage: string;
yourPhoneNumber: string;
numberToCall: string;
guestName?: string;
guestEmail?: string;
guestCompany?: string;
templateType: string;
schedulerName?: string;
};
locations: EventLocation[];
aiPhoneCallConfig: PhoneCallConfig;
customInputs: CustomInputParsed[];
schedule: number | null;
@@ -5,77 +5,84 @@ import { _DestinationCalendarModel, _EventTypeModel } from "@calcom/prisma/zod";
import { customInputSchema, EventTypeMetaDataSchema, stringOrNumber } from "@calcom/prisma/zod-utils";
import { eventTypeBookingFields } from "@calcom/prisma/zod-utils";
export const EventTypeUpdateInput = _EventTypeModel
/** Optional fields */
const aiPhoneCallConfig = z
.object({
generalPrompt: z.string(),
enabled: z.boolean(),
beginMessage: z.string().nullable(),
yourPhoneNumber: z.string(),
numberToCall: z.string(),
guestName: z.string().nullable().optional(),
guestEmail: z.string().nullable().optional(),
guestCompany: z.string().nullable().optional(),
templateType: templateTypeEnum,
})
.optional();
const hostSchema = z.object({
userId: z.number(),
profileId: z.number().or(z.null()).optional(),
isFixed: z.boolean().optional(),
priority: z.number().min(0).max(4).optional().nullable(),
weight: z.number().min(0).optional().nullable(),
scheduleId: z.number().optional().nullable(),
});
const childSchema = z.object({
owner: z.object({
id: z.number(),
name: z.string(),
email: z.string(),
eventTypeSlugs: z.array(z.string()),
}),
hidden: z.boolean(),
});
/** Optional fields */
const BaseEventTypeUpdateInput = _EventTypeModel
.extend({
isInstantEvent: z.boolean().optional(),
instantMeetingExpiryTimeOffsetInSeconds: z.number().optional(),
aiPhoneCallConfig: z
.object({
generalPrompt: z.string(),
enabled: z.boolean(),
beginMessage: z.string().nullable(),
yourPhoneNumber: z.string().default(""),
numberToCall: z.string().default(""),
guestName: z
.string()
.nullable()
.optional()
.transform((val) => (!!val ? val : undefined)),
guestEmail: z.string().nullable().default(null),
guestCompany: z.string().nullable().default(null),
templateType: templateTypeEnum,
})
.optional(),
calAiPhoneScript: z.string().optional(),
customInputs: z.array(customInputSchema).optional(),
isInstantEvent: z.boolean(),
instantMeetingExpiryTimeOffsetInSeconds: z.number(),
aiPhoneCallConfig,
calAiPhoneScript: z.string(),
customInputs: z.array(customInputSchema),
destinationCalendar: _DestinationCalendarModel
.pick({
integration: true,
externalId: true,
})
.nullable(),
users: z.array(stringOrNumber).optional(),
children: z
.array(
z.object({
owner: z.object({
id: z.number(),
name: z.string(),
email: z.string(),
eventTypeSlugs: z.array(z.string()),
}),
hidden: z.boolean(),
})
)
.optional(),
hosts: z
.array(
z.object({
userId: z.number(),
profileId: z.number().or(z.null()).optional(),
isFixed: z.boolean().optional(),
priority: z.number().min(0).max(4).optional().nullable(),
weight: z.number().min(0).optional().nullable(),
scheduleId: z.number().optional().nullable(),
})
)
.optional(),
schedule: z.number().nullable().optional(),
instantMeetingSchedule: z.number().nullable().optional(),
users: z.array(stringOrNumber),
children: z.array(childSchema),
hosts: z.array(hostSchema),
schedule: z.number().nullable(),
instantMeetingSchedule: z.number().nullable(),
multiplePrivateLinks: z.array(z.string()),
assignAllTeamMembers: z.boolean().optional(),
isRRWeightsEnabled: z.boolean().optional(),
assignAllTeamMembers: z.boolean(),
isRRWeightsEnabled: z.boolean(),
metadata: EventTypeMetaDataSchema,
bookingFields: eventTypeBookingFields,
})
.partial()
.extend({
metadata: EventTypeMetaDataSchema.optional(),
bookingFields: eventTypeBookingFields.optional(),
})
.merge(
_EventTypeModel
/** Required fields */
.pick({
id: true,
})
);
.extend(_EventTypeModel.pick({ id: true }).shape);
const ZUpdateInputSchema = BaseEventTypeUpdateInput.extend({
aiPhoneCallConfig: aiPhoneCallConfig.refine(
(data) => {
if (!data) return true;
data.yourPhoneNumber = data.yourPhoneNumber || "";
data.numberToCall = data.numberToCall || "";
data.guestName = data.guestName ?? undefined;
data.guestEmail = data.guestEmail ?? null;
data.guestCompany = data.guestCompany ?? null;
return true;
},
{
message: "Applying default values and transformations",
}
),
}).strict();
// only run infer over the simple type, excluding refines/transforms.
type TUpdateInputSchema = z.infer<typeof BaseEventTypeUpdateInput>;
export { ZUpdateInputSchema, type TUpdateInputSchema };
@@ -1,7 +1,2 @@
import type { z } from "zod";
import { EventTypeUpdateInput } from "./types";
export const ZUpdateInputSchema = EventTypeUpdateInput.strict();
export type TUpdateInputSchema = z.infer<typeof ZUpdateInputSchema>;
export { ZUpdateInputSchema } from "./types";
export type { TUpdateInputSchema } from "./types";
@@ -12,7 +12,7 @@ import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
import { TRPCError } from "@trpc/server";
import authedProcedure from "../../../procedures/authedProcedure";
import type { EventTypeUpdateInput } from "./types";
import type { TUpdateInputSchema } from "./types";
type EventType = Awaited<ReturnType<typeof EventTypeRepository.findAllByUpId>>[number];
@@ -142,7 +142,7 @@ export function handleCustomInputs(customInputs: CustomInputSchema[], eventTypeI
};
}
export function ensureUniqueBookingFields(fields: z.infer<typeof EventTypeUpdateInput>["bookingFields"]) {
export function ensureUniqueBookingFields(fields: TUpdateInputSchema["bookingFields"]) {
if (!fields) {
return;
}
@@ -161,9 +161,7 @@ export function ensureUniqueBookingFields(fields: z.infer<typeof EventTypeUpdate
}, {} as Record<string, true>);
}
export function ensureEmailOrPhoneNumberIsPresent(
fields: z.infer<typeof EventTypeUpdateInput>["bookingFields"]
) {
export function ensureEmailOrPhoneNumberIsPresent(fields: TUpdateInputSchema["bookingFields"]) {
if (!fields || fields.length === 0) {
return;
}