Lowercase the router.query.user & some typescript fixes
This commit is contained in:
parent
6c6d262184
commit
892ba8a335
3 changed files with 199 additions and 202 deletions
|
@ -18,7 +18,8 @@
|
|||
"rules": {
|
||||
"prettier/prettier": ["error"],
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"react/react-in-jsx-scope": "off"
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off"
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
|
|
167
pages/[user].tsx
167
pages/[user].tsx
|
@ -1,92 +1,95 @@
|
|||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import prisma from '../lib/prisma';
|
||||
import Avatar from '../components/Avatar';
|
||||
import { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import prisma from "../lib/prisma";
|
||||
import Avatar from "../components/Avatar";
|
||||
|
||||
export default function User(props) {
|
||||
const eventTypes = props.eventTypes.map(type =>
|
||||
<li key={type.id}>
|
||||
<Link href={'/' + props.user.username + '/' + type.slug}>
|
||||
<a className="block px-6 py-4">
|
||||
<div className="inline-block w-3 h-3 rounded-full mr-2" style={{backgroundColor:getRandomColorCode()}}></div>
|
||||
<h2 className="inline-block font-medium">{type.title}</h2>
|
||||
<p className="inline-block text-gray-400 ml-2">{type.description}</p>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
);
|
||||
return (
|
||||
<div>
|
||||
<Head>
|
||||
<title>{props.user.name || props.user.username} | Calendso</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
export default function User(props): User {
|
||||
const eventTypes = props.eventTypes.map((type) => (
|
||||
<li key={type.id}>
|
||||
<Link href={`/${props.user.username}/${type.slug}`}>
|
||||
<a className="block px-6 py-4">
|
||||
<div
|
||||
className="inline-block w-3 h-3 rounded-full mr-2"
|
||||
style={{ backgroundColor: getRandomColorCode() }}></div>
|
||||
<h2 className="inline-block font-medium">{type.title}</h2>
|
||||
<p className="inline-block text-gray-400 ml-2">{type.description}</p>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
));
|
||||
return (
|
||||
<div>
|
||||
<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">
|
||||
<div className="mb-8 text-center">
|
||||
<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>
|
||||
<p className="text-gray-600">{props.user.bio}</p>
|
||||
</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't set up any event types yet.</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</main>
|
||||
<main className="max-w-2xl mx-auto my-24">
|
||||
<div className="mb-8 text-center">
|
||||
<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>
|
||||
<p className="text-gray-600">{props.user.bio}</p>
|
||||
</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't set up any event types yet.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: context.query.user,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email:true,
|
||||
name: true,
|
||||
bio: true,
|
||||
avatar: true,
|
||||
eventTypes: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
}
|
||||
|
||||
const eventTypes = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
hidden: false
|
||||
}
|
||||
});
|
||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username: context.query.user.toLowerCase(),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
email: true,
|
||||
name: true,
|
||||
bio: true,
|
||||
avatar: true,
|
||||
eventTypes: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
props: {
|
||||
user,
|
||||
eventTypes
|
||||
},
|
||||
}
|
||||
}
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
|
||||
const eventTypes = await prisma.eventType.findMany({
|
||||
where: {
|
||||
userId: user.id,
|
||||
hidden: false,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
props: {
|
||||
user,
|
||||
eventTypes,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
// Auxiliary methods
|
||||
|
||||
export function getRandomColorCode() {
|
||||
let color = '#';
|
||||
for (let idx = 0; idx < 6; idx++) {
|
||||
color += Math.floor(Math.random() * 10);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
export function getRandomColorCode(): string {
|
||||
let color = "#";
|
||||
for (let idx = 0; idx < 6; idx++) {
|
||||
color += Math.floor(Math.random() * 10);
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
|
|
@ -1,25 +1,31 @@
|
|||
import {useEffect, useMemo, useState} from 'react';
|
||||
import Head from 'next/head';
|
||||
import Link from 'next/link';
|
||||
import prisma from '../../lib/prisma';
|
||||
import { useRouter } from 'next/router';
|
||||
import dayjs, { Dayjs } from 'dayjs';
|
||||
import { ClockIcon, 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';
|
||||
import { useEffect, useState } from "react";
|
||||
import { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import prisma from "../../lib/prisma";
|
||||
import { useRouter } from "next/router";
|
||||
import dayjs, { Dayjs } from "dayjs";
|
||||
import {
|
||||
ClockIcon,
|
||||
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(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
import {collectPageParameters, telemetryEventTypes, useTelemetry} from "../../lib/telemetry";
|
||||
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";
|
||||
|
||||
export default function Type(props) {
|
||||
import TimeOptions from "../../components/booking/TimeOptions";
|
||||
import Avatar from "../../components/Avatar";
|
||||
import { timeZone } from "../../lib/clock";
|
||||
|
||||
export default function Type(props): Type {
|
||||
// Get router variables
|
||||
const router = useRouter();
|
||||
const { rescheduleUid } = router.query;
|
||||
|
@ -28,65 +34,83 @@ export default function Type(props) {
|
|||
const [selectedDate, setSelectedDate] = useState<Dayjs>();
|
||||
const [selectedMonth, setSelectedMonth] = useState(dayjs().month());
|
||||
const [isTimeOptionsOpen, setIsTimeOptionsOpen] = useState(false);
|
||||
const [timeFormat, setTimeFormat] = useState('h:mma');
|
||||
const [timeFormat, setTimeFormat] = useState("h:mma");
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
useEffect(() => {
|
||||
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters()))
|
||||
}, []);
|
||||
useEffect((): void => {
|
||||
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters()));
|
||||
}, [telemetry]);
|
||||
|
||||
// Handle month changes
|
||||
const incrementMonth = () => {
|
||||
setSelectedMonth(selectedMonth + 1);
|
||||
}
|
||||
setSelectedMonth(selectedMonth + 1);
|
||||
};
|
||||
|
||||
const decrementMonth = () => {
|
||||
setSelectedMonth(selectedMonth - 1);
|
||||
}
|
||||
setSelectedMonth(selectedMonth - 1);
|
||||
};
|
||||
|
||||
// Set up calendar
|
||||
var daysInMonth = dayjs().month(selectedMonth).daysInMonth();
|
||||
var days = [];
|
||||
const daysInMonth = dayjs().month(selectedMonth).daysInMonth();
|
||||
const days = [];
|
||||
for (let i = 1; i <= daysInMonth; i++) {
|
||||
days.push(i);
|
||||
days.push(i);
|
||||
}
|
||||
|
||||
// Create placeholder elements for empty days in first week
|
||||
let weekdayOfFirst = dayjs().month(selectedMonth).date(1).day();
|
||||
if (props.user.weekStart === 'Monday') {
|
||||
if (props.user.weekStart === "Monday") {
|
||||
weekdayOfFirst -= 1;
|
||||
if (weekdayOfFirst < 0)
|
||||
weekdayOfFirst = 6;
|
||||
if (weekdayOfFirst < 0) 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"}>
|
||||
{null}
|
||||
{null}
|
||||
</div>
|
||||
);
|
||||
));
|
||||
|
||||
const changeDate = (day) => {
|
||||
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.dateSelected, collectPageParameters()))
|
||||
setSelectedDate(dayjs().month(selectedMonth).date(day))
|
||||
const changeDate = (day): void => {
|
||||
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.dateSelected, collectPageParameters()));
|
||||
setSelectedDate(dayjs().month(selectedMonth).date(day));
|
||||
};
|
||||
|
||||
// Combine placeholder days with actual days
|
||||
const calendar = [...emptyDays, ...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}
|
||||
const calendar = [
|
||||
...emptyDays,
|
||||
...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>
|
||||
)];
|
||||
)),
|
||||
];
|
||||
|
||||
const handleSelectTimeZone = (selectedTimeZone: string) => {
|
||||
const handleSelectTimeZone = (selectedTimeZone: string): void => {
|
||||
if (selectedDate) {
|
||||
setSelectedDate(selectedDate.tz(selectedTimeZone))
|
||||
setSelectedDate(selectedDate.tz(selectedTimeZone));
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggle24hClock = (is24hClock: boolean) => {
|
||||
const handleToggle24hClock = (is24hClock: boolean): void => {
|
||||
if (selectedDate) {
|
||||
setTimeFormat(is24hClock ? 'HH:mm' : 'h:mma');
|
||||
setTimeFormat(is24hClock ? "HH:mm" : "h:mma");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -101,60 +125,41 @@ export default function Type(props) {
|
|||
className={
|
||||
"mx-auto my-24 transition-max-width ease-in-out duration-500 " +
|
||||
(selectedDate ? "max-w-6xl" : "max-w-3xl")
|
||||
}
|
||||
>
|
||||
}>
|
||||
<div className="bg-white shadow rounded-lg">
|
||||
<div className="sm:flex px-4 py-5 sm:p-4">
|
||||
<div
|
||||
className={
|
||||
"pr-8 sm:border-r " + (selectedDate ? "sm:w-1/3" : "sm:w-1/2")
|
||||
}
|
||||
>
|
||||
<div 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" />
|
||||
<h2 className="font-medium text-gray-500">{props.user.name}</h2>
|
||||
<h1 className="text-3xl font-semibold text-gray-800 mb-4">
|
||||
{props.eventType.title}
|
||||
</h1>
|
||||
<h1 className="text-3xl font-semibold text-gray-800 mb-4">{props.eventType.title}</h1>
|
||||
<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" />
|
||||
{props.eventType.length} minutes
|
||||
</p>
|
||||
<button
|
||||
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" />
|
||||
{timeZone()}
|
||||
<ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
|
||||
</button>
|
||||
{ isTimeOptionsOpen && <TimeOptions onSelectTimeZone={handleSelectTimeZone}
|
||||
onToggle24hClock={handleToggle24hClock} />}
|
||||
<p className="text-gray-600 mt-3 mb-8">
|
||||
{props.eventType.description}
|
||||
</p>
|
||||
{isTimeOptionsOpen && (
|
||||
<TimeOptions
|
||||
onSelectTimeZone={handleSelectTimeZone}
|
||||
onToggle24hClock={handleToggle24hClock}
|
||||
/>
|
||||
)}
|
||||
<p className="text-gray-600 mt-3 mb-8">{props.eventType.description}</p>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
"mt-8 sm:mt-0 " +
|
||||
(selectedDate
|
||||
? "sm:w-1/3 border-r sm:px-4"
|
||||
: "sm:w-1/2 sm:pl-4")
|
||||
}
|
||||
>
|
||||
className={"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">
|
||||
<span className="w-1/2">
|
||||
{dayjs().month(selectedMonth).format("MMMM YYYY")}
|
||||
</span>
|
||||
<span className="w-1/2">{dayjs().month(selectedMonth).format("MMMM YYYY")}</span>
|
||||
<div className="w-1/2 text-right">
|
||||
<button
|
||||
onClick={decrementMonth}
|
||||
className={
|
||||
"mr-4 " +
|
||||
(selectedMonth < parseInt(dayjs().format("MM")) &&
|
||||
"text-gray-400")
|
||||
}
|
||||
disabled={selectedMonth < parseInt(dayjs().format("MM"))}
|
||||
>
|
||||
className={"mr-4 " + (selectedMonth < parseInt(dayjs().format("MM")) && "text-gray-400")}
|
||||
disabled={selectedMonth < parseInt(dayjs().format("MM"))}>
|
||||
<ChevronLeftIcon className="w-5 h-5" />
|
||||
</button>
|
||||
<button onClick={incrementMonth}>
|
||||
|
@ -163,38 +168,29 @@ export default function Type(props) {
|
|||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-7 gap-y-4 text-center">
|
||||
{props.user.weekStart !== 'Monday' ? (
|
||||
<div className="uppercase text-gray-400 text-xs tracking-widest">
|
||||
Sun
|
||||
</div>
|
||||
{props.user.weekStart !== "Monday" ? (
|
||||
<div className="uppercase text-gray-400 text-xs tracking-widest">Sun</div>
|
||||
) : null}
|
||||
<div className="uppercase text-gray-400 text-xs tracking-widest">
|
||||
Mon
|
||||
</div>
|
||||
<div className="uppercase text-gray-400 text-xs tracking-widest">
|
||||
Tue
|
||||
</div>
|
||||
<div className="uppercase text-gray-400 text-xs tracking-widest">
|
||||
Wed
|
||||
</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>
|
||||
<div className="uppercase text-gray-400 text-xs tracking-widest">Mon</div>
|
||||
<div className="uppercase text-gray-400 text-xs tracking-widest">Tue</div>
|
||||
<div className="uppercase text-gray-400 text-xs tracking-widest">Wed</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}
|
||||
{calendar}
|
||||
</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>
|
||||
{/* note(peer):
|
||||
|
@ -202,10 +198,7 @@ export default function Type(props) {
|
|||
*/}
|
||||
<div className="text-xs text-right pt-1">
|
||||
<Link href="https://calendso.com">
|
||||
<a
|
||||
style={{ color: "#104D86" }}
|
||||
className="opacity-50 hover:opacity-100"
|
||||
>
|
||||
<a style={{ color: "#104D86" }} className="opacity-50 hover:opacity-100">
|
||||
powered by{" "}
|
||||
<img
|
||||
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({
|
||||
where: {
|
||||
username: context.query.user,
|
||||
username: context.query.user.toLowerCase(),
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
|
@ -238,13 +231,13 @@ export async function getServerSideProps(context) {
|
|||
timeZone: true,
|
||||
endTime: true,
|
||||
weekStart: true,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const eventType = await prisma.eventType.findFirst({
|
||||
|
@ -258,14 +251,14 @@ export async function getServerSideProps(context) {
|
|||
id: true,
|
||||
title: true,
|
||||
description: true,
|
||||
length: true
|
||||
}
|
||||
length: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!eventType) {
|
||||
return {
|
||||
notFound: true,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -273,5 +266,5 @@ export async function getServerSideProps(context) {
|
|||
user,
|
||||
eventType,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue