feat: Allow only first slot to be booked (#12636)

Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
Haran Rajkumar 2023-12-02 19:07:06 -05:00 committed by GitHub
parent 8e1ce633fb
commit 2f4b1818d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 47 additions and 0 deletions

View File

@ -60,6 +60,7 @@ export const schemaEventTypeBaseBodyParams = EventType.pick({
successRedirectUrl: true,
locations: true,
bookingLimits: true,
onlyShowFirstAvailableSlot: true,
durationLimits: true,
})
.merge(
@ -147,6 +148,7 @@ export const schemaEventTypeReadPublic = EventType.pick({
seatsShowAvailabilityCount: true,
bookingFields: true,
bookingLimits: true,
onlyShowFirstAvailableSlot: true,
durationLimits: true,
}).merge(
z.object({

View File

@ -298,6 +298,29 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
);
}}
/>
<Controller
name="onlyShowFirstAvailableSlot"
control={formMethods.control}
render={({ field: { value } }) => {
const isChecked = value;
return (
<SettingsToggle
toggleSwitchAtTheEnd={true}
labelClassName="text-sm"
title={t("limit_booking_only_first_slot")}
description={t("limit_booking_only_first_slot_description")}
checked={isChecked}
onCheckedChange={(active) => {
formMethods.setValue("onlyShowFirstAvailableSlot", active ?? false);
}}
switchContainerClassName={classNames(
"border-subtle mt-6 rounded-lg border py-6 px-4 sm:px-6",
isChecked && "rounded-b-none"
)}
/>
);
}}
/>
<Controller
name="durationLimits"
control={formMethods.control}

View File

@ -131,6 +131,7 @@ export type FormValues = {
successRedirectUrl: string;
durationLimits?: IntervalLimit;
bookingLimits?: IntervalLimit;
onlyShowFirstAvailableSlot: boolean;
children: ChildrenEventType[];
hosts: { userId: number; isFixed: boolean }[];
bookingFields: z.infer<typeof eventTypeBookingFields>;
@ -250,6 +251,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
description: eventType.description ?? undefined,
schedule: eventType.schedule || undefined,
bookingLimits: eventType.bookingLimits || undefined,
onlyShowFirstAvailableSlot: eventType.onlyShowFirstAvailableSlot || undefined,
durationLimits: eventType.durationLimits || undefined,
length: eventType.length,
hidden: eventType.hidden,
@ -429,6 +431,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
seatsShowAttendees,
seatsShowAvailabilityCount,
bookingLimits,
onlyShowFirstAvailableSlot,
durationLimits,
recurringEvent,
locations,
@ -491,6 +494,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
beforeEventBuffer: beforeBufferTime,
afterEventBuffer: afterBufferTime,
bookingLimits,
onlyShowFirstAvailableSlot,
durationLimits,
seatsPerTimeSlot,
seatsShowAttendees,
@ -532,6 +536,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
seatsShowAttendees,
seatsShowAvailabilityCount,
bookingLimits,
onlyShowFirstAvailableSlot,
durationLimits,
recurringEvent,
locations,
@ -584,6 +589,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
beforeEventBuffer: beforeBufferTime,
afterEventBuffer: afterBufferTime,
bookingLimits,
onlyShowFirstAvailableSlot,
durationLimits,
seatsPerTimeSlot,
seatsShowAttendees,

View File

@ -1481,6 +1481,8 @@
"report_app": "Report app",
"limit_booking_frequency": "Limit booking frequency",
"limit_booking_frequency_description": "Limit how many times this event can be booked",
"limit_booking_only_first_slot": "Limit booking only first slot",
"limit_booking_only_first_slot_description": "Allow only the first slot of every day to be booked",
"limit_total_booking_duration": "Limit total booking duration",
"limit_total_booking_duration_description": "Limit total amount of time that this event can be booked",
"add_limit": "Add Limit",

View File

@ -186,6 +186,7 @@ export default async function handleChildrenEventTypes({
metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined,
bookingFields: (managedEventTypeValues.bookingFields as Prisma.InputJsonValue) ?? undefined,
durationLimits: (managedEventTypeValues.durationLimits as Prisma.InputJsonValue) ?? undefined,
onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false,
userId,
users: {
connect: [{ id: userId }],
@ -235,6 +236,7 @@ export default async function handleChildrenEventTypes({
hidden: children?.find((ch) => ch.owner.id === userId)?.hidden ?? false,
bookingLimits:
(managedEventTypeValues.bookingLimits as unknown as Prisma.InputJsonObject) ?? undefined,
onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false,
recurringEvent:
(managedEventTypeValues.recurringEvent as unknown as Prisma.InputJsonValue) ?? undefined,
metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined,

View File

@ -81,6 +81,7 @@ const commons = {
seatsPerTimeSlot: null,
seatsShowAttendees: null,
seatsShowAvailabilityCount: null,
onlyShowFirstAvailableSlot: false,
id: 0,
hideCalendarNotes: false,
recurringEvent: null,

View File

@ -101,6 +101,7 @@ export default async function getEventTypeById({
slotInterval: true,
hashedLink: true,
bookingLimits: true,
onlyShowFirstAvailableSlot: true,
durationLimits: true,
successRedirectUrl: true,
currency: true,

View File

@ -92,6 +92,7 @@ export const buildEventType = (eventType?: Partial<EventType>): EventType => {
minimumBookingNotice: 120,
beforeEventBuffer: 0,
afterEventBuffer: 0,
onlyShowFirstAvailableSlot: false,
seatsPerTimeSlot: null,
seatsShowAttendees: null,
seatsShowAvailabilityCount: null,

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "EventType" ADD COLUMN "onlyShowFirstAvailableSlot" BOOLEAN NOT NULL DEFAULT false;

View File

@ -98,6 +98,7 @@ model EventType {
beforeEventBuffer Int @default(0)
afterEventBuffer Int @default(0)
seatsPerTimeSlot Int?
onlyShowFirstAvailableSlot Boolean @default(false)
seatsShowAttendees Boolean? @default(false)
seatsShowAvailabilityCount Boolean? @default(true)
schedulingType SchedulingType?

View File

@ -585,6 +585,7 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
destinationCalendar: true,
periodCountCalendarDays: true,
bookingLimits: true,
onlyShowFirstAvailableSlot: true,
slotInterval: true,
scheduleId: true,
workflows: true,

View File

@ -157,6 +157,7 @@ export async function getEventType(
periodType: true,
periodStartDate: true,
periodEndDate: true,
onlyShowFirstAvailableSlot: true,
periodCountCalendarDays: true,
periodDays: true,
metadata: true,
@ -368,6 +369,7 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions) {
eventType: {
select: {
id: true,
onlyShowFirstAvailableSlot: true,
afterEventBuffer: true,
beforeEventBuffer: true,
seatsPerTimeSlot: true,
@ -577,6 +579,9 @@ export async function getAvailableSlots({ input, ctx }: GetScheduleOptions) {
const dateString = formatter.format(time.toDate());
r[dateString] = r[dateString] || [];
if (eventType.onlyShowFirstAvailableSlot && r[dateString].length > 0) {
return r;
}
r[dateString].push({
...passThroughProps,
time: time.toISOString(),