feat: Allow only first slot to be booked (#12636)
Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
parent
8e1ce633fb
commit
2f4b1818d0
|
@ -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({
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -81,6 +81,7 @@ const commons = {
|
|||
seatsPerTimeSlot: null,
|
||||
seatsShowAttendees: null,
|
||||
seatsShowAvailabilityCount: null,
|
||||
onlyShowFirstAvailableSlot: false,
|
||||
id: 0,
|
||||
hideCalendarNotes: false,
|
||||
recurringEvent: null,
|
||||
|
|
|
@ -101,6 +101,7 @@ export default async function getEventTypeById({
|
|||
slotInterval: true,
|
||||
hashedLink: true,
|
||||
bookingLimits: true,
|
||||
onlyShowFirstAvailableSlot: true,
|
||||
durationLimits: true,
|
||||
successRedirectUrl: true,
|
||||
currency: true,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "EventType" ADD COLUMN "onlyShowFirstAvailableSlot" BOOLEAN NOT NULL DEFAULT false;
|
|
@ -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?
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
|
|
Loading…
Reference in New Issue
Block a user