make pages/[user]/[type].tsx type-safe (#484)

* make `pages/[user]/[type].tsx` type-safe
* deprecate `whereAndSelect`
This commit is contained in:
Alex Johansson 2021-08-18 14:21:52 +02:00 committed by GitHub
parent aed9757409
commit 2af160a13e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 78 deletions

View file

@ -27,6 +27,11 @@ const pluck = (select: Record<string, boolean>, 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<string, unknown>, pluckedAttributes: string[]) => const whereAndSelect = (modelQuery, criteria: Record<string, unknown>, pluckedAttributes: string[]) =>
modelQuery({ modelQuery({
where: criteria, where: criteria,

View file

@ -1,22 +1,23 @@
import { useEffect, useState } from "react"; /* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { GetServerSideProps, GetServerSidePropsContext } from "next"; import { Availability } from "@prisma/client";
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";
import Theme from "@components/Theme"; 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<typeof getServerSideProps>) {
// Get router variables // Get router variables
const router = useRouter(); const router = useRouter();
const { rescheduleUid } = router.query; const { rescheduleUid } = router.query;
@ -64,7 +65,7 @@ export default function Type(props): Type {
shallow: true, shallow: true,
} }
); );
}, [selectedDate]); }, [router, selectedDate]);
const handleSelectTimeZone = (selectedTimeZone: string): void => { const handleSelectTimeZone = (selectedTimeZone: string): void => {
if (selectedDate) { 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 " + "mx-auto my-0 md:my-24 transition-max-width ease-in-out duration-500 " +
(selectedDate ? "max-w-5xl" : "max-w-3xl") (selectedDate ? "max-w-5xl" : "max-w-3xl")
}> }>
<div className="sm:dark:border-gray-600 dark:bg-gray-900 bg-white md:border border-gray-200 rounded-sm"> <div className="bg-white border-gray-200 rounded-sm sm:dark:border-gray-600 dark:bg-gray-900 md:border">
{/* mobile: details */} {/* mobile: details */}
<div className="p-4 sm:p-8 block md:hidden"> <div className="block p-4 sm:p-8 md:hidden">
<div className="flex items-center"> <div className="flex items-center">
<Avatar user={props.user} className="inline-block h-9 w-9 rounded-full" /> <Avatar user={props.user} className="inline-block rounded-full h-9 w-9" />
<div className="ml-3"> <div className="ml-3">
<p className="text-sm font-medium dark:text-gray-300 text-black">{props.user.name}</p> <p className="text-sm font-medium text-black dark:text-gray-300">{props.user.name}</p>
<div className="flex gap-2 text-xs font-medium text-gray-600"> <div className="flex gap-2 text-xs font-medium text-gray-600">
{props.eventType.title} {props.eventType.title}
<div> <div>
@ -149,28 +150,28 @@ export default function Type(props): Type {
</div> </div>
</div> </div>
</div> </div>
<p className="dark:text-gray-200 text-gray-600 mt-3">{props.eventType.description}</p> <p className="mt-3 text-gray-600 dark:text-gray-200">{props.eventType.description}</p>
</div> </div>
<div className="sm:flex px-4 sm:py-5 sm:p-4"> <div className="px-4 sm:flex sm:py-5 sm:p-4">
<div <div
className={ className={
"hidden md:block pr-8 sm:border-r sm:dark:border-gray-800 " + "hidden md:block pr-8 sm:border-r sm:dark:border-gray-800 " +
(selectedDate ? "sm:w-1/3" : "sm:w-1/2") (selectedDate ? "sm:w-1/3" : "sm:w-1/2")
}> }>
<Avatar user={props.user} className="w-16 h-16 rounded-full mb-4" /> <Avatar user={props.user} className="w-16 h-16 mb-4 rounded-full" />
<h2 className="font-medium dark:text-gray-300 text-gray-500">{props.user.name}</h2> <h2 className="font-medium text-gray-500 dark:text-gray-300">{props.user.name}</h2>
<h1 className="text-3xl font-semibold dark:text-white text-gray-800 mb-4"> <h1 className="mb-4 text-3xl font-semibold text-gray-800 dark:text-white">
{props.eventType.title} {props.eventType.title}
</h1> </h1>
<p className="text-gray-500 mb-1 px-2 py-1 -ml-2"> <p className="px-2 py-1 mb-1 -ml-2 text-gray-500">
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> <ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{props.eventType.length} minutes {props.eventType.length} minutes
</p> </p>
<TimezoneDropdown /> <TimezoneDropdown />
<p className="dark:text-gray-200 text-gray-600 mt-3 mb-8">{props.eventType.description}</p> <p className="mt-3 mb-8 text-gray-600 dark:text-gray-200">{props.eventType.description}</p>
</div> </div>
<DatePicker <DatePicker
date={selectedDate} date={selectedDate}
@ -188,7 +189,7 @@ export default function Type(props): Type {
minimumBookingNotice={props.eventType.minimumBookingNotice} minimumBookingNotice={props.eventType.minimumBookingNotice}
/> />
<div className="ml-1 mt-4 block sm:hidden"> <div className="block mt-4 ml-1 sm:hidden">
<TimezoneDropdown /> <TimezoneDropdown />
</div> </div>
@ -216,7 +217,7 @@ export default function Type(props): Type {
function TimezoneDropdown() { function TimezoneDropdown() {
return ( return (
<Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}> <Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
<Collapsible.Trigger className="text-gray-500 mb-1 px-2 py-1 -ml-2 text-left min-w-32"> <Collapsible.Trigger className="px-2 py-1 mb-1 -ml-2 text-left text-gray-500 min-w-32">
<GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> <GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{timeZone()} {timeZone()}
{isTimeOptionsOpen ? ( {isTimeOptionsOpen ? (
@ -233,72 +234,73 @@ export default function Type(props): Type {
} }
} }
export const getServerSideProps: GetServerSideProps = async (context: GetServerSidePropsContext) => { export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const dateQuery = context.query?.date ?? null; // get query params and typecast them to string
const date = Array.isArray(dateQuery) && dateQuery.length > 0 ? dateQuery.pop() : dateQuery; // (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( const user = await prisma.user.findFirst({
prisma.user.findFirst, where: {
{ username: userParam.toLowerCase(),
username: context.query.user.toLowerCase(),
}, },
[ select: {
"id", id: true,
"username", username: true,
"name", name: true,
"email", email: true,
"bio", bio: true,
"avatar", avatar: true,
"startTime", startTime: true,
"endTime", endTime: true,
"timeZone", timeZone: true,
"weekStart", weekStart: true,
"availability", availability: true,
"hideBranding", hideBranding: true,
"theme", theme: true,
] },
); });
if (!user) { if (!user) {
return { return {
notFound: true, notFound: true,
}; } as const;
} }
const eventType = await whereAndSelect( const eventType = await prisma.eventType.findFirst({
prisma.eventType.findFirst, where: {
{
userId: user.id, userId: user.id,
slug: context.query.type, slug: typeParam,
}, },
[ select: {
"id", id: true,
"title", title: true,
"description", description: true,
"length", length: true,
"availability", availability: true,
"timeZone", timeZone: true,
"periodType", periodType: true,
"periodDays", periodDays: true,
"periodStartDate", periodStartDate: true,
"periodEndDate", periodEndDate: true,
"periodCountCalendarDays", periodCountCalendarDays: true,
"minimumBookingNotice", minimumBookingNotice: true,
] },
); });
if (!eventType) { if (!eventType) {
return { return {
notFound: true, notFound: true,
}; } as const;
} }
const getWorkingHours = (providesAvailability) => const getWorkingHours = (providesAvailability: { availability: Availability[] }) =>
providesAvailability.availability && providesAvailability.availability.length providesAvailability.availability && providesAvailability.availability.length
? providesAvailability.availability ? providesAvailability.availability
: null; : null;
const workingHours: [] = const workingHours =
getWorkingHours(eventType) || getWorkingHours(eventType) ||
getWorkingHours(user) || getWorkingHours(user) ||
[ [
@ -319,7 +321,7 @@ export const getServerSideProps: GetServerSideProps = async (context: GetServerS
return { return {
props: { props: {
user, user,
date, date: dateParam,
eventType: eventTypeObject, eventType: eventTypeObject,
workingHours, workingHours,
}, },