diff --git a/components/booking/AvailableTimes.tsx b/components/booking/AvailableTimes.tsx
index 2e1f5f3e..6f4e4130 100644
--- a/components/booking/AvailableTimes.tsx
+++ b/components/booking/AvailableTimes.tsx
@@ -1,7 +1,7 @@
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween";
dayjs.extend(isBetween);
-import { useEffect, useState } from "react";
+import { useEffect, useState, useMemo } from "react";
import getSlots from "../../lib/slots";
import Link from "next/link";
import { timeZone } from "../../lib/clock";
@@ -14,14 +14,18 @@ const AvailableTimes = (props) => {
const [loaded, setLoaded] = useState(false);
const [error, setError] = useState(false);
- const times = getSlots({
- calendarTimeZone: props.user.timeZone,
- selectedTimeZone: timeZone(),
- eventLength: props.eventType.length,
- selectedDate: props.date,
- dayStartTime: props.user.startTime,
- dayEndTime: props.user.endTime,
- });
+ const times = useMemo(() => {
+ const slots = getSlots({
+ calendarTimeZone: props.user.timeZone,
+ selectedTimeZone: timeZone(),
+ eventLength: props.eventType.length,
+ selectedDate: props.date,
+ dayStartTime: props.user.startTime,
+ dayEndTime: props.user.endTime,
+ });
+
+ return slots;
+ }, [props.date]);
const handleAvailableSlots = (busyTimes: []) => {
// Check for conflicts
@@ -80,6 +84,7 @@ const AvailableTimes = (props) => {
{!error &&
loaded &&
+ times.length > 0 &&
times.map((time) => (
{
))}
+ {!error && loaded && times.length == 0 && (
+
+
{props.user.name} is all booked today.
+
+ )}
{!error && !loaded && }
{error && (
diff --git a/pages/api/book/[user].ts b/pages/api/book/[user].ts
index db82b3e6..1140a653 100644
--- a/pages/api/book/[user].ts
+++ b/pages/api/book/[user].ts
@@ -1,21 +1,53 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "../../../lib/prisma";
-import { CalendarEvent, createEvent, updateEvent } from "../../../lib/calendarClient";
+import { CalendarEvent, createEvent, updateEvent, getBusyCalendarTimes } from "../../../lib/calendarClient";
import async from "async";
import { v5 as uuidv5 } from "uuid";
import short from "short-uuid";
-import { createMeeting, updateMeeting } from "../../../lib/videoClient";
+import { createMeeting, updateMeeting, getBusyVideoTimes } from "../../../lib/videoClient";
import EventAttendeeMail from "../../../lib/emails/EventAttendeeMail";
import { getEventName } from "../../../lib/event";
import { LocationType } from "../../../lib/location";
import merge from "lodash.merge";
const translator = short();
+import dayjs from "dayjs";
-interface p {
+const isAvailable = (busyTimes, time, length) => {
+ // Check for conflicts
+ let t = true;
+ busyTimes.forEach((busyTime) => {
+ const startTime = dayjs(busyTime.start);
+ const endTime = dayjs(busyTime.end);
+
+ // Check if start times are the same
+ if (dayjs(time).format("HH:mm") == startTime.format("HH:mm")) {
+ t = false;
+ }
+
+ // Check if time is between start and end times
+ if (dayjs(time).isBetween(startTime, endTime)) {
+ t = false;
+ }
+
+ // Check if slot end time is between start and end time
+ if (dayjs(time).add(length, "minutes").isBetween(startTime, endTime)) {
+ t = false;
+ }
+
+ // Check if startTime is between slot
+ if (startTime.isBetween(dayjs(time), dayjs(time).add(length, "minutes"))) {
+ t = false;
+ }
+ });
+
+ return t;
+};
+
+interface GetLocationRequestFromIntegrationRequest {
location: string;
}
-const getLocationRequestFromIntegration = ({ location }: p) => {
+const getLocationRequestFromIntegration = ({ location }: GetLocationRequestFromIntegrationRequest) => {
if (location === LocationType.GoogleMeet.valueOf()) {
const requestId = uuidv5(location, uuidv5.URL);
@@ -47,10 +79,43 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
+ const selectedCalendars = await prisma.selectedCalendar.findMany({
+ where: {
+ userId: currentUser.id,
+ },
+ });
// Split credentials up into calendar credentials and video credentials
const calendarCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar"));
const videoCredentials = currentUser.credentials.filter((cred) => cred.type.endsWith("_video"));
+ const hasCalendarIntegrations =
+ currentUser.credentials.filter((cred) => cred.type.endsWith("_calendar")).length > 0;
+ const hasVideoIntegrations =
+ currentUser.credentials.filter((cred) => cred.type.endsWith("_video")).length > 0;
+
+ const calendarAvailability = await getBusyCalendarTimes(
+ currentUser.credentials,
+ dayjs(req.body.start).startOf("day").utc().format(),
+ dayjs(req.body.end).endOf("day").utc().format(),
+ selectedCalendars
+ );
+ const videoAvailability = await getBusyVideoTimes(
+ currentUser.credentials,
+ dayjs(req.body.start).startOf("day").utc().format(),
+ dayjs(req.body.end).endOf("day").utc().format()
+ );
+ let commonAvailability = [];
+
+ if (hasCalendarIntegrations && hasVideoIntegrations) {
+ commonAvailability = calendarAvailability.filter((availability) =>
+ videoAvailability.includes(availability)
+ );
+ } else if (hasVideoIntegrations) {
+ commonAvailability = videoAvailability;
+ } else if (hasCalendarIntegrations) {
+ commonAvailability = calendarAvailability;
+ }
+
const rescheduleUid = req.body.rescheduleUid;
const selectedEventType = await prisma.eventType.findFirst({
@@ -61,6 +126,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
select: {
eventName: true,
title: true,
+ length: true,
},
});
@@ -103,6 +169,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
+ const isAvailableToBeBooked = isAvailable(commonAvailability, req.body.start, selectedEventType.length);
+
+ if (!isAvailableToBeBooked) {
+ return res.status(400).json({ message: `${currentUser.name} is unavailable at this time.` });
+ }
+
let results = [];
let referencesToCreate = [];