From 2af160a13e2141da856ce813897feda7c136ef72 Mon Sep 17 00:00:00 2001 From: Alex Johansson Date: Wed, 18 Aug 2021 14:21:52 +0200 Subject: [PATCH] make `pages/[user]/[type].tsx` type-safe (#484) * make `pages/[user]/[type].tsx` type-safe * deprecate `whereAndSelect` --- lib/prisma.ts | 5 ++ pages/[user]/[type].tsx | 158 ++++++++++++++++++++-------------------- 2 files changed, 85 insertions(+), 78 deletions(-) diff --git a/lib/prisma.ts b/lib/prisma.ts index 5d75ada2..2c556fbe 100644 --- a/lib/prisma.ts +++ b/lib/prisma.ts @@ -27,6 +27,11 @@ const pluck = (select: Record, attr: string) => { }; }; +/** + * @deprecated + * This function will always return `any` type, + * See https://github.com/calendso/calendso/pull/460 for alternative approach + */ const whereAndSelect = (modelQuery, criteria: Record, pluckedAttributes: string[]) => modelQuery({ where: criteria, diff --git a/pages/[user]/[type].tsx b/pages/[user]/[type].tsx index 677cabfb..d1b68fe5 100644 --- a/pages/[user]/[type].tsx +++ b/pages/[user]/[type].tsx @@ -1,22 +1,23 @@ -import { useEffect, useState } from "react"; -import { GetServerSideProps, GetServerSidePropsContext } from "next"; -import Head from "next/head"; -import { ChevronDownIcon, ChevronUpIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid"; -import { useRouter } from "next/router"; -import dayjs, { Dayjs } from "dayjs"; -import * as Collapsible from "@radix-ui/react-collapsible"; - -import prisma, { whereAndSelect } from "@lib/prisma"; -import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry"; -import AvailableTimes from "../../components/booking/AvailableTimes"; -import TimeOptions from "../../components/booking/TimeOptions"; -import Avatar from "../../components/Avatar"; -import { timeZone } from "../../lib/clock"; -import DatePicker from "../../components/booking/DatePicker"; -import PoweredByCalendso from "../../components/ui/PoweredByCalendso"; +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { Availability } from "@prisma/client"; import Theme from "@components/Theme"; +import { ChevronDownIcon, ChevronUpIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid"; +import prisma from "@lib/prisma"; +import * as Collapsible from "@radix-ui/react-collapsible"; +import dayjs, { Dayjs } from "dayjs"; +import { GetServerSidePropsContext, InferGetServerSidePropsType } from "next"; +import Head from "next/head"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +import Avatar from "../../components/Avatar"; +import AvailableTimes from "../../components/booking/AvailableTimes"; +import DatePicker from "../../components/booking/DatePicker"; +import TimeOptions from "../../components/booking/TimeOptions"; +import PoweredByCalendso from "../../components/ui/PoweredByCalendso"; +import { timeZone } from "../../lib/clock"; +import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry"; -export default function Type(props): Type { +export default function Type(props: InferGetServerSidePropsType) { // Get router variables const router = useRouter(); const { rescheduleUid } = router.query; @@ -64,7 +65,7 @@ export default function Type(props): Type { shallow: true, } ); - }, [selectedDate]); + }, [router, selectedDate]); const handleSelectTimeZone = (selectedTimeZone: string): void => { if (selectedDate) { @@ -133,13 +134,13 @@ export default function Type(props): Type { "mx-auto my-0 md:my-24 transition-max-width ease-in-out duration-500 " + (selectedDate ? "max-w-5xl" : "max-w-3xl") }> -
+
{/* mobile: details */} -
+
- +
-

{props.user.name}

+

{props.user.name}

{props.eventType.title}
@@ -149,28 +150,28 @@ export default function Type(props): Type {
-

{props.eventType.description}

+

{props.eventType.description}

-
+
- -

{props.user.name}

-

+ +

{props.user.name}

+

{props.eventType.title}

-

+

{props.eventType.length} minutes

-

{props.eventType.description}

+

{props.eventType.description}

-
+
@@ -216,7 +217,7 @@ export default function Type(props): Type { function TimezoneDropdown() { return ( - + {timeZone()} {isTimeOptionsOpen ? ( @@ -233,72 +234,73 @@ export default function Type(props): Type { } } -export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => { - const dateQuery = context.query?.date ?? null; - const date = Array.isArray(dateQuery) && dateQuery.length > 0 ? dateQuery.pop() : dateQuery; +export const getServerSideProps = async (context: GetServerSidePropsContext) => { + // get query params and typecast them to string + // (would be even better to assert them instead of typecasting) + const userParam = context.query.user as string; + const typeParam = context.query.type as string; + const dateParam = context.query.date as string | undefined; - const user = await whereAndSelect( - prisma.user.findFirst, - { - username: context.query.user.toLowerCase(), + const user = await prisma.user.findFirst({ + where: { + username: userParam.toLowerCase(), }, - [ - "id", - "username", - "name", - "email", - "bio", - "avatar", - "startTime", - "endTime", - "timeZone", - "weekStart", - "availability", - "hideBranding", - "theme", - ] - ); + select: { + id: true, + username: true, + name: true, + email: true, + bio: true, + avatar: true, + startTime: true, + endTime: true, + timeZone: true, + weekStart: true, + availability: true, + hideBranding: true, + theme: true, + }, + }); if (!user) { return { notFound: true, - }; + } as const; } - const eventType = await whereAndSelect( - prisma.eventType.findFirst, - { + const eventType = await prisma.eventType.findFirst({ + where: { userId: user.id, - slug: context.query.type, + slug: typeParam, }, - [ - "id", - "title", - "description", - "length", - "availability", - "timeZone", - "periodType", - "periodDays", - "periodStartDate", - "periodEndDate", - "periodCountCalendarDays", - "minimumBookingNotice", - ] - ); + select: { + id: true, + title: true, + description: true, + length: true, + availability: true, + timeZone: true, + periodType: true, + periodDays: true, + periodStartDate: true, + periodEndDate: true, + periodCountCalendarDays: true, + minimumBookingNotice: true, + }, + }); if (!eventType) { return { notFound: true, - }; + } as const; } - const getWorkingHours = (providesAvailability) => + const getWorkingHours = (providesAvailability: { availability: Availability[] }) => providesAvailability.availability && providesAvailability.availability.length ? providesAvailability.availability : null; - const workingHours: [] = + const workingHours = getWorkingHours(eventType) || getWorkingHours(user) || [ @@ -319,7 +321,7 @@ export const getServerSideProps: GetServerSideProps = async (context: GetServerS return { props: { user, - date, + date: dateParam, eventType: eventTypeObject, workingHours, },