From 3c8b9da54d5bb1ba2e7941232d423656a6977fdb Mon Sep 17 00:00:00 2001 From: femyeda Date: Thu, 24 Jun 2021 15:00:09 -0500 Subject: [PATCH 1/2] fix: busy times are shown on booking --- components/booking/AvailableTimes.tsx | 28 ++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) 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 && (
From a53cdf266049f649f9e37f7556a082bd75d6986f Mon Sep 17 00:00:00 2001 From: femyeda Date: Thu, 24 Jun 2021 16:20:16 -0500 Subject: [PATCH 2/2] fix: issue where user could book a meeting when unavailable --- pages/api/book/[user].ts | 80 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 4 deletions(-) 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 = [];