Merge pull request #310 from femyeda/fix-244-can-book-when-not-available

Fix 244 can book when not available
pull/314/head
Bailey Pumfleet 2021-06-25 16:51:28 +01:00 committed by GitHub
commit cc02558ac6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 95 additions and 13 deletions

View File

@ -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) => {
</div>
{!error &&
loaded &&
times.length > 0 &&
times.map((time) => (
<div key={dayjs(time).utc().format()}>
<Link
@ -95,6 +100,11 @@ const AvailableTimes = (props) => {
</Link>
</div>
))}
{!error && loaded && times.length == 0 && (
<div className="w-full h-full flex flex-col justify-center content-center items-center -mt-4">
<h1 className="text-xl font">{props.user.name} is all booked today.</h1>
</div>
)}
{!error && !loaded && <div className="loader" />}
{error && (
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">

View File

@ -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 = [];