Lowercase the router.query.user & some typescript fixes

This commit is contained in:
Alex van Andel 2021-06-22 17:42:32 +00:00
parent 6c6d262184
commit 892ba8a335
3 changed files with 199 additions and 202 deletions

View file

@ -18,7 +18,8 @@
"rules": { "rules": {
"prettier/prettier": ["error"], "prettier/prettier": ["error"],
"@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-unused-vars": "error",
"react/react-in-jsx-scope": "off" "react/react-in-jsx-scope": "off",
"react/prop-types": "off"
}, },
"env": { "env": {
"browser": true, "browser": true,

View file

@ -1,92 +1,95 @@
import Head from 'next/head'; import { GetServerSideProps } from "next";
import Link from 'next/link'; import Head from "next/head";
import prisma from '../lib/prisma'; import Link from "next/link";
import Avatar from '../components/Avatar'; import prisma from "../lib/prisma";
import Avatar from "../components/Avatar";
export default function User(props) { export default function User(props): User {
const eventTypes = props.eventTypes.map(type => const eventTypes = props.eventTypes.map((type) => (
<li key={type.id}> <li key={type.id}>
<Link href={'/' + props.user.username + '/' + type.slug}> <Link href={`/${props.user.username}/${type.slug}`}>
<a className="block px-6 py-4"> <a className="block px-6 py-4">
<div className="inline-block w-3 h-3 rounded-full mr-2" style={{backgroundColor:getRandomColorCode()}}></div> <div
<h2 className="inline-block font-medium">{type.title}</h2> className="inline-block w-3 h-3 rounded-full mr-2"
<p className="inline-block text-gray-400 ml-2">{type.description}</p> style={{ backgroundColor: getRandomColorCode() }}></div>
</a> <h2 className="inline-block font-medium">{type.title}</h2>
</Link> <p className="inline-block text-gray-400 ml-2">{type.description}</p>
</li> </a>
); </Link>
return ( </li>
<div> ));
<Head> return (
<title>{props.user.name || props.user.username} | Calendso</title> <div>
<link rel="icon" href="/favicon.ico" /> <Head>
</Head> <title>{props.user.name || props.user.username} | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="max-w-2xl mx-auto my-24"> <main className="max-w-2xl mx-auto my-24">
<div className="mb-8 text-center"> <div className="mb-8 text-center">
<Avatar user={props.user} className="mx-auto w-24 h-24 rounded-full mb-4" /> <Avatar user={props.user} className="mx-auto w-24 h-24 rounded-full mb-4" />
<h1 className="text-3xl font-semibold text-gray-800 mb-1">{props.user.name || props.user.username}</h1> <h1 className="text-3xl font-semibold text-gray-800 mb-1">
<p className="text-gray-600">{props.user.bio}</p> {props.user.name || props.user.username}
</div> </h1>
<div className="bg-white shadow overflow-hidden rounded-md"> <p className="text-gray-600">{props.user.bio}</p>
<ul className="divide-y divide-gray-200">
{eventTypes}
</ul>
{eventTypes.length == 0 &&
<div className="p-8 text-center text-gray-400">
<h2 className="font-semibold text-3xl text-gray-600">Uh oh!</h2>
<p className="max-w-md mx-auto">This user hasn't set up any event types yet.</p>
</div>
}
</div>
</main>
</div> </div>
) <div className="bg-white shadow overflow-hidden rounded-md">
<ul className="divide-y divide-gray-200">{eventTypes}</ul>
{eventTypes.length == 0 && (
<div className="p-8 text-center text-gray-400">
<h2 className="font-semibold text-3xl text-gray-600">Uh oh!</h2>
<p className="max-w-md mx-auto">This user hasn&apos;t set up any event types yet.</p>
</div>
)}
</div>
</main>
</div>
);
} }
export async function getServerSideProps(context) { export const getServerSideProps: GetServerSideProps = async (context) => {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { where: {
username: context.query.user, username: context.query.user.toLowerCase(),
}, },
select: { select: {
id: true, id: true,
username: true, username: true,
email:true, email: true,
name: true, name: true,
bio: true, bio: true,
avatar: true, avatar: true,
eventTypes: true eventTypes: true,
} },
}); });
if (!user) {
return {
notFound: true,
}
}
const eventTypes = await prisma.eventType.findMany({
where: {
userId: user.id,
hidden: false
}
});
if (!user) {
return { return {
props: { notFound: true,
user, };
eventTypes }
},
} const eventTypes = await prisma.eventType.findMany({
} where: {
userId: user.id,
hidden: false,
},
});
return {
props: {
user,
eventTypes,
},
};
};
// Auxiliary methods // Auxiliary methods
export function getRandomColorCode() { export function getRandomColorCode(): string {
let color = '#'; let color = "#";
for (let idx = 0; idx < 6; idx++) { for (let idx = 0; idx < 6; idx++) {
color += Math.floor(Math.random() * 10); color += Math.floor(Math.random() * 10);
} }
return color; return color;
} }

View file

@ -1,25 +1,31 @@
import {useEffect, useMemo, useState} from 'react'; import { useEffect, useState } from "react";
import Head from 'next/head'; import { GetServerSideProps } from "next";
import Link from 'next/link'; import Head from "next/head";
import prisma from '../../lib/prisma'; import Link from "next/link";
import { useRouter } from 'next/router'; import prisma from "../../lib/prisma";
import dayjs, { Dayjs } from 'dayjs'; import { useRouter } from "next/router";
import { ClockIcon, GlobeIcon, ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'; import dayjs, { Dayjs } from "dayjs";
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore'; import {
import utc from 'dayjs/plugin/utc'; ClockIcon,
import timezone from 'dayjs/plugin/timezone'; GlobeIcon,
ChevronDownIcon,
ChevronLeftIcon,
ChevronRightIcon,
} from "@heroicons/react/solid";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(isSameOrBefore); dayjs.extend(isSameOrBefore);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
import {collectPageParameters, telemetryEventTypes, useTelemetry} from "../../lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
import AvailableTimes from "../../components/booking/AvailableTimes"; import AvailableTimes from "../../components/booking/AvailableTimes";
import TimeOptions from "../../components/booking/TimeOptions" import TimeOptions from "../../components/booking/TimeOptions";
import Avatar from '../../components/Avatar'; import Avatar from "../../components/Avatar";
import {timeZone} from "../../lib/clock"; import { timeZone } from "../../lib/clock";
export default function Type(props) {
export default function Type(props): Type {
// Get router variables // Get router variables
const router = useRouter(); const router = useRouter();
const { rescheduleUid } = router.query; const { rescheduleUid } = router.query;
@ -28,65 +34,83 @@ export default function Type(props) {
const [selectedDate, setSelectedDate] = useState<Dayjs>(); const [selectedDate, setSelectedDate] = useState<Dayjs>();
const [selectedMonth, setSelectedMonth] = useState(dayjs().month()); const [selectedMonth, setSelectedMonth] = useState(dayjs().month());
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false); const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
const [timeFormat, setTimeFormat] = useState('h:mma'); const [timeFormat, setTimeFormat] = useState("h:mma");
const telemetry = useTelemetry(); const telemetry = useTelemetry();
useEffect(() => { useEffect((): void => {
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters())) telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters()));
}, []); }, [telemetry]);
// Handle month changes // Handle month changes
const incrementMonth = () => { const incrementMonth = () => {
setSelectedMonth(selectedMonth + 1); setSelectedMonth(selectedMonth + 1);
} };
const decrementMonth = () => { const decrementMonth = () => {
setSelectedMonth(selectedMonth - 1); setSelectedMonth(selectedMonth - 1);
} };
// Set up calendar // Set up calendar
var daysInMonth = dayjs().month(selectedMonth).daysInMonth(); const daysInMonth = dayjs().month(selectedMonth).daysInMonth();
var days = []; const days = [];
for (let i = 1; i <= daysInMonth; i++) { for (let i = 1; i <= daysInMonth; i++) {
days.push(i); days.push(i);
} }
// Create placeholder elements for empty days in first week // Create placeholder elements for empty days in first week
let weekdayOfFirst = dayjs().month(selectedMonth).date(1).day(); let weekdayOfFirst = dayjs().month(selectedMonth).date(1).day();
if (props.user.weekStart === 'Monday') { if (props.user.weekStart === "Monday") {
weekdayOfFirst -= 1; weekdayOfFirst -= 1;
if (weekdayOfFirst < 0) if (weekdayOfFirst < 0) weekdayOfFirst = 6;
weekdayOfFirst = 6;
} }
const emptyDays = Array(weekdayOfFirst).fill(null).map((day, i) => const emptyDays = Array(weekdayOfFirst)
.fill(null)
.map((day, i) => (
<div key={`e-${i}`} className={"text-center w-10 h-10 rounded-full mx-auto"}> <div key={`e-${i}`} className={"text-center w-10 h-10 rounded-full mx-auto"}>
{null} {null}
</div> </div>
); ));
const changeDate = (day) => { const changeDate = (day): void => {
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.dateSelected, collectPageParameters())) telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.dateSelected, collectPageParameters()));
setSelectedDate(dayjs().month(selectedMonth).date(day)) setSelectedDate(dayjs().month(selectedMonth).date(day));
}; };
// Combine placeholder days with actual days // Combine placeholder days with actual days
const calendar = [...emptyDays, ...days.map((day) => const calendar = [
<button key={day} onClick={() => changeDate(day)} disabled={selectedMonth < parseInt(dayjs().format('MM')) && dayjs().month(selectedMonth).format("D") > day} className={"text-center w-10 h-10 rounded-full mx-auto " + (dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth)) ? 'bg-blue-50 text-blue-600 font-medium' : 'text-gray-400 font-light') + (dayjs(selectedDate).month(selectedMonth).format("D") == day ? ' bg-blue-600 text-white-important' : '')}> ...emptyDays,
{day} ...days.map((day) => (
<button
key={day}
onClick={() => changeDate(day)}
disabled={
selectedMonth < parseInt(dayjs().format("MM")) && dayjs().month(selectedMonth).format("D") > day
}
className={
"text-center w-10 h-10 rounded-full mx-auto " +
(dayjs().isSameOrBefore(dayjs().date(day).month(selectedMonth))
? "bg-blue-50 text-blue-600 font-medium"
: "text-gray-400 font-light") +
(dayjs(selectedDate).month(selectedMonth).format("D") == day
? " bg-blue-600 text-white-important"
: "")
}>
{day}
</button> </button>
)]; )),
];
const handleSelectTimeZone = (selectedTimeZone: string) => { const handleSelectTimeZone = (selectedTimeZone: string): void => {
if (selectedDate) { if (selectedDate) {
setSelectedDate(selectedDate.tz(selectedTimeZone)) setSelectedDate(selectedDate.tz(selectedTimeZone));
} }
}; };
const handleToggle24hClock = (is24hClock: boolean) => { const handleToggle24hClock = (is24hClock: boolean): void => {
if (selectedDate) { if (selectedDate) {
setTimeFormat(is24hClock ? 'HH:mm' : 'h:mma'); setTimeFormat(is24hClock ? "HH:mm" : "h:mma");
} }
} };
return ( return (
<div> <div>
@ -101,60 +125,41 @@ export default function Type(props) {
className={ className={
"mx-auto my-24 transition-max-width ease-in-out duration-500 " + "mx-auto my-24 transition-max-width ease-in-out duration-500 " +
(selectedDate ? "max-w-6xl" : "max-w-3xl") (selectedDate ? "max-w-6xl" : "max-w-3xl")
} }>
>
<div className="bg-white shadow rounded-lg"> <div className="bg-white shadow rounded-lg">
<div className="sm:flex px-4 py-5 sm:p-4"> <div className="sm:flex px-4 py-5 sm:p-4">
<div <div className={"pr-8 sm:border-r " + (selectedDate ? "sm:w-1/3" : "sm:w-1/2")}>
className={
"pr-8 sm:border-r " + (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 rounded-full mb-4" />
<h2 className="font-medium text-gray-500">{props.user.name}</h2> <h2 className="font-medium text-gray-500">{props.user.name}</h2>
<h1 className="text-3xl font-semibold text-gray-800 mb-4"> <h1 className="text-3xl font-semibold text-gray-800 mb-4">{props.eventType.title}</h1>
{props.eventType.title}
</h1>
<p className="text-gray-500 mb-1 px-2 py-1 -ml-2"> <p className="text-gray-500 mb-1 px-2 py-1 -ml-2">
<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>
<button <button
onClick={() => setIsTimeOptionsOpen(!isTimeOptionsOpen)} onClick={() => setIsTimeOptionsOpen(!isTimeOptionsOpen)}
className="text-gray-500 mb-1 px-2 py-1 -ml-2" className="text-gray-500 mb-1 px-2 py-1 -ml-2">
>
<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()}
<ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" /> <ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
</button> </button>
{ isTimeOptionsOpen && <TimeOptions onSelectTimeZone={handleSelectTimeZone} {isTimeOptionsOpen && (
onToggle24hClock={handleToggle24hClock} />} <TimeOptions
<p className="text-gray-600 mt-3 mb-8"> onSelectTimeZone={handleSelectTimeZone}
{props.eventType.description} onToggle24hClock={handleToggle24hClock}
</p> />
)}
<p className="text-gray-600 mt-3 mb-8">{props.eventType.description}</p>
</div> </div>
<div <div
className={ className={"mt-8 sm:mt-0 " + (selectedDate ? "sm:w-1/3 border-r sm:px-4" : "sm:w-1/2 sm:pl-4")}>
"mt-8 sm:mt-0 " +
(selectedDate
? "sm:w-1/3 border-r sm:px-4"
: "sm:w-1/2 sm:pl-4")
}
>
<div className="flex text-gray-600 font-light text-xl mb-4 ml-2"> <div className="flex text-gray-600 font-light text-xl mb-4 ml-2">
<span className="w-1/2"> <span className="w-1/2">{dayjs().month(selectedMonth).format("MMMM YYYY")}</span>
{dayjs().month(selectedMonth).format("MMMM YYYY")}
</span>
<div className="w-1/2 text-right"> <div className="w-1/2 text-right">
<button <button
onClick={decrementMonth} onClick={decrementMonth}
className={ className={"mr-4 " + (selectedMonth < parseInt(dayjs().format("MM")) && "text-gray-400")}
"mr-4 " + disabled={selectedMonth < parseInt(dayjs().format("MM"))}>
(selectedMonth < parseInt(dayjs().format("MM")) &&
"text-gray-400")
}
disabled={selectedMonth < parseInt(dayjs().format("MM"))}
>
<ChevronLeftIcon className="w-5 h-5" /> <ChevronLeftIcon className="w-5 h-5" />
</button> </button>
<button onClick={incrementMonth}> <button onClick={incrementMonth}>
@ -163,38 +168,29 @@ export default function Type(props) {
</div> </div>
</div> </div>
<div className="grid grid-cols-7 gap-y-4 text-center"> <div className="grid grid-cols-7 gap-y-4 text-center">
{props.user.weekStart !== 'Monday' ? ( {props.user.weekStart !== "Monday" ? (
<div className="uppercase text-gray-400 text-xs tracking-widest"> <div className="uppercase text-gray-400 text-xs tracking-widest">Sun</div>
Sun
</div>
) : null} ) : null}
<div className="uppercase text-gray-400 text-xs tracking-widest"> <div className="uppercase text-gray-400 text-xs tracking-widest">Mon</div>
Mon <div className="uppercase text-gray-400 text-xs tracking-widest">Tue</div>
</div> <div className="uppercase text-gray-400 text-xs tracking-widest">Wed</div>
<div className="uppercase text-gray-400 text-xs tracking-widest"> <div className="uppercase text-gray-400 text-xs tracking-widest">Thu</div>
Tue <div className="uppercase text-gray-400 text-xs tracking-widest">Fri</div>
</div> <div className="uppercase text-gray-400 text-xs tracking-widest">Sat</div>
<div className="uppercase text-gray-400 text-xs tracking-widest"> {props.user.weekStart === "Monday" ? (
Wed <div className="uppercase text-gray-400 text-xs tracking-widest">Sun</div>
</div>
<div className="uppercase text-gray-400 text-xs tracking-widest">
Thu
</div>
<div className="uppercase text-gray-400 text-xs tracking-widest">
Fri
</div>
<div className="uppercase text-gray-400 text-xs tracking-widest">
Sat
</div>
{props.user.weekStart === 'Monday' ? (
<div className="uppercase text-gray-400 text-xs tracking-widest">
Sun
</div>
) : null} ) : null}
{calendar} {calendar}
</div> </div>
</div> </div>
{selectedDate && <AvailableTimes timeFormat={timeFormat} user={props.user} eventType={props.eventType} date={selectedDate} />} {selectedDate && (
<AvailableTimes
timeFormat={timeFormat}
user={props.user}
eventType={props.eventType}
date={selectedDate}
/>
)}
</div> </div>
</div> </div>
{/* note(peer): {/* note(peer):
@ -202,10 +198,7 @@ export default function Type(props) {
*/} */}
<div className="text-xs text-right pt-1"> <div className="text-xs text-right pt-1">
<Link href="https://calendso.com"> <Link href="https://calendso.com">
<a <a style={{ color: "#104D86" }} className="opacity-50 hover:opacity-100">
style={{ color: "#104D86" }}
className="opacity-50 hover:opacity-100"
>
powered by{" "} powered by{" "}
<img <img
style={{ top: -2 }} style={{ top: -2 }}
@ -221,10 +214,10 @@ export default function Type(props) {
); );
} }
export async function getServerSideProps(context) { export const getServerSideProps: GetServerSideProps = async (context) => {
const user = await prisma.user.findFirst({ const user = await prisma.user.findFirst({
where: { where: {
username: context.query.user, username: context.query.user.toLowerCase(),
}, },
select: { select: {
id: true, id: true,
@ -238,13 +231,13 @@ export async function getServerSideProps(context) {
timeZone: true, timeZone: true,
endTime: true, endTime: true,
weekStart: true, weekStart: true,
} },
}); });
if (!user) { if (!user) {
return { return {
notFound: true, notFound: true,
} };
} }
const eventType = await prisma.eventType.findFirst({ const eventType = await prisma.eventType.findFirst({
@ -258,14 +251,14 @@ export async function getServerSideProps(context) {
id: true, id: true,
title: true, title: true,
description: true, description: true,
length: true length: true,
} },
}); });
if (!eventType) { if (!eventType) {
return { return {
notFound: true, notFound: true,
} };
} }
return { return {
@ -273,5 +266,5 @@ export async function getServerSideProps(context) {
user, user,
eventType, eventType,
}, },
} };
} };