diff --git a/pages/bookings/index.tsx b/pages/bookings/index.tsx index 64d0bbdc66..19258f00e6 100644 --- a/pages/bookings/index.tsx +++ b/pages/bookings/index.tsx @@ -5,9 +5,11 @@ import { DotsHorizontalIcon } from "@heroicons/react/solid"; import { BookingStatus } from "@prisma/client"; import dayjs from "dayjs"; import { Fragment } from "react"; +import { useMutation } from "react-query"; import classNames from "@lib/classNames"; -import { trpc } from "@lib/trpc"; +import { HttpError } from "@lib/core/http/error"; +import { inferQueryOutput, trpc } from "@lib/trpc"; import EmptyScreen from "@components/EmptyScreen"; import Loader from "@components/Loader"; @@ -15,23 +17,229 @@ import Shell from "@components/Shell"; import { Alert } from "@components/ui/Alert"; import { Button } from "@components/ui/Button"; +type BookingItem = inferQueryOutput<"viewer.bookings">[number]; + +function BookingListItem(booking: BookingItem) { + const utils = trpc.useContext(); + const mutation = useMutation( + async (confirm: boolean) => { + const res = await fetch("/api/book/confirm", { + method: "PATCH", + body: JSON.stringify({ id: booking.id, confirmed: confirm }), + headers: { + "Content-Type": "application/json", + }, + }); + if (!res.ok) { + throw new HttpError({ statusCode: res.status }); + } + }, + { + async onSettled() { + await utils.invalidateQuery(["viewer.bookings"]); + }, + } + ); + return ( + + + {!booking.confirmed && !booking.rejected && ( + + Unconfirmed + + )} +
+ {booking.eventType?.team && {booking.eventType.team.name}: } + {booking.title} +
+
+
+ {dayjs(booking.startTime).format("D MMMM YYYY")}:{" "} + + {dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")} + +
+
+ {booking.attendees.length !== 0 && ( +
+ {booking.attendees[0].email} +
+ )} + + +
{dayjs(booking.startTime).format("D MMMM YYYY")}
+
+ {dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")} +
+ + + {!booking.confirmed && !booking.rejected && ( + <> +
+ + +
+ + {({ open }) => ( + <> +
+ + Open options + +
+ + +
+ + {({ active }) => ( + mutation.mutate(true)} + className={classNames( + active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700", + "group flex items-center px-4 py-2 text-sm font-medium" + )}> + + )} + + + {({ active }) => ( + mutation.mutate(false)} + className={classNames( + active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700", + "group flex items-center px-4 py-2 text-sm w-full font-medium" + )}> + + )} + +
+
+
+ + )} +
+ + )} + {booking.confirmed && !booking.rejected && ( + <> +
+ + +
+ + {({ open }) => ( + <> +
+ + Open options + +
+ + + +
+ + {({ active }) => ( + + + )} + + + {({ active }) => ( + + + )} + +
+
+
+ + )} +
+ + )} + {!booking.confirmed && booking.rejected &&
Rejected
} + + + ); +} + export default function Bookings() { const query = trpc.useQuery(["viewer.bookings"]); const bookings = query.data; - async function confirmBookingHandler(booking: { id: number }, confirm: boolean) { - const res = await fetch("/api/book/confirm", { - method: "PATCH", - body: JSON.stringify({ id: booking.id, confirmed: confirm }), - headers: { - "Content-Type": "application/json", - }, - }); - if (res.ok) { - await query.refetch(); - } - } - return (
@@ -55,223 +263,7 @@ export default function Bookings() { {bookings .filter((booking) => booking.status !== BookingStatus.CANCELLED) .map((booking) => ( - - - {!booking.confirmed && !booking.rejected && ( - - Unconfirmed - - )} -
- {booking.eventType?.team && {booking.eventType.team.name}: } - {booking.title} -
-
-
- {dayjs(booking.startTime).format("D MMMM YYYY")}:{" "} - - {dayjs(booking.startTime).format("HH:mm")} -{" "} - {dayjs(booking.endTime).format("HH:mm")} - -
-
- {booking.attendees.length !== 0 && ( -
- - {booking.attendees[0].email} - -
- )} - - -
- {dayjs(booking.startTime).format("D MMMM YYYY")} -
-
- {dayjs(booking.startTime).format("HH:mm")} -{" "} - {dayjs(booking.endTime).format("HH:mm")} -
- - - {!booking.confirmed && !booking.rejected && ( - <> -
- - -
- - {({ open }) => ( - <> -
- - Open options - -
- - -
- - {({ active }) => ( - confirmBookingHandler(booking, true)} - className={classNames( - active - ? "bg-neutral-100 text-neutral-900" - : "text-neutral-700", - "group flex items-center px-4 py-2 text-sm font-medium" - )}> - - )} - - - {({ active }) => ( - confirmBookingHandler(booking, false)} - className={classNames( - active - ? "bg-neutral-100 text-neutral-900" - : "text-neutral-700", - "group flex items-center px-4 py-2 text-sm w-full font-medium" - )}> - - )} - -
-
-
- - )} -
- - )} - {booking.confirmed && !booking.rejected && ( - <> -
- - -
- - {({ open }) => ( - <> -
- - Open options - -
- - - -
- - {({ active }) => ( - - - )} - - - {({ active }) => ( - - - )} - -
-
-
- - )} -
- - )} - {!booking.confirmed && booking.rejected && ( -
Rejected
- )} - - + ))} diff --git a/scripts/seed.ts b/scripts/seed.ts index 80f3afce0a..58d6ffe3db 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -6,57 +6,13 @@ import { hashPassword } from "../lib/auth"; const prisma = new PrismaClient(); -async function createBookingForEventType(opts: { - uid: string; - title: string; - slug: string; - startTime: Date | string; - endTime: Date | string; - userEmail: string; -}) { - const eventType = await prisma.eventType.findFirst({ - where: { - slug: opts.slug, - }, - }); - - if (!eventType) { - // should not happen - throw new Error("Eventtype missing"); - } - - const bookingData: Prisma.BookingCreateArgs["data"] = { - uid: opts.uid, - title: opts.title, - startTime: opts.startTime, - endTime: opts.endTime, - user: { - connect: { - email: opts.userEmail, - }, - }, - attendees: { - create: { - email: opts.userEmail, - name: "Some name", - timeZone: "Europe/London", - }, - }, - eventType: { - connect: { - id: eventType.id, - }, - }, - }; - - await prisma.booking.create({ - data: bookingData, - }); -} - async function createUserAndEventType(opts: { user: { email: string; password: string; username: string; plan: UserPlan; name: string }; - eventTypes: Array; + eventTypes: Array< + Prisma.EventTypeCreateInput & { + _bookings?: Prisma.BookingCreateInput[]; + } + >; }) { const userData: Prisma.UserCreateArgs["data"] = { ...opts.user, @@ -73,8 +29,8 @@ async function createUserAndEventType(opts: { console.log( `👤 Upserted '${opts.user.username}' with email "${opts.user.email}" & password "${opts.user.password}". Booking page 👉 http://localhost:3000/${opts.user.username}` ); - for (const rawData of opts.eventTypes) { - const eventTypeData: Prisma.EventTypeCreateArgs["data"] = { ...rawData }; + for (const eventTypeInput of opts.eventTypes) { + const { _bookings: bookingInputs = [], ...eventTypeData } = eventTypeInput; eventTypeData.userId = user.id; eventTypeData.users = { connect: { id: user.id } }; @@ -93,21 +49,48 @@ async function createUserAndEventType(opts: { }); if (eventType) { - await prisma.eventType.update({ - where: { - id: eventType.id, - }, - data: eventTypeData, - }); - } else { - await prisma.eventType.create({ - data: eventTypeData, - }); + console.log( + `\t📆 Event type ${eventTypeData.slug} already seems seeded - http://localhost:3000/${user.username}/${eventTypeData.slug}` + ); + continue; } + const { id } = await prisma.eventType.create({ + data: eventTypeData, + }); console.log( - `\t📆 Event type ${eventTypeData.slug}, length ${eventTypeData.length}: http://localhost:3000/${user.username}/${eventTypeData.slug}` + `\t📆 Event type ${eventTypeData.slug}, length ${eventTypeData.length}min - http://localhost:3000/${user.username}/${eventTypeData.slug}` ); + for (const bookingInput of bookingInputs) { + await prisma.booking.create({ + data: { + ...bookingInput, + user: { + connect: { + email: opts.user.email, + }, + }, + attendees: { + create: { + email: opts.user.email, + name: opts.user.name, + timeZone: "Europe/London", + }, + }, + eventType: { + connect: { + id, + }, + }, + confirmed: bookingInput.confirmed, + }, + }); + console.log( + `\t\t☎️ Created booking ${bookingInput.title} at ${new Date( + bookingInput.startTime + ).toLocaleDateString()}` + ); + } } } @@ -170,6 +153,21 @@ async function main() { title: "30min", slug: "30min", length: 30, + _bookings: [ + { + uid: uuid(), + title: "30min", + startTime: dayjs().add(1, "day").toDate(), + endTime: dayjs().add(1, "day").add(30, "minutes").toDate(), + }, + { + uid: uuid(), + title: "30min", + startTime: dayjs().add(2, "day").toDate(), + endTime: dayjs().add(2, "day").add(30, "minutes").toDate(), + confirmed: false, + }, + ], }, { title: "60min", @@ -179,15 +177,6 @@ async function main() { ], }); - await createBookingForEventType({ - title: "30min", - slug: "30min", - startTime: dayjs().add(1, "day").toDate(), - endTime: dayjs().add(1, "day").add(60, "minutes").toDate(), - uid: uuid(), - userEmail: "pro@example.com", - }); - await createUserAndEventType({ user: { email: "trial@example.com",