Merge branch 'main' into bugfix/unify-email-sending

This commit is contained in:
nicolas 2021-08-08 21:21:33 +02:00
commit 560bd19472
59 changed files with 2105 additions and 1276 deletions

38
components/Dialog.tsx Normal file
View file

@ -0,0 +1,38 @@
import React from "react";
import * as DialogPrimitive from "@radix-ui/react-dialog";
export function Dialog({ children, ...props }) {
return (
<DialogPrimitive.Root {...props}>
<DialogPrimitive.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
{children}
</DialogPrimitive.Root>
);
}
export const DialogContent = React.forwardRef(({ children, ...props }, forwardedRef) => (
<DialogPrimitive.Content
{...props}
className="fixed bg-white min-w-[360px] rounded top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-left overflow-hidden shadow-xl sm:align-middle sm:max-w-lg sm:w-full p-6"
ref={forwardedRef}>
{children}
</DialogPrimitive.Content>
));
export function DialogHeader({ title, subtitle }: { title: string; subtitle: string }) {
return (
<div className="mb-8">
<h3 className="text-lg leading-6 font-bold text-gray-900" id="modal-title">
{title}
</h3>
<div>
<p className="text-sm text-gray-400">{subtitle}</p>
</div>
</div>
);
}
DialogContent.displayName = "DialogContent";
export const DialogTrigger = DialogPrimitive.Trigger;
export const DialogClose = DialogPrimitive.Close;

7
components/Loader.tsx Normal file
View file

@ -0,0 +1,7 @@
export default function Loader() {
return (
<div className="loader border-black dark:border-white">
<span className="loader-inner bg-black dark:bg-white"></span>
</div>
);
}

View file

@ -8,7 +8,7 @@ export default function Modal(props) {
<Dialog
as="div"
static
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
open={props.open}
onClose={props.handleClose}>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
@ -20,7 +20,7 @@ export default function Modal(props) {
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0">
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
<Dialog.Overlay className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}

View file

@ -1,10 +1,7 @@
import Link from "next/link";
import { CodeIcon, CreditCardIcon, KeyIcon, UserGroupIcon, UserIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
import classNames from "@lib/classNames";
export default function SettingsShell(props) {
const router = useRouter();
@ -38,9 +35,9 @@ export default function SettingsShell(props) {
];
return (
<div className="max-w-6xl">
<div className="min-w-full overflow-scroll sm:mx-auto -mx-4 min-h-16">
<nav className="-mb-px flex space-x-8 px-4 sm:px-0 " aria-label="Tabs">
<div>
<div className="sm:mx-auto">
<nav className="-mb-px flex space-x-2 sm:space-x-8" aria-label="Tabs">
{tabs.map((tab) => (
<Link key={tab.name} href={tab.href}>
<a
@ -54,7 +51,7 @@ export default function SettingsShell(props) {
<tab.icon
className={classNames(
tab.current ? "text-neutral-900" : "text-gray-400 group-hover:text-gray-500",
"-ml-0.5 mr-2 h-5 w-5"
"-ml-0.5 mr-2 h-5 w-5 hidden sm:inline-block"
)}
aria-hidden="true"
/>
@ -63,8 +60,9 @@ export default function SettingsShell(props) {
</Link>
))}
</nav>
<hr />
</div>
<main>{props.children}</main>
<main className="max-w-4xl">{props.children}</main>
</div>
);
}

View file

@ -16,13 +16,11 @@ import {
PuzzleIcon,
} from "@heroicons/react/solid";
import Logo from "./Logo";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
import classNames from "@lib/classNames";
export default function Shell(props) {
const router = useRouter();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const telemetry = useTelemetry();
@ -46,7 +44,7 @@ export default function Shell(props) {
current: router.pathname.startsWith("/availability"),
},
{
name: "Integrations",
name: "App Store",
href: "/integrations",
icon: PuzzleIcon,
current: router.pathname.startsWith("/integrations"),
@ -70,117 +68,121 @@ export default function Shell(props) {
}
return session ? (
<div className="h-screen flex overflow-hidden bg-gray-100">
{/* Static sidebar for desktop */}
<div className="hidden md:flex md:flex-shrink-0">
<div className="flex flex-col w-64">
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex flex-col h-0 flex-1 border-r border-gray-200 bg-white">
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<Link href="/">
<a className="px-4">
<Logo small />
</a>
</Link>
<nav className="mt-5 flex-1 px-2 bg-white space-y-1">
{navigation.map((item) => (
<Link key={item.name} href={item.href}>
<a
className={classNames(
item.current
? "bg-neutral-100 text-neutral-900"
: "text-neutral-500 hover:bg-gray-50 hover:text-neutral-900",
"group flex items-center px-2 py-2 text-sm font-medium rounded-sm"
)}>
<item.icon
<>
<div className="h-screen flex overflow-hidden bg-gray-100">
{/* Static sidebar for desktop */}
<div className="hidden md:flex md:flex-shrink-0">
<div className="flex flex-col w-56">
{/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex flex-col h-0 flex-1 border-r border-gray-200 bg-white">
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<Link href="/event-types">
<a className="px-4">
<Logo small />
</a>
</Link>
<nav className="mt-5 flex-1 px-2 bg-white space-y-1">
{navigation.map((item) => (
<Link key={item.name} href={item.href}>
<a
className={classNames(
item.current ? "text-neutral-500" : "text-neutral-400 group-hover:text-neutral-500",
"mr-3 flex-shrink-0 h-5 w-5"
)}
aria-hidden="true"
/>
{item.name}
</a>
</Link>
))}
</nav>
</div>
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
<UserDropdown session={session} />
item.current
? "bg-neutral-100 text-neutral-900"
: "text-neutral-500 hover:bg-gray-50 hover:text-neutral-900",
"group flex items-center px-2 py-2 text-sm font-medium rounded-sm"
)}>
<item.icon
className={classNames(
item.current
? "text-neutral-500"
: "text-neutral-400 group-hover:text-neutral-500",
"mr-3 flex-shrink-0 h-5 w-5"
)}
aria-hidden="true"
/>
{item.name}
</a>
</Link>
))}
</nav>
</div>
<div className="flex-shrink-0 flex p-4">
<UserDropdown session={session} />
</div>
</div>
</div>
</div>
</div>
<div className="flex flex-col w-0 flex-1 overflow-hidden">
{/* show top navigation for md and smaller (tablet and phones) */}
<nav className="md:hidden bg-white shadow p-4 flex justify-between items-center">
<Link href="/">
<a>
<Logo />
</a>
</Link>
<div className="flex gap-3 items-center self-center">
<button className="bg-white p-2 rounded-full text-gray-400 hover:text-gray-500 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
<span className="sr-only">View notifications</span>
<Link href="/settings/profile">
<div className="flex flex-col w-0 flex-1 overflow-hidden">
<main className="flex-1 relative z-0 overflow-y-auto focus:outline-none">
{/* show top navigation for md and smaller (tablet and phones) */}
<nav className="md:hidden bg-white shadow p-4 flex justify-between items-center">
<Link href="/event-types">
<a>
<CogIcon className="h-6 w-6" aria-hidden="true" />
<Logo />
</a>
</Link>
</button>
<div className="mt-1">
<UserDropdown small bottom session={session} />
</div>
</div>
</nav>
<main className="flex-1 relative z-0 overflow-y-auto focus:outline-none">
<div className="py-6">
<div className="block sm:flex justify-between px-4 sm:px-6 md:px-8">
<div className="mb-6">
<h1 className="text-2xl font-semibold text-gray-900">{props.heading}</h1>
<p className="text-sm text-neutral-500 mr-4">{props.subtitle}</p>
</div>
<div className="mb-4 flex-shrink-0">{props.CTA}</div>
</div>
<div className="px-4 sm:px-6 md:px-8">{props.children}</div>
{/* show bottom navigation for md and smaller (tablet and phones) */}
<nav className="md:hidden flex fixed bottom-0 bg-white w-full rounded-lg shadow">
{/* note(PeerRich): using flatMap instead of map to remove settings from bottom nav */}
{navigation.flatMap((item, itemIdx) =>
item.name === "Settings" ? (
[]
) : (
<Link key={item.name} href={item.href}>
<a
className={classNames(
item.current ? "text-gray-900" : "text-neutral-400 hover:text-gray-700",
itemIdx === 0 ? "rounded-l-lg" : "",
itemIdx === navigation.length - 1 ? "rounded-r-lg" : "",
"group relative min-w-0 flex-1 overflow-hidden bg-white py-2 px-2 text-xs sm:text-sm font-medium text-center hover:bg-gray-50 focus:z-10"
)}
aria-current={item.current ? "page" : undefined}>
<item.icon
className={classNames(
item.current ? "text-gray-900" : "text-gray-400 group-hover:text-gray-500",
"block mx-auto flex-shrink-0 h-5 w-5 mb-1 text-center"
)}
aria-hidden="true"
/>
<span>{item.name}</span>
<div className="flex gap-3 items-center self-center">
<button className="bg-white p-2 rounded-full text-gray-400 hover:text-gray-500 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black">
<span className="sr-only">View notifications</span>
<Link href="/settings/profile">
<a>
<CogIcon className="h-6 w-6" aria-hidden="true" />
</a>
</Link>
)
)}
</button>
<div className="mt-1">
<UserDropdown small bottom session={session} />
</div>
</div>
</nav>
<div className="py-8">
<div className="block sm:flex justify-between px-4 sm:px-6 md:px-8">
<div className="mb-8">
<h1 className="text-xl font-bold text-gray-900">{props.heading}</h1>
<p className="text-sm text-neutral-500 mr-4">{props.subtitle}</p>
</div>
<div className="mb-4 flex-shrink-0">{props.CTA}</div>
</div>
<div className="px-4 sm:px-6 md:px-8">{props.children}</div>
{/* add padding to content for mobile navigation*/}
<div className="block md:hidden pt-12" />
</div>
</main>
{/* show bottom navigation for md and smaller (tablet and phones) */}
<nav className="bottom-nav md:hidden flex fixed bottom-0 bg-white w-full shadow">
{/* note(PeerRich): using flatMap instead of map to remove settings from bottom nav */}
{navigation.flatMap((item, itemIdx) =>
item.name === "Settings" ? (
[]
) : (
<Link key={item.name} href={item.href}>
<a
className={classNames(
item.current ? "text-gray-900" : "text-neutral-400 hover:text-gray-700",
itemIdx === 0 ? "rounded-l-lg" : "",
itemIdx === navigation.length - 1 ? "rounded-r-lg" : "",
"group relative min-w-0 flex-1 overflow-hidden bg-white py-2 px-2 text-xs sm:text-sm font-medium text-center hover:bg-gray-50 focus:z-10"
)}
aria-current={item.current ? "page" : undefined}>
<item.icon
className={classNames(
item.current ? "text-gray-900" : "text-gray-400 group-hover:text-gray-500",
"block mx-auto flex-shrink-0 h-5 w-5 mb-1 text-center"
)}
aria-hidden="true"
/>
<span>{item.name}</span>
</a>
</Link>
)
)}
</nav>
{/* add padding to content for mobile navigation*/}
<div className="block md:hidden pt-12" />
</div>
</main>
</div>
</div>
</div>
</>
) : null;
}
@ -210,7 +212,7 @@ function UserDropdown({ session, small, bottom }: { session: any; small?: boolea
<span className="flex-1 flex flex-col min-w-0">
<span className="text-gray-900 text-sm font-medium truncate">{session.user.name}</span>
<span className="text-neutral-500 font-normal text-sm truncate">
{session.user.username}
/{session.user.username}
</span>
</span>
)}

36
components/Tooltip.tsx Normal file
View file

@ -0,0 +1,36 @@
import React from "react";
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import { Slot } from "@radix-ui/react-slot";
export function Tooltip({
children,
content,
open,
defaultOpen,
onOpenChange,
...props
}: {
[x: string]: any;
children: React.ReactNode;
content: React.ReactNode;
open: boolean;
defaultOpen: boolean;
onOpenChange: (open: boolean) => void;
}) {
return (
<TooltipPrimitive.Root
delayDuration={150}
open={open}
defaultOpen={defaultOpen}
onOpenChange={onOpenChange}>
<TooltipPrimitive.Trigger as={Slot}>{children}</TooltipPrimitive.Trigger>
<TooltipPrimitive.Content
className="bg-black text-xs -mt-2 text-white px-1 py-0.5 shadow-lg rounded-sm"
side="top"
align="center"
{...props}>
{content}
</TooltipPrimitive.Content>
</TooltipPrimitive.Root>
);
}

View file

@ -2,6 +2,8 @@ import Link from "next/link";
import { useRouter } from "next/router";
import Slots from "./Slots";
import { ExclamationIcon } from "@heroicons/react/solid";
import React from "react";
import Loader from "@components/Loader";
const AvailableTimes = ({
date,
@ -25,7 +27,7 @@ const AvailableTimes = ({
});
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 className="sm:pl-4 mt-8 sm:mt-0 text-center sm:w-1/3 md:max-h-97 overflow-y-auto">
<div className="text-gray-600 font-light text-xl mb-4 text-left">
<span className="w-1/2 dark:text-white text-gray-600">{date.format("dddd DD MMMM YYYY")}</span>
</div>
@ -37,7 +39,7 @@ const AvailableTimes = ({
`/${user.username}/book?date=${slot.utc().format()}&type=${eventTypeId}` +
(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-900 text-primary-500 dark:text-neutral-200 border border-primary-500 dark:border-black rounded-sm hover:text-white hover:bg-primary-500 dark:hover:border-black py-4 dark:hover:bg-black">
{slot.format(timeFormat)}
</a>
</Link>
@ -49,7 +51,7 @@ const AvailableTimes = ({
</div>
)}
{!isFullyBooked && slots.length === 0 && !hasErrors && <div className="loader" />}
{!isFullyBooked && slots.length === 0 && !hasErrors && <Loader />}
{hasErrors && (
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4">

View file

@ -147,12 +147,14 @@ const DatePicker = ({
onClick={() => setSelectedDate(inviteeDate.date(day))}
disabled={isDisabled(day)}
className={
"text-center w-10 h-10 rounded-full mx-auto" +
(isDisabled(day) ? " text-gray-400 font-light" : " text-blue-600 font-medium") +
"text-center w-10 h-10 mx-auto hover:border hover:border-black dark:hover:border-white" +
(isDisabled(day)
? " text-gray-400 font-light hover:border-0 cursor-default"
: " dark:text-white text-primary-500 font-medium") +
(selectedDate && selectedDate.isSame(inviteeDate.date(day), "day")
? " bg-blue-600 text-white-important"
? " bg-black text-white-important"
: !isDisabled(day)
? " bg-blue-50 dark:bg-gray-900 dark:bg-opacity-30"
? " bg-gray-100 dark:bg-black dark:bg-opacity-30"
: "")
}>
{day}
@ -164,26 +166,34 @@ const DatePicker = ({
return selectedMonth ? (
<div
className={
"mt-8 sm:mt-0 " +
(selectedDate ? "sm:w-1/3 sm:border-r sm:dark:border-gray-900 sm:px-4" : "sm:w-1/2 sm:pl-4")
"mt-8 sm:mt-0 min-w-[350px] " +
(selectedDate
? "w-full sm:w-1/2 md:w-1/3 sm:border-r sm:dark:border-black sm:pl-4 sm:pr-6"
: "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 text-gray-600 dark:text-white">
{dayjs().month(selectedMonth).format("MMMM YYYY")}
<strong className="text-gray-900 dark:text-white">
{dayjs().month(selectedMonth).format("MMMM")}
</strong>
<span className="text-gray-500"> {dayjs().month(selectedMonth).format("YYYY")}</span>
</span>
<div className="w-1/2 text-right">
<div className="w-1/2 text-right text-gray-600 dark:text-gray-400">
<button
onClick={decrementMonth}
className={"mr-4 " + (selectedMonth <= dayjs().tz(inviteeTimeZone).month() && "text-gray-400")}
className={
"group mr-2 p-1" +
(selectedMonth <= dayjs().tz(inviteeTimeZone).month() && "text-gray-400 dark:text-gray-600")
}
disabled={selectedMonth <= dayjs().tz(inviteeTimeZone).month()}>
<ChevronLeftIcon className="w-5 h-5" />
<ChevronLeftIcon className="group-hover:text-black dark:group-hover:text-white w-5 h-5" />
</button>
<button onClick={incrementMonth}>
<ChevronRightIcon className="w-5 h-5" />
<button className="group p-1" onClick={incrementMonth}>
<ChevronRightIcon className="group-hover:text-black dark:group-hover:text-white w-5 h-5" />
</button>
</div>
</div>
<div className="grid grid-cols-7 gap-y-4 text-center">
<div className="grid grid-cols-7 gap-4 text-center">
{["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
.sort((a, b) => (weekStart.startsWith(a) ? -1 : weekStart.startsWith(b) ? 1 : 0))
.map((weekDay) => (

View file

@ -2,10 +2,7 @@ import { Switch } from "@headlessui/react";
import TimezoneSelect from "react-timezone-select";
import { useEffect, useState } from "react";
import { is24h, timeZone } from "../../lib/clock";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
import classNames from "@lib/classNames";
const TimeOptions = (props) => {
const [selectedTimeZone, setSelectedTimeZone] = useState("");
@ -28,7 +25,7 @@ const TimeOptions = (props) => {
return (
selectedTimeZone !== "" && (
<div className="w-full rounded shadow border dark:bg-gray-700 dark:border-0 bg-white px-4 py-2">
<div className="absolute w-full max-w-80 rounded-sm border border-gray-200 dark:bg-gray-700 dark:border-0 bg-white px-4 py-2">
<div className="flex mb-4">
<div className="w-1/2 dark:text-white text-gray-600 font-medium">Time Options</div>
<div className="w-1/2">
@ -40,7 +37,7 @@ const TimeOptions = (props) => {
checked={is24hClock}
onChange={setIs24hClock}
className={classNames(
is24hClock ? "bg-blue-600" : "dark:bg-gray-600 bg-gray-200",
is24hClock ? "bg-black" : "dark:bg-gray-600 bg-gray-200",
"relative inline-flex flex-shrink-0 h-5 w-8 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black"
)}>
<span className="sr-only">Use setting</span>

View file

@ -35,12 +35,14 @@ export default function EditTeamModal(props) {
return (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<div
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;

View file

@ -40,12 +40,14 @@ export default function MemberInvitationModal(props) {
return (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<div
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;

View file

@ -1,15 +1,26 @@
import { useState } from 'react';
export default function Button(props) {
return(
<button type="submit" className="btn btn-primary">
return (
<button type="submit" className="btn btn-primary dark:btn-white">
{!props.loading && props.children}
{props.loading &&
<svg className="animate-spin mx-4 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
{props.loading && (
<svg
className="animate-spin mx-4 h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24">
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
}
)}
</button>
);
}

View file

@ -1,7 +1,7 @@
import Link from "next/link";
const PoweredByCalendso = () => (
<div className="text-xs text-center sm:text-right pt-1">
<div className="text-xs text-center sm:text-right p-1">
<Link href={`https://calendso.com?utm_source=embed&utm_medium=powered-by-button`}>
<a target="_blank" className="dark:text-white text-gray-500 opacity-50 hover:opacity-100">
powered by{" "}

View file

@ -91,7 +91,7 @@ export const Scheduler = ({
type="button"
onClick={() => removeScheduleAt(idx)}
className="btn-sm bg-transparent px-2 py-1 ml-1">
<TrashIcon className="h-6 w-6 inline text-gray-400 -mt-1" />
<TrashIcon className="h-5 w-5 inline text-gray-400 -mt-1" />
</button>
</li>
);

View file

@ -28,12 +28,14 @@ export default function SetTimesModal(props) {
return (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<div
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;

View file

@ -39,7 +39,7 @@ export default class CalEventParser {
* Returns a footer section with links to change the event (as HTML).
*/
public getChangeEventFooterHtml(): string {
return `<p style="color: #4b5563; margin-top: 20px;">Need to make a change? <a href="${this.getCancelLink()}" style="color: #161e2e;">Cancel</a> or <a href="${this.getRescheduleLink()}" style="color: #161e2e;">reschedule</a>.</p>`;
return `<p style="color: #4b5563; margin-top: 20px;">Need to make a change? <a href="${this.getCancelLink()}" style="color: #161e2e;">Cancel</a> or <a href="${this.getRescheduleLink()}" style="color: #161e2e;">reschedule</a></p>`;
}
/**

View file

@ -210,7 +210,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
return {
getAvailability: (dateFrom, dateTo, selectedCalendars) => {
const filter = "?$filter=start/dateTime ge '" + dateFrom + "' and end/dateTime le '" + dateTo + "'";
const filter = "?startdatetime=" + dateFrom + "&enddatetime=" + dateTo;
return auth
.getToken()
.then((accessToken) => {
@ -233,7 +233,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
headers: {
Prefer: 'outlook.timezone="Etc/GMT"',
},
url: `/me/calendars/${calendarId}/events${filter}`,
url: `/me/calendars/${calendarId}/calendarView${filter}`,
}));
return fetch("https://graph.microsoft.com/v1.0/$batch", {
@ -313,7 +313,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
getAvailability: (dateFrom, dateTo, selectedCalendars) =>
new Promise((resolve, reject) =>
auth.getToken().then((myGoogleAuth) => {
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth });
const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
const selectedCalendarIds = selectedCalendars
.filter((e) => e.integration === integrationType)
.map((e) => e.externalId);
@ -379,7 +382,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
payload["conferenceData"] = event.conferenceData;
}
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth });
const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
calendar.events.insert(
{
auth: myGoogleAuth,
@ -422,7 +428,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
payload["location"] = event.location;
}
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth });
const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
calendar.events.update(
{
auth: myGoogleAuth,
@ -445,7 +454,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
deleteEvent: (uid: string) =>
new Promise((resolve, reject) =>
auth.getToken().then((myGoogleAuth) => {
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth });
const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
calendar.events.delete(
{
auth: myGoogleAuth,
@ -467,7 +479,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
listCalendars: () =>
new Promise((resolve, reject) =>
auth.getToken().then((myGoogleAuth) => {
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth });
const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
calendar.calendarList
.list()
.then((cals) => {

3
lib/classNames.ts Normal file
View file

@ -0,0 +1,3 @@
export default function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}

View file

@ -41,6 +41,10 @@ module.exports = withTM({
future: {
webpack5: true,
},
i18n: {
locales: ["en"],
defaultLocale: "en",
},
typescript: {
ignoreBuildErrors: true,
},

View file

@ -17,6 +17,10 @@
"@heroicons/react": "^1.0.1",
"@jitsu/sdk-js": "^2.0.1",
"@prisma/client": "^2.23.0",
"@radix-ui/react-collapsible": "^0.0.16",
"@radix-ui/react-dialog": "^0.0.19",
"@radix-ui/react-slot": "^0.0.12",
"@radix-ui/react-tooltip": "^0.0.21",
"@tailwindcss/forms": "^0.2.1",
"async": "^3.2.0",
"bcryptjs": "^2.4.3",
@ -36,6 +40,7 @@
"react": "17.0.1",
"react-dates": "^21.8.0",
"react-dom": "17.0.1",
"react-multi-email": "^0.5.3",
"react-phone-number-input": "^3.1.21",
"react-select": "^4.3.0",
"react-timezone-select": "^1.0.2",

154
pages/404.tsx Normal file
View file

@ -0,0 +1,154 @@
import { ChevronRightIcon } from "@heroicons/react/solid";
import { BookOpenIcon, CheckIcon, CodeIcon, DocumentTextIcon } from "@heroicons/react/outline";
import { useRouter } from "next/router";
import React from "react";
import Link from "next/link";
import Head from "next/head";
const links = [
{
title: "Documentation",
description: "Learn how to integrate our tools with your app",
icon: DocumentTextIcon,
href: "https://docs.calendso.com",
},
{
title: "API Reference",
description: "A complete API reference for our libraries",
icon: CodeIcon,
href: "https://api.docs.calendso.com",
},
{
title: "Blog",
description: "Read our latest news and articles",
icon: BookOpenIcon,
href: "https://calendso.com/blog",
},
];
export default function Custom404() {
const router = useRouter();
const username = router.asPath.replace("%20", "-");
return (
<>
<Head>
<title>404: This page could not be found.</title>
</Head>
<div className="bg-white min-h-screen px-4">
<main className="max-w-xl mx-auto pb-6 pt-16 sm:pt-24">
<div className="text-center">
<p className="text-sm font-semibold text-black uppercase tracking-wide">404 error</p>
<h1 className="mt-2 text-4xl font-extrabold text-gray-900 tracking-tight sm:text-5xl">
This page does not exist.
</h1>
<a href="https://checkout.calendso.com" className="inline-block mt-2 text-lg ">
The username <strong className="text-blue-500">calendso.com{username}</strong> is still
available. <span className="text-blue-500">Register now</span>.
</a>
</div>
<div className="mt-12">
<h2 className="text-sm font-semibold text-gray-500 tracking-wide uppercase">Popular pages</h2>
<ul role="list" className="mt-4">
<li className="border-2 border-green-500 px-4 py-2">
<a href="https://checkout.calendso.com" className="relative py-6 flex items-start space-x-4">
<div className="flex-shrink-0">
<span className="flex items-center justify-center h-12 w-12 rounded-lg bg-green-50">
<CheckIcon className="h-6 w-6 text-green-500" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-gray-500">
<span className="focus:outline-none">
<span className="absolute inset-0" aria-hidden="true" />
Register <strong className="text-green-500">{username}</strong>
</span>
</span>
</h3>
<p className="text-base text-gray-500">Claim your username and schedule events</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRightIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</li>
</ul>
<ul role="list" className="mt-4 border-gray-200 divide-y divide-gray-200">
{links.map((link, linkIdx) => (
<li key={linkIdx} className="px-4 py-2">
<Link href={link.href}>
<a className="relative py-6 flex items-start space-x-4">
<div className="flex-shrink-0">
<span className="flex items-center justify-center h-12 w-12 rounded-lg bg-gray-50">
<link.icon className="h-6 w-6 text-gray-700" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-gray-500">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</span>
</h3>
<p className="text-base text-gray-500">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRightIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</Link>
</li>
))}
<li className="px-4 py-2">
<a href="https://calendso.com/slack" className="relative py-6 flex items-start space-x-4">
<div className="flex-shrink-0">
<span className="flex items-center justify-center h-12 w-12 rounded-lg bg-gray-50">
<svg viewBox="0 0 2447.6 2452.5" className="h-6 w-6" xmlns="http://www.w3.org/2000/svg">
<g clipRule="evenodd" fillRule="evenodd">
<path
d="m897.4 0c-135.3.1-244.8 109.9-244.7 245.2-.1 135.3 109.5 245.1 244.8 245.2h244.8v-245.1c.1-135.3-109.5-245.1-244.9-245.3.1 0 .1 0 0 0m0 654h-652.6c-135.3.1-244.9 109.9-244.8 245.2-.2 135.3 109.4 245.1 244.7 245.3h652.7c135.3-.1 244.9-109.9 244.8-245.2.1-135.4-109.5-245.2-244.8-245.3z"
fill="rgba(55, 65, 81)"></path>
<path
d="m2447.6 899.2c.1-135.3-109.5-245.1-244.8-245.2-135.3.1-244.9 109.9-244.8 245.2v245.3h244.8c135.3-.1 244.9-109.9 244.8-245.3zm-652.7 0v-654c.1-135.2-109.4-245-244.7-245.2-135.3.1-244.9 109.9-244.8 245.2v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.3z"
fill="rgba(55, 65, 81)"></path>
<path
d="m1550.1 2452.5c135.3-.1 244.9-109.9 244.8-245.2.1-135.3-109.5-245.1-244.8-245.2h-244.8v245.2c-.1 135.2 109.5 245 244.8 245.2zm0-654.1h652.7c135.3-.1 244.9-109.9 244.8-245.2.2-135.3-109.4-245.1-244.7-245.3h-652.7c-135.3.1-244.9 109.9-244.8 245.2-.1 135.4 109.4 245.2 244.7 245.3z"
fill="rgba(55, 65, 81)"></path>
<path
d="m0 1553.2c-.1 135.3 109.5 245.1 244.8 245.2 135.3-.1 244.9-109.9 244.8-245.2v-245.2h-244.8c-135.3.1-244.9 109.9-244.8 245.2zm652.7 0v654c-.2 135.3 109.4 245.1 244.7 245.3 135.3-.1 244.9-109.9 244.8-245.2v-653.9c.2-135.3-109.4-245.1-244.7-245.3-135.4 0-244.9 109.8-244.8 245.1 0 0 0 .1 0 0"
fill="rgba(55, 65, 81)"></path>
</g>
</svg>
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-gray-500">
<span className="absolute inset-0" aria-hidden="true" />
Slack
</span>
</h3>
<p className="text-base text-gray-500">Join our community</p>
</div>
<div className="flex-shrink-0 self-center">
<ChevronRightIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</li>
</ul>
<div className="mt-8">
<Link href="/">
<a className="text-base font-medium text-black hover:text-gray-500">
Or go back home<span aria-hidden="true"> &rarr;</span>
</a>
</Link>
</div>
</div>
</main>
</div>
</>
);
}

View file

@ -4,50 +4,73 @@ import Link from "next/link";
import prisma, { whereAndSelect } from "@lib/prisma";
import Avatar from "../components/Avatar";
import Theme from "@components/Theme";
import { ClockIcon, InformationCircleIcon, UserIcon } from "@heroicons/react/solid";
import React from "react";
import { ArrowRightIcon } from "@heroicons/react/outline";
export default function User(props): User {
const { isReady } = Theme(props.user.theme);
const eventTypes = props.eventTypes.map((type) => (
<li
<div
key={type.id}
className="dark:bg-gray-800 dark:opacity-90 dark:hover:opacity-100 dark:hover:bg-gray-800 bg-white hover:bg-gray-50 ">
className="group relative dark:bg-neutral-900 dark:border-0 dark:hover:border-neutral-600 bg-white hover:bg-gray-50 border border-neutral-200 hover:border-black rounded-sm">
<ArrowRightIcon className="absolute transition-opacity h-4 w-4 right-3 top-3 text-black dark:text-white opacity-0 group-hover:opacity-100" />
<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 dark:text-white">{type.title}</h2>
<p className="inline-block text-gray-400 dark:text-gray-100 ml-2">{type.description}</p>
<h2 className="font-semibold text-neutral-900 dark:text-white">{type.title}</h2>
<div className="mt-2 flex space-x-4">
<div className="flex text-sm text-neutral-500">
<ClockIcon
className="flex-shrink-0 mt-0.5 mr-1.5 h-4 w-4 text-neutral-400 dark:text-white"
aria-hidden="true"
/>
<p className="dark:text-white">{type.length}m</p>
</div>
<div className="flex text-sm min-w-16 text-neutral-500">
<UserIcon
className="flex-shrink-0 mt-0.5 mr-1.5 h-4 w-4 text-neutral-400 dark:text-white"
aria-hidden="true"
/>
<p className="dark:text-white">1-on-1</p>
</div>
<div className="flex text-sm text-neutral-500">
<InformationCircleIcon
className="flex-shrink-0 mt-0.5 mr-1.5 h-4 w-4 text-neutral-400 dark:text-white"
aria-hidden="true"
/>
<p className="dark:text-white">{type.description}</p>
</div>
</div>
</a>
</Link>
</li>
</div>
));
return (
isReady && (
<div>
<div className="bg-neutral-50 dark:bg-black h-screen">
<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-3xl mx-auto py-24 px-4">
<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 dark:text-white mb-1">
<h1 className="text-3xl font-bold text-neutral-900 dark:text-white mb-1">
{props.user.name || props.user.username}
</h1>
<p className="text-gray-600 dark:text-white">{props.user.bio}</p>
<p className="text-neutral-500 dark:text-white">{props.user.bio}</p>
</div>
<div className="shadow overflow-hidden rounded-md">
<ul className="divide-y divide-gray-200 dark:divide-gray-900">{eventTypes}</ul>
{eventTypes.length == 0 && (
<div className="space-y-6">{eventTypes}</div>
{eventTypes.length == 0 && (
<div className="shadow overflow-hidden rounded-sm">
<div className="p-8 text-center text-gray-400 dark:text-white">
<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>
</div>
)}
</main>
</div>
)
@ -76,6 +99,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
select: {
slug: true,
title: true,
length: true,
description: true,
},
});

View file

@ -1,9 +1,10 @@
import { useEffect, useState } from "react";
import { GetServerSideProps, GetServerSidePropsContext } from "next";
import Head from "next/head";
import { ChevronDownIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid";
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";
@ -51,13 +52,11 @@ export default function Type(props): Type {
query: Object.assign(
{},
{
date: formattedDate,
...router.query,
},
rescheduleUid
? {
rescheduleUid: rescheduleUid,
}
: {}
{
date: formattedDate,
}
),
},
undefined,
@ -130,13 +129,13 @@ export default function Type(props): Type {
<main
className={
"mx-auto my-0 sm:my-24 transition-max-width ease-in-out duration-500 " +
(selectedDate ? "max-w-6xl" : "max-w-3xl")
(selectedDate ? "max-w-5xl" : "max-w-3xl")
}>
<div className="dark:bg-gray-800 bg-white sm:shadow sm:rounded-lg">
<div className="dark:bg-neutral-900 bg-white border border-gray-200 rounded-sm dark:border-0">
<div className="sm:flex px-4 py-5 sm:p-4">
<div
className={
"pr-8 sm:border-r sm:dark:border-gray-900 " + (selectedDate ? "sm:w-1/3" : "sm:w-1/2")
"pr-8 sm:border-r sm:dark:border-black " + (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 dark:text-gray-300 text-gray-500">{props.user.name}</h2>
@ -147,19 +146,25 @@ export default function Type(props): Type {
<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">
<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}
/>
)}
<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 ">
<GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{timeZone()}
{isTimeOptionsOpen ? (
<ChevronUpIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
) : (
<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

View file

@ -15,6 +15,8 @@ import Avatar from "../../components/Avatar";
import Button from "../../components/ui/Button";
import { EventTypeCustomInputType } from "../../lib/eventTypeInput";
import Theme from "@components/Theme";
import { ReactMultiEmail } from "react-multi-email";
import "react-multi-email/style.css";
dayjs.extend(utc);
dayjs.extend(timezone);
@ -27,7 +29,8 @@ export default function Book(props: any): JSX.Element {
const [preferredTimeZone, setPreferredTimeZone] = useState("");
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const [guestToggle, setGuestToggle] = useState(false);
const [guestEmails, setGuestEmails] = useState([]);
const locations = props.eventType.locations || [];
const [selectedLocation, setSelectedLocation] = useState<LocationType>(
@ -44,6 +47,10 @@ export default function Book(props: any): JSX.Element {
telemetry.withJitsu((jitsu) => jitsu.track(telemetryEventTypes.timeSelected, collectPageParameters()));
});
function toggleGuestEmailInput() {
setGuestToggle(!guestToggle);
}
const locationInfo = (type: LocationType) => locations.find((location) => location.type === type);
// TODO: Move to translations
@ -65,7 +72,7 @@ export default function Book(props: any): JSX.Element {
const data = event.target["custom_" + input.id];
if (data) {
if (input.type === EventTypeCustomInputType.Bool) {
return input.label + "\n" + (data.value ? "Yes" : "No");
return input.label + "\n" + (data.checked ? "Yes" : "No");
} else {
return input.label + "\n" + data.value;
}
@ -85,6 +92,7 @@ export default function Book(props: any): JSX.Element {
name: event.target.name.value,
email: event.target.email.value,
notes: notes,
guests: guestEmails,
timeZone: preferredTimeZone,
eventTypeId: props.eventType.id,
rescheduleUid: rescheduleUid,
@ -153,9 +161,9 @@ export default function Book(props: any): JSX.Element {
</Head>
<main className="max-w-3xl mx-auto my-0 sm:my-24">
<div className="dark:bg-gray-800 bg-white overflow-hidden sm:shadow sm:rounded-lg">
<div className="dark:bg-neutral-900 bg-white overflow-hidden border border-gray-200 dark:border-0 sm:rounded-sm">
<div className="sm:flex px-4 py-5 sm:p-4">
<div className="sm:w-1/2 sm:border-r sm:dark:border-gray-900">
<div className="sm:w-1/2 sm:border-r sm:dark:border-black">
<Avatar user={props.user} className="w-16 h-16 rounded-full mb-4" />
<h2 className="font-medium dark:text-gray-300 text-gray-500">{props.user.name}</h2>
<h1 className="text-3xl font-semibold dark:text-white text-gray-800 mb-4">
@ -171,7 +179,7 @@ export default function Book(props: any): JSX.Element {
{locationInfo(selectedLocation).address}
</p>
)}
<p className="text-blue-600 mb-4">
<p className="text-green-500 mb-4">
<CalendarIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{preferredTimeZone &&
dayjs(date)
@ -192,7 +200,7 @@ export default function Book(props: any): JSX.Element {
name="name"
id="name"
required
className="shadow-sm dark:bg-gray-700 dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
className="shadow-sm dark:bg-black dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
placeholder="John Doe"
defaultValue={props.booking ? props.booking.attendees[0].name : ""}
/>
@ -210,7 +218,7 @@ export default function Book(props: any): JSX.Element {
name="email"
id="email"
required
className="shadow-sm dark:bg-gray-700 dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
className="shadow-sm dark:bg-black dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
placeholder="you@example.com"
defaultValue={props.booking ? props.booking.attendees[0].email : ""}
/>
@ -218,7 +226,9 @@ export default function Book(props: any): JSX.Element {
</div>
{locations.length > 1 && (
<div className="mb-4">
<span className="block text-sm font-medium text-gray-700">Location</span>
<span className="block text-sm font-medium dark:text-white text-gray-700">
Location
</span>
{locations.map((location) => (
<label key={location.type} className="block">
<input
@ -230,7 +240,9 @@ export default function Book(props: any): JSX.Element {
value={location.type}
checked={selectedLocation === location.type}
/>
<span className="text-sm ml-2">{locationLabels[location.type]}</span>
<span className="text-sm ml-2 dark:text-gray-500">
{locationLabels[location.type]}
</span>
</label>
))}
</div>
@ -248,7 +260,7 @@ export default function Book(props: any): JSX.Element {
placeholder="Enter phone number"
id="phone"
required
className="shadow-sm dark:bg-gray-700 dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
className="shadow-sm dark:bg-black dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
onChange={() => {
/* DO NOT REMOVE: Callback required by PhoneInput, comment added to satisfy eslint:no-empty-function */
}}
@ -274,7 +286,7 @@ export default function Book(props: any): JSX.Element {
id={"custom_" + input.id}
required={input.required}
rows={3}
className="shadow-sm dark:bg-gray-700 dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
className="shadow-sm dark:bg-black dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
placeholder=""
/>
)}
@ -284,7 +296,7 @@ export default function Book(props: any): JSX.Element {
name={"custom_" + input.id}
id={"custom_" + input.id}
required={input.required}
className="shadow-sm dark:bg-gray-700 dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
className="shadow-sm dark:bg-black dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
placeholder=""
/>
)}
@ -294,7 +306,7 @@ export default function Book(props: any): JSX.Element {
name={"custom_" + input.id}
id={"custom_" + input.id}
required={input.required}
className="shadow-sm dark:bg-gray-700 dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
className="shadow-sm dark:bg-black dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
placeholder=""
/>
)}
@ -304,7 +316,7 @@ export default function Book(props: any): JSX.Element {
type="checkbox"
name={"custom_" + input.id}
id={"custom_" + input.id}
className="focus:ring-black h-4 w-4 text-blue-600 border-gray-300 rounded mr-2"
className="focus:ring-black h-4 w-4 text-black border-gray-300 rounded mr-2"
placeholder=""
/>
<label
@ -316,6 +328,35 @@ export default function Book(props: any): JSX.Element {
)}
</div>
))}
<div className="mb-4">
{!guestToggle && (
<label
onClick={toggleGuestEmailInput}
htmlFor="guests"
className="block text-sm font-medium text-blue-500 mb-1 hover:cursor-pointer">
+ Additional Guests
</label>
)}
{guestToggle && (
<ReactMultiEmail
placeholder="Input your Email Address"
emails={guestEmails}
onChange={(_emails: string[]) => {
setGuestEmails(_emails);
}}
getLabel={(email: string, index: number, removeEmail: (index: number) => void) => {
return (
<div data-tag key={index}>
{email}
<span data-tag-handle onClick={() => removeEmail(index)}>
×
</span>
</div>
);
}}
/>
)}
</div>
<div className="mb-4">
<label
htmlFor="notes"
@ -326,13 +367,14 @@ export default function Book(props: any): JSX.Element {
name="notes"
id="notes"
rows={3}
className="shadow-sm dark:bg-gray-700 dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
className="shadow-sm dark:bg-black dark:text-white dark:border-gray-900 focus:ring-black focus:border-black block w-full sm:text-sm border-gray-300 rounded-md"
placeholder="Please share anything that will help prepare for our meeting."
defaultValue={props.booking ? props.booking.description : ""}
/>
</div>
<div className="flex items-start">
<Button type="submit" loading={loading} className="btn btn-primary">
{/* TODO: add styling props to <Button variant="" color="" /> and get rid of btn-primary */}
<Button type="submit" loading={loading}>
{rescheduleUid ? "Reschedule" : "Confirm"}
</Button>
<Link
@ -343,7 +385,7 @@ export default function Book(props: any): JSX.Element {
props.eventType.slug +
(rescheduleUid ? "?rescheduleUid=" + rescheduleUid : "")
}>
<a className="ml-2 btn btn-white">Cancel</a>
<a className="ml-2 text-sm dark:text-white p-2">Cancel</a>
</Link>
</div>
</form>
@ -404,10 +446,13 @@ export async function getServerSideProps(context) {
},
});
const eventTypeObject = Object.assign({}, eventType, {
periodStartDate: eventType.periodStartDate?.toString() ?? null,
periodEndDate: eventType.periodEndDate?.toString() ?? null,
});
const eventTypeObject = [eventType].map((e) => {
return {
...e,
periodStartDate: e.periodStartDate?.toString() ?? null,
periodEndDate: e.periodEndDate?.toString() ?? null,
};
})[0];
let booking = null;

View file

@ -1,12 +1,16 @@
import "../styles/globals.css";
import { createTelemetryClient, TelemetryProvider } from "../lib/telemetry";
import { createTelemetryClient, TelemetryProvider } from "@lib/telemetry";
import { Provider } from "next-auth/client";
import type { AppProps } from "next/app";
import Head from "next/head";
function MyApp({ Component, pageProps }: AppProps) {
return (
<TelemetryProvider value={createTelemetryClient()}>
<Provider session={pageProps.session}>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</Head>
<Component {...pageProps} />
</Provider>
</TelemetryProvider>

View file

@ -1,4 +1,4 @@
import Document, { Html, Head, Main, NextScript } from "next/document";
import Document, { Head, Html, Main, NextScript } from "next/document";
class MyDocument extends Document {
static async getInitialProps(ctx) {
@ -9,8 +9,16 @@ class MyDocument extends Document {
render() {
return (
<Html>
<Head />
<body className="dark:bg-gray-900 bg-white">
<Head>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#000000" />
<meta name="msapplication-TileColor" content="#ff0000" />
<meta name="theme-color" content="#ffffff" />
</Head>
<body className="dark:bg-black bg-gray-100">
<Main />
<NextScript />
</body>

View file

@ -13,12 +13,14 @@ import { User } from "@prisma/client";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import isBetween from "dayjs/plugin/isBetween";
import dayjsBusinessDays from "dayjs-business-days";
import { Exception } from "handlebars";
import EventOrganizerRequestMail from "@lib/emails/EventOrganizerRequestMail";
dayjs.extend(dayjsBusinessDays);
dayjs.extend(utc);
dayjs.extend(isBetween);
dayjs.extend(timezone);
const translator = short();
@ -27,7 +29,6 @@ const log = logger.getChildLogger({ prefix: ["[api] book:user"] });
function isAvailable(busyTimes, time, length) {
// Check for conflicts
let t = true;
if (Array.isArray(busyTimes) && busyTimes.length > 0) {
busyTimes.forEach((busyTime) => {
const startTime = dayjs(busyTime.start);
@ -148,8 +149,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const calendarAvailability = await getBusyCalendarTimes(
currentUser.credentials,
dayjs(req.body.start).startOf("day").utc().format(),
dayjs(req.body.end).endOf("day").utc().format(),
dayjs(req.body.start).startOf("day").utc().format("YYYY-MM-DDTHH:mm:ss[Z]"),
dayjs(req.body.end).endOf("day").utc().format("YYYY-MM-DDTHH:mm:ss[Z]"),
selectedCalendars
);
const videoAvailability = await getBusyVideoTimes(currentUser.credentials);
@ -201,6 +202,17 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
const invitee = [{ email: req.body.email, name: req.body.name, timeZone: req.body.timeZone }];
const guests = req.body.guests.map((guest) => {
const g = {
email: guest,
name: "",
timeZone: req.body.timeZone,
};
return g;
});
const attendeesList = [...invitee, ...guests];
const evt: CalendarEvent = {
type: selectedEventType.title,
title: getEventName(req.body.name, selectedEventType.title, selectedEventType.eventName),
@ -208,7 +220,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
startTime: req.body.start,
endTime: req.body.end,
organizer: { email: currentUser.email, name: currentUser.name, timeZone: currentUser.timeZone },
attendees: [{ email: req.body.email, name: req.body.name, timeZone: req.body.timeZone }],
attendees: attendeesList,
location: req.body.location, // Will be processed by the EventManager later.
};
@ -226,7 +238,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
try {
isAvailableToBeBooked = isAvailable(commonAvailability, req.body.start, selectedEventType.length);
} catch {
} catch (e) {
console.log(e);
log.debug({
message: "Unable set isAvailableToBeBooked. Using true. ",
});

View file

@ -9,7 +9,7 @@ export default function Error() {
return (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">

View file

@ -43,7 +43,7 @@ export default function Login({ csrfToken }) {
</div>
<div className="w-1/2 text-right">
<Link href="/auth/forgot-password">
<a className="font-medium text-secondary-600 text-sm">Forgot?</a>
<a className="font-medium text-primary-600 text-sm">Forgot?</a>
</Link>
</div>
</div>

View file

@ -5,7 +5,7 @@ import { CheckIcon } from "@heroicons/react/outline";
export default function Logout() {
return (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
@ -33,7 +33,7 @@ export default function Logout() {
</div>
<div className="mt-5 sm:mt-6">
<Link href="/auth/login">
<a className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black sm:text-sm">
<a className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-black text-base font-medium text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black sm:text-sm">
Go back to the login page
</a>
</Link>

View file

@ -848,13 +848,13 @@ export default function EventTypePage({
</div>
{showLocationModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
@ -897,13 +897,13 @@ export default function EventTypePage({
)}
{showAddCustomModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"
/>

View file

@ -7,18 +7,20 @@ import { useRouter } from "next/router";
import { useRef, useState } from "react";
import { getSession, useSession } from "next-auth/client";
import { ClockIcon } from "@heroicons/react/outline";
import Loader from "@components/Loader";
export default function Availability(props) {
const [, loading] = useSession();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const router = useRouter();
// const [showAddModal, setShowAddModal] = useState(false);
const [showAddModal, setShowAddModal] = useState(false);
const [successModalOpen, setSuccessModalOpen] = useState(false);
const [showChangeTimesModal, setShowChangeTimesModal] = useState(false);
// const titleRef = useRef<HTMLInputElement>(); Everything is unused
// const slugRef = useRef<HTMLInputElement>();
// const descriptionRef = useRef<HTMLTextAreaElement>();
// const lengthRef = useRef<HTMLInputElement>();
// const isHiddenRef = useRef<HTMLInputElement>();
const titleRef = useRef<HTMLInputElement>();
const slugRef = useRef<HTMLInputElement>();
const descriptionRef = useRef<HTMLTextAreaElement>();
const lengthRef = useRef<HTMLInputElement>();
const isHiddenRef = useRef<HTMLInputElement>();
const startHoursRef = useRef<HTMLInputElement>();
const startMinsRef = useRef<HTMLInputElement>();
@ -28,16 +30,12 @@ export default function Availability(props) {
const bufferMinsRef = useRef<HTMLInputElement>();
if (loading) {
return (
<div className="loader">
<span className="loader-inner"></span>
</div>
);
return <Loader />;
}
// function toggleAddModal() { Unused as well
// setShowAddModal(!showAddModal);
// }
function toggleAddModal() {
setShowAddModal(!showAddModal);
}
function toggleChangeTimesModal() {
setShowChangeTimesModal(!showChangeTimesModal);
@ -55,10 +53,7 @@ export default function Availability(props) {
m = m < 10 ? "0" + m : m;
return `${h}:${m}`;
}
/*
Currently unused, so it's commented out.
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async function createEventTypeHandler(event) {
event.preventDefault();
@ -69,7 +64,7 @@ export default function Availability(props) {
const enteredIsHidden = isHiddenRef.current.checked;
// TODO: Add validation
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const response = await fetch("/api/availability/eventtype", {
method: "POST",
body: JSON.stringify({
@ -89,7 +84,7 @@ export default function Availability(props) {
toggleAddModal();
}
}
*/
async function updateStartEndTimesHandler(event) {
event.preventDefault();
@ -105,8 +100,8 @@ export default function Availability(props) {
const bufferMins = enteredBufferHours * 60 + enteredBufferMins;
// TODO: Add validation
await fetch("/api/availability/day", {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const response = await fetch("/api/availability/day", {
method: "PATCH",
body: JSON.stringify({ start: startMins, end: endMins, buffer: bufferMins }),
headers: {
@ -130,7 +125,7 @@ export default function Availability(props) {
">
<div className="flex">
<div className="w-1/2 mr-2 bg-white shadow rounded-sm">
<div className="w-1/2 mr-2 bg-white border border-gray-200 rounded-sm">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
Change the start and end times of your day
@ -149,7 +144,7 @@ export default function Availability(props) {
</div>
</div>
<div className="w-1/2 ml-2 bg-white shadow rounded-sm">
<div className="w-1/2 ml-2 border border-gray-200 rounded-sm">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
Something doesn&apos;t look right?
@ -159,7 +154,7 @@ export default function Availability(props) {
</div>
<div className="mt-5">
<Link href="/availability/troubleshoot">
<a className="btn btn-primary">Launch troubleshooter</a>
<a className="btn btn-white">Launch troubleshooter</a>
</Link>
</div>
</div>
@ -167,13 +162,13 @@ export default function Availability(props) {
</div>
{showChangeTimesModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">

View file

@ -6,20 +6,19 @@ import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { GetServerSideProps } from "next";
import prisma from "@lib/prisma";
import Loader from "@components/Loader";
dayjs.extend(utc);
export default function Troubleshoot({ user }) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const [availability, setAvailability] = useState([]);
const [selectedDate] = useState(dayjs());
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [selectedDate, setSelectedDate] = useState(dayjs());
if (loading) {
return (
<div className="loader">
<span className="loader-inner"></span>
</div>
);
return <Loader />;
}
function convertMinsToHrsMins(mins) {
@ -85,11 +84,7 @@ export default function Troubleshoot({ user }) {
</div>
</div>
))}
{availability.length === 0 && (
<div className="loader">
<span className="loader-inner"></span>
</div>
)}
{availability.length === 0 && <Loader />}
<div className="bg-black overflow-hidden rounded-sm">
<div className="px-4 sm:px-6 py-2 text-white">
Your day ends at {convertMinsToHrsMins(user.endTime)}

View file

@ -3,9 +3,17 @@ import prisma from "../../lib/prisma";
import { getSession, useSession } from "next-auth/client";
import Shell from "../../components/Shell";
import { useRouter } from "next/router";
import dayjs from "dayjs";
import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react";
import { DotsHorizontalIcon } from "@heroicons/react/solid";
import classNames from "@lib/classNames";
import { ClockIcon, XIcon } from "@heroicons/react/outline";
export default function Bookings({ bookings }) {
const [, loading] = useSession();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const router = useRouter();
if (loading) {
@ -35,7 +43,7 @@ export default function Bookings({ bookings }) {
<div className="-mx-4 sm:mx-auto flex flex-col">
<div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
<div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-sm">
<div className="border border-gray-200 overflow-hidden border-b rounded-sm">
<table className="min-w-full divide-y divide-gray-200">
<tbody className="bg-white divide-y divide-gray-200">
{bookings
@ -49,32 +57,33 @@ export default function Bookings({ bookings }) {
Unconfirmed
</span>
)}
<div className="text-sm font-medium text-gray-900">
{booking.attendees[0].name}
</div>
<div className="text-sm text-gray-500">{booking.attendees[0].email}</div>
<div
style={{ maxWidth: 150 }}
className="block lg:hidden font-medium text-xs text-gray-900 truncate">
<div className="text-sm text-neutral-900 font-medium truncate max-w-60 md:max-w-96">
{booking.title}
</div>
</td>
<td
className={
"px-6 py-4 max-w-20 w-full" + (booking.rejected ? " line-through" : "")
}>
<div className="hidden lg:block text-sm text-neutral-900 font-medium">
{booking.title}
<div className="sm:hidden">
<div className="text-sm text-gray-900">
{dayjs(booking.startTime).format("D MMMM YYYY")}:{" "}
<small className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} -{" "}
{dayjs(booking.endTime).format("HH:mm")}
</small>
</div>
</div>
<div className="hidden lg:block text-sm text-neutral-500">
You and {booking.attendees[0].name}
<div className="text-sm text-blue-500">
<a href={"mailto:" + booking.attendees[0].email}>
{booking.attendees[0].email}
</a>
</div>
</td>
<td className="hidden sm:table-cell px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900">
{dayjs(booking.startTime).format("D MMMM YYYY")}
</div>
<div className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} -{" "}
{dayjs(booking.endTime).format("HH:mm")}
</div>
</td>
{/* <td className="px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-500">
{dayjs(booking.startTime).format("D MMMM YYYY HH:mm")}
</div>
</td> */}
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{!booking.confirmed && !booking.rejected && (
<>
@ -85,7 +94,7 @@ export default function Bookings({ bookings }) {
</button>
<button
onClick={() => confirmBookingHandler(booking, false)}
className="text-xs sm:text-sm ml-4 inline-flex items-center px-4 py-2 border-transparent font-medium rounded-sm shadow-sm text-neutral-700 bg-white hover:bg-neutral-100 border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ml-2">
className="text-xs sm:text-sm inline-flex items-center px-4 py-2 border-transparent font-medium rounded-sm shadow-sm text-neutral-700 bg-white hover:bg-neutral-100 border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ml-2">
Reject
</button>
</>
@ -94,14 +103,89 @@ export default function Bookings({ bookings }) {
<>
<a
href={window.location.href + "/../cancel/" + booking.uid}
className="text-xs sm:text-sm inline-flex items-center px-4 py-2 border-transparent font-medium rounded-sm shadow-sm text-neutral-700 bg-white hover:bg-neutral-100 border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ml-2">
className="hidden text-xs sm:text-sm lg:inline-flex items-center px-4 py-2 border-transparent font-medium rounded-sm shadow-sm text-neutral-700 bg-white hover:bg-neutral-100 border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ml-2">
<XIcon
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Cancel
</a>
<a
href={window.location.href + "/../reschedule/" + booking.uid}
className="text-xs sm:text-sm inline-flex items-center px-4 py-2 border-transparent font-medium rounded-sm shadow-sm text-neutral-700 bg-white hover:bg-neutral-100 border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ml-2">
className="hidden text-xs sm:text-sm lg:inline-flex items-center px-4 py-2 border-transparent font-medium rounded-sm shadow-sm text-neutral-700 bg-white hover:bg-neutral-100 border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ml-2">
<ClockIcon
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Reschedule
</a>
<Menu as="div" className="inline-block lg:hidden text-left ">
{({ open }) => (
<>
<div>
<Menu.Button className="text-neutral-400 mt-1 p-2 border border-transparent hover:border-gray-200">
<span className="sr-only">Open options</span>
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95">
<Menu.Items
static
className="origin-top-right absolute right-0 mt-2 w-56 rounded-sm shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none divide-y divide-neutral-100">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<a
href={window.location.href + "/../cancel/" + booking.uid}
className={classNames(
active
? "bg-neutral-100 text-neutral-900"
: "text-neutral-700",
"group flex items-center px-4 py-2 text-sm font-medium"
)}>
<XIcon
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Cancel
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<a
href={
window.location.href + "/../reschedule/" + booking.uid
}
className={classNames(
active
? "bg-neutral-100 text-neutral-900"
: "text-neutral-700",
"group flex items-center px-4 py-2 text-sm w-full font-medium"
)}>
<ClockIcon
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Reschedule
</a>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</>
)}
</Menu>
</>
)}
{!booking.confirmed && booking.rejected && (
@ -137,7 +221,7 @@ export async function getServerSideProps(context) {
},
});
const bookings = await prisma.booking.findMany({
const b = await prisma.booking.findMany({
where: {
userId: user.id,
},
@ -149,11 +233,17 @@ export async function getServerSideProps(context) {
confirmed: true,
rejected: true,
id: true,
startTime: true,
endTime: true,
},
orderBy: {
startTime: "desc",
startTime: "asc",
},
});
const bookings = b.reverse().map((booking) => {
return { ...booking, startTime: booking.startTime.toISOString(), endTime: booking.endTime.toISOString() };
});
return { props: { bookings } };
}

View file

@ -1,172 +1,179 @@
import {useState} from 'react';
import Head from 'next/head';
import prisma from '../../lib/prisma';
import {useRouter} from 'next/router';
import dayjs from 'dayjs';
import {CalendarIcon, ClockIcon, XIcon} from '@heroicons/react/solid';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import isBetween from 'dayjs/plugin/isBetween';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import {collectPageParameters, telemetryEventTypes, useTelemetry} from "../../lib/telemetry";
import { useState } from "react";
import Head from "next/head";
import prisma from "../../lib/prisma";
import { useRouter } from "next/router";
import dayjs from "dayjs";
import { CalendarIcon, ClockIcon, XIcon } from "@heroicons/react/solid";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
import isBetween from "dayjs/plugin/isBetween";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
dayjs.extend(isSameOrBefore);
dayjs.extend(isBetween);
dayjs.extend(utc);
dayjs.extend(timezone);
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Type(props) {
// Get router variables
const router = useRouter();
const { uid } = router.query;
// Get router variables
const router = useRouter();
const { uid } = router.query;
const [is24h, setIs24h] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const telemetry = useTelemetry();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [is24h, setIs24h] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const telemetry = useTelemetry();
const cancellationHandler = async (event) => {
setLoading(true);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const cancellationHandler = async (event) => {
setLoading(true);
let payload = {
uid: uid
};
const payload = {
uid: uid,
};
telemetry.withJitsu(jitsu => jitsu.track(telemetryEventTypes.bookingCancelled, collectPageParameters()));
const res = await fetch(
'/api/cancel',
{
body: JSON.stringify(payload),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
}
);
if(res.status >= 200 && res.status < 300) {
router.push('/cancel/success?user=' + props.user.username + '&title=' + props.eventType.title);
} else {
setLoading(false);
setError("An error with status code " + res.status + " occurred. Please try again later.");
}
}
return (
<div>
<Head>
<title>
Cancel {props.booking.title} | {props.user.name || props.user.username} |
Calendso
</title>
<link rel="icon" href="/favicon.ico"/>
</Head>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed z-10 inset-0 overflow-y-auto">
<div
className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 my-4 sm:my-0 transition-opacity" aria-hidden="true">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6"
role="dialog" aria-modal="true" aria-labelledby="modal-headline">
{error && <div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
<XIcon className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
{error}
</h3>
</div>
</div>}
{!error && <div>
<div
className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
<XIcon className="h-6 w-6 text-red-600"/>
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
Really cancel your booking?
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">
Instead, you could also reschedule it.
</p>
</div>
<div className="mt-4 border-t border-b py-4">
<h2 className="text-lg font-medium text-gray-600 mb-2">{props.booking.title}</h2>
<p className="text-gray-500 mb-1">
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1"/>
{props.eventType.length} minutes
</p>
<p className="text-gray-500">
<CalendarIcon className="inline-block w-4 h-4 mr-1 -mt-1"/>
{dayjs.utc(props.booking.startTime).format((is24h ? 'H:mm' : 'h:mma') + ", dddd DD MMMM YYYY")}
</p>
</div>
</div>
</div>}
<div className="mt-5 sm:mt-6 text-center">
<div className="mt-5">
<button onClick={cancellationHandler} disabled={loading} type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm mx-2 btn-white">
Cancel
</button>
<button onClick={() => router.push('/reschedule/' + uid)} disabled={loading} type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-gray-700 bg-gray-100 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:text-sm mx-2 btn-white">
Reschedule
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
telemetry.withJitsu((jitsu) =>
jitsu.track(telemetryEventTypes.bookingCancelled, collectPageParameters())
);
const res = await fetch("/api/cancel", {
body: JSON.stringify(payload),
headers: {
"Content-Type": "application/json",
},
method: "POST",
});
if (res.status >= 200 && res.status < 300) {
router.push("/cancel/success?user=" + props.user.username + "&title=" + props.eventType.title);
} else {
setLoading(false);
setError("An error with status code " + res.status + " occurred. Please try again later.");
}
};
return (
<div>
<Head>
<title>
Cancel {props.booking.title} | {props.user.name || props.user.username} | Calendso
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 my-4 sm:my-0 transition-opacity" aria-hidden="true">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<div
className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
{error && (
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
<XIcon className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
{error}
</h3>
</div>
</div>
)}
{!error && (
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-red-100">
<XIcon className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
Really cancel your booking?
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">Instead, you could also reschedule it.</p>
</div>
<div className="mt-4 border-t border-b py-4">
<h2 className="text-lg font-medium text-gray-600 mb-2">{props.booking.title}</h2>
<p className="text-gray-500 mb-1">
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{props.eventType.length} minutes
</p>
<p className="text-gray-500">
<CalendarIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{dayjs
.utc(props.booking.startTime)
.format((is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY")}
</p>
</div>
</div>
</div>
)}
<div className="mt-5 sm:mt-6 text-center">
<div className="mt-5">
<button
onClick={cancellationHandler}
disabled={loading}
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm mx-2 btn-white">
Cancel
</button>
<button
onClick={() => router.push("/reschedule/" + uid)}
disabled={loading}
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-gray-700 bg-gray-100 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:text-sm mx-2 btn-white">
Reschedule
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</div>
);
}
export async function getServerSideProps(context) {
const booking = await prisma.booking.findFirst({
where: {
uid: context.query.uid,
},
const booking = await prisma.booking.findFirst({
where: {
uid: context.query.uid,
},
select: {
id: true,
title: true,
description: true,
startTime: true,
endTime: true,
attendees: true,
eventType: true,
user: {
select: {
id: true,
title: true,
description: true,
startTime: true,
endTime: true,
attendees: true,
eventType: true,
user: {
select: {
id: true,
username: true,
name: true,
}
}
}
});
// Workaround since Next.js has problems serializing date objects (see https://github.com/vercel/next.js/issues/11993)
const bookingObj = Object.assign({}, booking, {
startTime: booking.startTime.toString(),
endTime: booking.endTime.toString()
});
return {
props: {
user: booking.user,
eventType: booking.eventType,
booking: bookingObj
id: true,
username: true,
name: true,
},
}
},
},
});
// Workaround since Next.js has problems serializing date objects (see https://github.com/vercel/next.js/issues/11993)
const bookingObj = Object.assign({}, booking, {
startTime: booking.startTime.toString(),
endTime: booking.endTime.toString(),
});
return {
props: {
user: booking.user,
eventType: booking.eventType,
booking: bookingObj,
},
};
}

View file

@ -1,98 +1,92 @@
import {useState} from 'react';
import Head from 'next/head';
import prisma from '../../lib/prisma';
import {useRouter} from 'next/router';
import dayjs from 'dayjs';
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
import isBetween from 'dayjs/plugin/isBetween';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
import {CheckIcon} from "@heroicons/react/outline";
import Head from "next/head";
import prisma from "../../lib/prisma";
import { useRouter } from "next/router";
import dayjs from "dayjs";
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
import isBetween from "dayjs/plugin/isBetween";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import { CheckIcon } from "@heroicons/react/outline";
dayjs.extend(isSameOrBefore);
dayjs.extend(isBetween);
dayjs.extend(utc);
dayjs.extend(timezone);
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Type(props) {
// Get router variables
const router = useRouter();
// Get router variables
const router = useRouter();
const [is24h, setIs24h] = useState(false);
return (
<div>
<Head>
<title>
Cancelled {props.title} | {props.user.name || props.user.username} |
Calendso
</title>
<link rel="icon" href="/favicon.ico"/>
</Head>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed z-10 inset-0 overflow-y-auto">
<div
className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 my-4 sm:my-0 transition-opacity" aria-hidden="true">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen"
aria-hidden="true">&#8203;</span>
<div
className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6"
role="dialog" aria-modal="true" aria-labelledby="modal-headline">
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
<CheckIcon className="h-6 w-6 text-green-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
Cancellation successful
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">
Feel free to pick another event anytime.
</p>
</div>
</div>
</div>
<div className="mt-5 sm:mt-6 text-center">
<div className="mt-5">
<button onClick={() => router.push('/' + props.user.username)} type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-gray-700 bg-gray-100 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:text-sm mx-2 btn-white">
Pick another
</button>
</div>
</div>
</div>
</div>
return (
<div>
<Head>
<title>
Cancelled {props.title} | {props.user.name || props.user.username} | Calendso
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 my-4 sm:my-0 transition-opacity" aria-hidden="true">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<div
className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
<CheckIcon className="h-6 w-6 text-green-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-headline">
Cancellation successful
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">Feel free to pick another event anytime.</p>
</div>
</div>
</div>
</main>
<div className="mt-5 sm:mt-6 text-center">
<div className="mt-5">
<button
onClick={() => router.push("/" + props.user.username)}
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-gray-700 bg-gray-100 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-gray-500 sm:text-sm mx-2 btn-white">
Pick another
</button>
</div>
</div>
</div>
</div>
</div>
</div>
);
</main>
</div>
);
}
export async function getServerSideProps(context) {
const user = await prisma.user.findFirst({
where: {
username: context.query.user,
},
select: {
username: true,
name: true,
bio: true,
avatar: true,
eventTypes: true
}
});
const user = await prisma.user.findFirst({
where: {
username: context.query.user,
},
select: {
username: true,
name: true,
bio: true,
avatar: true,
eventTypes: true,
},
});
return {
props: {
user,
title: context.query.title
},
}
return {
props: {
user,
title: context.query.title,
},
};
}

View file

@ -20,7 +20,6 @@ import {
ExternalLinkIcon,
LinkIcon,
LocationMarkerIcon,
PencilIcon,
PlusIcon,
TrashIcon,
} from "@heroicons/react/solid";
@ -100,7 +99,6 @@ export default function EventTypePage({
availability,
}: Props): JSX.Element {
const router = useRouter();
//const [session, loading] = useSession(); unused, so it's commented out
console.log(eventType);
const inputOptions: OptionBase[] = [
@ -265,7 +263,7 @@ export default function EventTypePage({
},
});
router.push("/availability");
router.push("/event-types");
}
const openLocationModal = (type: LocationType) => {
@ -388,39 +386,32 @@ export default function EventTypePage({
<title>{eventType.title} | Event Type | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell heading={"Event Type: " + eventType.title} subtitle={eventType.description}>
<div className="flex">
<div className="w-10/12 mr-2">
<div className="bg-white rounded-sm border border-neutral-200 p-8">
<Shell
heading={
<input
ref={titleRef}
type="text"
name="title"
id="title"
required
className="pl-0 text-xl font-bold text-gray-900 cursor-pointer border-none focus:ring-0 bg-transparent focus:outline-none"
placeholder="Quick Chat"
defaultValue={eventType.title}
/>
}
subtitle={eventType.description}>
<div className="block sm:flex">
<div className="w-full sm:w-10/12 mr-2">
<div className="bg-white rounded-sm border border-neutral-200 -mx-4 sm:mx-0 p-4 sm:p-8">
<form onSubmit={updateEventTypeHandler} className="space-y-4">
<div className="flex">
<div className="w-1/4">
<label htmlFor="title" className="flex font-medium text-neutral-700 mt-1">
<PencilIcon className="w-4 h-4 mr-2 mt-1 text-neutral-500" />
Title
</label>
</div>
<div className="w-3/4">
<input
ref={titleRef}
type="text"
name="title"
id="title"
required
className="shadow-sm focus:ring-primary-500 focus:border-primary-500 block w-full sm:text-sm border-neutral-300 rounded-sm"
placeholder="Quick Chat"
defaultValue={eventType.title}
/>
</div>
</div>
<div className="flex">
<div className="w-1/4">
<label htmlFor="slug" className="flex font-medium text-neutral-700 mt-1">
<LinkIcon className="w-4 h-4 mr-2 mt-1 text-neutral-500" />
<div className="block sm:flex items-center">
<div className="min-w-44 mb-4 sm:mb-0">
<label htmlFor="slug" className="text-sm flex font-medium text-neutral-700 mt-0">
<LinkIcon className="w-4 h-4 mr-2 mt-0.5 text-neutral-500" />
URL
</label>
</div>
<div className="w-3/4">
<div className="w-full">
<div className="flex rounded-sm shadow-sm">
<span className="inline-flex items-center px-3 rounded-l-sm border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
{typeof location !== "undefined" ? location.hostname : ""}/{user.username}/
@ -437,14 +428,45 @@ export default function EventTypePage({
</div>
</div>
</div>
<div className="flex">
<div className="w-1/4">
<label htmlFor="location" className="flex font-medium text-neutral-700 mt-1">
<LocationMarkerIcon className="w-4 h-4 mr-2 mt-1 text-neutral-500" />
<div className="block sm:flex items-center">
<div className="min-w-44 mb-4 sm:mb-0">
<label htmlFor="length" className="text-sm flex font-medium text-neutral-700 mt-0">
<ClockIcon className="w-4 h-4 mr-2 mt-0.5 text-neutral-500" />
Duration
</label>
</div>
<div className="w-full">
<div className="mt-1 relative rounded-sm shadow-sm">
<input
ref={lengthRef}
type="number"
name="length"
id="length"
required
className="focus:ring-primary-500 focus:border-primary-500 block w-full pl-2 pr-12 sm:text-sm border-gray-300 rounded-sm"
placeholder="15"
defaultValue={eventType.length}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm" id="duration">
mins
</span>
</div>
</div>
</div>
</div>
<hr />
<div className="block sm:flex items-center">
<div className="min-w-44 mb-4 sm:mb-0">
<label htmlFor="location" className="text-sm flex font-medium text-neutral-700 mt-0">
<LocationMarkerIcon className="w-4 h-4 mr-2 mt-0.5 text-neutral-500" />
Location
</label>
</div>
<div className="w-3/4">
<div className="w-full">
{locations.length === 0 && (
<div className="mt-1 mb-2">
<div className="flex">
@ -468,19 +490,19 @@ export default function EventTypePage({
className="mb-2 p-2 border border-neutral-300 rounded-sm shadow-sm">
<div className="flex justify-between">
{location.type === LocationType.InPerson && (
<div className="flex-grow flex">
<div className="flex-grow flex items-center">
<LocationMarkerIcon className="h-6 w-6" />
<span className="ml-2 text-sm">{location.address}</span>
</div>
)}
{location.type === LocationType.Phone && (
<div className="flex-grow flex">
<div className="flex-grow flex items-center">
<PhoneIcon className="h-6 w-6" />
<span className="ml-2 text-sm">Phone call</span>
</div>
)}
{location.type === LocationType.GoogleMeet && (
<div className="flex-grow flex">
<div className="flex-grow flex items-center">
<svg
className="h-6 w-6"
viewBox="0 0 64 54"
@ -511,7 +533,7 @@ export default function EventTypePage({
</div>
)}
{location.type === LocationType.Zoom && (
<div className="flex-grow flex">
<div className="flex-grow flex items-center">
<svg
className="h-6 w-6"
viewBox="0 0 64 64"
@ -555,10 +577,12 @@ export default function EventTypePage({
<li>
<button
type="button"
className="sm:flex sm:items-start text-sm text-primary-600"
className="bg-neutral-100 rounded-sm py-2 px-3 flex"
onClick={() => setShowLocationModal(true)}>
<PlusIcon className="h-5 w-5" />
<span className="font-medium">Add another location option</span>
<PlusIcon className="h-4 w-4 mt-0.5 text-neutral-900" />
<span className="ml-1 text-neutral-700 text-sm font-medium">
Add another location
</span>
</button>
</li>
)}
@ -566,41 +590,17 @@ export default function EventTypePage({
)}
</div>
</div>
<div className="flex">
<div className="w-1/4">
<label htmlFor="length" className="flex font-medium text-neutral-700 mt-1">
<ClockIcon className="w-4 h-4 mr-2 mt-1 text-neutral-500" />
Duration
</label>
</div>
<div className="w-3/4">
<div className="mt-1 relative rounded-sm shadow-sm">
<input
ref={lengthRef}
type="number"
name="length"
id="length"
required
className="focus:ring-primary-500 focus:border-primary-500 block w-full pl-2 pr-12 sm:text-sm border-gray-300 rounded-sm"
placeholder="15"
defaultValue={eventType.length}
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center pointer-events-none">
<span className="text-gray-500 sm:text-sm" id="duration">
mins
</span>
</div>
</div>
</div>
</div>
<div className="flex">
<div className="w-1/4">
<label htmlFor="description" className="flex font-medium text-neutral-700 mt-1">
<DocumentIcon className="w-4 h-4 mr-2 mt-1 text-neutral-500" />
<hr className="border-neutral-200" />
<div className="block sm:flex items-center">
<div className="min-w-44 mb-4 sm:mb-0">
<label htmlFor="description" className="text-sm flex font-medium text-neutral-700 mt-0">
<DocumentIcon className="w-4 h-4 mr-2 mt-0.5 text-neutral-500" />
Description
</label>
</div>
<div className="w-3/4">
<div className="w-full">
<textarea
ref={descriptionRef}
name="description"
@ -620,13 +620,15 @@ export default function EventTypePage({
<span className="text-neutral-700 text-sm font-medium">Show advanced settings</span>
</Disclosure.Button>
<Disclosure.Panel className="space-y-4">
<div className="flex">
<div className="w-1/4">
<label htmlFor="eventName" className="flex font-medium text-neutral-700 mt-2">
<div className="block sm:flex items-center">
<div className="min-w-44 mb-4 sm:mb-0">
<label
htmlFor="eventName"
className="text-sm flex font-medium text-neutral-700 mt-2">
Event name
</label>
</div>
<div className="w-3/4">
<div className="w-full">
<div className="mt-1 relative rounded-sm shadow-sm">
<input
ref={eventNameRef}
@ -640,15 +642,15 @@ export default function EventTypePage({
</div>
</div>
</div>
<div className="flex">
<div className="w-1/4">
<div className="block sm:flex items-center">
<div className="min-w-44 mb-4 sm:mb-0">
<label
htmlFor="additionalFields"
className="flex font-medium text-neutral-700 mt-2">
className="text-sm flex font-medium text-neutral-700 mt-2">
Additional inputs
</label>
</div>
<div className="w-3/4">
<div className="w-full">
<ul className="w-96 mt-1">
{customInputs.map((customInput) => (
<li key={customInput.label} className="bg-secondary-50 mb-2 p-2 border">
@ -694,13 +696,13 @@ export default function EventTypePage({
</ul>
</div>
</div>
<div className="flex">
<div className="w-1/4">
<label htmlFor="hidden" className="flex font-medium text-neutral-700">
<div className="block sm:flex items-center">
<div className="min-w-44 mb-4 sm:mb-0">
<label htmlFor="hidden" className="text-sm flex font-medium text-neutral-700">
Hide event type
</label>
</div>
<div className="w-3/4">
<div className="w-full">
<div className="relative flex items-start">
<div className="flex items-center h-5">
<input
@ -721,15 +723,15 @@ export default function EventTypePage({
</div>
</div>
</div>
<div className="flex">
<div className="w-1/4">
<div className="block sm:flex items-center">
<div className="min-w-44 mb-4 sm:mb-0">
<label
htmlFor="requiresConfirmation"
className="flex font-medium text-neutral-700">
className="text-sm flex font-medium text-neutral-700">
Opt-in booking
</label>
</div>
<div className="w-3/4">
<div className="w-full">
<div className="relative flex items-start">
<div className="flex items-center h-5">
<input
@ -750,15 +752,18 @@ export default function EventTypePage({
</div>
</div>
</div>
<div className="flex">
<div className="w-1/4">
<hr className="border-neutral-200" />
<div className="block sm:flex">
<div className="min-w-44 mb-4 sm:mb-0">
<label
htmlFor="inviteesCanSchedule"
className="flex font-medium text-neutral-700 mt-2">
className="text-sm flex font-medium text-neutral-700 mt-2">
Invitees can schedule
</label>
</div>
<div className="w-3/4">
<div className="w-full">
<RadioGroup value={periodType} onChange={setPeriodType}>
<RadioGroup.Label className="sr-only">Date Range</RadioGroup.Label>
<div>
@ -769,22 +774,22 @@ export default function EventTypePage({
className={({ checked }) =>
classnames(
checked ? "border-secondary-200 z-10" : "border-gray-200",
"relative min-h-14 lg:flex items-center cursor-pointer focus:outline-none"
"relative min-h-14 flex items-center cursor-pointer focus:outline-none"
)
}>
{({ active, checked }) => (
<>
<span
<div
className={classnames(
checked
? "bg-primary-600 border-transparent"
: "bg-white border-gray-300",
active ? "ring-2 ring-offset-2 ring-primary-500" : "",
"h-4 w-4 mt-0.5 cursor-pointer rounded-full border flex items-center justify-center"
"h-4 w-4 mt-0.5 mr-2 cursor-pointer rounded-full border items-center justify-center"
)}
aria-hidden="true">
<span className="rounded-full bg-white w-1.5 h-1.5" />
</span>
</div>
<div className="lg:ml-3 flex flex-col">
<RadioGroup.Label
as="span"
@ -849,13 +854,18 @@ export default function EventTypePage({
</RadioGroup>
</div>
</div>
<div className="flex">
<div className="w-1/4">
<label htmlFor="availability" className="flex font-medium text-neutral-700 mt-2">
<hr className="border-neutral-200" />
<div className="block sm:flex">
<div className="min-w-44 mb-4 sm:mb-0">
<label
htmlFor="availability"
className="text-sm flex font-medium text-neutral-700 mt-2">
Availability
</label>
</div>
<div className="w-3/4">
<div className="w-full">
<Scheduler
setAvailability={setEnteredAvailability}
setTimeZone={setSelectedTimeZone}
@ -883,7 +893,7 @@ export default function EventTypePage({
</form>
</div>
</div>
<div className="w-2/12 ml-2 px-4">
<div className="w-full sm:w-2/12 ml-2 px-4 mt-8 sm:mt-0 min-w-32">
<div className="space-y-4">
<a
href={"/" + user.username + "/" + eventType.slug}
@ -902,7 +912,7 @@ export default function EventTypePage({
type="button"
className="flex text-md font-medium text-neutral-700">
<LinkIcon className="w-4 h-4 mt-1 mr-2 text-neutral-500" />
Copy link to event
Copy link
</button>
<button
onClick={deleteEventTypeHandler}
@ -916,13 +926,13 @@ export default function EventTypePage({
</div>
{showLocationModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
@ -966,13 +976,13 @@ export default function EventTypePage({
)}
{showAddCustomModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"
/>

View file

@ -1,10 +1,6 @@
import Head from "next/head";
import Link from "next/link";
import prisma from "../../lib/prisma";
import Shell from "../../components/Shell";
import { useRouter } from "next/router";
import { getSession, useSession } from "next-auth/client";
import { Fragment, useRef, useState } from "react";
import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@components/Dialog";
import { Tooltip } from "@components/Tooltip";
import Loader from "@components/Loader";
import { Menu, Transition } from "@headlessui/react";
import {
ClockIcon,
@ -15,15 +11,18 @@ import {
PlusIcon,
UserIcon,
} from "@heroicons/react/solid";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
import classNames from "@lib/classNames";
import { getSession, useSession } from "next-auth/client";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { Fragment, useRef } from "react";
import Shell from "../../components/Shell";
import prisma from "../../lib/prisma";
export default function Availability({ user, types }) {
const [session, loading] = useSession();
const router = useRouter();
const [showAddModal, setShowAddModal] = useState(false);
const titleRef = useRef<HTMLInputElement>();
const slugRef = useRef<HTMLInputElement>();
@ -39,7 +38,6 @@ export default function Availability({ user, types }) {
const enteredLength = lengthRef.current.value;
// TODO: Add validation
await fetch("/api/availability/eventtype", {
method: "POST",
body: JSON.stringify({
@ -54,23 +52,120 @@ export default function Availability({ user, types }) {
});
if (enteredTitle && enteredLength) {
router.replace(router.asPath);
toggleAddModal();
await router.replace(router.asPath);
}
}
function toggleAddModal() {
setShowAddModal(!showAddModal);
function autoPopulateSlug() {
let t = titleRef.current.value;
t = t.replace(/\s+/g, "-").toLowerCase();
slugRef.current.value = t;
}
if (loading) {
return (
<div className="loader">
<span className="loader-inner"></span>
</div>
);
return <Loader />;
}
const CreateNewEventDialog = () => (
<Dialog>
<DialogTrigger className="py-2 px-4 mt-6 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<PlusIcon className="w-5 h-5 mr-1 inline" />
New event type
</DialogTrigger>
<DialogContent>
<div className="mb-8">
<h3 className="text-lg leading-6 font-bold text-gray-900" id="modal-title">
Add a new event type
</h3>
<div>
<p className="text-sm text-gray-500">Create a new event type for people to book times with.</p>
</div>
</div>
<form onSubmit={createEventTypeHandler}>
<div>
<div className="mb-4">
<label htmlFor="title" className="block text-sm font-medium text-gray-700">
Title
</label>
<div className="mt-1">
<input
onChange={autoPopulateSlug}
ref={titleRef}
type="text"
name="title"
id="title"
required
className="shadow-sm focus:ring-neutral-900 focus:border-neutral-900 block w-full sm:text-sm border-gray-300 rounded-sm"
placeholder="Quick Chat"
/>
</div>
</div>
<div className="mb-4">
<label htmlFor="slug" className="block text-sm font-medium text-gray-700">
URL
</label>
<div className="mt-1">
<div className="flex rounded-sm shadow-sm">
<span className="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
{location.hostname}/{user.username}/
</span>
<input
ref={slugRef}
type="text"
name="slug"
id="slug"
required
className="flex-1 block w-full focus:ring-neutral-900 focus:border-neutral-900 min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
/>
</div>
</div>
</div>
<div className="mb-4">
<label htmlFor="description" className="block text-sm font-medium text-gray-700">
Description
</label>
<div className="mt-1">
<textarea
ref={descriptionRef}
name="description"
id="description"
className="shadow-sm focus:ring-neutral-900 focus:border-neutral-900 block w-full sm:text-sm border-gray-300 rounded-sm"
placeholder="A quick video meeting."></textarea>
</div>
</div>
<div className="mb-4">
<label htmlFor="length" className="block text-sm font-medium text-gray-700">
Length
</label>
<div className="mt-1 relative rounded-sm shadow-sm">
<input
ref={lengthRef}
type="number"
name="length"
id="length"
required
className="focus:ring-neutral-900 focus:border-neutral-900 block w-full pr-20 sm:text-sm border-gray-300 rounded-sm"
placeholder="15"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm">
minutes
</div>
</div>
</div>
</div>
<div className="mt-8 sm:flex sm:flex-row-reverse">
<button type="submit" className="btn btn-primary">
Continue
</button>
<DialogClose as="button" className="btn btn-white mx-2">
Cancel
</DialogClose>
</div>
</form>
</DialogContent>
</Dialog>
);
return (
<div>
<Head>
@ -80,23 +175,16 @@ export default function Availability({ user, types }) {
<Shell
heading="Event Types"
subtitle="Create events to share for people to book on your calendar."
CTA={
<button
onClick={toggleAddModal}
className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<PlusIcon className="w-5 h-5 mr-1" />
New event type
</button>
}>
<div className="bg-white shadow overflow-hidden sm:rounded-sm">
CTA={types.length !== 0 && <CreateNewEventDialog />}>
<div className="bg-white border border-gray-200 rounded-sm overflow-hidden -mx-4 sm:mx-0">
<ul className="divide-y divide-neutral-200">
{types.map((type) => (
<li key={type.id}>
<Link href={"/event-types/" + type.id}>
<a className="block hover:bg-neutral-50">
<div className="px-4 py-4 flex items-center sm:px-6">
<div className="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
<div className="truncate">
<div className="hover:bg-neutral-50">
<div className="px-4 py-4 flex items-center sm:px-6">
<Link href={"/event-types/" + type.id}>
<a className="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
<span className="truncate ">
<div className="flex text-sm">
<p className="font-medium text-neutral-900 truncate">{type.title}</p>
{type.hidden && (
@ -108,164 +196,174 @@ export default function Availability({ user, types }) {
<div className="mt-2 flex space-x-4">
<div className="flex items-center text-sm text-neutral-500">
<ClockIcon
className="flex-shrink-0 mr-1.5 h-5 w-5 text-neutral-400"
className="flex-shrink-0 mr-1.5 h-4 w-4 text-neutral-400"
aria-hidden="true"
/>
<p>{type.length}m</p>
</div>
<div className="flex items-center text-sm text-neutral-500">
<UserIcon
className="flex-shrink-0 mr-1.5 h-5 w-5 text-neutral-400"
className="flex-shrink-0 mr-1.5 h-4 w-4 text-neutral-400"
aria-hidden="true"
/>
<p>1-on-1</p>
</div>
<div className="flex items-center text-sm text-neutral-500">
<InformationCircleIcon
className="flex-shrink-0 mr-1.5 h-5 w-5 text-neutral-400"
className="flex-shrink-0 mr-1.5 h-4 w-4 text-neutral-400"
aria-hidden="true"
/>
<p>{type.description.substring(0, 100)}</p>
<div className="max-w-32 sm:max-w-full truncate">
{type.description.substring(0, 100)}
</div>
</div>
</div>
</div>
<div className="mt-4 flex-shrink-0 sm:mt-0 sm:ml-5">
<div className="flex overflow-hidden space-x-5">
<Link href={"/" + session.user.username + "/" + type.slug}>
<a className="text-neutral-400">
<ExternalLinkIcon className="w-5 h-5" />
</a>
</Link>
<button
onClick={() => {
navigator.clipboard.writeText(
window.location.hostname + "/" + session.user.username + "/" + type.slug
);
}}
className="text-neutral-400">
<LinkIcon className="w-5 h-5" />
</button>
</div>
</div>
</div>
<div className="ml-5 flex-shrink-0">
<Menu as="div" className="inline-block text-left">
{({ open }) => (
<>
<div>
<Menu.Button className="text-neutral-400 mt-1">
<span className="sr-only">Open options</span>
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>
</span>
</a>
</Link>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95">
<Menu.Items
static
className="origin-top-right absolute right-0 mt-2 w-56 rounded-sm shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none divide-y divide-neutral-100">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<a
href={"/" + session.user.username + "/" + type.slug}
target="_blank"
rel="noreferrer"
className={classNames(
active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",
"group flex items-center px-4 py-2 text-sm font-medium"
)}>
<ExternalLinkIcon
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Preview
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
onClick={() => {
navigator.clipboard.writeText(
window.location.hostname +
"/" +
session.user.username +
"/" +
type.slug
);
}}
className={classNames(
active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",
"group flex items-center px-4 py-2 text-sm w-full font-medium"
)}>
<LinkIcon
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Copy link to event
</button>
)}
</Menu.Item>
{/*<Menu.Item>*/}
{/* {({ active }) => (*/}
{/* <a*/}
{/* href="#"*/}
{/* className={classNames(*/}
{/* active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",*/}
{/* "group flex items-center px-4 py-2 text-sm font-medium"*/}
{/* )}>*/}
{/* <DuplicateIcon*/}
{/* className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"*/}
{/* aria-hidden="true"*/}
{/* />*/}
{/* Duplicate*/}
{/* </a>*/}
{/* )}*/}
{/*</Menu.Item>*/}
</div>
{/*<div className="py-1">*/}
{/* <Menu.Item>*/}
{/* {({ active }) => (*/}
{/* <a*/}
{/* href="#"*/}
{/* className={classNames(*/}
{/* active ? "bg-red-100 text-red-900" : "text-red-700",*/}
{/* "group flex items-center px-4 py-2 text-sm font-medium"*/}
{/* )}>*/}
{/* <TrashIcon*/}
{/* className="mr-3 h-5 w-5 text-red-400 group-hover:text-red-700"*/}
{/* aria-hidden="true"*/}
{/* />*/}
{/* Delete*/}
{/* </a>*/}
{/* )}*/}
{/* </Menu.Item>*/}
{/*</div>*/}
</Menu.Items>
</Transition>
</>
)}
</Menu>
<div className="hidden sm:flex mt-4 flex-shrink-0 sm:mt-0 sm:ml-5">
<div className="flex overflow-hidden space-x-5">
<Tooltip content="Preview">
<a
href={"/" + session.user.username + "/" + type.slug}
target="_blank"
rel="noreferrer"
className="group cursor-pointer text-neutral-400 p-2 border border-transparent hover:border-gray-200">
<ExternalLinkIcon className="group-hover:text-black w-5 h-5" />
</a>
</Tooltip>
<Tooltip content="Copy link">
<button
onClick={() => {
navigator.clipboard.writeText(
window.location.hostname + "/" + session.user.username + "/" + type.slug
);
}}
className="group text-neutral-400 p-2 border border-transparent hover:border-gray-200">
<LinkIcon className="group-hover:text-black w-5 h-5" />
</button>
</Tooltip>
</div>
</div>
</a>
</Link>
<div className="flex sm:hidden ml-5 flex-shrink-0">
<Menu as="div" className="inline-block text-left">
{({ open }) => (
<>
<div>
<Menu.Button className="text-neutral-400 mt-1 p-2 border border-transparent hover:border-gray-200">
<span className="sr-only">Open options</span>
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95">
<Menu.Items
static
className="origin-top-right absolute right-0 mt-2 w-56 rounded-sm shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none divide-y divide-neutral-100">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<a
href={"/" + session.user.username + "/" + type.slug}
target="_blank"
rel="noreferrer"
className={classNames(
active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",
"group flex items-center px-4 py-2 text-sm font-medium"
)}>
<ExternalLinkIcon
className="mr-3 h-4 w-4 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Preview
</a>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<button
onClick={() => {
navigator.clipboard.writeText(
window.location.hostname +
"/" +
session.user.username +
"/" +
type.slug
);
}}
className={classNames(
active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",
"group flex items-center px-4 py-2 text-sm w-full font-medium"
)}>
<LinkIcon
className="mr-3 h-4 w-4 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Copy link to event
</button>
)}
</Menu.Item>
{/*<Menu.Item>*/}
{/* {({ active }) => (*/}
{/* <a*/}
{/* href="#"*/}
{/* className={classNames(*/}
{/* active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700",*/}
{/* "group flex items-center px-4 py-2 text-sm font-medium"*/}
{/* )}>*/}
{/* <DuplicateIcon*/}
{/* className="mr-3 h-4 w-4 text-neutral-400 group-hover:text-neutral-500"*/}
{/* aria-hidden="true"*/}
{/* />*/}
{/* Duplicate*/}
{/* </a>*/}
{/* )}*/}
{/*</Menu.Item>*/}
</div>
{/*<div className="py-1">*/}
{/* <Menu.Item>*/}
{/* {({ active }) => (*/}
{/* <a*/}
{/* href="#"*/}
{/* className={classNames(*/}
{/* active ? "bg-red-100 text-red-900" : "text-red-700",*/}
{/* "group flex items-center px-4 py-2 text-sm font-medium"*/}
{/* )}>*/}
{/* <TrashIcon*/}
{/* className="mr-3 h-5 w-5 text-red-400 group-hover:text-red-700"*/}
{/* aria-hidden="true"*/}
{/* />*/}
{/* Delete*/}
{/* </a>*/}
{/* )}*/}
{/* </Menu.Item>*/}
{/*</div>*/}
</Menu.Items>
</Transition>
</>
)}
</Menu>
</div>
</div>
</div>
</li>
))}
</ul>
</div>
{types.length === 0 && (
<div className="text-center max-w-lg mx-auto">
<div className="md:py-20">
<svg
className="mx-auto mb-4 w-32 h-32"
className="w-1/2 md:w-32 mx-auto block mb-4"
viewBox="0 0 132 132"
fill="none"
xmlns="http://www.w3.org/2000/svg">
@ -502,122 +600,13 @@ export default function Availability({ user, types }) {
</clipPath>
</defs>
</svg>
<h3 className="mt-2 text-xl font-bold text-neutral-900">Create your first event type</h3>
<p className="mt-1 text-md text-neutral-600">
Event types enable you to share links that show available times on your calendar and allow
people to make bookings with you.
</p>
</div>
)}
{showAddModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<div className="inline-block align-bottom bg-white rounded-sm px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div className="mb-4">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
Add a new event type
</h3>
<div>
<p className="text-sm text-gray-500">
Create a new event type for people to book times with.
</p>
</div>
</div>
<form onSubmit={createEventTypeHandler}>
<div>
<div className="mb-4">
<label htmlFor="title" className="block text-sm font-medium text-gray-700">
Title
</label>
<div className="mt-1">
<input
ref={titleRef}
type="text"
name="title"
id="title"
required
className="shadow-sm focus:ring-neutral-900 focus:border-neutral-900 block w-full sm:text-sm border-gray-300 rounded-sm"
placeholder="Quick Chat"
/>
</div>
</div>
<div className="mb-4">
<label htmlFor="slug" className="block text-sm font-medium text-gray-700">
URL
</label>
<div className="mt-1">
<div className="flex rounded-sm shadow-sm">
<span className="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
{location.hostname}/{user.username}/
</span>
<input
ref={slugRef}
type="text"
name="slug"
id="slug"
required
className="flex-1 block w-full focus:ring-neutral-900 focus:border-neutral-900 min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
/>
</div>
</div>
</div>
<div className="mb-4">
<label htmlFor="description" className="block text-sm font-medium text-gray-700">
Description
</label>
<div className="mt-1">
<textarea
ref={descriptionRef}
name="description"
id="description"
className="shadow-sm focus:ring-neutral-900 focus:border-neutral-900 block w-full sm:text-sm border-gray-300 rounded-sm"
placeholder="A quick video meeting."></textarea>
</div>
</div>
<div className="mb-4">
<label htmlFor="length" className="block text-sm font-medium text-gray-700">
Length
</label>
<div className="mt-1 relative rounded-sm shadow-sm">
<input
ref={lengthRef}
type="number"
name="length"
id="length"
required
className="focus:ring-neutral-900 focus:border-neutral-900 block w-full pr-20 sm:text-sm border-gray-300 rounded-sm"
placeholder="15"
/>
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-gray-400 text-sm">
minutes
</div>
</div>
</div>
</div>
{/* TODO: Add an error message when required input fields empty*/}
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button type="submit" className="btn btn-primary">
Create
</button>
<button onClick={toggleAddModal} type="button" className="btn btn-white mr-2">
Cancel
</button>
</div>
</form>
</div>
<div className="text-center block md:max-w-screen-sm mx-auto">
<h3 className="mt-2 text-xl font-bold text-neutral-900">Create your first event type</h3>
<p className="mt-1 text-md text-neutral-600">
Event types enable you to share links that show available times on your calendar and allow
people to make bookings with you.
</p>
<CreateNewEventDialog />
</div>
</div>
)}

View file

@ -1,3 +1,4 @@
import Loader from "@components/Loader";
import { useRouter } from "next/router";
function RedirectPage() {
@ -6,6 +7,7 @@ function RedirectPage() {
router.push("/event-types");
return;
}
return <Loader />;
}
RedirectPage.getInitialProps = (ctx) => {

View file

@ -4,19 +4,18 @@ import { getIntegrationName, getIntegrationType } from "../../lib/integrations";
import Shell from "../../components/Shell";
import { useState } from "react";
import { useRouter } from "next/router";
import { useSession } from "next-auth/client";
import { getSession, useSession } from "next-auth/client";
import Loader from "@components/Loader";
export default function Integration(props) {
const router = useRouter();
const [, loading] = useSession();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const [showAPIKey, setShowAPIKey] = useState(false);
if (loading) {
return (
<div className="loader">
<span className="loader-inner"></span>
</div>
);
return <Loader />;
}
function toggleShowAPIKey() {
@ -26,13 +25,15 @@ export default function Integration(props) {
async function deleteIntegrationHandler(event) {
event.preventDefault();
await fetch("/api/integrations", {
/*eslint-disable */
const response = await fetch("/api/integrations", {
method: "DELETE",
body: JSON.stringify({ id: props.integration.id }),
headers: {
"Content-Type": "application/json",
},
});
/*eslint-enable */
router.push("/integrations");
}
@ -40,27 +41,27 @@ export default function Integration(props) {
return (
<div>
<Head>
<title>{getIntegrationName(props.integration.type)} | Integrations | Calendso</title>
<title>{getIntegrationName(props.integration.type)} App | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell heading={getIntegrationName(props.integration.type)} subtitle="Manage and delete integrations.">
<div className="grid grid-cols-3 gap-4">
<div className="col-span-2 bg-white shadow overflow-hidden rounded-sm">
<Shell heading={getIntegrationName(props.integration.type)} subtitle="Manage and delete this app.">
<div className="block sm:grid grid-cols-3 gap-4">
<div className="col-span-2 bg-white border border-gray-200 mb-6 overflow-hidden rounded-sm">
<div className="px-4 py-5 sm:px-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">Integration Details</h3>
<p className="mt-1 max-w-2xl text-sm text-gray-500">
Information about your {getIntegrationName(props.integration.type)} integration.
Information about your {getIntegrationName(props.integration.type)} App.
</p>
</div>
<div className="border-t border-gray-200 px-4 py-5 sm:px-6">
<dl className="grid gap-y-8">
<div>
<dt className="text-sm font-medium text-gray-500">Integration name</dt>
<dt className="text-sm font-medium text-gray-500">App name</dt>
<dd className="mt-1 text-sm text-gray-900">{getIntegrationName(props.integration.type)}</dd>
</div>
<div>
<dt className="text-sm font-medium text-gray-500">Integration type</dt>
<dt className="text-sm font-medium text-gray-500">App Category</dt>
<dd className="mt-1 text-sm text-gray-900">{getIntegrationType(props.integration.type)}</dd>
</div>
<div>
@ -90,18 +91,18 @@ export default function Integration(props) {
</div>
</div>
<div>
<div className="bg-white shadow rounded-sm">
<div className="bg-white border border-gray-200 mb-6 rounded-sm">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">Delete this integration</h3>
<h3 className="text-lg leading-6 font-medium text-gray-900">Delete this app</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<p>Once you delete this integration, it will be permanently removed.</p>
<p>Once you delete this app, it will be permanently removed.</p>
</div>
<div className="mt-5">
<button
onClick={deleteIntegrationHandler}
type="button"
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-sm text-red-700 bg-red-100 hover:bg-red-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm">
Delete integration
Delete App
</button>
</div>
</div>
@ -114,7 +115,8 @@ export default function Integration(props) {
}
export async function getServerSideProps(context) {
//const session = await getSession(context); Session is never used right now
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const session = await getSession(context);
const integration = await prisma.credential.findFirst({
where: {

View file

@ -4,31 +4,18 @@ import prisma from "../../lib/prisma";
import Shell from "../../components/Shell";
import { useEffect, useState } from "react";
import { getSession, useSession } from "next-auth/client";
import {
CalendarIcon,
CheckCircleIcon,
ChevronRightIcon,
PlusIcon,
XCircleIcon,
} from "@heroicons/react/solid";
import { CheckCircleIcon, ChevronRightIcon, PlusIcon, XCircleIcon } from "@heroicons/react/solid";
import { InformationCircleIcon } from "@heroicons/react/outline";
import { Switch } from "@headlessui/react";
import Loader from "@components/Loader";
import classNames from "@lib/classNames";
import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTrigger } from "@components/Dialog";
export default function Home({ integrations }) {
const [, loading] = useSession();
const [showAddModal, setShowAddModal] = useState(false);
const [showSelectCalendarModal, setShowSelectCalendarModal] = useState(false);
export default function IntegrationHome({ integrations }) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const [selectableCalendars, setSelectableCalendars] = useState([]);
function toggleAddModal() {
setShowAddModal(!showAddModal);
}
function toggleShowCalendarModal() {
setShowSelectCalendarModal(!showSelectCalendarModal);
}
function loadCalendars() {
fetch("api/availability/calendar")
.then((response) => response.json())
@ -80,40 +67,119 @@ export default function Home({ integrations }) {
}
}
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
useEffect(loadCalendars, [integrations]);
if (loading) {
return (
<div className="loader">
<span className="loader-inner"></span>
</div>
);
return <Loader />;
}
const ConnectNewAppDialog = () => (
<Dialog>
<DialogTrigger className="py-2 px-4 mt-6 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<PlusIcon className="w-5 h-5 mr-1 inline" />
Connect a new App
</DialogTrigger>
<DialogContent>
<DialogHeader title="Connect a new App" subtitle="Connect a new app to your account." />
<div className="my-4">
<ul className="divide-y divide-gray-200">
{integrations
.filter((integration) => integration.installed)
.map((integration) => (
<li key={integration.type} className="flex py-4">
<div className="w-1/12 mr-4 pt-2">
<img className="h-8 w-8 mr-2" src={integration.imageSrc} alt={integration.title} />
</div>
<div className="w-10/12">
<h2 className="text-gray-800 font-medium">{integration.title}</h2>
<p className="text-gray-400 text-sm">{integration.description}</p>
</div>
<div className="w-2/12 text-right pt-2">
<button
onClick={() => integrationHandler(integration.type)}
className="font-medium text-neutral-900 hover:text-neutral-500">
Add
</button>
</div>
</li>
))}
</ul>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose as="button" className="btn btn-white mx-2">
Cancel
</DialogClose>
</div>
</DialogContent>
</Dialog>
);
const SelectCalendarDialog = () => (
<Dialog>
<DialogTrigger className="py-2 px-4 mt-6 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
Select calendars
</DialogTrigger>
<DialogContent>
<DialogHeader
title="Select calendars"
subtitle="If no entry is selected, all calendars will be checked"
/>
<div className="my-4">
<ul className="divide-y divide-gray-200">
{selectableCalendars.map((calendar) => (
<li key={calendar.name} className="flex py-4">
<div className="w-1/12 mr-4 pt-2">
<img
className="h-8 w-8 mr-2"
src={getCalendarIntegrationImage(calendar.integration)}
alt={calendar.integration}
/>
</div>
<div className="w-10/12 pt-3">
<h2 className="text-gray-800 font-medium">{calendar.name}</h2>
</div>
<div className="w-2/12 text-right pt-3">
<Switch
checked={calendar.selected}
onChange={calendarSelectionHandler(calendar)}
className={classNames(
calendar.selected ? "bg-neutral-900" : "bg-gray-200",
"relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500"
)}>
<span className="sr-only">Select calendar</span>
<span
aria-hidden="true"
className={classNames(
calendar.selected ? "translate-x-5" : "translate-x-0",
"pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
)}
/>
</Switch>
</div>
</li>
))}
</ul>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose as="button" className="btn btn-white mx-2">
Cancel
</DialogClose>
</div>
</DialogContent>
</Dialog>
);
return (
<div>
<Head>
<title>Integrations | Calendso</title>
<title>App Store | Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<Shell
heading="Integrations"
subtitle="Connect your favourite apps."
CTA={
<button
onClick={toggleAddModal}
type="button"
className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<PlusIcon className="w-5 h-5 mr-1" />
Add new integration
</button>
}>
<div className="bg-white shadow overflow-hidden rounded-sm mb-8">
<Shell heading="App Store" subtitle="Connect your favourite apps." CTA={<ConnectNewAppDialog />}>
<div className="bg-white border border-gray-200 overflow-hidden rounded-sm mb-8">
{integrations.filter((ig) => ig.credential).length !== 0 ? (
<ul className="divide-y divide-gray-200">
{integrations
@ -172,224 +238,41 @@ export default function Home({ integrations }) {
</div>
<div className="py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
You don&apos;t have any integrations added.
You don&apos;t have any apps connected.
</h3>
<div className="mt-2 text-sm text-gray-500">
<p>
You currently do not have any integrations set up. Add your first integration to get
started.
You currently do not have any apps connected. Connect your first app to get started.
</p>
</div>
<div className="mt-3 text-sm">
<button
onClick={toggleAddModal}
className="font-medium text-neutral-900 hover:text-neutral-500">
{" "}
Add your first integration <span aria-hidden="true">&rarr;</span>
</button>
</div>
<ConnectNewAppDialog />
</div>
</div>
</div>
)}
</div>
{showAddModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
{/* <!--
Background overlay, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "ease-in duration-200"
From: "opacity-100"
To: "opacity-0"
--> */}
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
{/* <!--
Modal panel, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
To: "opacity-100 translate-y-0 sm:scale-100"
Leaving: "ease-in duration-200"
From: "opacity-100 translate-y-0 sm:scale-100"
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
--> */}
<div className="inline-block align-bottom bg-white rounded-sm px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-neutral-100 sm:mx-0 sm:h-10 sm:w-10">
<PlusIcon className="h-6 w-6 text-neutral-900" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
Add a new integration
</h3>
<div>
<p className="text-sm text-gray-400">Link a new integration to your account.</p>
</div>
</div>
</div>
<div className="my-4">
<ul className="divide-y divide-gray-200">
{integrations
.filter((integration) => integration.installed)
.map((integration) => (
<li key={integration.type} className="flex py-4">
<div className="w-1/12 mr-4 pt-2">
<img
className="h-8 w-8 mr-2"
src={integration.imageSrc}
alt={integration.title}
/>
</div>
<div className="w-10/12">
<h2 className="text-gray-800 font-medium">{integration.title}</h2>
<p className="text-gray-400 text-sm">{integration.description}</p>
</div>
<div className="w-2/12 text-right pt-2">
<button
onClick={() => integrationHandler(integration.type)}
className="font-medium text-neutral-900 hover:text-neutral-500">
Add
</button>
</div>
</li>
))}
</ul>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
onClick={toggleAddModal}
type="button"
className="mt-3 w-full inline-flex justify-center rounded-sm border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500 sm:mt-0 sm:w-auto sm:text-sm">
Close
</button>
</div>
</div>
</div>
</div>
)}
<div className="bg-white shadow rounded-sm">
<div className="bg-white border border-gray-200 rounded-sm mb-8">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">Select calendars</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<p>Select which calendars are checked for availability to prevent double bookings.</p>
</div>
<SelectCalendarDialog />
</div>
</div>
<div className="border border-gray-200 rounded-sm">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">Launch your own App</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<p>If you want to add your own App here, get in touch with us.</p>
</div>
<div className="mt-5">
<button type="button" onClick={toggleShowCalendarModal} className="btn btn-primary">
Select calendars
</button>
<a href="apps@calendso.com" className="btn btn-white">
Contact us
</a>
</div>
</div>
</div>
{showSelectCalendarModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
{/* <!--
Background overlay, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0"
To: "opacity-100"
Leaving: "ease-in duration-200"
From: "opacity-100"
To: "opacity-0"
--> */}
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
{/* <!--
Modal panel, show/hide based on modal state.
Entering: "ease-out duration-300"
From: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
To: "opacity-100 translate-y-0 sm:scale-100"
Leaving: "ease-in duration-200"
From: "opacity-100 translate-y-0 sm:scale-100"
To: "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
--> */}
<div className="inline-block align-bottom bg-white rounded-sm px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div className="sm:flex sm:items-start">
<div className="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-neutral-100 sm:mx-0 sm:h-10 sm:w-10">
<CalendarIcon className="h-6 w-6 text-neutral-900" />
</div>
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
Select calendars
</h3>
<div>
<p className="text-sm text-gray-400">
If no entry is selected, all calendars will be checked
</p>
</div>
</div>
</div>
<div className="my-4">
<ul className="divide-y divide-gray-200">
{selectableCalendars.map((calendar) => (
<li key={calendar.name} className="flex py-4">
<div className="w-1/12 mr-4 pt-2">
<img
className="h-8 w-8 mr-2"
src={getCalendarIntegrationImage(calendar.integration)}
alt={calendar.integration}
/>
</div>
<div className="w-10/12 pt-3">
<h2 className="text-gray-800 font-medium">{calendar.name}</h2>
</div>
<div className="w-2/12 text-right pt-3">
<Switch
checked={calendar.selected}
onChange={calendarSelectionHandler(calendar)}
className={classNames(
calendar.selected ? "bg-neutral-900" : "bg-gray-200",
"relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500"
)}>
<span className="sr-only">Select calendar</span>
<span
aria-hidden="true"
className={classNames(
calendar.selected ? "translate-x-5" : "translate-x-0",
"pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
)}
/>
</Switch>
</div>
</li>
))}
</ul>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
onClick={toggleShowCalendarModal}
type="button"
className="mt-3 w-full inline-flex justify-center rounded-sm border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-500 sm:mt-0 sm:w-auto sm:text-sm">
Close
</button>
</div>
</div>
</div>
</div>
)}
</Shell>
</div>
);

View file

@ -3,17 +3,14 @@ import prisma from "../../lib/prisma";
import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
import { getSession, useSession } from "next-auth/client";
import Loader from "@components/Loader";
export default function Embed(props) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
//const router = useRouter(); Unused
if (loading) {
return (
<div className="loader">
<span className="loader-inner"></span>
</div>
);
return <Loader />;
}
return (

View file

@ -5,19 +5,18 @@ import Modal from "../../components/Modal";
import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
import { getSession, useSession } from "next-auth/client";
import Loader from "@components/Loader";
export default function Settings() {
const [, loading] = useSession();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const [successModalOpen, setSuccessModalOpen] = useState(false);
const oldPasswordRef = useRef<HTMLInputElement>();
const newPasswordRef = useRef<HTMLInputElement>();
if (loading) {
return (
<div className="loader">
<span className="loader-inner"></span>
</div>
);
return <Loader />;
}
const closeSuccessModal = () => {
@ -32,19 +31,21 @@ export default function Settings() {
// TODO: Add validation
await fetch("/api/auth/changepw", {
/*eslint-disable */
const response = await fetch("/api/auth/changepw", {
method: "PATCH",
body: JSON.stringify({ oldPassword: enteredOldPassword, newPassword: enteredNewPassword }),
headers: {
"Content-Type": "application/json",
},
});
/*eslint-enable */
setSuccessModalOpen(true);
}
return (
<Shell heading="Password" subtitle="Change the password that you use to sign in.">
<Shell heading="Password" subtitle="Change the password that you use to sign in to your account.">
<Head>
<title>Change Password | Calendso</title>
<link rel="icon" href="/favicon.ico" />

View file

@ -12,6 +12,11 @@ import TimezoneSelect from "react-timezone-select";
import { UsernameInput } from "../../components/ui/UsernameInput";
import ErrorAlert from "../../components/ui/alerts/Error";
const themeOptions = [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
];
export default function Settings(props) {
const [successModalOpen, setSuccessModalOpen] = useState(false);
const usernameRef = useRef<HTMLInputElement>();
@ -19,18 +24,13 @@ export default function Settings(props) {
const descriptionRef = useRef<HTMLTextAreaElement>();
const avatarRef = useRef<HTMLInputElement>();
const hideBrandingRef = useRef<HTMLInputElement>();
const [selectedTheme, setSelectedTheme] = useState({ value: "" });
const [selectedTheme, setSelectedTheme] = useState({ value: props.user.theme });
const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone });
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ value: "" });
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ value: props.user.weekStart });
const [hasErrors, setHasErrors] = useState(false);
const [errorMessage, setErrorMessage] = useState("");
const themeOptions = [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
];
useEffect(() => {
setSelectedTheme(
props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : null
@ -179,6 +179,7 @@ export default function Settings(props) {
id="theme"
isDisabled={!selectedTheme}
defaultValue={selectedTheme || themeOptions[0]}
value={selectedTheme || themeOptions[0]}
onChange={setSelectedTheme}
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-sm"
options={themeOptions}

View file

@ -126,13 +126,13 @@ export default function Teams() {
</div>
{showCreateTeamModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">

View file

@ -4,7 +4,7 @@ import prisma, { whereAndSelect } from "../lib/prisma";
import { useEffect, useState } from "react";
import { useRouter } from "next/router";
import { CheckIcon } from "@heroicons/react/outline";
import { CalendarIcon, ClockIcon, LocationMarkerIcon } from "@heroicons/react/solid";
import { ClockIcon } from "@heroicons/react/solid";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import toArray from "dayjs/plugin/toArray";
@ -60,7 +60,7 @@ export default function Success(props) {
return (
isReady && (
<div>
<div className="bg-neutral-50 dark:bg-neutral-900 h-screen">
<Head>
<title>
Booking {props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"} | {eventName} |
@ -68,69 +68,69 @@ export default function Success(props) {
</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="max-w-3xl mx-auto my-24">
<div className="fixed z-10 inset-0 overflow-y-auto">
<main className="max-w-3xl mx-auto py-24">
<div className="fixed z-50 inset-0 overflow-y-auto">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div className="fixed inset-0 my-4 sm:my-0 transition-opacity" aria-hidden="true">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<div
className="inline-block align-bottom dark:bg-gray-800 bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6"
className="inline-block align-bottom dark:bg-gray-800 bg-white rounded-sm px-8 pt-5 pb-4 text-left overflow-hidden border border-neutral-200 dark:border-neutral-700 transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:py-6"
role="dialog"
aria-modal="true"
aria-labelledby="modal-headline">
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
{!props.eventType.requiresConfirmation && (
<CheckIcon className="h-6 w-6 text-green-600" />
<CheckIcon className="h-8 w-8 text-green-600" />
)}
{props.eventType.requiresConfirmation && (
<ClockIcon className="h-6 w-6 text-green-600" />
<ClockIcon className="h-8 w-8 text-green-600" />
)}
</div>
<div className="mt-3 text-center sm:mt-5">
<h3
className="text-lg leading-6 font-medium dark:text-white text-gray-900"
className="text-2xl leading-6 font-semibold dark:text-white text-neutral-900"
id="modal-headline">
Booking {props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"}
{props.eventType.requiresConfirmation ? "Submitted" : "This meeting is scheduled"}
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500 dark:text-gray-300">
<div className="mt-3">
<p className="text-sm text-neutral-600 dark:text-gray-300">
{props.eventType.requiresConfirmation
? `${
props.user.name || props.user.username
} still needs to confirm or reject the booking.`
: `You are scheduled in with ${props.user.name || props.user.username}.`}
: `We emailed you and the other attendees a calendar invitation with all the details.`}
</p>
</div>
<div className="mt-4 border-t border-b dark:border-gray-900 py-4">
<h2 className="text-lg font-medium text-gray-600 dark:text-gray-100 mb-2">
{eventName}
</h2>
<p className="text-gray-500 dark:text-gray-50 mb-1">
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{props.eventType.length} minutes
</p>
<div className="mt-4 text-gray-700 dark:text-gray-300 border-t border-b dark:border-gray-900 py-4 grid grid-cols-3 text-left">
<div className="font-medium">What</div>
<div className="mb-6 col-span-2">{eventName}</div>
<div className="font-medium">When</div>
<div className="mb-6 col-span-2">
{date.format("dddd, DD MMMM YYYY")}
<br />
{date.format(is24h ? "H:mm" : "h:mma")} - {props.eventType.length} mins{" "}
<span className="text-gray-500">
({localStorage.getItem("timeOption.preferredTimeZone") || dayjs.tz.guess()})
</span>
</div>
{location && (
<p className="text-gray-500 mb-1">
<LocationMarkerIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{location}
</p>
<>
<div className="font-medium">Where</div>
<div className="col-span-2">{location}</div>
</>
)}
<p className="text-gray-500 dark:text-gray-50">
<CalendarIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{date.format((is24h ? "H:mm" : "h:mma") + ", dddd DD MMMM YYYY")}
</p>
</div>
</div>
</div>
{!props.eventType.requiresConfirmation && (
<div className="mt-5 sm:mt-0 pt-2 text-center">
<span className="font-medium text-gray-500 dark:text-gray-50">
Add to your calendar
<div className="mt-5 sm:mt-0 sm:pt-4 pt-2 text-center flex">
<span className="font-medium text-gray-700 dark:text-gray-50 flex self-center mr-6">
Add to calendar
</span>
<div className="flex mt-2">
<div className="flex">
<Link
href={
`https://calendar.google.com/calendar/r/eventedit?dates=${date
@ -142,9 +142,9 @@ export default function Success(props) {
props.eventType.description
}` + (location ? "&location=" + encodeURIComponent(location) : "")
}>
<a className="mx-2 btn-wide btn-white">
<a className="mx-2 rounded-sm border border-neutral-200 dark:border-neutral-700 py-2 px-3">
<svg
className="inline-block w-4 h-4 mr-1 -mt-1"
className="inline-block w-4 h-4 -mt-1"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
@ -166,7 +166,9 @@ export default function Success(props) {
eventName
) + (location ? "&location=" + location : "")
}>
<a className="mx-2 btn-wide btn-white" target="_blank">
<a
className="mx-2 rounded-sm border border-neutral-200 dark:border-neutral-700 py-2 px-3"
target="_blank">
<svg
className="inline-block w-4 h-4 mr-1 -mt-1"
fill="currentColor"
@ -190,7 +192,9 @@ export default function Success(props) {
eventName
) + (location ? "&location=" + location : "")
}>
<a className="mx-2 btn-wide btn-white" target="_blank">
<a
className="mx-2 rounded-sm border border-neutral-200 dark:border-neutral-700 py-2 px-3"
target="_blank">
<svg
className="inline-block w-4 h-4 mr-1 -mt-1"
fill="currentColor"
@ -202,7 +206,9 @@ export default function Success(props) {
</a>
</Link>
<Link href={"data:text/calendar," + eventLink()}>
<a className="mx-2 btn-wide btn-white" download={props.eventType.title + ".ics"}>
<a
className="mx-2 rounded-sm border border-neutral-200 dark:border-neutral-700 py-2 px-3"
download={props.eventType.title + ".ics"}>
<svg
version="1.1"
xmlns="http://www.w3.org/2000/svg"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

9
public/browserconfig.xml Normal file
View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#ff0000</TileColor>
</tile>
</msapplication>
</browserconfig>

View file

@ -1,11 +1,41 @@
<svg width="104" height="20" viewBox="0 0 104 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1C0 0.447715 0.447715 0 1 0H19C19.5523 0 20 0.447715 20 1V19C20 19.5523 19.5523 20 19 20H10.4142C10.149 20 9.89464 19.8946 9.70711 19.7071L5.21739 15.2174L0.292893 10.2929C0.105357 10.1054 0 9.851 0 9.58579V1Z" fill="black"/>
<path d="M32.0349 15.426C34.2609 15.426 35.8783 14.226 36.1914 12.313L33.5827 11.7391C33.4262 12.7826 32.887 13.3565 32.0175 13.3565C30.9218 13.3565 30.2957 12.4173 30.2957 10.7826C30.2957 9.26951 30.9392 8.36516 32.0001 8.36516C32.8522 8.36516 33.4088 8.90429 33.5653 9.93038L36.1914 9.39125C35.8609 7.42603 34.3479 6.31299 32.0696 6.31299C29.2175 6.31299 27.4609 8.03473 27.4609 10.8695C27.4609 13.6347 29.287 15.426 32.0349 15.426Z" fill="black"/>
<path d="M40.0978 15.426C41.4543 15.426 42.5673 14.8173 43.0021 13.8434C43.0195 14.3304 43.0543 14.7999 43.1412 15.2173H45.8543C45.6978 14.6782 45.6108 13.826 45.6108 12.7999V9.91299C45.6108 7.40864 44.3586 6.31299 41.5586 6.31299C39.0891 6.31299 37.5238 7.46082 37.3847 9.39125H40.1499C40.2021 8.55647 40.6891 8.10429 41.5412 8.10429C42.4108 8.10429 42.8282 8.53908 42.8282 9.53038V9.82603L40.7238 10.1217C39.4021 10.2956 38.5673 10.5739 37.9934 10.9739C37.4021 11.3912 37.1065 12.0347 37.1065 12.8521C37.1065 14.3999 38.3065 15.426 40.0978 15.426ZM41.1412 13.6521C40.4108 13.6521 39.9238 13.2521 39.9238 12.6434C39.9238 11.9999 40.3934 11.6347 41.4195 11.4434L42.9151 11.1999V11.8956C42.9151 12.9391 42.2021 13.6521 41.1412 13.6521Z" fill="black"/>
<path d="M50.6759 2.60864H47.8759V15.2173H50.6759V2.60864Z" fill="black"/>
<path d="M61.1052 10.6608C61.1052 7.8956 59.5748 6.31299 56.8444 6.31299C54.1313 6.31299 52.427 8.10429 52.427 10.9565C52.427 13.7739 54.1661 15.426 57.0357 15.426C58.8444 15.426 60.3226 14.5739 61.0183 13.1478L58.9661 12.2086C58.5661 13.0086 57.9226 13.426 57.0704 13.426C55.9052 13.426 55.0878 12.6782 55.0704 11.4956H61.1052V10.6608ZM58.4096 9.93038H55.0704C55.1052 8.6956 55.7139 7.96516 56.7922 7.96516C57.8357 7.96516 58.4096 8.62603 58.4096 9.86082V9.93038Z" fill="black"/>
<path d="M62.8535 15.2173H65.6535V10.5217C65.6535 9.18256 66.1926 8.43473 67.1665 8.43473C68.0535 8.43473 68.4709 9.00864 68.4709 10.2086V15.2173H71.2535V9.82603C71.2535 7.4956 70.2969 6.2956 68.4187 6.2956C67.0969 6.2956 66.1404 6.92169 65.5317 8.19125V6.52169H62.8535V15.2173Z" fill="black"/>
<path d="M76.3209 15.426C77.5904 15.426 78.6339 14.7652 79.0687 13.7217V15.2173H81.7122V2.60864H78.9295V7.6869C78.4948 6.78256 77.6252 6.31299 76.4774 6.31299C74.3035 6.31299 72.8426 8.13908 72.8426 10.9043C72.8426 13.6173 74.2339 15.426 76.3209 15.426ZM77.4165 13.4956C76.3556 13.4956 75.7122 12.4869 75.7122 10.8521C75.7122 9.18256 76.3209 8.24342 77.3817 8.24342C78.373 8.24342 78.9643 9.06082 78.9643 10.5391V11.2869C78.9643 12.6434 78.373 13.4956 77.4165 13.4956Z" fill="black"/>
<path d="M87.5423 15.426C89.9771 15.426 91.3858 14.3304 91.3858 12.5565C91.3858 11.0608 90.6554 10.1391 88.7945 9.87821L87.2467 9.63473C86.4815 9.53038 86.2206 9.30429 86.2206 8.8869C86.2206 8.39995 86.6032 8.17386 87.438 8.17386C88.5162 8.17386 89.3858 8.60864 90.0293 9.33908L91.4728 8.03473C90.7597 6.95647 89.2815 6.31299 87.5249 6.31299C85.1249 6.31299 83.6988 7.37386 83.6988 9.06082C83.6988 10.5391 84.5858 11.4608 86.4641 11.7391L87.7162 11.913C88.6728 12.0521 88.951 12.2434 88.951 12.7652C88.951 13.2521 88.4988 13.5304 87.5945 13.5304C86.3945 13.5304 85.4554 13.0608 84.8467 12.1217L83.2293 13.3391C84.0641 14.713 85.6293 15.426 87.5423 15.426Z" fill="black"/>
<path d="M96.9109 15.426C99.4848 15.426 101.433 13.8434 101.433 10.8521C101.433 7.86082 99.4848 6.31299 96.9109 6.31299C94.3544 6.31299 92.4066 7.86082 92.4066 10.8521C92.4066 13.8434 94.3544 15.426 96.9109 15.426ZM96.9109 13.4956C95.8327 13.4956 95.1892 12.626 95.1892 10.8521C95.1892 9.06082 95.8327 8.24342 96.9109 8.24342C98.0066 8.24342 98.6501 9.06082 98.6501 10.8521C98.6501 12.626 98.0066 13.4956 96.9109 13.4956Z" fill="black"/>
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 427 97.5" style="enable-background:new 0 0 427 97.5;" xml:space="preserve">
<style type="text/css">
.st0{fillRule:evenodd;clipRule:evenodd;fill:#104D86;}
</style>
<path class="st0" d="M27.5,88.2c-4.9,0-9.7-1.2-14-3.6c-4.2-2.4-7.6-5.8-9.9-10c-4.8-8.8-4.8-19.4,0-28.2c2.3-4.2,5.8-7.7,10-10
c4.3-2.4,9.1-3.7,14-3.6c6-0.1,11.8,1.7,16.5,5.3s8,8.7,9.9,15.4H42.8c-1.3-3-3.4-5.5-6.2-7.2c-2.6-1.6-5.6-2.5-8.7-2.5
c-2.8,0-5.6,0.7-8.1,2s-4.7,3.3-6.1,5.8c-3,5.3-3.1,11.9-0.2,17.3c1.4,2.5,3.4,4.5,5.8,6s5.2,2.3,8,2.2c7.2,0,12.4-3.3,15.4-9.9
h11.4c-1.1,4.2-3.1,8.1-5.8,11.5c-2.5,3-5.6,5.4-9.2,7C35.5,87.4,31.5,88.1,27.5,88.2L27.5,88.2z M99.6,82.1
c-4.6,4-10.6,6.2-16.8,6.1c-4.9,0-9.7-1.2-14-3.6c-4.1-2.4-7.5-5.8-9.8-10c-2.5-4.5-3.7-9.5-3.6-14.6c-0.1-15,11.9-27.2,26.9-27.3
c0.1,0,0.2,0,0.3,0c6.4-0.2,12.6,2.1,17.5,6.2v-5.2h11.5V87h-12V82.1z M83.5,43.7c-3,0-5.9,0.7-8.4,2.2c-2.5,1.4-4.6,3.5-6,6
c-1.5,2.5-2.2,5.4-2.2,8.4c-0.2,4.5,1.5,8.9,4.7,12.2c3.2,3.2,7.6,4.9,12.1,4.8c2.9,0,5.8-0.7,8.3-2.2c2.5-1.4,4.5-3.5,5.9-6
c2.9-5.3,2.9-11.7,0-17c-1.4-2.5-3.5-4.6-6-6C89.3,44.5,86.4,43.8,83.5,43.7L83.5,43.7z M122,14.8h9.7V87H122
C122,87.1,122,14.8,122,14.8z M149.8,65.2c0.5,2.3,1.5,4.4,3,6.3c1.4,1.8,3.3,3.2,5.4,4.1c2.2,1,4.6,1.5,7,1.5
c2.8,0.1,5.6-0.5,8.1-1.8c2.4-1.4,4.3-3.4,5.5-5.9h11.9c-2.7,6.5-6.2,11.2-10.4,14.2c-4.4,3.1-9.8,4.7-15.2,4.5
c-4.8,0.1-9.6-1.2-13.8-3.6c-4.1-2.4-7.5-5.8-9.7-10c-2.4-4.3-3.6-9.2-3.5-14.1c-0.1-4.9,1.1-9.8,3.5-14.1c2.3-4.2,5.7-7.6,9.8-10
c8.6-4.8,19.2-4.8,27.7,0.1c4.1,2.4,7.4,6,9.6,10.2c2.2,4.3,3.3,9.1,3.3,14c0,1-0.1,2.5-0.3,4.5h-41.9V65.2z M165.2,43.6
c-3.4-0.1-6.8,0.9-9.6,2.9c-2.7,2-4.7,4.8-5.6,8h30.2c-0.9-3.2-2.9-6-5.6-8C171.9,44.5,168.6,43.5,165.2,43.6z M234.8,57.3
c0-4.7-1-8.2-3-10.5s-4.9-3.5-8.7-3.5c-2.3,0-4.6,0.7-6.5,2c-2,1.3-3.7,3.2-4.8,5.4c-1.2,2.3-1.8,4.8-1.8,7.4V87h-11.2V33.8h10.5
v4.8c3.9-3.9,9.3-6,14.9-5.8c3.9,0,7.7,1,11,3s6,4.8,7.9,8.2c1.9,3.5,2.9,7.5,2.9,11.5v31.6h-11.2L234.8,57.3L234.8,57.3z
M296.7,82.2c-2.4,1.9-5,3.4-7.9,4.4c-3,1-6.2,1.5-9.4,1.5c-4.8,0.1-9.6-1.2-13.8-3.7c-4.1-2.4-7.5-5.9-9.8-10.1
c-2.4-4.3-3.6-9.1-3.6-14.1c-0.1-5.1,1.1-10.1,3.5-14.7c2.3-4.2,5.7-7.7,9.8-10.1c4.3-2.5,9.2-3.8,14.1-3.7c3.1,0,6.1,0.5,9,1.4
c2.8,0.9,5.4,2.3,7.8,4.1V14.9h11.3v72.2h-11.1L296.7,82.2L296.7,82.2z M280.4,43.1c-3,0-5.9,0.7-8.4,2.2c-2.5,1.4-4.6,3.5-6,6
c-1.5,2.6-2.2,5.5-2.2,8.5c-0.2,4.6,1.5,9.1,4.7,12.4c3.2,3.2,7.5,5,12.1,4.8c2.9,0,5.8-0.7,8.3-2.3c2.5-1.5,4.5-3.6,5.9-6.1
c2.9-5.4,2.9-11.8,0-17.2C291.8,46.2,286.3,43,280.4,43.1z M335.8,88.2c-3.8,0.1-7.5-0.8-10.8-2.4c-3.1-1.6-5.7-4-7.5-7
c-1.9-3.3-2.9-7.1-3-10.9h11c0,2.7,1.1,5.3,3.1,7.1c2.1,1.7,4.8,2.6,7.5,2.5c1.8,0,3.6-0.2,5.3-0.8c1.3-0.4,2.4-1.2,3.3-2.2
c0.7-0.8,1.1-1.9,1.1-3c0.1-1.2-0.4-2.4-1.2-3.2c-0.5-0.5-1.1-0.8-1.7-1.2c-0.7-0.3-1.4-0.6-2.2-0.8c-1.8-0.4-3.9-0.9-6.1-1.3
c-1.8-0.2-3.5-0.6-5.1-1s-3.5-1-5-1.6c-0.8-0.2-1.6-0.6-2.4-1l-1-0.7c-0.4-0.4-0.8-0.7-1.2-1c-0.7-0.5-1.3-1.1-1.8-1.8
c-0.6-0.8-1.1-1.7-1.5-2.6c-0.9-2-1.3-4.2-1.2-6.4c0-3,0.9-6,2.7-8.5c1.8-2.5,4.3-4.5,7.2-5.7c3.3-1.4,6.8-2.1,10.3-2
c3.6-0.1,7.1,0.7,10.3,2.4c3,1.5,5.4,3.8,7.1,6.7c1.8,3.2,2.7,6.8,2.7,10.4H345c-0.1-3-1-5.2-2.7-6.7s-4-2.3-7-2.3s-5.2,0.5-6.8,1.5
c-1.4,0.8-2.3,2.2-2.3,3.8c-0.1,1,0.3,2,1,2.6c0.6,0.5,1.2,0.9,1.8,1.2c0.6,0.3,1.3,0.5,2,0.7c0.8,0.2,1.7,0.4,2.7,0.6l3.1,0.5
c3.4,0.5,6.7,1.2,10,2.2c2.6,0.8,5,2.3,6.7,4.5c1.8,2.4,2.9,5.3,3,8.4l0.1,1.8c0.1,3.2-0.9,6.4-2.8,9.1s-4.5,4.8-7.5,6
C343.2,87.6,339.5,88.3,335.8,88.2L335.8,88.2z M412.4,74.6c-2.4,4.2-5.9,7.7-10.2,10c-4.5,2.4-9.6,3.7-14.8,3.6
c-4.9,0.1-9.7-1.2-14-3.6c-4.2-2.4-7.7-5.8-10.1-9.9c-2.5-4.2-3.8-9-3.7-13.8c-0.1-5.1,1.1-10.1,3.6-14.6c2.4-4.2,6-7.7,10.2-10
c4.4-2.4,9.3-3.6,14.3-3.6s9.9,1.2,14.3,3.6c4.2,2.3,7.8,5.7,10.2,9.9c2.4,4.3,3.7,9.2,3.6,14.2C416.1,65.4,414.8,70.3,412.4,74.6
L412.4,74.6z M387.8,43.3c-3,0-6,0.8-8.6,2.3s-4.8,3.6-6.3,6.2c-1.5,2.6-2.3,5.5-2.3,8.5c0,4.6,1.8,9,5.2,12.2
c3.2,3.4,7.6,5.3,12.3,5.3c3,0,6-0.8,8.5-2.4c2.6-1.5,4.7-3.7,6.2-6.3c1.5-2.6,2.3-5.6,2.3-8.6s-0.8-6-2.3-8.6s-3.7-4.7-6.3-6.2
C393.9,44.1,390.9,43.3,387.8,43.3z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/mstile-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1,21 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="363.000000pt" height="363.000000pt" viewBox="0 0 363.000000 363.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,363.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1738 2645 c-2 -2 -27 -6 -57 -9 -65 -8 -188 -48 -256 -84 -209 -110
-337 -273 -407 -517 -30 -107 -32 -313 -4 -425 64 -253 214 -439 439 -545 384
-181 845 -49 1053 303 45 76 93 185 97 221 l2 25 -160 1 c-88 0 -165 -1 -172
-3 -6 -2 -14 -14 -17 -26 -8 -32 -85 -129 -132 -167 -82 -66 -190 -100 -319
-101 -133 0 -247 52 -344 158 -206 225 -155 617 99 766 154 89 325 96 486 18
87 -42 170 -119 209 -196 l21 -39 167 0 c92 0 167 0 167 1 0 0 -11 33 -24 72
-25 73 -75 174 -88 180 -5 2 -8 8 -8 14 0 14 -67 95 -120 146 -96 92 -272 179
-400 197 -45 7 -227 14 -232 10z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

19
public/site.webmanifest Normal file
View file

@ -0,0 +1,19 @@
{
"name": "Calendso",
"short_name": "Calendso",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View file

@ -81,6 +81,27 @@
}
}
::-moz-selection {
color: white;
background: black;
}
::selection {
color: white;
background: black;
}
/* add padding bottom to bottom nav on standalone mode */
@media all and (display-mode: standalone) {
.bottom-nav {
padding-bottom: 24px;
}
}
.react-multi-email > [type='text'] {
--tw-ring-shadow: 0 0 0 0 0;
}
/* !important to override react-select */
.react-select__value-container{
border: 0 !important;
@ -140,7 +161,8 @@
height: 30px;
margin: 60px auto;
position: relative;
border: 4px solid #000;
border-width: 4px;
border-style: solid;
animation: loader 2s infinite ease;
}
@ -148,7 +170,6 @@
vertical-align: top;
display: inline-block;
width: 100%;
background-color: #000;
animation: loader-inner 2s infinite ease-in;
}

376
yarn.lock
View file

@ -753,6 +753,306 @@
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.26.0-23.9b816b3aa13cc270074f172f30d6eda8a8ce867d.tgz#cfdacfad3acc0f3bf1d7710aa8f3852fd85ac6d9"
integrity sha512-a0jIhLvw9rFh6nZTr5Y3uzP28I2xNDu3pqxANvwMNnmIoYr1wYEcO1pMXn/36BGXldDdAWMmAbhfloHA3IB8DA==
"@radix-ui/popper@0.0.10":
version "0.0.10"
resolved "https://registry.yarnpkg.com/@radix-ui/popper/-/popper-0.0.10.tgz#9f707d9cec8762423f81acaf8e650e40a554cb73"
integrity sha512-YFKuPqQPKscreQid7NuB4it3PMzSwGg03vgrud6sVliHkI43QNAOHyrHyMNo015jg6QK5GVDn+7J2W5uygqSGA==
dependencies:
"@babel/runtime" "^7.13.10"
csstype "^3.0.4"
"@radix-ui/primitive@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-0.0.5.tgz#8464fb4db04401bde72d36e27e05714080668d40"
integrity sha512-VeL6A5LpKYRJhDDj5tCTnzP3zm+FnvybsAkgBHQ4LUPPBnqRdWLoyKpZhlwFze/z22QHINaTIcE9Z/fTcrUR1g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-arrow@0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-0.0.15.tgz#2fb7e4cab626f87d4f7a403672c57bce74b0a7b4"
integrity sha512-lw3/3nPmEeK67IgndT764w/65EMm5psXnr2efCeo0eWOERTnFAswNka2bKJUSKY02FHECkH4qVzhwupFyeYv0g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic" "0.0.13"
"@radix-ui/react-primitive" "0.0.15"
"@radix-ui/react-collapsible@^0.0.16":
version "0.0.16"
resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-0.0.16.tgz#6a99068f70bb85a60f8cbd43f093bd3053ab61cc"
integrity sha512-kY9wojEbrpTge6sz3BZl1oXer2Szhi+MW60TSDf14mL6l8+e4ugp5y2ItGDjcW5B7AzL00dsMtqlxuAkhFjWxQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.0.5"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-context" "0.0.5"
"@radix-ui/react-id" "0.0.6"
"@radix-ui/react-polymorphic" "0.0.12"
"@radix-ui/react-presence" "0.0.14"
"@radix-ui/react-primitive" "0.0.14"
"@radix-ui/react-use-controllable-state" "0.0.6"
"@radix-ui/react-use-layout-effect" "0.0.5"
"@radix-ui/react-compose-refs@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-0.0.5.tgz#0f71f0de1dec341f30cebd420b6bc3d12a3037dd"
integrity sha512-O9mH9X/2EwuAEEoZXrU4alcrRbAhhZHGpIJ5bOH6rmRcokhaoWRBY1tOEe2lgHdb/bkKrY+viLi4Zq8Ju6/09Q==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-context@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-0.0.5.tgz#7c15f46795d7765dabfaf6f9c53791ad28c521c2"
integrity sha512-bwrzAc0qc2EPepSTLBT4+93uCiI9wP78VSmPg2K+k71O/vpx7dPs0VqrewwCBNCHT54NIwaRr2hEsm2uqYi02A==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-dialog@^0.0.19":
version "0.0.19"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-0.0.19.tgz#5a76fa380142a7a97c15c585ab071f63fba5297d"
integrity sha512-7FbWaj/C/TDpfJ+VJ4wNAQIjENDNfwAqNvAfeb+TEtBjgjmsfRDgA1AMenlA5N1QuRtAokRMTHUs3ukW49oQ+g==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.0.5"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-context" "0.0.5"
"@radix-ui/react-dismissable-layer" "0.0.14"
"@radix-ui/react-focus-guards" "0.0.7"
"@radix-ui/react-focus-scope" "0.0.14"
"@radix-ui/react-id" "0.0.6"
"@radix-ui/react-polymorphic" "0.0.12"
"@radix-ui/react-portal" "0.0.14"
"@radix-ui/react-presence" "0.0.14"
"@radix-ui/react-primitive" "0.0.14"
"@radix-ui/react-slot" "0.0.12"
"@radix-ui/react-use-controllable-state" "0.0.6"
aria-hidden "^1.1.1"
react-remove-scroll "^2.4.0"
"@radix-ui/react-dismissable-layer@0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-0.0.14.tgz#9d8a3415a2830688070c6596dec18b43c33df7b2"
integrity sha512-0pmRuGYYvWlEaED1igGFLjic0+hD0OqvsnrZaN3n1nDOkoCd7H5CA2geaShSrlBF5riI2Dr9jIZPGLbDRhs4DA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.0.5"
"@radix-ui/react-polymorphic" "0.0.12"
"@radix-ui/react-primitive" "0.0.14"
"@radix-ui/react-use-body-pointer-events" "0.0.6"
"@radix-ui/react-use-callback-ref" "0.0.5"
"@radix-ui/react-use-escape-keydown" "0.0.6"
"@radix-ui/react-focus-guards@0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-0.0.7.tgz#285ed081c877587acd4ee7e6d8260bdf9044e922"
integrity sha512-enAsmrUunptHVzPLTuZqwTP/X3WFBnyJ/jP9W+0g+bRvI3o7V1kxNc+T2Rp1eRTFBW+lUNnt08qkugPytyTRog==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-focus-scope@0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-0.0.14.tgz#778e2a3ea607621d82e0139616d7ea6d517d9533"
integrity sha512-D3v6Tw8vzpIBNd2I32Q2G4LCiXMIlmc6Pl2VV9CZjSatDOjkV/ckGbhkQyQ7QxnD/0CmiSxNo5hTeGRmZDjwmA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-polymorphic" "0.0.12"
"@radix-ui/react-primitive" "0.0.14"
"@radix-ui/react-use-callback-ref" "0.0.5"
"@radix-ui/react-id@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-0.0.6.tgz#c4b27d11861805e91ac296e7758ab47e3947b65c"
integrity sha512-PzmraF34fYggsYvTIZVJ5S68WMp3aKUN3HkSmGnz4zn9zpRjkAbbg7Xn3ueQI3FQsLWKgyUfnpsmWFDndpcqYg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic@0.0.12":
version "0.0.12"
resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.12.tgz#bf4ae516669b68e059549538104d97322f7c876b"
integrity sha512-/GYNMicBnGzjD1d2fCAuzql1VeFrp8mqM3xfzT1kxhnV85TKdURO45jBfMgqo17XNXoNhWIAProUsCO4qFAAIg==
"@radix-ui/react-polymorphic@0.0.13":
version "0.0.13"
resolved "https://registry.yarnpkg.com/@radix-ui/react-polymorphic/-/react-polymorphic-0.0.13.tgz#d010d48281626191c9513f11db5d82b37662418a"
integrity sha512-0sGqBp+v9/yrsMhPfAejxcem2MwAFgaSAxF3Sieaklm6ZVYM/hTZxxWI5NVOLGV+482GwW0wIqwpVUzREjmh+w==
"@radix-ui/react-popper@0.0.18":
version "0.0.18"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-0.0.18.tgz#e85ec077c18ffca92ce97cc19586dcc6f022fffb"
integrity sha512-j8nPqX5scAmeGuyW9VQv+M4MkKsV/ulR1Yt0eu13LyGLT3L7FM2YBMt3KlbpUxrT3mrNGC0eEQAiVgm/G3/fGQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/popper" "0.0.10"
"@radix-ui/react-arrow" "0.0.15"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-context" "0.0.5"
"@radix-ui/react-polymorphic" "0.0.13"
"@radix-ui/react-primitive" "0.0.15"
"@radix-ui/react-use-rect" "0.0.7"
"@radix-ui/react-use-size" "0.0.6"
"@radix-ui/rect" "0.0.5"
"@radix-ui/react-portal@0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.0.14.tgz#31513d8777cf5e50a3a30ebc9deb34821e890e9e"
integrity sha512-Wi9arVwVenonjZIX6znCBYaasua03Tb+UtrBZShepJkLGtbGxDlzExijiGIaIRNetl46Oc2pw0F6Y6HffDnUww==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic" "0.0.12"
"@radix-ui/react-primitive" "0.0.14"
"@radix-ui/react-use-layout-effect" "0.0.5"
"@radix-ui/react-portal@0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-0.0.15.tgz#833bccb192aafb9420bd037d5827e88caf429dc4"
integrity sha512-qMESsdqph1gbRGzy9oSzUoeZYXnR2egXVcEZDqmesfn8w/o1rC1wadKkyBf7qo/YyjUX4mvXknAA+ftp1aQp+w==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic" "0.0.13"
"@radix-ui/react-primitive" "0.0.15"
"@radix-ui/react-use-layout-effect" "0.0.5"
"@radix-ui/react-presence@0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.0.14.tgz#6a86058bbbf46234dd8840dacd620b3ac5797025"
integrity sha512-ufof9B76DHXV0sC8H7Lswh2AepdJFG8qEtF32JWrbA9N1bl2Jnf9px76KsagyC0MA8crGEZO5A96wizGuSgGWQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-presence@0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-0.0.15.tgz#4ff12feb436f1499148feb11c3a63a5d8fab568a"
integrity sha512-+5+ePKUdTkqN1ze7nYmcoeHSsmKCcREwt0NhvNgDocPaqEUoZSkK9Mq6eMiMXSj02NkXH9P+bK32VCClYFnMBQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-use-layout-effect" "0.0.5"
"@radix-ui/react-primitive@0.0.14":
version "0.0.14"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.14.tgz#752a967cb05d4c5643634fe20274e7dc905d1cce"
integrity sha512-FYOWGCrxFpLdB534aWTwMK4Pjg8cxFb+745qWhPfI+cYi+aYUddJQD3ilRHHXxCBD72ve7/PufqeB7Y/QlKqgg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic" "0.0.12"
"@radix-ui/react-primitive@0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-0.0.15.tgz#c0cf609ee565a32969d20943e2697b42a04fbdf3"
integrity sha512-Y7JLnen/G3AT0cQXXkBo3A1OuWaKGerkd2gKs0Fuqxv+kTxEmYoqSp/soo0Mm3Ccw61LKLQAjPiE37GK9/Zqwg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic" "0.0.13"
"@radix-ui/react-slot@0.0.12", "@radix-ui/react-slot@^0.0.12":
version "0.0.12"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-0.0.12.tgz#c4d8a75fffca561aeeca2ed9603384d86757f60a"
integrity sha512-Em8P/xYyh3O/32IhrmARJNH+J/XCAVnw6h2zGu6oeReliIX7ktU67pMSeyyIZiU2hNXzaXYB/xDdixizQe/DGA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-tooltip@^0.0.21":
version "0.0.21"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-0.0.21.tgz#86160645cf0441fa7f465c8aaa265887cc3ff9b4"
integrity sha512-+QLMXclfX0XM3inY5LEAvmKsomQ+S0cqzo1v/oS8CiIcawg01RDLV9mzjDYLnpE4eKokn30d+gk4r1YAtWIbZA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/primitive" "0.0.5"
"@radix-ui/react-compose-refs" "0.0.5"
"@radix-ui/react-context" "0.0.5"
"@radix-ui/react-id" "0.0.6"
"@radix-ui/react-polymorphic" "0.0.13"
"@radix-ui/react-popper" "0.0.18"
"@radix-ui/react-portal" "0.0.15"
"@radix-ui/react-presence" "0.0.15"
"@radix-ui/react-primitive" "0.0.15"
"@radix-ui/react-slot" "0.0.12"
"@radix-ui/react-use-controllable-state" "0.0.6"
"@radix-ui/react-use-escape-keydown" "0.0.6"
"@radix-ui/react-use-layout-effect" "0.0.5"
"@radix-ui/react-use-previous" "0.0.5"
"@radix-ui/react-use-rect" "0.0.7"
"@radix-ui/react-visually-hidden" "0.0.15"
"@radix-ui/react-use-body-pointer-events@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-body-pointer-events/-/react-use-body-pointer-events-0.0.6.tgz#30b21301880417e7dbb345871ff5a83f2abe0d8d"
integrity sha512-ouYb7u1+K9TsiEcNs3HceNUBUgB2PV41EyD5O6y6ZPMxl1lW/QAy5dfyfJMRyaRWQ6kxwmGoKlCSb4uPTruzuQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-layout-effect" "0.0.5"
"@radix-ui/react-use-callback-ref@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-0.0.5.tgz#fa8db050229cda573dfeeae213d74ef06f6130db"
integrity sha512-z1AI221vmq9f3vsDyrCsGLCatKagbM1YeCGdRMhMsUBzFFCaJ+Axyoe/ndVqW8wwsraGWr1zYVAhIEdlC0GvPg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-controllable-state@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-0.0.6.tgz#c4b16bc911a25889333388a684a04df937e5fec7"
integrity sha512-fBk4hUSKc4N7X/NAaifWYfKKfNuOB9xvj0MBQQYS5oOTNRgg4y8/Ax3jZ0adsplXDm7ix75sxqWm0nrvUoAjcw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "0.0.5"
"@radix-ui/react-use-escape-keydown@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-0.0.6.tgz#1ad1c81b99961b7dbe376ef54151ebc8bef627a0"
integrity sha512-MJpVj21BYwWllmp2xbXPqpKPssJ1WWrZi+Qx7PY5hVcBhQr5Jo6yKwIX677pH5Yql95ENTTT5LW3q+LVFYIISw==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-callback-ref" "0.0.5"
"@radix-ui/react-use-layout-effect@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-0.0.5.tgz#cbbd059090edc765749da00d9f562a9abd43cbac"
integrity sha512-bNPW2JNOr/p2hXr0hfKKqrEy5deNSRF17sw3l9Z7qlEnvIbBtQ7iwY/wrxIz5P7XFyYGoXodIUDH5G8PEucE3A==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-previous@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-previous/-/react-use-previous-0.0.5.tgz#75191d1fa0ac24c560fe8cfbaa2f1174858cbb2f"
integrity sha512-GjtJlWlDAEMqCm2RDnVdWI6tk4/ZQfRq/VlP05Xy5rFZj6lD37VZWVWUELMBasRPzd2AS/9wPmphOgjH0VnE5A==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-use-rect@0.0.7":
version "0.0.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-0.0.7.tgz#e3a55fa7183ef436042198787bf38f8c9befcc14"
integrity sha512-OmaeFTgyiGNAchaxzDu+kFLz4Ly8RUcT5nwfoz4Nddd86I8Zdq93iNFnOpVLoVYqBnFEmvR6zexHXNFATrMbbQ==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/rect" "0.0.5"
"@radix-ui/react-use-size@0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-0.0.6.tgz#998eaf6e8871b868f81f3b7faac06c3e896c37a0"
integrity sha512-kP4RIb2I5oHQzwzXJ21Hu8htNqf+sdaRzywxQpbj+hmqeUhpvIkhoq+ShNWV9wE/3c1T7gPnka8/nKYsKaKdCg==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-visually-hidden@0.0.15":
version "0.0.15"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-0.0.15.tgz#7bd18af3fb5da1349f9b04006d22c3d6e9ce0453"
integrity sha512-8J13Nzu9MfT2z+mDTGRfBukPi5L9LXLV7w1HvNZPVqxGLK8p7/CoXnt8XdS1HKSFm6akZmWJXMZVNVBUsONOcA==
dependencies:
"@babel/runtime" "^7.13.10"
"@radix-ui/react-polymorphic" "0.0.13"
"@radix-ui/react-primitive" "0.0.15"
"@radix-ui/rect@0.0.5":
version "0.0.5"
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-0.0.5.tgz#6000d8d800288114af4bbc5863e6b58755d7d978"
integrity sha512-gXw171KbjyttA7K1DRIvPguLmKsg8raitB67MIcsdZwcquy+a1O2w3xY21NIKEqGhJwqJkECPUmMJDXgMNYuAg==
dependencies:
"@babel/runtime" "^7.13.10"
"@sinonjs/commons@^1.7.0":
version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"
@ -1220,6 +1520,13 @@ argparse@^2.0.1:
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
aria-hidden@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.1.3.tgz#bb48de18dc84787a3c6eee113709c473c64ec254"
integrity sha512-RhVWFtKH5BiGMycI72q2RAFMLQi8JP9bLuQXgR5a8Znp7P5KOIADSJeyfI8PCVxLEp067B2HbP5JIiI/PXIZeA==
dependencies:
tslib "^1.0.0"
array-includes@^3.1.2, array-includes@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a"
@ -2006,7 +2313,7 @@ cssstyle@^2.3.0:
dependencies:
cssom "~0.3.6"
csstype@^3.0.2:
csstype@^3.0.2, csstype@^3.0.4:
version "3.0.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw==
@ -2109,6 +2416,11 @@ detect-newline@^3.0.0:
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
detect-node-es@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==
detective@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b"
@ -2780,6 +3092,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1:
has "^1.0.3"
has-symbols "^1.0.1"
get-nonce@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3"
integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==
get-orientation@1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/get-orientation/-/get-orientation-1.1.2.tgz#20507928951814f8a91ded0a0e67b29dfab98947"
@ -3186,6 +3503,13 @@ internal-slot@^1.0.3:
has "^1.0.3"
side-channel "^1.0.4"
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
dependencies:
loose-envify "^1.0.0"
is-arguments@^1.0.4:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9"
@ -4213,7 +4537,7 @@ log-update@^4.0.0:
slice-ansi "^4.0.0"
wrap-ansi "^6.2.0"
loose-envify@^1.1.0, loose-envify@^1.4.0:
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -5211,6 +5535,11 @@ react-moment-proptypes@^1.6.0:
dependencies:
moment ">=1.6.0"
react-multi-email@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/react-multi-email/-/react-multi-email-0.5.3.tgz#734a0d4d1af23feef5cb5e635bde23963b0a9e8b"
integrity sha512-1AneeJlAwjvzkPV740q2SXes/kW3HKOzR3gs+U7whrHN5nz+yH5Unosf/rvz8kRj/eFwBf6fTzMqlJiupu7S5Q==
react-outside-click-handler@^1.2.4:
version "1.3.0"
resolved "https://registry.yarnpkg.com/react-outside-click-handler/-/react-outside-click-handler-1.3.0.tgz#3831d541ac059deecd38ec5423f81e80ad60e115"
@ -5245,6 +5574,25 @@ react-refresh@0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg==
react-remove-scroll-bar@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.2.0.tgz#d4d545a7df024f75d67e151499a6ab5ac97c8cdd"
integrity sha512-UU9ZBP1wdMR8qoUs7owiVcpaPwsQxUDC2lypP6mmixaGlARZa7ZIBx1jcuObLdhMOvCsnZcvetOho0wzPa9PYg==
dependencies:
react-style-singleton "^2.1.0"
tslib "^1.0.0"
react-remove-scroll@^2.4.0:
version "2.4.3"
resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.4.3.tgz#83d19b02503b04bd8141ed6e0b9e6691a2e935a6"
integrity sha512-lGWYXfV6jykJwbFpsuPdexKKzp96f3RbvGapDSIdcyGvHb7/eqyn46C7/6h+rUzYar1j5mdU+XECITHXCKBk9Q==
dependencies:
react-remove-scroll-bar "^2.1.0"
react-style-singleton "^2.1.0"
tslib "^1.0.0"
use-callback-ref "^1.2.3"
use-sidecar "^1.0.1"
react-select@^4.3.0, react-select@^4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81"
@ -5258,6 +5606,15 @@ react-select@^4.3.0, react-select@^4.3.1:
react-input-autosize "^3.0.0"
react-transition-group "^4.3.0"
react-style-singleton@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.1.1.tgz#ce7f90b67618be2b6b94902a30aaea152ce52e66"
integrity sha512-jNRp07Jza6CBqdRKNgGhT3u9umWvils1xsuMOjZlghBDH2MU0PL2WZor4PGYjXpnRCa9DQSlHMs/xnABWOwYbA==
dependencies:
get-nonce "^1.0.0"
invariant "^2.2.4"
tslib "^1.0.0"
react-timezone-select@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/react-timezone-select/-/react-timezone-select-1.0.4.tgz#66664f508f927e9f9c0f051aea51fd3196534401"
@ -6103,7 +6460,7 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
tslib@^1.8.1, tslib@^1.9.0:
tslib@^1.0.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@ -6256,6 +6613,19 @@ url@^0.11.0:
punycode "1.3.2"
querystring "0.2.0"
use-callback-ref@^1.2.3:
version "1.2.5"
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.5.tgz#6115ed242cfbaed5915499c0a9842ca2912f38a5"
integrity sha512-gN3vgMISAgacF7sqsLPByqoePooY3n2emTH59Ur5d/M8eg4WTWu1xp8i8DHjohftIyEx0S08RiYxbffr4j8Peg==
use-sidecar@^1.0.1:
version "1.0.5"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.5.tgz#ffff2a17c1df42e348624b699ba6e5c220527f2b"
integrity sha512-k9jnrjYNwN6xYLj1iaGhonDghfvmeTmYjAiGvOr7clwKfPjMXJf4/HOr7oT5tJwYafgp2tG2l3eZEOfoELiMcA==
dependencies:
detect-node-es "^1.1.0"
tslib "^1.9.3"
use-subscription@1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1"