Compare commits
89 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
825418eeb6 | ||
|
9abecc57a0 | ||
|
c585365807 | ||
|
0a15644cbe | ||
|
632d5f2592 | ||
|
1d1afc69c3 | ||
|
f54dd2a502 | ||
|
04b73d39a6 | ||
|
1671836b5c | ||
|
ca093f6307 | ||
|
1bcbd4bd73 | ||
|
93106fcbe5 | ||
|
262a103461 | ||
|
656d586b52 | ||
|
41910ff1b5 | ||
|
1aa21b69a3 | ||
|
51d2ec115c | ||
|
9915c4746e | ||
|
7e3aab88ba | ||
|
a355b18db5 | ||
|
778c4cb332 | ||
|
0b127bc922 | ||
|
8d259093cb | ||
|
a8bda978d3 | ||
|
6eb2a3f567 | ||
|
fd5852ebb9 | ||
|
a4cd2db2dd | ||
|
37a0152a2b | ||
|
221d9e2c40 | ||
|
61f8f1dd41 | ||
|
6d75920c70 | ||
|
35a0c74c7b | ||
|
c06817c753 | ||
|
972724ea57 | ||
|
6954afe023 | ||
|
4aa789de9f | ||
|
06fe6464d9 | ||
|
97c12247ca | ||
|
9a2e99ee64 | ||
|
7d8b746de5 | ||
|
1782399672 | ||
|
618e3cdf92 | ||
|
310a82ac3f | ||
|
ade30ad272 | ||
|
1f12fdcf7f | ||
|
d212d2d04b | ||
|
169614ba95 | ||
|
729c3ec213 | ||
|
c60afd0a8b | ||
|
1be35792c7 | ||
|
a4bd255761 | ||
|
c8cc4d3c0e | ||
|
44e65d22fc | ||
|
fefc24c802 | ||
|
841f170aea | ||
|
4b83854327 | ||
|
80d8b76736 | ||
|
0384058c90 | ||
|
ab42cfd4bd | ||
|
16e9545dba | ||
|
c5ce792a62 | ||
|
535dd93836 | ||
|
e83057fe0e | ||
|
660060b091 | ||
|
03f243db21 | ||
|
c6a1ebfcab | ||
|
cdc1d915ed | ||
|
bc25a1d75f | ||
|
ebec4ad440 | ||
|
7543045c4f | ||
|
ed18248df5 | ||
|
af7d3eb0db | ||
|
7591fed08b | ||
|
4946f32a1c | ||
|
1acf4b7ee4 | ||
|
e3e56f2113 | ||
|
f0a83b2d8d | ||
|
2a3748f31e | ||
|
68fd207f53 | ||
|
444520485e | ||
|
c904532b81 | ||
|
ad25e4fec0 | ||
|
efe1bf9422 | ||
|
ffd49ef3b2 | ||
|
9b3af6dc10 | ||
|
c2ad171ce0 | ||
|
fff14a2a2a | ||
|
7bfdebe7b9 | ||
|
27a31d70e2 |
|
@ -1,23 +1,29 @@
|
|||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import * as RadioGroup from "@radix-ui/react-radio-group";
|
||||
import { EventTypeSetupInfered, FormValues } from "pages/event-types/[type]";
|
||||
import { useState } from "react";
|
||||
import { useFormContext, Controller, useWatch } from "react-hook-form";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType";
|
||||
import findDurationType from "@calcom/lib/findDurationType";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { PeriodType } from "@calcom/prisma/client";
|
||||
import type { BookingLimit } from "@calcom/types/Calendar";
|
||||
import { Icon } from "@calcom/ui";
|
||||
import { Button } from "@calcom/ui/components";
|
||||
import { Label, Input, MinutesField } from "@calcom/ui/components/form";
|
||||
import { Input, InputField, Label } from "@calcom/ui/components/form";
|
||||
import { Select, SettingsToggle } from "@calcom/ui/v2";
|
||||
import DateRangePicker from "@calcom/ui/v2/core/form/date-range-picker/DateRangePicker";
|
||||
|
||||
export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">) => {
|
||||
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupInfered, "eventType">) => {
|
||||
const { t } = useLocale();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const { eventType } = props;
|
||||
const minimumBookingNoticeType = useRef<DurationType>(findDurationType(eventType.minimumBookingNotice));
|
||||
const prevBookingNoticeType = useRef<DurationType>(minimumBookingNoticeType.current);
|
||||
|
||||
const minimumBookingNoticeInDurationTypeFormValue = formMethods.watch("minimumBookingNoticeInDurationType");
|
||||
|
||||
const PERIOD_TYPES = [
|
||||
{
|
||||
type: "ROLLING" as const,
|
||||
|
@ -47,6 +53,57 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
|
|||
defaultValue: periodType?.type,
|
||||
});
|
||||
|
||||
const onMinimumNoticeDurationTypeChange = useMemo(
|
||||
() => (durationType?: DurationType) => {
|
||||
if (typeof durationType === "undefined") return;
|
||||
|
||||
// Store current selected type in ref to use in previous run for comparrison.
|
||||
minimumBookingNoticeType.current = durationType;
|
||||
|
||||
// Uses the previous selected type and converts the value to the new type.
|
||||
// Eg day was selected before, user selects hours now, so we multiple * 24 and round up.
|
||||
const minimumBookingNoticeInDurationType = convertToNewDurationType(
|
||||
prevBookingNoticeType.current,
|
||||
durationType,
|
||||
Number(minimumBookingNoticeInDurationTypeFormValue)
|
||||
);
|
||||
|
||||
// Uses the round up value in the new type to calculate the value in minutes.
|
||||
// We should NOT use minimumBookingNoticeInDurationTypeFormValue here, since that
|
||||
// would break in the case that the user enters 90 minutes, changes the type to
|
||||
// hours (that will show 2 hours), but the value in minutes would then remain 90,
|
||||
// which is something else than the user things they save.
|
||||
const minimumBookingNoticeInMinutes = convertToNewDurationType(
|
||||
durationType,
|
||||
"minutes",
|
||||
Number(minimumBookingNoticeInDurationType)
|
||||
);
|
||||
|
||||
// Updates both form values as well as stores the current type as previous type in ref.
|
||||
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
|
||||
formMethods.setValue("minimumBookingNoticeInDurationType", minimumBookingNoticeInDurationType);
|
||||
prevBookingNoticeType.current = durationType;
|
||||
},
|
||||
[formMethods, minimumBookingNoticeInDurationTypeFormValue]
|
||||
);
|
||||
|
||||
/**
|
||||
* When the user inputs a new value for minimumBookingNoticeInDurationType,
|
||||
* we calculate the value in minutes, and update this hidden field as well.
|
||||
*/
|
||||
const onMinimumNoticeChange = useMemo(
|
||||
() => (duration: string) => {
|
||||
const minimumBookingNoticeInMinutes = convertToNewDurationType(
|
||||
minimumBookingNoticeType.current,
|
||||
"minutes",
|
||||
Number(duration)
|
||||
);
|
||||
|
||||
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
|
||||
},
|
||||
[formMethods]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||
|
@ -116,13 +173,54 @@ export const EventLimitsTab = (props: Pick<EventTypeSetupInfered, "eventType">)
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||
<div className="w-full">
|
||||
<MinutesField
|
||||
required
|
||||
label={t("minimum_booking_notice")}
|
||||
type="number"
|
||||
placeholder="120"
|
||||
{...formMethods.register("minimumBookingNotice", { valueAsNumber: true })}
|
||||
<div className="flex w-full items-end">
|
||||
<Controller
|
||||
name="minimumBookingNotice"
|
||||
control={formMethods.control}
|
||||
render={() => {
|
||||
const durationTypeOptions = [
|
||||
{
|
||||
label: t("minutes"),
|
||||
value: "minutes",
|
||||
},
|
||||
{
|
||||
label: t("hours"),
|
||||
value: "hours",
|
||||
},
|
||||
{
|
||||
label: t("days"),
|
||||
value: "days",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-1/2 md:w-3/4">
|
||||
<InputField
|
||||
required
|
||||
label={t("minimum_booking_notice")}
|
||||
type="number"
|
||||
placeholder="120"
|
||||
className="mr-2 rounded-[4px]"
|
||||
{...formMethods.register("minimumBookingNoticeInDurationType", {
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onMinimumNoticeChange(event.target.value),
|
||||
})}
|
||||
/>
|
||||
<input type="hidden" {...formMethods.register("minimumBookingNotice")} />
|
||||
</div>
|
||||
<Select
|
||||
isSearchable={false}
|
||||
className="mb-2 ml-2 w-full capitalize md:min-w-[150px] md:max-w-[200px]"
|
||||
defaultValue={durationTypeOptions.find(
|
||||
(option) => option.value === minimumBookingNoticeType.current
|
||||
)}
|
||||
onChange={(input) => onMinimumNoticeDurationTypeChange(input?.value as DurationType)}
|
||||
options={durationTypeOptions}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
|
|
|
@ -12,6 +12,8 @@ import getApps, { getEventTypeAppData, getLocationOptions } from "@calcom/app-st
|
|||
import { LocationObject, EventLocationType } from "@calcom/core/location";
|
||||
import { parseRecurringEvent, parseBookingLimit, validateBookingLimitOrder } from "@calcom/lib";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
|
||||
import findDurationType from "@calcom/lib/findDurationType";
|
||||
import getStripeAppData from "@calcom/lib/getStripeAppData";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
@ -73,6 +75,7 @@ export type FormValues = {
|
|||
seatsShowAttendees: boolean | null;
|
||||
seatsPerTimeSlotEnabled: boolean;
|
||||
minimumBookingNotice: number;
|
||||
minimumBookingNoticeInDurationType: number;
|
||||
beforeBufferTime: number;
|
||||
afterBufferTime: number;
|
||||
slotInterval: number | null;
|
||||
|
@ -163,6 +166,11 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
},
|
||||
schedulingType: eventType.schedulingType,
|
||||
minimumBookingNotice: eventType.minimumBookingNotice,
|
||||
minimumBookingNoticeInDurationType: convertToNewDurationType(
|
||||
"minutes",
|
||||
findDurationType(eventType.minimumBookingNotice),
|
||||
eventType.minimumBookingNotice
|
||||
),
|
||||
metadata: eventType.metadata,
|
||||
},
|
||||
});
|
||||
|
@ -237,9 +245,11 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
|
|||
recurringEvent,
|
||||
locations,
|
||||
metadata,
|
||||
// We don't need to send it to the backend
|
||||
// We don't need to send send these values to the backend
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
seatsPerTimeSlotEnabled,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
minimumBookingNoticeInDurationType,
|
||||
...input
|
||||
} = values;
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
export const MINUTES_IN_HOUR = 60;
|
||||
export const MINUTES_IN_DAY = 1440;
|
||||
export const HOURS_IN_DAY = 24;
|
||||
|
||||
export type DurationType = "minutes" | "hours" | "days";
|
||||
|
||||
export default function convertToNewDurationType(
|
||||
prevType: DurationType,
|
||||
newType: DurationType,
|
||||
prevValue: number
|
||||
) {
|
||||
/** Convert `prevValue` from `prevType` to `newType` */
|
||||
const newDurationTypeMap = {
|
||||
minutes_minutes: () => prevValue,
|
||||
minutes_hours: () => prevValue * MINUTES_IN_HOUR,
|
||||
minutes_days: () => prevValue * MINUTES_IN_DAY,
|
||||
hours_minutes: () => prevValue / MINUTES_IN_HOUR,
|
||||
hours_hours: () => prevValue,
|
||||
hours_days: () => prevValue * HOURS_IN_DAY,
|
||||
days_minutes: () => prevValue / MINUTES_IN_DAY,
|
||||
days_hours: () => prevValue / HOURS_IN_DAY,
|
||||
days_days: () => prevValue,
|
||||
};
|
||||
const getNewValue = newDurationTypeMap[`${prevType}_${newType}`];
|
||||
const newValue = getNewValue();
|
||||
return Math.ceil(newValue);
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { DurationType, MINUTES_IN_DAY, MINUTES_IN_HOUR } from "./convertToNewDurationType";
|
||||
|
||||
export default function findDurationType(value: number): DurationType {
|
||||
if (value % MINUTES_IN_DAY === 0) return "days";
|
||||
if (value % MINUTES_IN_HOUR === 0) return "hours";
|
||||
return "minutes";
|
||||
}
|
|
@ -341,7 +341,3 @@ export function InputGroupBox(props: JSX.IntrinsicElements["div"]) {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const MinutesField = forwardRef<HTMLInputElement, InputFieldProps>(function MinutesField(props, ref) {
|
||||
return <InputField ref={ref} type="number" min={0} {...props} addOnSuffix="mins" />;
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue
Block a user