Feature: Reserve slots currently being booked (#6909)
* Reserving slot picked up on cache * change memory-cache to database table to block slots while reservation completes * remove memory-cache * update realeaseAt field when same user change te selected Slot * Change default time to book Co-authored-by: alannnc <alannnc@gmail.com> * remove ip field and renews the session when the user remains in the booking form * Remove duplicate router * types fixes * nit picks * Update turbo.json * Revert unrelated change * Uses constant * Constant already has a fallback * Update slots.ts * Unit test fixes * slot reservation on user level and support seats * types fixes and reserve slots on click * Fix nit var name --------- Co-authored-by: Efraín Rochín <roae.85@gmail.com> Co-authored-by: zomars <zomars@me.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com>
This commit is contained in:
11 files changed
+227
-8
No files matched your search
@@ -10,6 +10,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useMediaQuery from "@calcom/lib/hooks/useMediaQuery";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { nameOfDay } from "@calcom/lib/weekday";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import type { Slot } from "@calcom/trpc/server/routers/viewer/slots";
|
||||
import { SkeletonContainer, SkeletonText, ToggleGroup } from "@calcom/ui";
|
||||
|
||||
@@ -28,6 +29,7 @@ type AvailableTimesProps = {
|
||||
slots?: Slot[];
|
||||
isLoading: boolean;
|
||||
ethSignature?: string;
|
||||
duration: number;
|
||||
};
|
||||
|
||||
const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
@@ -42,7 +44,9 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
seatsPerTimeSlot,
|
||||
bookingAttendees,
|
||||
ethSignature,
|
||||
duration,
|
||||
}) => {
|
||||
const reserveSlotMutation = trpc.viewer.public.slots.reserveSlot.useMutation();
|
||||
const [slotPickerRef] = useAutoAnimate<HTMLDivElement>();
|
||||
const { t, i18n } = useLocale();
|
||||
const router = useRouter();
|
||||
@@ -63,6 +67,14 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
[isMobile]
|
||||
);
|
||||
|
||||
const reserveSlot = (slot: Slot) => {
|
||||
reserveSlotMutation.mutate({
|
||||
slotUtcStartDate: slot.time,
|
||||
eventTypeId,
|
||||
slotUtcEndDate: dayjs(slot.time).utc().add(duration, "minutes").format(),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={slotPickerRef}>
|
||||
{!!date ? (
|
||||
@@ -150,6 +162,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
||||
" bg-default dark:bg-muted border-default hover:bg-subtle hover:border-brand-default text-emphasis mb-2 block rounded-md border py-2 text-sm font-medium",
|
||||
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
||||
)}
|
||||
onClick={() => reserveSlot(slot)}
|
||||
data-testid="time">
|
||||
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
|
||||
{!!seatsPerTimeSlot && (
|
||||
|
||||
@@ -194,6 +194,7 @@ export const SlotPicker = ({
|
||||
bookingAttendees={bookingAttendees}
|
||||
recurringCount={recurringEventCount}
|
||||
ethSignature={ethSignature}
|
||||
duration={parseInt(duration)}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ import getLocationOptionsForSelect from "@calcom/features/bookings/lib/getLocati
|
||||
import { FormBuilderField } from "@calcom/features/form-builder/FormBuilder";
|
||||
import { bookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect";
|
||||
import classNames from "@calcom/lib/classNames";
|
||||
import { APP_NAME } from "@calcom/lib/constants";
|
||||
import { APP_NAME, MINUTES_TO_BOOK } from "@calcom/lib/constants";
|
||||
import useGetBrandingColours from "@calcom/lib/getBrandColours";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||
@@ -41,6 +41,7 @@ import { HttpError } from "@calcom/lib/http-error";
|
||||
import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||
import { TimeFormat } from "@calcom/lib/timeFormat";
|
||||
import { trpc } from "@calcom/trpc";
|
||||
import { Button, Form, Tooltip, useCalcomTheme } from "@calcom/ui";
|
||||
import { AlertTriangle, Calendar, RefreshCw, User } from "@calcom/ui/components/icon";
|
||||
|
||||
@@ -210,8 +211,11 @@ const BookingPage = ({
|
||||
hashedLink,
|
||||
...restProps
|
||||
}: BookingPageProps) => {
|
||||
const removeSelectedSlotMarkMutation = trpc.viewer.public.slots.removeSelectedSlotMark.useMutation();
|
||||
const reserveSlotMutation = trpc.viewer.public.slots.reserveSlot.useMutation();
|
||||
const { t, i18n } = useLocale();
|
||||
const { duration: queryDuration } = useRouterQuery("duration");
|
||||
const { date: queryDate } = useRouterQuery("date");
|
||||
const isEmbed = useIsEmbed(restProps.isEmbed);
|
||||
const embedUiConfig = useEmbedUiConfig();
|
||||
const shouldAlignCentrallyInEmbed = useEmbedNonStylesConfig("align") !== "left";
|
||||
@@ -227,6 +231,15 @@ const BookingPage = ({
|
||||
}),
|
||||
{}
|
||||
);
|
||||
const reserveSlot = () => {
|
||||
if (queryDuration) {
|
||||
reserveSlotMutation.mutate({
|
||||
eventTypeId: eventType.id,
|
||||
slotUtcStartDate: dayjs(queryDate).utc().format(),
|
||||
slotUtcEndDate: dayjs(queryDate).utc().add(parseInt(queryDuration), "minutes").format(),
|
||||
});
|
||||
}
|
||||
};
|
||||
// Define duration now that we support multiple duration eventTypes
|
||||
let duration = eventType.length;
|
||||
if (
|
||||
@@ -246,6 +259,12 @@ const BookingPage = ({
|
||||
collectPageParameters("/book", { isTeamBooking: document.URL.includes("team/") })
|
||||
);
|
||||
}
|
||||
reserveSlot();
|
||||
const interval = setInterval(reserveSlot, parseInt(MINUTES_TO_BOOK) * 60 * 1000 - 2000);
|
||||
return () => {
|
||||
clearInterval(interval);
|
||||
removeSelectedSlotMarkMutation.mutate();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
@@ -649,9 +668,9 @@ const BookingPage = ({
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
{(mutation.isError || recurringMutation.isError) && (
|
||||
{mutation.isError || recurringMutation.isError ? (
|
||||
<ErrorMessage error={mutation.error || recurringMutation.error} />
|
||||
)}
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { prismaMock, CalendarManagerMock } from "../../../../tests/config/single
|
||||
// TODO: Mock properly
|
||||
prismaMock.eventType.findUnique.mockResolvedValue(null);
|
||||
prismaMock.user.findMany.mockResolvedValue([]);
|
||||
prismaMock.selectedSlots.findMany.mockResolvedValue([]);
|
||||
|
||||
jest.mock("@calcom/lib/constants", () => ({
|
||||
IS_PRODUCTION: true,
|
||||
@@ -152,7 +153,7 @@ const TestData = {
|
||||
};
|
||||
|
||||
const ctx = {
|
||||
prisma,
|
||||
prisma: prismaMock,
|
||||
};
|
||||
|
||||
type App = {
|
||||
|
||||
Reference in new issue
Block a user