Implemented rescheduling and concurrent usage of all integrations

This commit is contained in:
nicolas 2021-06-09 21:46:41 +02:00
parent 403823fc62
commit af08c74c8a
6 changed files with 4124 additions and 130 deletions

3908
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -14,6 +14,7 @@
"@jitsu/sdk-js": "^2.0.1",
"@prisma/client": "^2.23.0",
"@tailwindcss/forms": "^0.2.1",
"async": "^3.2.0",
"bcryptjs": "^2.4.3",
"dayjs": "^1.10.4",
"googleapis": "^67.1.1",
@ -26,7 +27,8 @@
"react-dom": "17.0.1",
"react-phone-number-input": "^3.1.21",
"react-select": "^4.3.0",
"react-timezone-select": "^1.0.2"
"react-timezone-select": "^1.0.2",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/node": "^14.14.33",

View file

@ -1,7 +1,7 @@
import Head from 'next/head';
import Link from 'next/link';
import {useRouter} from 'next/router';
import { ClockIcon, CalendarIcon, LocationMarkerIcon } from '@heroicons/react/solid';
import {CalendarIcon, ClockIcon, LocationMarkerIcon} from '@heroicons/react/solid';
import prisma from '../../lib/prisma';
import {collectPageParameters, telemetryEventTypes, useTelemetry} from "../../lib/telemetry";
import {useEffect, useState} from "react";
@ -117,13 +117,13 @@ export default function Book(props) {
<div className="mb-4">
<label htmlFor="name" className="block text-sm font-medium text-gray-700">Your name</label>
<div className="mt-1">
<input type="text" name="name" id="name" required className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="John Doe" defaultValue={props.booking.attendees[0].name} />
<input type="text" name="name" id="name" required className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="John Doe" defaultValue={props.booking && props.booking.attendees[0].name} />
</div>
</div>
<div className="mb-4">
<label htmlFor="email" className="block text-sm font-medium text-gray-700">Email address</label>
<div className="mt-1">
<input type="email" name="email" id="email" required className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="you@example.com" defaultValue={props.booking.attendees[0].email} />
<input type="email" name="email" id="email" required className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="you@example.com" defaultValue={props.booking && props.booking.attendees[0].email} />
</div>
</div>
{locations.length > 1 && (
@ -145,7 +145,7 @@ export default function Book(props) {
</div>)}
<div className="mb-4">
<label htmlFor="notes" className="block text-sm font-medium text-gray-700 mb-1">Additional notes</label>
<textarea name="notes" id="notes" rows={3} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="Please share anything that will help prepare for our meeting." defaultValue={props.booking.description}></textarea>
<textarea name="notes" id="notes" rows={3} className="shadow-sm focus:ring-blue-500 focus:border-blue-500 block w-full sm:text-sm border-gray-300 rounded-md" placeholder="Please share anything that will help prepare for our meeting." defaultValue={props.booking && props.booking.description}></textarea>
</div>
<div className="flex items-start">
<Button type="submit" className="btn btn-primary">{rescheduleUid ? 'Reschedule' : 'Confirm'}</Button>
@ -191,7 +191,7 @@ export async function getServerSideProps(context) {
}
});
let booking = undefined;
let booking = null;
if(context.query.rescheduleUid) {
booking = await prisma.booking.findFirst({

View file

@ -1,8 +1,9 @@
import type {NextApiRequest, NextApiResponse} from 'next';
import prisma from '../../../lib/prisma';
import {createEvent, CalendarEvent} from '../../../lib/calendarClient';
import {CalendarEvent, createEvent, updateEvent} from '../../../lib/calendarClient';
import createConfirmBookedEmail from "../../../lib/emails/confirm-booked";
import sha256 from "../../../lib/sha256";
import async from 'async';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const {user} = req.query;
@ -20,6 +21,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
});
const rescheduleUid = req.body.rescheduleUid;
const evt: CalendarEvent = {
type: req.body.eventName,
title: req.body.eventName + ' with ' + req.body.name,
@ -33,6 +36,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
]
};
// TODO: Use UUID algorithm to shorten this
const hashUID = sha256(JSON.stringify(evt));
const eventType = await prisma.eventType.findFirst({
where: {
userId: currentUser.id,
@ -43,15 +49,80 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
});
const result = await createEvent(currentUser.credentials[0], evt);
let results = undefined;
let referencesToCreate = undefined;
const hashUID = sha256(JSON.stringify(evt));
const referencesToCreate = currentUser.credentials.length == 0 ? [] : [
{
type: currentUser.credentials[0].type,
uid: result.id
if (rescheduleUid) {
// Reschedule event
const booking = await prisma.booking.findFirst({
where: {
uid: rescheduleUid
},
select: {
id: true,
references: {
select: {
id: true,
type: true,
uid: true
}
}
}
});
// Use all integrations
results = await async.mapLimit(currentUser.credentials, 5, async (credential) => {
const bookingRefUid = booking.references.filter((ref) => ref.type === credential.type)[0].uid;
return await updateEvent(credential, bookingRefUid, evt)
});
// Clone elements
referencesToCreate = [...booking.references];
// Now we can delete the old booking and its references.
let bookingReferenceDeletes = prisma.bookingReference.deleteMany({
where: {
bookingId: booking.id
}
});
let attendeeDeletes = prisma.attendee.deleteMany({
where: {
bookingId: booking.id
}
});
let bookingDeletes = prisma.booking.delete({
where: {
uid: rescheduleUid
}
});
await Promise.all([
bookingReferenceDeletes,
attendeeDeletes,
bookingDeletes
]);
} else {
// Schedule event
results = await async.mapLimit(currentUser.credentials, 5, async (credential) => {
const response = await createEvent(credential, evt);
return {
type: credential.type,
response
};
});
referencesToCreate = results.map((result => {
return {
type: result.type,
uid: result.response.id
};
}));
}
];
await prisma.booking.create({
data: {
@ -73,11 +144,12 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
});
if (!result.disableConfirmationEmail) {
// If one of the integrations allows email confirmations, send it.
if (!results.every((result) => result.disableConfirmationEmail)) {
await createConfirmBookedEmail(
evt, hashUID
);
}
res.status(200).json(result);
res.status(200).json(results);
}

View file

@ -1,5 +1,6 @@
import prisma from '../../lib/prisma';
import {createEvent, deleteEvent} from "../../lib/calendarClient";
import {deleteEvent} from "../../lib/calendarClient";
import async from 'async';
export default async function handler(req, res) {
if (req.method == "POST") {
@ -19,38 +20,44 @@ export default async function handler(req, res) {
attendees: true,
references: {
select: {
uid: true
uid: true,
type: true
}
}
}
});
const credentials = bookingToDelete.user.credentials[0];
//TODO Delete from multiple references later
const refUid = bookingToDelete.references[0].uid;
await prisma.attendee.deleteMany({
const apiDeletes = async.mapLimit(bookingToDelete.user.credentials, 5, async (credential) => {
const bookingRefUid = bookingToDelete.references.filter((ref) => ref.type === credential.type)[0].uid;
return await deleteEvent(credential, bookingRefUid);
});
const attendeeDeletes = prisma.attendee.deleteMany({
where: {
bookingId: bookingToDelete.id
}
});
await prisma.bookingReference.deleteMany({
const bookingReferenceDeletes = prisma.bookingReference.deleteMany({
where: {
bookingId: bookingToDelete.id
}
});
//TODO Perhaps send emails to user and client to tell about the cancellation
const deleteBooking = await prisma.booking.delete({
const bookingDeletes = prisma.booking.delete({
where: {
id: bookingToDelete.id,
},
});
await deleteEvent(credentials, refUid);
await Promise.all([
apiDeletes,
attendeeDeletes,
bookingReferenceDeletes,
bookingDeletes
]);
res.status(200).json({message: 'Booking deleted successfully'});
//TODO Perhaps send emails to user and client to tell about the cancellation
res.status(200).json({message: 'Booking successfully deleted.'});
} else {
res.status(405).json({message: 'This endpoint only accepts POST requests.'});
}
}

View file

@ -457,6 +457,11 @@ ast-types@0.13.2:
resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.2.tgz#df39b677a911a83f3a049644fb74fdded23cea48"
integrity sha512-uWMHxJxtfj/1oZClOxDEV1sQ1HCDkA4MG8Gr69KKeBjEVH0R84WlejZ0y2DcwyBlpAEMltmVYkVgqfLFb2oyiA==
async@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/async/-/async-3.2.0.tgz#b3a2685c5ebb641d3de02d161002c60fc9f85720"
integrity sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw==
at-least-node@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/at-least-node/-/at-least-node-1.0.0.tgz#602cd4b46e844ad4effc92a8011a3c46e0238dc2"
@ -3275,7 +3280,7 @@ uuid@^3.3.3:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^8.0.0:
uuid@^8.0.0, uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==