From cb4a1e031e02c2ef92ff766113c4396899831a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Omar=20L=C3=B3pez?= Date: Thu, 23 Sep 2021 08:08:44 -0600 Subject: [PATCH] Fixes user event availability page (#749) * Abstracts MinutesField * Adds missing Minimum booking notice * Refactoring * Fixes int field sent as string * Sorts slots by time * Fixes availability page * Fixes available days --- components/booking/DatePicker.tsx | 2 +- components/booking/pages/AvailabilityPage.tsx | 23 +----- components/ui/form/MinutesField.tsx | 36 +++++++++ lib/hooks/useSlots.ts | 2 + pages/[user]/[type].tsx | 78 +++++++++---------- pages/event-types/[type].tsx | 53 ++++++------- 6 files changed, 108 insertions(+), 86 deletions(-) create mode 100644 components/ui/form/MinutesField.tsx diff --git a/components/booking/DatePicker.tsx b/components/booking/DatePicker.tsx index 0938cb3a..79e9f58c 100644 --- a/components/booking/DatePicker.tsx +++ b/components/booking/DatePicker.tsx @@ -72,7 +72,7 @@ const DatePicker = ({ ? dayjs().tz(organizerTimeZone).add(periodDays, "days").endOf("day") : dayjs().tz(organizerTimeZone).businessDaysAdd(periodDays, "days").endOf("day"); return ( - date.endOf("day").isBefore(dayjs().utcOffsett(date.utcOffset())) || + date.endOf("day").isBefore(dayjs().utcOffset(date.utcOffset())) || date.endOf("day").isAfter(periodRollingEndDay) || !getSlots({ inviteeDate: date, diff --git a/components/booking/pages/AvailabilityPage.tsx b/components/booking/pages/AvailabilityPage.tsx index 075a930a..ba9621f9 100644 --- a/components/booking/pages/AvailabilityPage.tsx +++ b/components/booking/pages/AvailabilityPage.tsx @@ -1,12 +1,11 @@ // Get router variables import { ChevronDownIcon, ChevronUpIcon, ClockIcon, CreditCardIcon, GlobeIcon } from "@heroicons/react/solid"; -import { EventType } from "@prisma/client"; import * as Collapsible from "@radix-ui/react-collapsible"; import dayjs, { Dayjs } from "dayjs"; import customParseFormat from "dayjs/plugin/customParseFormat"; import utc from "dayjs/plugin/utc"; import { useRouter } from "next/router"; -import { useEffect, useState, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { FormattedNumber, IntlProvider } from "react-intl"; import { asStringOrNull } from "@lib/asStringOrNull"; @@ -22,19 +21,11 @@ import { HeadSeo } from "@components/seo/head-seo"; import AvatarGroup from "@components/ui/AvatarGroup"; import PoweredByCalendso from "@components/ui/PoweredByCalendso"; +import { AvailabilityPageProps } from "../../../pages/[user]/[type]"; + dayjs.extend(utc); dayjs.extend(customParseFormat); -type AvailabilityPageProps = { - eventType: EventType; - profile: { - name: string; - image: string; - theme?: string; - }; - workingHours: []; -}; - const AvailabilityPage = ({ profile, eventType, workingHours }: AvailabilityPageProps) => { const router = useRouter(); const { rescheduleUid } = router.query; @@ -201,13 +192,7 @@ const AvailabilityPage = ({ profile, eventType, workingHours }: AvailabilityPage periodDays={eventType?.periodDays} periodCountCalendarDays={eventType?.periodCountCalendarDays} onDatePicked={changeDate} - workingHours={[ - { - days: [0, 1, 2, 3, 4, 5, 6], - endTime: 1440, - startTime: 0, - }, - ]} + workingHours={workingHours} weekStart="Sunday" eventLength={eventType.length} minimumBookingNotice={eventType.minimumBookingNotice} diff --git a/components/ui/form/MinutesField.tsx b/components/ui/form/MinutesField.tsx new file mode 100644 index 00000000..146d7df4 --- /dev/null +++ b/components/ui/form/MinutesField.tsx @@ -0,0 +1,36 @@ +import React, { forwardRef, InputHTMLAttributes, ReactNode } from "react"; + +type Props = InputHTMLAttributes & { + label: ReactNode; +}; + +const MinutesField = forwardRef(({ label, ...rest }, ref) => { + return ( +
+
+ +
+
+
+ +
+ + mins + +
+
+
+
+ ); +}); + +MinutesField.displayName = "MinutesField"; + +export default MinutesField; diff --git a/lib/hooks/useSlots.ts b/lib/hooks/useSlots.ts index b19f10d3..cd8d4c29 100644 --- a/lib/hooks/useSlots.ts +++ b/lib/hooks/useSlots.ts @@ -61,6 +61,7 @@ export const useSlots = (props: UseSlotsProps) => { ).then((results) => { let loadedSlots: Slot[] = results[0]; if (results.length === 1) { + loadedSlots = loadedSlots.sort((a, b) => (a.time.isAfter(b.time) ? 1 : -1)); setSlots(loadedSlots); setLoading(false); return; @@ -93,6 +94,7 @@ export const useSlots = (props: UseSlotsProps) => { for (let i = 1; i < results.length; i++) { loadedSlots = poolingMethod(loadedSlots, results[i]); } + loadedSlots = loadedSlots.sort((a, b) => (a.time.isAfter(b.time) ? 1 : -1)); setSlots(loadedSlots); setLoading(false); }); diff --git a/pages/[user]/[type].tsx b/pages/[user]/[type].tsx index 2827bc31..687788e4 100644 --- a/pages/[user]/[type].tsx +++ b/pages/[user]/[type].tsx @@ -1,4 +1,4 @@ -import { User } from "@prisma/client"; +import { Prisma } from "@prisma/client"; import { GetServerSidePropsContext } from "next"; import { serverSideTranslations } from "next-i18next/serverSideTranslations"; @@ -9,7 +9,9 @@ import { inferSSRProps } from "@lib/types/inferSSRProps"; import AvailabilityPage from "@components/booking/pages/AvailabilityPage"; -export default function Type(props: inferSSRProps) { +export type AvailabilityPageProps = inferSSRProps; + +export default function Type(props: AvailabilityPageProps) { return ; } @@ -25,7 +27,33 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => throw new Error(`File is not named [type]/[user]`); } - const user: User = await prisma.user.findUnique({ + const eventTypeSelect = Prisma.validator()({ + id: true, + title: true, + availability: true, + description: true, + length: true, + price: true, + currency: true, + periodType: true, + periodStartDate: true, + periodEndDate: true, + periodDays: true, + periodCountCalendarDays: true, + schedulingType: true, + minimumBookingNotice: true, + users: { + select: { + avatar: true, + name: true, + username: true, + hideBranding: true, + plan: true, + }, + }, + }); + + const user = await prisma.user.findUnique({ where: { username: userParam.toLowerCase(), }, @@ -55,22 +83,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => }, ], }, - select: { - id: true, - title: true, - availability: true, - description: true, - length: true, - price: true, - currency: true, - users: { - select: { - avatar: true, - name: true, - username: true, - }, - }, - }, + select: eventTypeSelect, }, }, }); @@ -93,22 +106,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => }, ], }, - select: { - id: true, - title: true, - availability: true, - description: true, - length: true, - price: true, - currency: true, - users: { - select: { - avatar: true, - name: true, - username: true, - }, - }, - }, + select: eventTypeSelect, }); if (!eventTypeBackwardsCompat) { return { @@ -119,11 +117,13 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => avatar: user.avatar, name: user.name, username: user.username, + hideBranding: user.hideBranding, + plan: user.plan, }); user.eventTypes.push(eventTypeBackwardsCompat); } - const eventType = user.eventTypes[0]; + const [eventType] = user.eventTypes; // check this is the first event @@ -155,10 +155,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => } as const; } }*/ - const getWorkingHours = (providesAvailability: { availability: Availability[] }) => - providesAvailability.availability && providesAvailability.availability.length - ? providesAvailability.availability - : null; + const getWorkingHours = (availability: typeof user.availability | typeof eventType.availability) => + availability && availability.length ? availability : null; const workingHours = getWorkingHours(eventType.availability) || diff --git a/pages/event-types/[type].tsx b/pages/event-types/[type].tsx index 5dc6c66f..ba3ee40d 100644 --- a/pages/event-types/[type].tsx +++ b/pages/event-types/[type].tsx @@ -54,6 +54,7 @@ import { Scheduler } from "@components/ui/Scheduler"; import Switch from "@components/ui/Switch"; import CheckboxField from "@components/ui/form/CheckboxField"; import CheckedSelect from "@components/ui/form/CheckedSelect"; +import MinutesField from "@components/ui/form/MinutesField"; import * as RadioArea from "@components/ui/form/radio-area"; dayjs.extend(utc); @@ -218,7 +219,10 @@ const EventTypePage = (props: inferSSRProps) => { title: enteredTitle, slug: enteredSlug, description: formData.description as string, + // note(zomars) Why does this field doesnt need to be parsed... length: formData.length as unknown as number, + // note(zomars) ...But this does? (Is being sent as string, despite it's a number field) + minimumBookingNotice: parseInt(formData.minimumBookingNotice as unknown as string), requiresConfirmation: formData.requiresConfirmation === "on", disableGuests: formData.disableGuests === "on", hidden, @@ -418,32 +422,19 @@ const EventTypePage = (props: inferSSRProps) => { -
-
- -
-
-
- -
- - mins - -
-
-
-
+ + + Duration + + } + name="length" + id="length" + required + placeholder="15" + defaultValue={eventType.length} + />
@@ -754,6 +745,15 @@ const EventTypePage = (props: inferSSRProps) => {
+ +