Redesign date & time selection page
This commit is contained in:
parent
bf935130ab
commit
f3a780188f
4 changed files with 107 additions and 91 deletions
|
@ -14,6 +14,7 @@ const AvailableTimes = ({
|
||||||
timeFormat,
|
timeFormat,
|
||||||
user,
|
user,
|
||||||
organizerTimeZone,
|
organizerTimeZone,
|
||||||
|
height,
|
||||||
}) => {
|
}) => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { rescheduleUid } = router.query;
|
const { rescheduleUid } = router.query;
|
||||||
|
@ -27,9 +28,11 @@ const AvailableTimes = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="sm:pl-4 mt-8 sm:mt-0 text-center sm:w-1/3 md:max-h-97 overflow-y-auto">
|
<div
|
||||||
<div className="text-gray-600 font-light text-xl mb-4 text-left">
|
className="sm:pl-4 mt-16 pt-4 pr-6 bg-neutral-50 dark:bg-neutral-800 rounded-r-sm border border-l-0 border-neutral-300 dark:border-neutral-700 text-center h-full overflow-y-auto"
|
||||||
<span className="w-1/2 dark:text-white text-gray-600">{date.format("dddd DD MMMM YYYY")}</span>
|
style={{ maxHeight: height }}>
|
||||||
|
<div className="font-semibold mb-4 text-left border-b border-neutral-200 pb-4">
|
||||||
|
<span className="w-1/2 dark:text-white text-primary-500">{date.format("dddd DD MMMM YYYY")}</span>
|
||||||
</div>
|
</div>
|
||||||
{slots.length > 0 &&
|
{slots.length > 0 &&
|
||||||
slots.map((slot) => (
|
slots.map((slot) => (
|
||||||
|
@ -39,7 +42,7 @@ const AvailableTimes = ({
|
||||||
`/${user.username}/book?date=${slot.utc().format()}&type=${eventTypeId}` +
|
`/${user.username}/book?date=${slot.utc().format()}&type=${eventTypeId}` +
|
||||||
(rescheduleUid ? "&rescheduleUid=" + rescheduleUid : "")
|
(rescheduleUid ? "&rescheduleUid=" + rescheduleUid : "")
|
||||||
}>
|
}>
|
||||||
<a className="block font-medium mb-4 text-blue-600 border border-blue-600 rounded hover:text-white hover:bg-blue-600 py-4">
|
<a className="block font-medium mb-4 bg-white dark:bg-neutral-700 text-primary-500 dark:text-neutral-200 border border-primary-500 dark:border-neutral-600 rounded hover:text-white hover:bg-primary-500 dark:hover:bg-primary-500 dark:hover:border-primary-500 py-4">
|
||||||
{slot.format(timeFormat)}
|
{slot.format(timeFormat)}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/solid";
|
import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/outline";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
|
@ -24,6 +24,7 @@ const DatePicker = ({
|
||||||
periodDays,
|
periodDays,
|
||||||
periodCountCalendarDays,
|
periodCountCalendarDays,
|
||||||
minimumBookingNotice,
|
minimumBookingNotice,
|
||||||
|
setHeight,
|
||||||
}) => {
|
}) => {
|
||||||
const [calendar, setCalendar] = useState([]);
|
const [calendar, setCalendar] = useState([]);
|
||||||
const [selectedMonth, setSelectedMonth] = useState<number>();
|
const [selectedMonth, setSelectedMonth] = useState<number>();
|
||||||
|
@ -147,12 +148,14 @@ const DatePicker = ({
|
||||||
onClick={() => setSelectedDate(inviteeDate.date(day))}
|
onClick={() => setSelectedDate(inviteeDate.date(day))}
|
||||||
disabled={isDisabled(day)}
|
disabled={isDisabled(day)}
|
||||||
className={
|
className={
|
||||||
"text-center w-10 h-10 rounded-full mx-auto" +
|
"w-36 mx-auto h-28 mx-auto p-3 text-left flex self-start" +
|
||||||
(isDisabled(day) ? " text-gray-400 font-light" : " text-blue-600 font-medium") +
|
(isDisabled(day)
|
||||||
|
? " text-neutral-400 font-light"
|
||||||
|
: " text-neutral-900 dark:text-neutral-200 font-medium") +
|
||||||
(selectedDate && selectedDate.isSame(inviteeDate.date(day), "day")
|
(selectedDate && selectedDate.isSame(inviteeDate.date(day), "day")
|
||||||
? " bg-blue-600 text-white-important"
|
? " bg-neutral-100 dark:bg-neutral-700 border border-neutral-900 dark:border-neutral-600 dark:text-white"
|
||||||
: !isDisabled(day)
|
: !isDisabled(day)
|
||||||
? " bg-blue-50 dark:bg-gray-900 dark:bg-opacity-30"
|
? " bg-neutral-100 dark:bg-neutral-700 dark:bg-opacity-30"
|
||||||
: "")
|
: "")
|
||||||
}>
|
}>
|
||||||
{day}
|
{day}
|
||||||
|
@ -162,35 +165,44 @@ const DatePicker = ({
|
||||||
}, [selectedMonth, inviteeTimeZone, selectedDate]);
|
}, [selectedMonth, inviteeTimeZone, selectedDate]);
|
||||||
|
|
||||||
return selectedMonth ? (
|
return selectedMonth ? (
|
||||||
<div
|
<div className="mt-8 sm:mt-0">
|
||||||
className={
|
<div className="flex text-gray-600 text-xl mb-8">
|
||||||
"mt-8 sm:mt-0 " +
|
<div className="w-1/2 text-2xl">
|
||||||
(selectedDate ? "sm:w-1/3 sm:border-r sm:dark:border-gray-900 sm:px-4" : "sm:w-1/2 sm:pl-4")
|
<span className="font-semibold text-neutral-900 dark:text-white">
|
||||||
}>
|
{dayjs().month(selectedMonth).format("MMMM")}
|
||||||
<div className="flex text-gray-600 font-light text-xl mb-4 ml-2">
|
</span>
|
||||||
<span className="w-1/2 text-gray-600 dark:text-white">
|
|
||||||
{dayjs().month(selectedMonth).format("MMMM YYYY")}
|
<span className="text-neutral-400">{dayjs().month(selectedMonth).format("YYYY")}</span>
|
||||||
</span>
|
</div>
|
||||||
<div className="w-1/2 text-right text-gray-600 dark:text-gray-400">
|
<div className="w-1/2 text-right text-gray-600 dark:text-gray-400">
|
||||||
<button
|
<button
|
||||||
onClick={decrementMonth}
|
onClick={decrementMonth}
|
||||||
className={
|
className={
|
||||||
"mr-4 " +
|
"p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 dark:text-white rounded-sm border border-neutral-300 text-black mr-4 " +
|
||||||
(selectedMonth <= dayjs().tz(inviteeTimeZone).month() && "text-gray-400 dark:text-gray-600")
|
(selectedMonth <= dayjs().tz(inviteeTimeZone).month() && "opacity-50")
|
||||||
}
|
}
|
||||||
disabled={selectedMonth <= dayjs().tz(inviteeTimeZone).month()}>
|
disabled={selectedMonth <= dayjs().tz(inviteeTimeZone).month()}>
|
||||||
<ChevronLeftIcon className="w-5 h-5" />
|
<ChevronLeftIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
<button onClick={incrementMonth}>
|
<button
|
||||||
|
className="p-1 bg-white dark:bg-neutral-800 dark:border-neutral-700 dark:text-white rounded-sm border border-neutral-300"
|
||||||
|
onClick={incrementMonth}>
|
||||||
<ChevronRightIcon className="w-5 h-5" />
|
<ChevronRightIcon className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-7 gap-y-4 text-center">
|
<div
|
||||||
|
className="bg-white dark:bg-neutral-800 p-6 rounded-l-sm border border-neutral-300 dark:border-neutral-700 grid grid-cols-7 gap-y-4"
|
||||||
|
ref={(el) => {
|
||||||
|
if (!el) return;
|
||||||
|
setHeight(el.getBoundingClientRect().height);
|
||||||
|
}}>
|
||||||
{["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
{["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
|
||||||
.sort((a, b) => (weekStart.startsWith(a) ? -1 : weekStart.startsWith(b) ? 1 : 0))
|
.sort((a, b) => (weekStart.startsWith(a) ? -1 : weekStart.startsWith(b) ? 1 : 0))
|
||||||
.map((weekDay) => (
|
.map((weekDay) => (
|
||||||
<div key={weekDay} className="uppercase text-gray-400 text-xs tracking-widest">
|
<div
|
||||||
|
key={weekDay}
|
||||||
|
className="uppercase text-neutral-600 text-xs tracking-widest border-b border-neutral-200 dark:border-neutral-700 pb-4">
|
||||||
{weekDay}
|
{weekDay}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { GetServerSideProps, GetServerSidePropsContext } from "next";
|
import { GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { ChevronDownIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid";
|
import { ChevronDownIcon, ClockIcon, GlobeIcon, InformationCircleIcon } from "@heroicons/react/solid";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import dayjs, { Dayjs } from "dayjs";
|
import dayjs, { Dayjs } from "dayjs";
|
||||||
import * as Collapsible from "@radix-ui/react-collapsible";
|
import * as Collapsible from "@radix-ui/react-collapsible";
|
||||||
|
@ -30,6 +30,8 @@ export default function Type(props): Type {
|
||||||
const [timeFormat, setTimeFormat] = useState("h:mma");
|
const [timeFormat, setTimeFormat] = useState("h:mma");
|
||||||
const telemetry = useTelemetry();
|
const telemetry = useTelemetry();
|
||||||
|
|
||||||
|
const [calHeight, setCalHeight] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleToggle24hClock(localStorage.getItem("timeOption.is24hClock") === "true");
|
handleToggle24hClock(localStorage.getItem("timeOption.is24hClock") === "true");
|
||||||
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters()));
|
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.pageView, collectPageParameters()));
|
||||||
|
@ -79,7 +81,7 @@ export default function Type(props): Type {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isReady && (
|
isReady && (
|
||||||
<div>
|
<div className="bg-neutral-50 dark:bg-neutral-900 h-screen">
|
||||||
<Head>
|
<Head>
|
||||||
<title>
|
<title>
|
||||||
{rescheduleUid && "Reschedule"} {props.eventType.title} | {props.user.name || props.user.username}{" "}
|
{rescheduleUid && "Reschedule"} {props.eventType.title} | {props.user.name || props.user.username}{" "}
|
||||||
|
@ -126,73 +128,72 @@ export default function Type(props): Type {
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
<main
|
<main className={"flex sm:py-12 w-11/12 mx-auto"}>
|
||||||
className={
|
<div className="w-2/12 md:pt-32">
|
||||||
"mx-auto my-0 sm:my-24 transition-max-width ease-in-out duration-500 " +
|
<Avatar user={props.user} className="w-12 h-12 rounded-full mb-4" />
|
||||||
(selectedDate ? "max-w-6xl" : "max-w-3xl")
|
<h1 className="text-xl font-semibold dark:text-neutral-100 text-neutral-900 mb-2">
|
||||||
}>
|
{props.user.name}
|
||||||
<div className="dark:bg-gray-800 bg-white sm:shadow sm:rounded-lg">
|
</h1>
|
||||||
<div className="sm:flex px-4 py-5 sm:p-4">
|
<p className="text-neutral-500 mb-1 px-2 py-1 -ml-2">
|
||||||
<div
|
<InformationCircleIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
|
||||||
className={
|
{props.eventType.title}
|
||||||
"pr-8 sm:border-r sm:dark:border-gray-900 " + (selectedDate ? "sm:w-1/3" : "sm:w-1/2")
|
</p>
|
||||||
}>
|
<p className="text-neutral-500 mb-1 px-2 py-1 -ml-2">
|
||||||
<Avatar user={props.user} className="w-16 h-16 rounded-full mb-4" />
|
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
|
||||||
<h2 className="font-medium dark:text-gray-300 text-gray-500">{props.user.name}</h2>
|
{props.eventType.length} minutes
|
||||||
<h1 className="text-3xl font-semibold dark:text-white text-gray-800 mb-4">
|
</p>
|
||||||
{props.eventType.title}
|
<Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
|
||||||
</h1>
|
<Collapsible.Trigger className="text-neutral-500 mb-1 px-2 py-1 -ml-2">
|
||||||
<p className="text-gray-500 mb-1 px-2 py-1 -ml-2">
|
<GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
|
||||||
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
|
{timeZone()}
|
||||||
{props.eventType.length} minutes
|
<ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
|
||||||
</p>
|
</Collapsible.Trigger>
|
||||||
|
<Collapsible.Content>
|
||||||
<Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
|
<TimeOptions
|
||||||
<Collapsible.Trigger className="text-gray-500 mb-1 px-2 py-1 -ml-2">
|
onSelectTimeZone={handleSelectTimeZone}
|
||||||
<GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
|
onToggle24hClock={handleToggle24hClock}
|
||||||
{timeZone()}
|
|
||||||
<ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
|
|
||||||
</Collapsible.Trigger>
|
|
||||||
<Collapsible.Content>
|
|
||||||
<TimeOptions
|
|
||||||
onSelectTimeZone={handleSelectTimeZone}
|
|
||||||
onToggle24hClock={handleToggle24hClock}
|
|
||||||
/>
|
|
||||||
</Collapsible.Content>
|
|
||||||
</Collapsible.Root>
|
|
||||||
|
|
||||||
<p className="dark:text-gray-200 text-gray-600 mt-3 mb-8">{props.eventType.description}</p>
|
|
||||||
</div>
|
|
||||||
<DatePicker
|
|
||||||
date={selectedDate}
|
|
||||||
periodType={props.eventType?.periodType}
|
|
||||||
periodStartDate={props.eventType?.periodStartDate}
|
|
||||||
periodEndDate={props.eventType?.periodEndDate}
|
|
||||||
periodDays={props.eventType?.periodDays}
|
|
||||||
periodCountCalendarDays={props.eventType?.periodCountCalendarDays}
|
|
||||||
weekStart={props.user.weekStart}
|
|
||||||
onDatePicked={changeDate}
|
|
||||||
workingHours={props.workingHours}
|
|
||||||
organizerTimeZone={props.eventType.timeZone || props.user.timeZone}
|
|
||||||
inviteeTimeZone={timeZone()}
|
|
||||||
eventLength={props.eventType.length}
|
|
||||||
minimumBookingNotice={props.eventType.minimumBookingNotice}
|
|
||||||
/>
|
|
||||||
{selectedDate && (
|
|
||||||
<AvailableTimes
|
|
||||||
workingHours={props.workingHours}
|
|
||||||
timeFormat={timeFormat}
|
|
||||||
organizerTimeZone={props.eventType.timeZone || props.user.timeZone}
|
|
||||||
minimumBookingNotice={props.eventType.minimumBookingNotice}
|
|
||||||
eventTypeId={props.eventType.id}
|
|
||||||
eventLength={props.eventType.length}
|
|
||||||
date={selectedDate}
|
|
||||||
user={props.user}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</Collapsible.Content>
|
||||||
</div>
|
</Collapsible.Root>
|
||||||
</div>
|
</div>
|
||||||
{!props.user.hideBranding && <PoweredByCalendso />}
|
<div className="w-8/12">
|
||||||
|
<DatePicker
|
||||||
|
date={selectedDate}
|
||||||
|
periodType={props.eventType?.periodType}
|
||||||
|
periodStartDate={props.eventType?.periodStartDate}
|
||||||
|
periodEndDate={props.eventType?.periodEndDate}
|
||||||
|
periodDays={props.eventType?.periodDays}
|
||||||
|
periodCountCalendarDays={props.eventType?.periodCountCalendarDays}
|
||||||
|
weekStart={props.user.weekStart}
|
||||||
|
onDatePicked={changeDate}
|
||||||
|
workingHours={props.workingHours}
|
||||||
|
organizerTimeZone={props.eventType.timeZone || props.user.timeZone}
|
||||||
|
inviteeTimeZone={timeZone()}
|
||||||
|
eventLength={props.eventType.length}
|
||||||
|
minimumBookingNotice={props.eventType.minimumBookingNotice}
|
||||||
|
setHeight={setCalHeight}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="w-2/12">
|
||||||
|
{selectedDate && (
|
||||||
|
<AvailableTimes
|
||||||
|
workingHours={props.workingHours}
|
||||||
|
timeFormat={timeFormat}
|
||||||
|
organizerTimeZone={props.eventType.timeZone || props.user.timeZone}
|
||||||
|
minimumBookingNotice={props.eventType.minimumBookingNotice}
|
||||||
|
eventTypeId={props.eventType.id}
|
||||||
|
eventLength={props.eventType.length}
|
||||||
|
date={selectedDate}
|
||||||
|
user={props.user}
|
||||||
|
height={calHeight}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{!props.user.hideBranding && (
|
||||||
|
<div className="absolute top-4 right-4">
|
||||||
|
<PoweredByCalendso />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -18,7 +18,7 @@ class MyDocument extends Document {
|
||||||
<meta name="msapplication-TileColor" content="#ff0000" />
|
<meta name="msapplication-TileColor" content="#ff0000" />
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<meta name="theme-color" content="#ffffff" />
|
||||||
</Head>
|
</Head>
|
||||||
<body className="dark:bg-gray-900 bg-white">
|
<body className="dark:bg-neutral-900 bg-white">
|
||||||
<Main />
|
<Main />
|
||||||
<NextScript />
|
<NextScript />
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in a new issue