Merge branch 'main' into main

This commit is contained in:
Peer_Rich 2021-08-08 23:19:31 +02:00 committed by GitHub
commit f664afb371
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
59 changed files with 1807 additions and 1044 deletions

View file

@ -229,16 +229,13 @@ Contributions are what make the open source community such an amazing place to b
2. On the upper right, click "Develop" => "Build App". 2. On the upper right, click "Develop" => "Build App".
3. On "OAuth", select "Create". 3. On "OAuth", select "Create".
4. Name your App. 4. Name your App.
5. Choose "Account-level app" as the app type. 5. Choose "User-managed app" as the app type.
6. De-select the option to publish the app on the Zoom App Marketplace. 6. De-select the option to publish the app on the Zoom App Marketplace.
7. Click "Create". 7. Click "Create".
8. Now copy the Client ID and Client Secret to your .env file into the `ZOOM_CLIENT_ID` and `ZOOM_CLIENT_SECRET` fields. 8. Now copy the Client ID and Client Secret to your .env file into the `ZOOM_CLIENT_ID` and `ZOOM_CLIENT_SECRET` fields.
9. Set the Redirect URL for OAuth `<CALENDSO URL>/api/integrations/zoomvideo/callback` replacing CALENDSO URL with the URI at which your application runs. 9. Set the Redirect URL for OAuth `<CALENDSO URL>/api/integrations/zoomvideo/callback` replacing CALENDSO URL with the URI at which your application runs.
10. Also add the redirect URL given above as a whitelist URL and enable "Subdomain check". Make sure, it says "saved" below the form. 10. Also add the redirect URL given above as a whitelist URL and enable "Subdomain check". Make sure, it says "saved" below the form.
11. You don't need to provide basic information about your app. Instead click at "Scopes" and then at "+ Add Scopes". Search for and check the following scopes: 11. You don't need to provide basic information about your app. Instead click at "Scopes" and then at "+ Add Scopes". On the left, click the category "Meeting" and check the scope `meeting:write`.
1. account:write:admin
2. meeting:write:admin
3. user:write:admin
12. Click "Done". 12. Click "Done".
13. You're good to go. Now you can easily add your Zoom integration in the Calendso settings. 13. You're good to go. Now you can easily add your Zoom integration in the Calendso settings.

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;

View file

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

View file

@ -5,7 +5,7 @@ import { CheckIcon } from '@heroicons/react/outline'
export default function Modal(props) { export default function Modal(props) {
return ( return (
<Transition.Root show={props.open} as={Fragment}> <Transition.Root show={props.open} as={Fragment}>
<Dialog as="div" static className="fixed z-10 inset-0 overflow-y-auto" open={props.open} onClose={props.handleClose}> <Dialog as="div" static 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"> <div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<Transition.Child <Transition.Child
as={Fragment} as={Fragment}
@ -16,7 +16,7 @@ export default function Modal(props) {
leaveFrom="opacity-100" leaveFrom="opacity-100"
leaveTo="opacity-0" 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> </Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */} {/* This element is to trick the browser into centering the modal contents. */}

View file

@ -1,10 +1,7 @@
import Link from "next/link"; import Link from "next/link";
import { CreditCardIcon, UserIcon, CodeIcon, KeyIcon, UserGroupIcon } from "@heroicons/react/solid"; import { CreditCardIcon, UserIcon, CodeIcon, KeyIcon, UserGroupIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import classNames from "@lib/classNames";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
export default function SettingsShell(props) { export default function SettingsShell(props) {
const router = useRouter(); const router = useRouter();
@ -38,7 +35,7 @@ export default function SettingsShell(props) {
]; ];
return ( return (
<div className="max-w-6xl"> <div>
<div className="sm:mx-auto"> <div className="sm:mx-auto">
<nav className="-mb-px flex space-x-2 sm:space-x-8" aria-label="Tabs"> <nav className="-mb-px flex space-x-2 sm:space-x-8" aria-label="Tabs">
{tabs.map((tab) => ( {tabs.map((tab) => (
@ -63,8 +60,9 @@ export default function SettingsShell(props) {
</Link> </Link>
))} ))}
</nav> </nav>
<hr />
</div> </div>
<main>{props.children}</main> <main className="max-w-4xl">{props.children}</main>
</div> </div>
); );
} }

View file

@ -16,13 +16,11 @@ import {
LinkIcon, LinkIcon,
} from "@heroicons/react/solid"; } from "@heroicons/react/solid";
import Logo from "./Logo"; import Logo from "./Logo";
import classNames from "@lib/classNames";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
export default function Shell(props) { export default function Shell(props) {
const router = useRouter(); const router = useRouter();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession(); const [session, loading] = useSession();
const telemetry = useTelemetry(); const telemetry = useTelemetry();
@ -46,7 +44,7 @@ export default function Shell(props) {
current: router.pathname.startsWith("/availability"), current: router.pathname.startsWith("/availability"),
}, },
{ {
name: "Integrations", name: "App Store",
href: "/integrations", href: "/integrations",
icon: PuzzleIcon, icon: PuzzleIcon,
current: router.pathname.startsWith("/integrations"), current: router.pathname.startsWith("/integrations"),
@ -70,117 +68,121 @@ export default function Shell(props) {
} }
return session ? ( return session ? (
<div className="h-screen flex overflow-hidden bg-gray-100"> <>
{/* Static sidebar for desktop */} <div className="h-screen flex overflow-hidden bg-gray-100">
<div className="hidden md:flex md:flex-shrink-0"> {/* Static sidebar for desktop */}
<div className="flex flex-col w-64"> <div className="hidden md:flex md:flex-shrink-0">
{/* Sidebar component, swap this element with another sidebar if you like */} <div className="flex flex-col w-56">
<div className="flex flex-col h-0 flex-1 border-r border-gray-200 bg-white"> {/* Sidebar component, swap this element with another sidebar if you like */}
<div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto"> <div className="flex flex-col h-0 flex-1 border-r border-gray-200 bg-white">
<Link href="/event-types"> <div className="flex-1 flex flex-col pt-5 pb-4 overflow-y-auto">
<a className="px-4"> <Link href="/event-types">
<Logo small /> <a className="px-4">
</a> <Logo small />
</Link> </a>
<nav className="mt-5 flex-1 px-2 bg-white space-y-1"> </Link>
{navigation.map((item) => ( <nav className="mt-5 flex-1 px-2 bg-white space-y-1">
<Link key={item.name} href={item.href}> {navigation.map((item) => (
<a <Link key={item.name} href={item.href}>
className={classNames( <a
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( className={classNames(
item.current ? "text-neutral-500" : "text-neutral-400 group-hover:text-neutral-500", item.current
"mr-3 flex-shrink-0 h-5 w-5" ? "bg-neutral-100 text-neutral-900"
)} : "text-neutral-500 hover:bg-gray-50 hover:text-neutral-900",
aria-hidden="true" "group flex items-center px-2 py-2 text-sm font-medium rounded-sm"
/> )}>
{item.name} <item.icon
</a> className={classNames(
</Link> item.current
))} ? "text-neutral-500"
</nav> : "text-neutral-400 group-hover:text-neutral-500",
</div> "mr-3 flex-shrink-0 h-5 w-5"
<div className="flex-shrink-0 flex border-t border-gray-200 p-4"> )}
<UserDropdown session={session} /> 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> </div>
</div>
<div className="flex flex-col w-0 flex-1 overflow-hidden"> <div className="flex flex-col w-0 flex-1 overflow-hidden">
{/* show top navigation for md and smaller (tablet and phones) */} <main className="flex-1 relative z-0 overflow-y-auto focus:outline-none">
<nav className="md:hidden bg-white shadow p-4 flex justify-between items-center"> {/* show top navigation for md and smaller (tablet and phones) */}
<Link href="/event-types"> <nav className="md:hidden bg-white shadow p-4 flex justify-between items-center">
<a> <Link href="/event-types">
<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">
<a> <a>
<CogIcon className="h-6 w-6" aria-hidden="true" /> <Logo />
</a> </a>
</Link> </Link>
</button> <div className="flex gap-3 items-center self-center">
<div className="mt-1"> <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">
<UserDropdown small bottom session={session} /> <span className="sr-only">View notifications</span>
</div> <Link href="/settings/profile">
</div> <a>
</nav> <CogIcon className="h-6 w-6" aria-hidden="true" />
<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>
</a> </a>
</Link> </Link>
) </button>
)} <div className="mt-1">
<UserDropdown small bottom session={session} />
</div>
</div>
</nav> </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*/} {/* show bottom navigation for md and smaller (tablet and phones) */}
<div className="block md:hidden pt-12" /> <nav className="bottom-nav md:hidden flex fixed bottom-0 bg-white w-full shadow">
</div> {/* note(PeerRich): using flatMap instead of map to remove settings from bottom nav */}
</main> {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>
</div> </>
) : null; ) : 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="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-gray-900 text-sm font-medium truncate">{session.user.name}</span>
<span className="text-neutral-500 font-normal text-sm truncate"> <span className="text-neutral-500 font-normal text-sm truncate">
{session.user.username} /{session.user.username}
</span> </span>
</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 { useRouter } from "next/router";
import Slots from "./Slots"; import Slots from "./Slots";
import { ExclamationIcon } from "@heroicons/react/solid"; import { ExclamationIcon } from "@heroicons/react/solid";
import React from "react";
import Loader from "@components/Loader";
const AvailableTimes = ({ const AvailableTimes = ({
date, date,
@ -25,7 +27,7 @@ const AvailableTimes = ({
}); });
return ( return (
<div className="sm:pl-4 mt-8 sm:mt-0 text-center sm:w-1/3 md:max-h-97 overflow-y-auto"> <div 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"> <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> <span className="w-1/2 dark:text-white text-gray-600">{date.format("dddd DD MMMM YYYY")}</span>
</div> </div>
@ -37,7 +39,7 @@ const AvailableTimes = ({
`/${user.username}/book?date=${slot.utc().format()}&type=${eventTypeId}` + `/${user.username}/book?date=${slot.utc().format()}&type=${eventTypeId}` +
(rescheduleUid ? "&rescheduleUid=" + rescheduleUid : "") (rescheduleUid ? "&rescheduleUid=" + rescheduleUid : "")
}> }>
<a className="block font-medium mb-4 text-blue-600 border border-blue-600 rounded hover:text-white hover:bg-blue-600 py-4"> <a className="block font-medium mb-4 bg-white dark:bg-neutral-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)} {slot.format(timeFormat)}
</a> </a>
</Link> </Link>
@ -49,7 +51,7 @@ const AvailableTimes = ({
</div> </div>
)} )}
{!isFullyBooked && slots.length === 0 && !hasErrors && <div className="loader" />} {!isFullyBooked && slots.length === 0 && !hasErrors && <Loader />}
{hasErrors && ( {hasErrors && (
<div className="bg-yellow-50 border-l-4 border-yellow-400 p-4"> <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))} onClick={() => setSelectedDate(inviteeDate.date(day))}
disabled={isDisabled(day)} disabled={isDisabled(day)}
className={ className={
"text-center w-10 h-10 rounded-full mx-auto" + "text-center w-10 h-10 mx-auto hover:border hover:border-black dark:hover:border-white" +
(isDisabled(day) ? " text-gray-400 font-light" : " text-blue-600 font-medium") + (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") (selectedDate && selectedDate.isSame(inviteeDate.date(day), "day")
? " bg-blue-600 text-white-important" ? " bg-black text-white-important"
: !isDisabled(day) : !isDisabled(day)
? " bg-blue-50 dark:bg-gray-900 dark:bg-opacity-30" ? " bg-gray-100 dark:bg-black dark:bg-opacity-30"
: "") : "")
}> }>
{day} {day}
@ -164,26 +166,34 @@ const DatePicker = ({
return selectedMonth ? ( return selectedMonth ? (
<div <div
className={ className={
"mt-8 sm:mt-0 " + "mt-8 sm:mt-0 min-w-[350px] " +
(selectedDate ? "sm:w-1/3 sm:border-r sm:dark:border-gray-900 sm:px-4" : "sm:w-1/2 sm:pl-4") (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"> <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"> <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> </span>
<div className="w-1/2 text-right"> <div className="w-1/2 text-right text-gray-600 dark:text-gray-400">
<button <button
onClick={decrementMonth} 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()}> 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>
<button onClick={incrementMonth}> <button className="group p-1" onClick={incrementMonth}>
<ChevronRightIcon className="w-5 h-5" /> <ChevronRightIcon className="group-hover:text-black dark:group-hover:text-white w-5 h-5" />
</button> </button>
</div> </div>
</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"] {["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
.sort((a, b) => (weekStart.startsWith(a) ? -1 : weekStart.startsWith(b) ? 1 : 0)) .sort((a, b) => (weekStart.startsWith(a) ? -1 : weekStart.startsWith(b) ? 1 : 0))
.map((weekDay) => ( .map((weekDay) => (

View file

@ -2,10 +2,7 @@ import { Switch } from "@headlessui/react";
import TimezoneSelect from "react-timezone-select"; import TimezoneSelect from "react-timezone-select";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { is24h, timeZone } from "../../lib/clock"; import { is24h, timeZone } from "../../lib/clock";
import classNames from "@lib/classNames";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const TimeOptions = (props) => { const TimeOptions = (props) => {
const [selectedTimeZone, setSelectedTimeZone] = useState(""); const [selectedTimeZone, setSelectedTimeZone] = useState("");
@ -28,7 +25,7 @@ const TimeOptions = (props) => {
return ( return (
selectedTimeZone !== "" && ( 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="flex mb-4">
<div className="w-1/2 dark:text-white text-gray-600 font-medium">Time Options</div> <div className="w-1/2 dark:text-white text-gray-600 font-medium">Time Options</div>
<div className="w-1/2"> <div className="w-1/2">
@ -40,7 +37,7 @@ const TimeOptions = (props) => {
checked={is24hClock} checked={is24hClock}
onChange={setIs24hClock} onChange={setIs24hClock}
className={classNames( 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" "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> <span className="sr-only">Use setting</span>

View file

@ -32,9 +32,9 @@ export default function EditTeamModal(props) {
}).then(loadMembers); }).then(loadMembers);
} }
return (<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true"> return (<div 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="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;</span> <span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>

View file

@ -40,12 +40,12 @@ export default function MemberInvitationModal(props) {
return ( return (
<div <div
className="fixed z-10 inset-0 overflow-y-auto" className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title" aria-labelledby="modal-title"
role="dialog" role="dialog"
aria-modal="true"> 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="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"> <span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203; &#8203;

View file

@ -1,15 +1,26 @@
import { useState } from 'react';
export default function Button(props) { export default function Button(props) {
return( return (
<button type="submit" className="btn btn-primary"> <button type="submit" className="btn btn-primary dark:btn-white">
{!props.loading && props.children} {!props.loading && props.children}
{props.loading && {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"> <svg
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle> className="animate-spin mx-4 h-5 w-5 text-white"
<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> 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> </svg>
} )}
</button> </button>
); );
} }

View file

@ -1,7 +1,7 @@
import Link from "next/link"; import Link from "next/link";
const PoweredByCalendso = () => ( 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`}> <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"> <a target="_blank" className="dark:text-white text-gray-500 opacity-50 hover:opacity-100">
powered by{" "} powered by{" "}

View file

@ -91,7 +91,7 @@ export const Scheduler = ({
type="button" type="button"
onClick={() => removeScheduleAt(idx)} onClick={() => removeScheduleAt(idx)}
className="btn-sm bg-transparent px-2 py-1 ml-1"> 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> </button>
</li> </li>
); );

View file

@ -28,12 +28,12 @@ export default function SetTimesModal(props) {
return ( return (
<div <div
className="fixed z-10 inset-0 overflow-y-auto" className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title" aria-labelledby="modal-title"
role="dialog" role="dialog"
aria-modal="true"> 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="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"> <span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203; &#8203;

View file

@ -37,7 +37,7 @@ export default class CalEventParser {
* Returns a footer section with links to change the event (as HTML). * Returns a footer section with links to change the event (as HTML).
*/ */
public getChangeEventFooterHtml(): string { 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

@ -10,8 +10,14 @@ import CalEventParser from "./CalEventParser";
const { google } = require("googleapis"); const { google } = require("googleapis");
const googleAuth = (credential) => { const googleAuth = (credential) => {
const { client_secret, client_id, redirect_uris } = JSON.parse(process.env.GOOGLE_API_CREDENTIALS).web; const { client_secret, client_id, redirect_uris } = JSON.parse(
const myGoogleAuth = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0]); process.env.GOOGLE_API_CREDENTIALS
).web;
const myGoogleAuth = new google.auth.OAuth2(
client_id,
client_secret,
redirect_uris[0]
);
myGoogleAuth.setCredentials(credential.key); myGoogleAuth.setCredentials(credential.key);
const isExpired = () => myGoogleAuth.isTokenExpiring(); const isExpired = () => myGoogleAuth.isTokenExpiring();
@ -43,7 +49,8 @@ const googleAuth = (credential) => {
}); });
return { return {
getToken: () => (!isExpired() ? Promise.resolve(myGoogleAuth) : refreshAccessToken()), getToken: () =>
!isExpired() ? Promise.resolve(myGoogleAuth) : refreshAccessToken(),
}; };
}; };
@ -81,7 +88,9 @@ const o365Auth = (credential) => {
.then(handleErrorsJson) .then(handleErrorsJson)
.then((responseBody) => { .then((responseBody) => {
credential.key.access_token = responseBody.access_token; credential.key.access_token = responseBody.access_token;
credential.key.expiry_date = Math.round(+new Date() / 1000 + responseBody.expires_in); credential.key.expiry_date = Math.round(
+new Date() / 1000 + responseBody.expires_in
);
return prisma.credential return prisma.credential
.update({ .update({
where: { where: {
@ -139,7 +148,11 @@ export interface CalendarApiAdapter {
deleteEvent(uid: string); deleteEvent(uid: string);
getAvailability(dateFrom, dateTo, selectedCalendars: IntegrationCalendar[]): Promise<unknown>; getAvailability(
dateFrom,
dateTo,
selectedCalendars: IntegrationCalendar[]
): Promise<unknown>;
listCalendars(): Promise<IntegrationCalendar[]>; listCalendars(): Promise<IntegrationCalendar[]>;
} }
@ -206,7 +219,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
return { return {
getAvailability: (dateFrom, dateTo, selectedCalendars) => { getAvailability: (dateFrom, dateTo, selectedCalendars) => {
const filter = "?$filter=start/dateTime ge '" + dateFrom + "' and end/dateTime le '" + dateTo + "'"; const filter = "?startdatetime=" + dateFrom + "&enddatetime=" + dateTo;
return auth return auth
.getToken() .getToken()
.then((accessToken) => { .then((accessToken) => {
@ -229,7 +242,7 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
headers: { headers: {
Prefer: 'outlook.timezone="Etc/GMT"', 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", { return fetch("https://graph.microsoft.com/v1.0/$batch", {
@ -309,7 +322,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
getAvailability: (dateFrom, dateTo, selectedCalendars) => getAvailability: (dateFrom, dateTo, selectedCalendars) =>
new Promise((resolve, reject) => new Promise((resolve, reject) =>
auth.getToken().then((myGoogleAuth) => { auth.getToken().then((myGoogleAuth) => {
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth }); const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
const selectedCalendarIds = selectedCalendars const selectedCalendarIds = selectedCalendars
.filter((e) => e.integration === integrationType) .filter((e) => e.integration === integrationType)
.map((e) => e.externalId); .map((e) => e.externalId);
@ -320,7 +336,9 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
} }
(selectedCalendarIds.length == 0 (selectedCalendarIds.length == 0
? calendar.calendarList.list().then((cals) => cals.data.items.map((cal) => cal.id)) ? calendar.calendarList
.list()
.then((cals) => cals.data.items.map((cal) => cal.id))
: Promise.resolve(selectedCalendarIds) : Promise.resolve(selectedCalendarIds)
) )
.then((calsIds) => { .then((calsIds) => {
@ -336,12 +354,19 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
if (err) { if (err) {
reject(err); reject(err);
} }
resolve(Object.values(apires.data.calendars).flatMap((item) => item["busy"])); resolve(
Object.values(apires.data.calendars).flatMap(
(item) => item["busy"]
)
);
} }
); );
}) })
.catch((err) => { .catch((err) => {
console.error("There was an error contacting google calendar service: ", err); console.error(
"There was an error contacting google calendar service: ",
err
);
reject(err); reject(err);
}); });
}) })
@ -375,7 +400,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
payload["conferenceData"] = event.conferenceData; payload["conferenceData"] = event.conferenceData;
} }
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth }); const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
calendar.events.insert( calendar.events.insert(
{ {
auth: myGoogleAuth, auth: myGoogleAuth,
@ -385,7 +413,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
}, },
function (err, event) { function (err, event) {
if (err) { if (err) {
console.error("There was an error contacting google calendar service: ", err); console.error(
"There was an error contacting google calendar service: ",
err
);
return reject(err); return reject(err);
} }
return resolve(event.data); return resolve(event.data);
@ -418,7 +449,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
payload["location"] = event.location; payload["location"] = event.location;
} }
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth }); const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
calendar.events.update( calendar.events.update(
{ {
auth: myGoogleAuth, auth: myGoogleAuth,
@ -430,7 +464,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
}, },
function (err, event) { function (err, event) {
if (err) { if (err) {
console.error("There was an error contacting google calendar service: ", err); console.error(
"There was an error contacting google calendar service: ",
err
);
return reject(err); return reject(err);
} }
return resolve(event.data); return resolve(event.data);
@ -441,7 +478,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
deleteEvent: (uid: string) => deleteEvent: (uid: string) =>
new Promise((resolve, reject) => new Promise((resolve, reject) =>
auth.getToken().then((myGoogleAuth) => { auth.getToken().then((myGoogleAuth) => {
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth }); const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
calendar.events.delete( calendar.events.delete(
{ {
auth: myGoogleAuth, auth: myGoogleAuth,
@ -452,7 +492,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
}, },
function (err, event) { function (err, event) {
if (err) { if (err) {
console.error("There was an error contacting google calendar service: ", err); console.error(
"There was an error contacting google calendar service: ",
err
);
return reject(err); return reject(err);
} }
return resolve(event.data); return resolve(event.data);
@ -463,7 +506,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
listCalendars: () => listCalendars: () =>
new Promise((resolve, reject) => new Promise((resolve, reject) =>
auth.getToken().then((myGoogleAuth) => { auth.getToken().then((myGoogleAuth) => {
const calendar = google.calendar({ version: "v3", auth: myGoogleAuth }); const calendar = google.calendar({
version: "v3",
auth: myGoogleAuth,
});
calendar.calendarList calendar.calendarList
.list() .list()
.then((cals) => { .then((cals) => {
@ -480,7 +526,10 @@ const GoogleCalendar = (credential): CalendarApiAdapter => {
); );
}) })
.catch((err) => { .catch((err) => {
console.error("There was an error contacting google calendar service: ", err); console.error(
"There was an error contacting google calendar service: ",
err
);
reject(err); reject(err);
}); });
}) })
@ -503,19 +552,29 @@ const calendars = (withCredentials): CalendarApiAdapter[] =>
}) })
.filter(Boolean); .filter(Boolean);
const getBusyCalendarTimes = (withCredentials, dateFrom, dateTo, selectedCalendars) => const getBusyCalendarTimes = (
withCredentials,
dateFrom,
dateTo,
selectedCalendars
) =>
Promise.all( Promise.all(
calendars(withCredentials).map((c) => c.getAvailability(dateFrom, dateTo, selectedCalendars)) calendars(withCredentials).map((c) =>
c.getAvailability(dateFrom, dateTo, selectedCalendars)
)
).then((results) => { ).then((results) => {
return results.reduce((acc, availability) => acc.concat(availability), []); return results.reduce((acc, availability) => acc.concat(availability), []);
}); });
const listCalendars = (withCredentials) => const listCalendars = (withCredentials) =>
Promise.all(calendars(withCredentials).map((c) => c.listCalendars())).then((results) => Promise.all(calendars(withCredentials).map((c) => c.listCalendars())).then(
results.reduce((acc, calendars) => acc.concat(calendars), []) (results) => results.reduce((acc, calendars) => acc.concat(calendars), [])
); );
const createEvent = async (credential: Credential, calEvent: CalendarEvent): Promise<unknown> => { const createEvent = async (
credential: Credential,
calEvent: CalendarEvent
): Promise<unknown> => {
const parser: CalEventParser = new CalEventParser(calEvent); const parser: CalEventParser = new CalEventParser(calEvent);
const uid: string = parser.getUid(); const uid: string = parser.getUid();
/* /*
@ -525,7 +584,9 @@ const createEvent = async (credential: Credential, calEvent: CalendarEvent): Pro
*/ */
const richEvent: CalendarEvent = parser.asRichEventPlain(); const richEvent: CalendarEvent = parser.asRichEventPlain();
const creationResult = credential ? await calendars([credential])[0].createEvent(richEvent) : null; const creationResult = credential
? await calendars([credential])[0].createEvent(richEvent)
: null;
const maybeHangoutLink = creationResult?.hangoutLink; const maybeHangoutLink = creationResult?.hangoutLink;
const maybeEntryPoints = creationResult?.entryPoints; const maybeEntryPoints = creationResult?.entryPoints;

3
lib/classNames.ts Normal file
View file

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

View file

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

View file

@ -17,6 +17,10 @@
"@heroicons/react": "^1.0.1", "@heroicons/react": "^1.0.1",
"@jitsu/sdk-js": "^2.0.1", "@jitsu/sdk-js": "^2.0.1",
"@prisma/client": "^2.23.0", "@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", "@tailwindcss/forms": "^0.2.1",
"async": "^3.2.0", "async": "^3.2.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",

154
pages/404.tsx Normal file
View file

@ -0,0 +1,154 @@
import { ChevronRightIcon } from "@heroicons/react/solid";
import { DocumentTextIcon, BookOpenIcon, CodeIcon, CheckIcon } 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 prisma, { whereAndSelect } from "@lib/prisma";
import Avatar from "../components/Avatar"; import Avatar from "../components/Avatar";
import Theme from "@components/Theme"; 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 { export default function User(props): User {
const { isReady } = Theme(props.user.theme); const { isReady } = Theme(props.user.theme);
const eventTypes = props.eventTypes.map((type) => ( const eventTypes = props.eventTypes.map((type) => (
<li <div
key={type.id} 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}`}> <Link href={`/${props.user.username}/${type.slug}`}>
<a className="block px-6 py-4"> <a className="block px-6 py-4">
<div <h2 className="font-semibold text-neutral-900 dark:text-white">{type.title}</h2>
className="inline-block w-3 h-3 rounded-full mr-2" <div className="mt-2 flex space-x-4">
style={{ backgroundColor: getRandomColorCode() }}></div> <div className="flex text-sm text-neutral-500">
<h2 className="inline-block font-medium dark:text-white">{type.title}</h2> <ClockIcon
<p className="inline-block text-gray-400 dark:text-gray-100 ml-2">{type.description}</p> 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> </a>
</Link> </Link>
</li> </div>
)); ));
return ( return (
isReady && ( isReady && (
<div> <div className="bg-neutral-50 dark:bg-black h-screen">
<Head> <Head>
<title>{props.user.name || props.user.username} | Calendso</title> <title>{props.user.name || props.user.username} | Calendso</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </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"> <div className="mb-8 text-center">
<Avatar user={props.user} className="mx-auto w-24 h-24 rounded-full mb-4" /> <Avatar user={props.user} className="mx-auto w-24 h-24 rounded-full mb-4" />
<h1 className="text-3xl font-semibold text-gray-800 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} {props.user.name || props.user.username}
</h1> </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>
<div className="shadow overflow-hidden rounded-md"> <div className="space-y-6">{eventTypes}</div>
<ul className="divide-y divide-gray-200 dark:divide-gray-900">{eventTypes}</ul> {eventTypes.length == 0 && (
{eventTypes.length == 0 && ( <div className="shadow overflow-hidden rounded-sm">
<div className="p-8 text-center text-gray-400 dark:text-white"> <div className="p-8 text-center text-gray-400 dark:text-white">
<h2 className="font-semibold text-3xl text-gray-600">Uh oh!</h2> <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> <p className="max-w-md mx-auto">This user hasn&apos;t set up any event types yet.</p>
</div> </div>
)} </div>
</div> )}
</main> </main>
</div> </div>
) )
@ -76,6 +99,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
select: { select: {
slug: true, slug: true,
title: true, title: true,
length: true,
description: true, description: true,
}, },
}); });

View file

@ -1,9 +1,10 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { GetServerSideProps, GetServerSidePropsContext } from "next"; import { GetServerSideProps, GetServerSidePropsContext } from "next";
import Head from "next/head"; import Head from "next/head";
import { ChevronDownIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid"; import { ChevronDownIcon, ChevronUpIcon, ClockIcon, GlobeIcon } from "@heroicons/react/solid";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import dayjs, { Dayjs } from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import * as Collapsible from "@radix-ui/react-collapsible";
import prisma, { whereAndSelect } from "@lib/prisma"; import prisma, { whereAndSelect } from "@lib/prisma";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "../../lib/telemetry";
@ -48,9 +49,15 @@ export default function Type(props): Type {
router.replace( router.replace(
{ {
query: { query: Object.assign(
date: formattedDate, {},
}, {
...router.query,
},
{
date: formattedDate,
}
),
}, },
undefined, undefined,
{ {
@ -122,13 +129,13 @@ export default function Type(props): Type {
<main <main
className={ className={
"mx-auto my-0 sm:my-24 transition-max-width ease-in-out duration-500 " + "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="sm:flex px-4 py-5 sm:p-4">
<div <div
className={ 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" /> <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> <h2 className="font-medium dark:text-gray-300 text-gray-500">{props.user.name}</h2>
@ -139,19 +146,25 @@ export default function Type(props): Type {
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> <ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{props.eventType.length} minutes {props.eventType.length} minutes
</p> </p>
<button
onClick={() => setIsTimeOptionsOpen(!isTimeOptionsOpen)} <Collapsible.Root open={isTimeOptionsOpen} onOpenChange={setIsTimeOptionsOpen}>
className="text-gray-500 mb-1 px-2 py-1 -ml-2"> <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" /> <GlobeIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{timeZone()} {timeZone()}
<ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" /> {isTimeOptionsOpen ? (
</button> <ChevronUpIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
{isTimeOptionsOpen && ( ) : (
<TimeOptions <ChevronDownIcon className="inline-block w-4 h-4 ml-1 -mt-1" />
onSelectTimeZone={handleSelectTimeZone} )}
onToggle24hClock={handleToggle24hClock} </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> <p className="dark:text-gray-200 text-gray-600 mt-3 mb-8">{props.eventType.description}</p>
</div> </div>
<DatePicker <DatePicker

View file

@ -15,7 +15,8 @@ import Avatar from "../../components/Avatar";
import Button from "../../components/ui/Button"; import Button from "../../components/ui/Button";
import { EventTypeCustomInputType } from "../../lib/eventTypeInput"; import { EventTypeCustomInputType } from "../../lib/eventTypeInput";
import Theme from "@components/Theme"; import Theme from "@components/Theme";
import { ReactMultiEmail, isEmail } from 'react-multi-email'; import { ReactMultiEmail } from "react-multi-email";
import "react-multi-email/style.css";
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
@ -71,7 +72,7 @@ export default function Book(props: any): JSX.Element {
const data = event.target["custom_" + input.id]; const data = event.target["custom_" + input.id];
if (data) { if (data) {
if (input.type === EventTypeCustomInputType.Bool) { if (input.type === EventTypeCustomInputType.Bool) {
return input.label + "\n" + (data.value ? "Yes" : "No"); return input.label + "\n" + (data.checked ? "Yes" : "No");
} else { } else {
return input.label + "\n" + data.value; return input.label + "\n" + data.value;
} }
@ -160,9 +161,9 @@ export default function Book(props: any): JSX.Element {
</Head> </Head>
<main className="max-w-3xl mx-auto my-0 sm:my-24"> <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: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" /> <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> <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"> <h1 className="text-3xl font-semibold dark:text-white text-gray-800 mb-4">
@ -178,7 +179,7 @@ export default function Book(props: any): JSX.Element {
{locationInfo(selectedLocation).address} {locationInfo(selectedLocation).address}
</p> </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" /> <CalendarIcon className="inline-block w-4 h-4 mr-1 -mt-1" />
{preferredTimeZone && {preferredTimeZone &&
dayjs(date) dayjs(date)
@ -199,7 +200,7 @@ export default function Book(props: any): JSX.Element {
name="name" name="name"
id="name" id="name"
required 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" placeholder="John Doe"
defaultValue={props.booking ? props.booking.attendees[0].name : ""} defaultValue={props.booking ? props.booking.attendees[0].name : ""}
/> />
@ -217,7 +218,7 @@ export default function Book(props: any): JSX.Element {
name="email" name="email"
id="email" id="email"
required 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" placeholder="you@example.com"
defaultValue={props.booking ? props.booking.attendees[0].email : ""} defaultValue={props.booking ? props.booking.attendees[0].email : ""}
/> />
@ -225,7 +226,9 @@ export default function Book(props: any): JSX.Element {
</div> </div>
{locations.length > 1 && ( {locations.length > 1 && (
<div className="mb-4"> <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) => ( {locations.map((location) => (
<label key={location.type} className="block"> <label key={location.type} className="block">
<input <input
@ -237,7 +240,9 @@ export default function Book(props: any): JSX.Element {
value={location.type} value={location.type}
checked={selectedLocation === 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> </label>
))} ))}
</div> </div>
@ -255,7 +260,7 @@ export default function Book(props: any): JSX.Element {
placeholder="Enter phone number" placeholder="Enter phone number"
id="phone" id="phone"
required 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={() => { onChange={() => {
/* DO NOT REMOVE: Callback required by PhoneInput, comment added to satisfy eslint:no-empty-function */ /* DO NOT REMOVE: Callback required by PhoneInput, comment added to satisfy eslint:no-empty-function */
}} }}
@ -281,7 +286,7 @@ export default function Book(props: any): JSX.Element {
id={"custom_" + input.id} id={"custom_" + input.id}
required={input.required} required={input.required}
rows={3} 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="" placeholder=""
/> />
)} )}
@ -291,7 +296,7 @@ export default function Book(props: any): JSX.Element {
name={"custom_" + input.id} name={"custom_" + input.id}
id={"custom_" + input.id} id={"custom_" + input.id}
required={input.required} 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="" placeholder=""
/> />
)} )}
@ -301,7 +306,7 @@ export default function Book(props: any): JSX.Element {
name={"custom_" + input.id} name={"custom_" + input.id}
id={"custom_" + input.id} id={"custom_" + input.id}
required={input.required} 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="" placeholder=""
/> />
)} )}
@ -311,7 +316,7 @@ export default function Book(props: any): JSX.Element {
type="checkbox" type="checkbox"
name={"custom_" + input.id} name={"custom_" + input.id}
id={"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="" placeholder=""
/> />
<label <label
@ -374,13 +379,14 @@ export default function Book(props: any): JSX.Element {
name="notes" name="notes"
id="notes" id="notes"
rows={3} 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." placeholder="Please share anything that will help prepare for our meeting."
defaultValue={props.booking ? props.booking.description : ""} defaultValue={props.booking ? props.booking.description : ""}
/> />
</div> </div>
<div className="flex items-start"> <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"} {rescheduleUid ? "Reschedule" : "Confirm"}
</Button> </Button>
<Link <Link
@ -391,7 +397,7 @@ export default function Book(props: any): JSX.Element {
props.eventType.slug + props.eventType.slug +
(rescheduleUid ? "?rescheduleUid=" + rescheduleUid : "") (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> </Link>
</div> </div>
</form> </form>
@ -452,8 +458,12 @@ export async function getServerSideProps(context) {
}, },
}); });
const eventTypeObject = [eventType].map(e => { const eventTypeObject = [eventType].map((e) => {
return ({...e, periodStartDate:e.periodStartDate?.toString() ?? null, periodEndDate:e.periodEndDate?.toString() ?? null,}) return {
...e,
periodStartDate: e.periodStartDate?.toString() ?? null,
periodEndDate: e.periodEndDate?.toString() ?? null,
};
})[0]; })[0];
let booking = null; let booking = null;

View file

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

View file

@ -9,8 +9,16 @@ class MyDocument extends Document {
render() { render() {
return ( return (
<Html> <Html>
<Head /> <Head>
<body className="dark:bg-gray-900 bg-white"> <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 /> <Main />
<NextScript /> <NextScript />
</body> </body>

View file

@ -14,12 +14,14 @@ import logger from "../../../lib/logger";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone"; import timezone from "dayjs/plugin/timezone";
import isBetween from "dayjs/plugin/isBetween";
import dayjsBusinessDays from "dayjs-business-days"; import dayjsBusinessDays from "dayjs-business-days";
import { Exception } from "handlebars"; import { Exception } from "handlebars";
import EventOrganizerRequestMail from "@lib/emails/EventOrganizerRequestMail"; import EventOrganizerRequestMail from "@lib/emails/EventOrganizerRequestMail";
dayjs.extend(dayjsBusinessDays); dayjs.extend(dayjsBusinessDays);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(isBetween);
dayjs.extend(timezone); dayjs.extend(timezone);
const translator = short(); const translator = short();
@ -28,7 +30,6 @@ const log = logger.getChildLogger({ prefix: ["[api] book:user"] });
function isAvailable(busyTimes, time, length) { function isAvailable(busyTimes, time, length) {
// Check for conflicts // Check for conflicts
let t = true; let t = true;
if (Array.isArray(busyTimes) && busyTimes.length > 0) { if (Array.isArray(busyTimes) && busyTimes.length > 0) {
busyTimes.forEach((busyTime) => { busyTimes.forEach((busyTime) => {
const startTime = dayjs(busyTime.start); const startTime = dayjs(busyTime.start);
@ -312,8 +313,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const calendarAvailability = await getBusyCalendarTimes( const calendarAvailability = await getBusyCalendarTimes(
currentUser.credentials, currentUser.credentials,
dayjs(req.body.start).startOf("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(), dayjs(req.body.end).endOf("day").utc().format("YYYY-MM-DDTHH:mm:ss[Z]"),
selectedCalendars selectedCalendars
); );
const videoAvailability = await getBusyVideoTimes( const videoAvailability = await getBusyVideoTimes(
@ -423,7 +424,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
try { try {
isAvailableToBeBooked = isAvailable(commonAvailability, req.body.start, selectedEventType.length); isAvailableToBeBooked = isAvailable(commonAvailability, req.body.start, selectedEventType.length);
} catch { } catch (e) {
console.log(e);
log.debug({ log.debug({
message: "Unable set isAvailableToBeBooked. Using true. ", message: "Unable set isAvailableToBeBooked. Using true. ",
}); });

View file

@ -8,7 +8,7 @@ export default function Error() {
const { error } = router.query; const { error } = router.query;
return ( return (
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true"> <div className="fixed z-50 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<Head> <Head>
<title>{error} - Calendso</title> <title>{error} - Calendso</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />

View file

@ -43,7 +43,7 @@ export default function Login({ csrfToken }) {
</div> </div>
<div className="w-1/2 text-right"> <div className="w-1/2 text-right">
<Link href="/auth/forgot-password"> <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> </Link>
</div> </div>
</div> </div>

View file

@ -4,7 +4,7 @@ import { CheckIcon } from '@heroicons/react/outline';
export default function Logout() { export default function Logout() {
return ( return (
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true"> <div className="fixed z-50 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<Head> <Head>
<title>Logged out - Calendso</title> <title>Logged out - Calendso</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
@ -29,7 +29,7 @@ export default function Logout() {
</div> </div>
<div className="mt-5 sm:mt-6"> <div className="mt-5 sm:mt-6">
<Link href="/auth/login"> <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 Go back to the login page
</a> </a>
</Link> </Link>

View file

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

View file

@ -10,6 +10,7 @@ import { ClockIcon } from "@heroicons/react/outline";
import Loader from '@components/Loader'; import Loader from '@components/Loader';
export default function Availability(props) { export default function Availability(props) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession(); const [session, loading] = useSession();
const router = useRouter(); const router = useRouter();
const [showAddModal, setShowAddModal] = useState(false); const [showAddModal, setShowAddModal] = useState(false);
@ -29,7 +30,7 @@ export default function Availability(props) {
const bufferMinsRef = useRef<HTMLInputElement>(); const bufferMinsRef = useRef<HTMLInputElement>();
if (loading) { if (loading) {
return <Loader/>; return <Loader />;
} }
function toggleAddModal() { function toggleAddModal() {
@ -124,7 +125,7 @@ export default function Availability(props) {
"> ">
<div className="flex"> <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"> <div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900"> <h3 className="text-lg leading-6 font-medium text-gray-900">
Change the start and end times of your day Change the start and end times of your day
@ -143,7 +144,7 @@ export default function Availability(props) {
</div> </div>
</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"> <div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900"> <h3 className="text-lg leading-6 font-medium text-gray-900">
Something doesn&apos;t look right? Something doesn&apos;t look right?
@ -153,7 +154,7 @@ export default function Availability(props) {
</div> </div>
<div className="mt-5"> <div className="mt-5">
<Link href="/availability/troubleshoot"> <Link href="/availability/troubleshoot">
<a className="btn btn-primary">Launch troubleshooter</a> <a className="btn btn-white">Launch troubleshooter</a>
</Link> </Link>
</div> </div>
</div> </div>
@ -161,13 +162,13 @@ export default function Availability(props) {
</div> </div>
{showChangeTimesModal && ( {showChangeTimesModal && (
<div <div
className="fixed z-10 inset-0 overflow-y-auto" className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title" aria-labelledby="modal-title"
role="dialog" role="dialog"
aria-modal="true"> 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="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div <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> aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"> <span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">

View file

@ -10,12 +10,13 @@ import Loader from '@components/Loader';
dayjs.extend(utc); dayjs.extend(utc);
export default function Troubleshoot({ user }) { export default function Troubleshoot({ user }) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession(); const [session, loading] = useSession();
const [availability, setAvailability] = useState([]); const [availability, setAvailability] = useState([]);
const [selectedDate, setSelectedDate] = useState(dayjs()); const [selectedDate, setSelectedDate] = useState(dayjs());
if (loading) { if (loading) {
return <Loader/>; return <Loader />;
} }
function convertMinsToHrsMins(mins) { function convertMinsToHrsMins(mins) {

View file

@ -4,14 +4,21 @@ import { getSession, useSession } from "next-auth/client";
import Shell from "../../components/Shell"; import Shell from "../../components/Shell";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import dayjs from "dayjs"; 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";
import Loader from "@components/Loader";
export default function Bookings({ bookings }) { export default function Bookings({ bookings }) {
const [, loading] = useSession(); // eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
const router = useRouter(); const router = useRouter();
if (loading) { if (loading) {
return <p className="text-gray-400">Loading...</p>; return <Loader />;
} }
async function confirmBookingHandler(booking, confirm: boolean) { async function confirmBookingHandler(booking, confirm: boolean) {
@ -37,30 +44,8 @@ export default function Bookings({ bookings }) {
<div className="-mx-4 sm:mx-auto flex flex-col"> <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="-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="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"> <table className="min-w-full divide-y divide-gray-200">
<thead className="bg-gray-50">
<tr>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Person
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Event
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
Date
</th>
<th scope="col" className="relative px-6 py-3">
<span className="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody className="bg-white divide-y divide-gray-200"> <tbody className="bg-white divide-y divide-gray-200">
{bookings {bookings
.filter((booking) => !booking.confirmed && !booking.rejected) .filter((booking) => !booking.confirmed && !booking.rejected)
@ -73,35 +58,33 @@ export default function Bookings({ bookings }) {
Unconfirmed Unconfirmed
</span> </span>
)} )}
<div className="text-sm font-medium text-gray-900"> <div className="text-sm text-neutral-900 font-medium truncate max-w-60 md:max-w-96">
{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">
{booking.title} {booking.title}
</div> </div>
</td> <div className="sm:hidden">
<td <div className="text-sm text-gray-900">
className={ {dayjs(booking.startTime).format("D MMMM YYYY")}:{" "}
"px-6 py-4 max-w-20 w-full" + (booking.rejected ? " line-through" : "") <small className="text-sm text-gray-500">
}> {dayjs(booking.startTime).format("HH:mm")} -{" "}
<div className="hidden lg:block text-sm text-neutral-900 font-medium"> {dayjs(booking.endTime).format("HH:mm")}
{booking.title} </small>
</div>
</div> </div>
<div className="hidden lg:block text-sm text-neutral-500"> <div className="text-sm text-blue-500">
You and {booking.attendees[0].name} <a href={"mailto:" + booking.attendees[0].email}>
{booking.attendees[0].email}
</a>
</div> </div>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap"> <td className="hidden sm:table-cell px-6 py-4 whitespace-nowrap">
<div className="text-sm text-gray-900"> <div className="text-sm text-gray-900">
{dayjs(booking.startTime).format("D MMMM YYYY")} {dayjs(booking.startTime).format("D MMMM YYYY")}
</div> </div>
<div className="text-sm text-gray-500"> <div className="text-sm text-gray-500">
{dayjs(booking.startTime).format("HH:mm")} - {dayjs(booking.endTime).format("HH:mm")} {dayjs(booking.startTime).format("HH:mm")} -{" "}
{dayjs(booking.endTime).format("HH:mm")}
</div> </div>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium"> <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{!booking.confirmed && !booking.rejected && ( {!booking.confirmed && !booking.rejected && (
<> <>
@ -112,7 +95,7 @@ export default function Bookings({ bookings }) {
</button> </button>
<button <button
onClick={() => confirmBookingHandler(booking, false)} 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 Reject
</button> </button>
</> </>
@ -121,14 +104,89 @@ export default function Bookings({ bookings }) {
<> <>
<a <a
href={window.location.href + "/../cancel/" + booking.uid} 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 Cancel
</a> </a>
<a <a
href={window.location.href + "/../reschedule/" + booking.uid} 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 Reschedule
</a> </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 && ( {!booking.confirmed && booking.rejected && (
@ -184,8 +242,8 @@ export async function getServerSideProps(context) {
}, },
}); });
const bookings = b.map(booking=>{ const bookings = b.reverse().map((booking) => {
return ({...booking, startTime:booking.startTime.toISOString(), endTime:booking.endTime.toISOString(),}) return { ...booking, startTime: booking.startTime.toISOString(), endTime: booking.endTime.toISOString() };
}); });
return { props: { bookings } }; return { props: { bookings } };

View file

@ -15,10 +15,6 @@ dayjs.extend(isBetween);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Type(props) { export default function Type(props) {
// Get router variables // Get router variables
const router = useRouter(); const router = useRouter();
@ -66,7 +62,7 @@ export default function Type(props) {
<link rel="icon" href="/favicon.ico"/> <link rel="icon" href="/favicon.ico"/>
</Head> </Head>
<main className="max-w-3xl mx-auto my-24"> <main className="max-w-3xl mx-auto my-24">
<div className="fixed z-10 inset-0 overflow-y-auto"> <div className="fixed z-50 inset-0 overflow-y-auto">
<div <div
className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> 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"> <div className="fixed inset-0 my-4 sm:my-0 transition-opacity" aria-hidden="true">

View file

@ -1,4 +1,3 @@
import {useState} from 'react';
import Head from 'next/head'; import Head from 'next/head';
import prisma from '../../lib/prisma'; import prisma from '../../lib/prisma';
import {useRouter} from 'next/router'; import {useRouter} from 'next/router';
@ -14,16 +13,10 @@ dayjs.extend(isBetween);
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
function classNames(...classes) {
return classes.filter(Boolean).join(' ')
}
export default function Type(props) { export default function Type(props) {
// Get router variables // Get router variables
const router = useRouter(); const router = useRouter();
const [is24h, setIs24h] = useState(false);
return ( return (
<div> <div>
<Head> <Head>
@ -34,7 +27,7 @@ export default function Type(props) {
<link rel="icon" href="/favicon.ico"/> <link rel="icon" href="/favicon.ico"/>
</Head> </Head>
<main className="max-w-3xl mx-auto my-24"> <main className="max-w-3xl mx-auto my-24">
<div className="fixed z-10 inset-0 overflow-y-auto"> <div className="fixed z-50 inset-0 overflow-y-auto">
<div <div
className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> 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"> <div className="fixed inset-0 my-4 sm:my-0 transition-opacity" aria-hidden="true">

View file

@ -7,7 +7,7 @@ import Select, { OptionBase } from "react-select";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
import { LocationType } from "@lib/location"; import { LocationType } from "@lib/location";
import Shell from "@components/Shell"; import Shell from "@components/Shell";
import { getSession, useSession } from "next-auth/client"; import { getSession } from "next-auth/client";
import { Scheduler } from "@components/ui/Scheduler"; import { Scheduler } from "@components/ui/Scheduler";
import { Disclosure } from "@headlessui/react"; import { Disclosure } from "@headlessui/react";
@ -16,7 +16,6 @@ import { EventTypeCustomInput, EventTypeCustomInputType } from "@lib/eventTypeIn
import { import {
LocationMarkerIcon, LocationMarkerIcon,
LinkIcon, LinkIcon,
PencilIcon,
PlusIcon, PlusIcon,
DocumentIcon, DocumentIcon,
ChevronRightIcon, ChevronRightIcon,
@ -30,7 +29,6 @@ import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone"; import timezone from "dayjs/plugin/timezone";
import { Availability, EventType, User } from "@prisma/client"; import { Availability, EventType, User } from "@prisma/client";
import { validJson } from "@lib/jsonUtils"; import { validJson } from "@lib/jsonUtils";
import Text from "@components/ui/Text";
import { RadioGroup } from "@headlessui/react"; import { RadioGroup } from "@headlessui/react";
import classnames from "classnames"; import classnames from "classnames";
import throttle from "lodash.throttle"; import throttle from "lodash.throttle";
@ -96,13 +94,12 @@ const PERIOD_TYPES = [
]; ];
export default function EventTypePage({ export default function EventTypePage({
user, user,
eventType, eventType,
locationOptions, locationOptions,
availability, availability,
}: Props): JSX.Element { }: Props): JSX.Element {
const router = useRouter(); const router = useRouter();
const [session, loading] = useSession();
console.log(eventType); console.log(eventType);
const inputOptions: OptionBase[] = [ const inputOptions: OptionBase[] = [
@ -267,7 +264,7 @@ export default function EventTypePage({
}, },
}); });
router.push("/availability"); router.push("/event-types");
} }
const openLocationModal = (type: LocationType) => { const openLocationModal = (type: LocationType) => {
@ -390,39 +387,32 @@ export default function EventTypePage({
<title>{eventType.title} | Event Type | Calendso</title> <title>{eventType.title} | Event Type | Calendso</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Shell heading={"Event Type: " + eventType.title} subtitle={eventType.description}> <Shell
<div className="flex"> heading={
<div className="w-10/12 mr-2"> <input
<div className="bg-white rounded-sm border border-neutral-200 p-8"> 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"> <form onSubmit={updateEventTypeHandler} className="space-y-4">
<div className="flex"> <div className="block sm:flex items-center">
<div className="w-1/4"> <div className="min-w-44 mb-4 sm:mb-0">
<label htmlFor="title" className="flex font-medium text-neutral-700 mt-1"> <label htmlFor="slug" className="text-sm flex font-medium text-neutral-700 mt-0">
<PencilIcon className="w-4 h-4 mr-2 mt-1 text-neutral-500" /> <LinkIcon className="w-4 h-4 mr-2 mt-0.5 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" />
URL URL
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
<div className="flex rounded-sm shadow-sm"> <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"> <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}/ {typeof location !== "undefined" ? location.hostname : ""}/{user.username}/
@ -439,14 +429,45 @@ export default function EventTypePage({
</div> </div>
</div> </div>
</div> </div>
<div className="flex">
<div className="w-1/4"> <div className="block sm:flex items-center">
<label htmlFor="location" className="flex font-medium text-neutral-700 mt-1"> <div className="min-w-44 mb-4 sm:mb-0">
<LocationMarkerIcon className="w-4 h-4 mr-2 mt-1 text-neutral-500" /> <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 Location
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
{locations.length === 0 && ( {locations.length === 0 && (
<div className="mt-1 mb-2"> <div className="mt-1 mb-2">
<div className="flex"> <div className="flex">
@ -470,19 +491,19 @@ export default function EventTypePage({
className="mb-2 p-2 border border-neutral-300 rounded-sm shadow-sm"> className="mb-2 p-2 border border-neutral-300 rounded-sm shadow-sm">
<div className="flex justify-between"> <div className="flex justify-between">
{location.type === LocationType.InPerson && ( {location.type === LocationType.InPerson && (
<div className="flex-grow flex"> <div className="flex-grow flex items-center">
<LocationMarkerIcon className="h-6 w-6" /> <LocationMarkerIcon className="h-6 w-6" />
<span className="ml-2 text-sm">{location.address}</span> <span className="ml-2 text-sm">{location.address}</span>
</div> </div>
)} )}
{location.type === LocationType.Phone && ( {location.type === LocationType.Phone && (
<div className="flex-grow flex"> <div className="flex-grow flex items-center">
<PhoneIcon className="h-6 w-6" /> <PhoneIcon className="h-6 w-6" />
<span className="ml-2 text-sm">Phone call</span> <span className="ml-2 text-sm">Phone call</span>
</div> </div>
)} )}
{location.type === LocationType.GoogleMeet && ( {location.type === LocationType.GoogleMeet && (
<div className="flex-grow flex"> <div className="flex-grow flex items-center">
<svg <svg
className="h-6 w-6" className="h-6 w-6"
viewBox="0 0 64 54" viewBox="0 0 64 54"
@ -513,7 +534,7 @@ export default function EventTypePage({
</div> </div>
)} )}
{location.type === LocationType.Zoom && ( {location.type === LocationType.Zoom && (
<div className="flex-grow flex"> <div className="flex-grow flex items-center">
<svg <svg
className="h-6 w-6" className="h-6 w-6"
viewBox="0 0 64 64" viewBox="0 0 64 64"
@ -557,10 +578,12 @@ export default function EventTypePage({
<li> <li>
<button <button
type="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)}> onClick={() => setShowLocationModal(true)}>
<PlusIcon className="h-5 w-5" /> <PlusIcon className="h-4 w-4 mt-0.5 text-neutral-900" />
<span className="font-medium">Add another location option</span> <span className="ml-1 text-neutral-700 text-sm font-medium">
Add another location
</span>
</button> </button>
</li> </li>
)} )}
@ -568,41 +591,17 @@ export default function EventTypePage({
)} )}
</div> </div>
</div> </div>
<div className="flex">
<div className="w-1/4"> <hr className="border-neutral-200" />
<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" /> <div className="block sm:flex items-center">
Duration <div className="min-w-44 mb-4 sm:mb-0">
</label> <label htmlFor="description" className="text-sm flex font-medium text-neutral-700 mt-0">
</div> <DocumentIcon className="w-4 h-4 mr-2 mt-0.5 text-neutral-500" />
<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" />
Description Description
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
<textarea <textarea
ref={descriptionRef} ref={descriptionRef}
name="description" name="description"
@ -622,13 +621,15 @@ export default function EventTypePage({
<span className="text-neutral-700 text-sm font-medium">Show advanced settings</span> <span className="text-neutral-700 text-sm font-medium">Show advanced settings</span>
</Disclosure.Button> </Disclosure.Button>
<Disclosure.Panel className="space-y-4"> <Disclosure.Panel className="space-y-4">
<div className="flex"> <div className="block sm:flex items-center">
<div className="w-1/4"> <div className="min-w-44 mb-4 sm:mb-0">
<label htmlFor="eventName" className="flex font-medium text-neutral-700 mt-2"> <label
htmlFor="eventName"
className="text-sm flex font-medium text-neutral-700 mt-2">
Event name Event name
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
<div className="mt-1 relative rounded-sm shadow-sm"> <div className="mt-1 relative rounded-sm shadow-sm">
<input <input
ref={eventNameRef} ref={eventNameRef}
@ -642,15 +643,15 @@ export default function EventTypePage({
</div> </div>
</div> </div>
</div> </div>
<div className="flex"> <div className="block sm:flex items-center">
<div className="w-1/4"> <div className="min-w-44 mb-4 sm:mb-0">
<label <label
htmlFor="additionalFields" htmlFor="additionalFields"
className="flex font-medium text-neutral-700 mt-2"> className="text-sm flex font-medium text-neutral-700 mt-2">
Additional inputs Additional inputs
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
<ul className="w-96 mt-1"> <ul className="w-96 mt-1">
{customInputs.map((customInput) => ( {customInputs.map((customInput) => (
<li key={customInput.label} className="bg-secondary-50 mb-2 p-2 border"> <li key={customInput.label} className="bg-secondary-50 mb-2 p-2 border">
@ -696,13 +697,13 @@ export default function EventTypePage({
</ul> </ul>
</div> </div>
</div> </div>
<div className="flex"> <div className="block sm:flex items-center">
<div className="w-1/4"> <div className="min-w-44 mb-4 sm:mb-0">
<label htmlFor="hidden" className="flex font-medium text-neutral-700"> <label htmlFor="hidden" className="text-sm flex font-medium text-neutral-700">
Hide event type Hide event type
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
<div className="relative flex items-start"> <div className="relative flex items-start">
<div className="flex items-center h-5"> <div className="flex items-center h-5">
<input <input
@ -723,15 +724,15 @@ export default function EventTypePage({
</div> </div>
</div> </div>
</div> </div>
<div className="flex"> <div className="block sm:flex items-center">
<div className="w-1/4"> <div className="min-w-44 mb-4 sm:mb-0">
<label <label
htmlFor="requiresConfirmation" htmlFor="requiresConfirmation"
className="flex font-medium text-neutral-700"> className="text-sm flex font-medium text-neutral-700">
Opt-in booking Opt-in booking
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
<div className="relative flex items-start"> <div className="relative flex items-start">
<div className="flex items-center h-5"> <div className="flex items-center h-5">
<input <input
@ -752,15 +753,18 @@ export default function EventTypePage({
</div> </div>
</div> </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 <label
htmlFor="inviteesCanSchedule" 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 Invitees can schedule
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
<RadioGroup value={periodType} onChange={setPeriodType}> <RadioGroup value={periodType} onChange={setPeriodType}>
<RadioGroup.Label className="sr-only">Date Range</RadioGroup.Label> <RadioGroup.Label className="sr-only">Date Range</RadioGroup.Label>
<div> <div>
@ -771,22 +775,22 @@ export default function EventTypePage({
className={({ checked }) => className={({ checked }) =>
classnames( classnames(
checked ? "border-secondary-200 z-10" : "border-gray-200", 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 }) => ( {({ active, checked }) => (
<> <>
<span <div
className={classnames( className={classnames(
checked checked
? "bg-primary-600 border-transparent" ? "bg-primary-600 border-transparent"
: "bg-white border-gray-300", : "bg-white border-gray-300",
active ? "ring-2 ring-offset-2 ring-primary-500" : "", 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"> aria-hidden="true">
<span className="rounded-full bg-white w-1.5 h-1.5" /> <span className="rounded-full bg-white w-1.5 h-1.5" />
</span> </div>
<div className="lg:ml-3 flex flex-col"> <div className="lg:ml-3 flex flex-col">
<RadioGroup.Label <RadioGroup.Label
as="span" as="span"
@ -851,13 +855,18 @@ export default function EventTypePage({
</RadioGroup> </RadioGroup>
</div> </div>
</div> </div>
<div className="flex">
<div className="w-1/4"> <hr className="border-neutral-200" />
<label htmlFor="availability" className="flex font-medium text-neutral-700 mt-2">
<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 Availability
</label> </label>
</div> </div>
<div className="w-3/4"> <div className="w-full">
<Scheduler <Scheduler
setAvailability={setEnteredAvailability} setAvailability={setEnteredAvailability}
setTimeZone={setSelectedTimeZone} setTimeZone={setSelectedTimeZone}
@ -885,7 +894,7 @@ export default function EventTypePage({
</form> </form>
</div> </div>
</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"> <div className="space-y-4">
<a <a
href={"/" + user.username + "/" + eventType.slug} href={"/" + user.username + "/" + eventType.slug}
@ -904,7 +913,7 @@ export default function EventTypePage({
type="button" type="button"
className="flex text-md font-medium text-neutral-700"> className="flex text-md font-medium text-neutral-700">
<LinkIcon className="w-4 h-4 mt-1 mr-2 text-neutral-500" /> <LinkIcon className="w-4 h-4 mt-1 mr-2 text-neutral-500" />
Copy link to event Copy link
</button> </button>
<button <button
onClick={deleteEventTypeHandler} onClick={deleteEventTypeHandler}
@ -918,13 +927,13 @@ export default function EventTypePage({
</div> </div>
{showLocationModal && ( {showLocationModal && (
<div <div
className="fixed z-10 inset-0 overflow-y-auto" className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title" aria-labelledby="modal-title"
role="dialog" role="dialog"
aria-modal="true"> 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="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div <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> aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"> <span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
@ -968,13 +977,13 @@ export default function EventTypePage({
)} )}
{showAddCustomModal && ( {showAddCustomModal && (
<div <div
className="fixed z-10 inset-0 overflow-y-auto" className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title" aria-labelledby="modal-title"
role="dialog" role="dialog"
aria-modal="true"> 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="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div <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" aria-hidden="true"
/> />

View file

@ -1,16 +1,7 @@
import Head from "next/head"; import { Dialog, DialogClose, DialogContent, DialogTrigger } from "@components/Dialog";
import Link from "next/link"; import { Tooltip } from "@components/Tooltip";
import prisma from "../../lib/prisma"; import Loader from "@components/Loader";
import Shell from "../../components/Shell";
import { useRouter } from "next/router";
import { getSession, useSession } from "next-auth/client";
import { Fragment, useRef, useState } from "react";
import { Menu, Transition } from "@headlessui/react"; import { Menu, Transition } from "@headlessui/react";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
import { import {
ClockIcon, ClockIcon,
DotsHorizontalIcon, DotsHorizontalIcon,
@ -20,12 +11,18 @@ import {
PlusIcon, PlusIcon,
UserIcon, UserIcon,
} from "@heroicons/react/solid"; } from "@heroicons/react/solid";
import Loader from '@components/Loader'; 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 }) { export default function Availability({ user, types }) {
const [session, loading] = useSession(); const [session, loading] = useSession();
const router = useRouter(); const router = useRouter();
const [showAddModal, setShowAddModal] = useState(false);
const titleRef = useRef<HTMLInputElement>(); const titleRef = useRef<HTMLInputElement>();
const slugRef = useRef<HTMLInputElement>(); const slugRef = useRef<HTMLInputElement>();
@ -41,8 +38,7 @@ export default function Availability({ user, types }) {
const enteredLength = lengthRef.current.value; const enteredLength = lengthRef.current.value;
// TODO: Add validation // TODO: Add validation
await fetch("/api/availability/eventtype", {
const response = await fetch("/api/availability/eventtype", {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
title: enteredTitle, title: enteredTitle,
@ -56,19 +52,120 @@ export default function Availability({ user, types }) {
}); });
if (enteredTitle && enteredLength) { if (enteredTitle && enteredLength) {
router.replace(router.asPath); await router.replace(router.asPath);
toggleAddModal();
} }
} }
function toggleAddModal() { function autoPopulateSlug() {
setShowAddModal(!showAddModal); let t = titleRef.current.value;
t = t.replace(/\s+/g, "-").toLowerCase();
slugRef.current.value = t;
} }
if (loading) { if (loading) {
return <Loader/>; 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 ( return (
<div> <div>
<Head> <Head>
@ -78,23 +175,16 @@ export default function Availability({ user, types }) {
<Shell <Shell
heading="Event Types" heading="Event Types"
subtitle="Create events to share for people to book on your calendar." subtitle="Create events to share for people to book on your calendar."
CTA={ CTA={types.length !== 0 && <CreateNewEventDialog />}>
<button <div className="bg-white border border-gray-200 rounded-sm overflow-hidden -mx-4 sm:mx-0">
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">
<ul className="divide-y divide-neutral-200"> <ul className="divide-y divide-neutral-200">
{types.map((type) => ( {types.map((type) => (
<li key={type.id}> <li key={type.id}>
<Link href={"/event-types/" + type.id}> <div className="hover:bg-neutral-50">
<a className="block hover:bg-neutral-50"> <div className="px-4 py-4 flex items-center sm:px-6">
<div className="px-4 py-4 flex items-center sm:px-6"> <Link href={"/event-types/" + type.id}>
<div className="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between"> <a className="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
<div className="truncate"> <span className="truncate ">
<div className="flex text-sm"> <div className="flex text-sm">
<p className="font-medium text-neutral-900 truncate">{type.title}</p> <p className="font-medium text-neutral-900 truncate">{type.title}</p>
{type.hidden && ( {type.hidden && (
@ -106,164 +196,174 @@ export default function Availability({ user, types }) {
<div className="mt-2 flex space-x-4"> <div className="mt-2 flex space-x-4">
<div className="flex items-center text-sm text-neutral-500"> <div className="flex items-center text-sm text-neutral-500">
<ClockIcon <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" aria-hidden="true"
/> />
<p>{type.length}m</p> <p>{type.length}m</p>
</div> </div>
<div className="flex items-center text-sm text-neutral-500"> <div className="flex items-center text-sm text-neutral-500">
<UserIcon <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" aria-hidden="true"
/> />
<p>1-on-1</p> <p>1-on-1</p>
</div> </div>
<div className="flex items-center text-sm text-neutral-500"> <div className="flex items-center text-sm text-neutral-500">
<InformationCircleIcon <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" 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>
</div> </span>
<div className="mt-4 flex-shrink-0 sm:mt-0 sm:ml-5"> </a>
<div className="flex overflow-hidden space-x-5"> </Link>
<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>
<Transition <div className="hidden sm:flex mt-4 flex-shrink-0 sm:mt-0 sm:ml-5">
show={open} <div className="flex overflow-hidden space-x-5">
as={Fragment} <Tooltip content="Preview">
enter="transition ease-out duration-100" <a
enterFrom="transform opacity-0 scale-95" href={"/" + session.user.username + "/" + type.slug}
enterTo="transform opacity-100 scale-100" target="_blank"
leave="transition ease-in duration-75" rel="noreferrer"
leaveFrom="transform opacity-100 scale-100" className="group cursor-pointer text-neutral-400 p-2 border border-transparent hover:border-gray-200">
leaveTo="transform opacity-0 scale-95"> <ExternalLinkIcon className="group-hover:text-black w-5 h-5" />
<Menu.Items </a>
static </Tooltip>
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"> <Tooltip content="Copy link">
<Menu.Item> <button
{({ active }) => ( onClick={() => {
<a navigator.clipboard.writeText(
href={"/" + session.user.username + "/" + type.slug} window.location.hostname + "/" + session.user.username + "/" + type.slug
target="_blank" );
rel="noreferrer" }}
className={classNames( className="group text-neutral-400 p-2 border border-transparent hover:border-gray-200">
active ? "bg-neutral-100 text-neutral-900" : "text-neutral-700", <LinkIcon className="group-hover:text-black w-5 h-5" />
"group flex items-center px-4 py-2 text-sm font-medium" </button>
)}> </Tooltip>
<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> </div>
</div> </div>
</a> <div className="flex sm:hidden ml-5 flex-shrink-0">
</Link> <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> </li>
))} ))}
</ul> </ul>
</div> </div>
{types.length === 0 && ( {types.length === 0 && (
<div className="text-center max-w-lg mx-auto"> <div className="md:py-20">
<svg <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" viewBox="0 0 132 132"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
@ -500,122 +600,13 @@ export default function Availability({ user, types }) {
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>
<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> <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"> <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 Event types enable you to share links that show available times on your calendar and allow
people to make bookings with you. people to make bookings with you.
</p> </p>
</div> <CreateNewEventDialog />
)}
{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> </div>
</div> </div>
)} )}

View file

@ -5,15 +5,17 @@ import Shell from "../../components/Shell";
import { useState } from "react"; import { useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useSession, getSession } from "next-auth/client"; import { useSession, getSession } from "next-auth/client";
import Loader from '@components/Loader'; import Loader from "@components/Loader";
export default function integration(props) { export default function Integration(props) {
const router = useRouter(); const router = useRouter();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession(); const [session, loading] = useSession();
const [showAPIKey, setShowAPIKey] = useState(false); const [showAPIKey, setShowAPIKey] = useState(false);
if (loading) { if (loading) {
return <Loader/>; return <Loader />;
} }
function toggleShowAPIKey() { function toggleShowAPIKey() {
@ -23,6 +25,7 @@ export default function integration(props) {
async function deleteIntegrationHandler(event) { async function deleteIntegrationHandler(event) {
event.preventDefault(); event.preventDefault();
/*eslint-disable */
const response = await fetch("/api/integrations", { const response = await fetch("/api/integrations", {
method: "DELETE", method: "DELETE",
body: JSON.stringify({ id: props.integration.id }), body: JSON.stringify({ id: props.integration.id }),
@ -30,6 +33,7 @@ export default function integration(props) {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}); });
/*eslint-enable */
router.push("/integrations"); router.push("/integrations");
} }
@ -37,27 +41,27 @@ export default function integration(props) {
return ( return (
<div> <div>
<Head> <Head>
<title>{getIntegrationName(props.integration.type)} | Integrations | Calendso</title> <title>{getIntegrationName(props.integration.type)} App | Calendso</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Shell heading={getIntegrationName(props.integration.type)} subtitle="Manage and delete integrations."> <Shell heading={getIntegrationName(props.integration.type)} subtitle="Manage and delete this app.">
<div className="grid grid-cols-3 gap-4"> <div className="block sm:grid grid-cols-3 gap-4">
<div className="col-span-2 bg-white shadow overflow-hidden rounded-sm"> <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"> <div className="px-4 py-5 sm:px-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">Integration Details</h3> <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"> <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> </p>
</div> </div>
<div className="border-t border-gray-200 px-4 py-5 sm:px-6"> <div className="border-t border-gray-200 px-4 py-5 sm:px-6">
<dl className="grid gap-y-8"> <dl className="grid gap-y-8">
<div> <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> <dd className="mt-1 text-sm text-gray-900">{getIntegrationName(props.integration.type)}</dd>
</div> </div>
<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> <dd className="mt-1 text-sm text-gray-900">{getIntegrationType(props.integration.type)}</dd>
</div> </div>
<div> <div>
@ -87,18 +91,18 @@ export default function integration(props) {
</div> </div>
</div> </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"> <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"> <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>
<div className="mt-5"> <div className="mt-5">
<button <button
onClick={deleteIntegrationHandler} onClick={deleteIntegrationHandler}
type="button" 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"> 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> </button>
</div> </div>
</div> </div>
@ -111,6 +115,7 @@ export default function integration(props) {
} }
export async function getServerSideProps(context) { export async function getServerSideProps(context) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const session = await getSession(context); const session = await getSession(context);
const integration = await prisma.credential.findFirst({ const integration = await prisma.credential.findFirst({

View file

@ -4,32 +4,18 @@ import prisma from "../../lib/prisma";
import Shell from "../../components/Shell"; import Shell from "../../components/Shell";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getSession, useSession } from "next-auth/client"; import { getSession, useSession } from "next-auth/client";
import { import { CheckCircleIcon, ChevronRightIcon, PlusIcon, XCircleIcon } from "@heroicons/react/solid";
CalendarIcon,
CheckCircleIcon,
ChevronRightIcon,
PlusIcon,
XCircleIcon,
} from "@heroicons/react/solid";
import { InformationCircleIcon } from "@heroicons/react/outline"; import { InformationCircleIcon } from "@heroicons/react/outline";
import { Switch } from "@headlessui/react"; import { Switch } from "@headlessui/react";
import Loader from '@components/Loader'; import Loader from "@components/Loader";
import classNames from "@lib/classNames";
import { Dialog, DialogClose, DialogContent, DialogTrigger, DialogHeader } from "@components/Dialog";
export default function Home({ integrations }) { export default function IntegrationHome({ integrations }) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession(); const [session, loading] = useSession();
const [showAddModal, setShowAddModal] = useState(false);
const [showSelectCalendarModal, setShowSelectCalendarModal] = useState(false);
const [selectableCalendars, setSelectableCalendars] = useState([]); const [selectableCalendars, setSelectableCalendars] = useState([]);
function toggleAddModal() {
setShowAddModal(!showAddModal);
}
function toggleShowCalendarModal() {
setShowSelectCalendarModal(!showSelectCalendarModal);
}
function loadCalendars() { function loadCalendars() {
fetch("api/availability/calendar") fetch("api/availability/calendar")
.then((response) => response.json()) .then((response) => response.json())
@ -81,38 +67,119 @@ export default function Home({ integrations }) {
} }
} }
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
useEffect(loadCalendars, [integrations]); useEffect(loadCalendars, [integrations]);
if (loading) { if (loading) {
return ( return <Loader />;
<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 ( return (
<div> <div>
<Head> <Head>
<title>Integrations | Calendso</title> <title>App Store | Calendso</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<Shell <Shell heading="App Store" subtitle="Connect your favourite apps." CTA={<ConnectNewAppDialog />}>
heading="Integrations" <div className="bg-white border border-gray-200 overflow-hidden rounded-sm mb-8">
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">
{integrations.filter((ig) => ig.credential).length !== 0 ? ( {integrations.filter((ig) => ig.credential).length !== 0 ? (
<ul className="divide-y divide-gray-200"> <ul className="divide-y divide-gray-200">
{integrations {integrations
@ -171,224 +238,41 @@ export default function Home({ integrations }) {
</div> </div>
<div className="py-5 sm:p-6"> <div className="py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900"> <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> </h3>
<div className="mt-2 text-sm text-gray-500"> <div className="mt-2 text-sm text-gray-500">
<p> <p>
You currently do not have any integrations set up. Add your first integration to get You currently do not have any apps connected. Connect your first app to get started.
started.
</p> </p>
</div> </div>
<div className="mt-3 text-sm"> <ConnectNewAppDialog />
<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>
</div> </div>
</div> </div>
</div> </div>
)} )}
</div> </div>
{showAddModal && ( <div className="bg-white border border-gray-200 rounded-sm mb-8">
<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="px-4 py-5 sm:p-6"> <div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">Select calendars</h3> <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"> <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> <p>Select which calendars are checked for availability to prevent double bookings.</p>
</div> </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"> <div className="mt-5">
<button type="button" onClick={toggleShowCalendarModal} className="btn btn-primary"> <a href="mailto:apps@calendso.com" className="btn btn-white">
Select calendars Contact us
</button> </a>
</div> </div>
</div> </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> </Shell>
</div> </div>
); );

View file

@ -1,7 +1,4 @@
import Head from "next/head"; import Head from "next/head";
import Link from "next/link";
import { useState } from "react";
import { useRouter } from "next/router";
import prisma from "../../lib/prisma"; import prisma from "../../lib/prisma";
import Shell from "../../components/Shell"; import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings"; import SettingsShell from "../../components/Settings";
@ -9,10 +6,11 @@ import { useSession, getSession } from "next-auth/client";
import Loader from '@components/Loader'; import Loader from '@components/Loader';
export default function Embed(props) { export default function Embed(props) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession(); const [session, loading] = useSession();
if (loading) { if (loading) {
return <Loader/>; return <Loader />;
} }
return ( return (

View file

@ -1,21 +1,22 @@
import Head from "next/head"; import Head from "next/head";
import Link from "next/link";
import { useRef, useState } from "react"; import { useRef, useState } from "react";
import prisma from "../../lib/prisma"; import prisma from "../../lib/prisma";
import Modal from "../../components/Modal"; import Modal from "../../components/Modal";
import Shell from "../../components/Shell"; import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings"; import SettingsShell from "../../components/Settings";
import { useSession, getSession } from "next-auth/client"; import { useSession, getSession } from "next-auth/client";
import Loader from '@components/Loader'; import Loader from "@components/Loader";
export default function Settings(props) { export default function Settings() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession(); const [session, loading] = useSession();
const [successModalOpen, setSuccessModalOpen] = useState(false); const [successModalOpen, setSuccessModalOpen] = useState(false);
const oldPasswordRef = useRef<HTMLInputElement>(); const oldPasswordRef = useRef<HTMLInputElement>();
const newPasswordRef = useRef<HTMLInputElement>(); const newPasswordRef = useRef<HTMLInputElement>();
if (loading) { if (loading) {
return <Loader/>; return <Loader />;
} }
const closeSuccessModal = () => { const closeSuccessModal = () => {
@ -30,6 +31,7 @@ export default function Settings(props) {
// TODO: Add validation // TODO: Add validation
/*eslint-disable */
const response = await fetch("/api/auth/changepw", { const response = await fetch("/api/auth/changepw", {
method: "PATCH", method: "PATCH",
body: JSON.stringify({ oldPassword: enteredOldPassword, newPassword: enteredNewPassword }), body: JSON.stringify({ oldPassword: enteredOldPassword, newPassword: enteredNewPassword }),
@ -37,12 +39,13 @@ export default function Settings(props) {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
}); });
/*eslint-enable */
setSuccessModalOpen(true); setSuccessModalOpen(true);
} }
return ( 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> <Head>
<title>Change Password | Calendso</title> <title>Change Password | Calendso</title>
<link rel="icon" href="/favicon.ico" /> <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 { UsernameInput } from "../../components/ui/UsernameInput";
import ErrorAlert from "../../components/ui/alerts/Error"; import ErrorAlert from "../../components/ui/alerts/Error";
const themeOptions = [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
];
export default function Settings(props) { export default function Settings(props) {
const [successModalOpen, setSuccessModalOpen] = useState(false); const [successModalOpen, setSuccessModalOpen] = useState(false);
const usernameRef = useRef<HTMLInputElement>(); const usernameRef = useRef<HTMLInputElement>();
@ -19,18 +24,13 @@ export default function Settings(props) {
const descriptionRef = useRef<HTMLTextAreaElement>(); const descriptionRef = useRef<HTMLTextAreaElement>();
const avatarRef = useRef<HTMLInputElement>(); const avatarRef = useRef<HTMLInputElement>();
const hideBrandingRef = 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 [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 [hasErrors, setHasErrors] = useState(false);
const [errorMessage, setErrorMessage] = useState(""); const [errorMessage, setErrorMessage] = useState("");
const themeOptions = [
{ value: "light", label: "Light" },
{ value: "dark", label: "Dark" },
];
useEffect(() => { useEffect(() => {
setSelectedTheme( setSelectedTheme(
props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : null props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : null
@ -179,6 +179,7 @@ export default function Settings(props) {
id="theme" id="theme"
isDisabled={!selectedTheme} isDisabled={!selectedTheme}
defaultValue={selectedTheme || themeOptions[0]} defaultValue={selectedTheme || themeOptions[0]}
value={selectedTheme || themeOptions[0]}
onChange={setSelectedTheme} 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" 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} options={themeOptions}

View file

@ -8,6 +8,7 @@ import { getSession, useSession } from "next-auth/client";
import { UsersIcon } from "@heroicons/react/outline"; import { UsersIcon } from "@heroicons/react/outline";
import TeamList from "../../components/team/TeamList"; import TeamList from "../../components/team/TeamList";
import TeamListItem from "../../components/team/TeamListItem"; import TeamListItem from "../../components/team/TeamListItem";
import Loader from "@components/Loader";
export default function Teams() { export default function Teams() {
const [, loading] = useSession(); const [, loading] = useSession();
@ -38,7 +39,7 @@ export default function Teams() {
}, []); }, []);
if (loading) { if (loading) {
return <p className="text-gray-400">Loading...</p>; return <Loader />;
} }
const createTeam = (e) => { const createTeam = (e) => {
@ -126,13 +127,13 @@ export default function Teams() {
</div> </div>
{showCreateTeamModal && ( {showCreateTeamModal && (
<div <div
className="fixed z-10 inset-0 overflow-y-auto" className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title" aria-labelledby="modal-title"
role="dialog" role="dialog"
aria-modal="true"> 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="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div <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> aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"> <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 { useEffect, useState } from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { CheckIcon } from "@heroicons/react/outline"; 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 dayjs from "dayjs";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import toArray from "dayjs/plugin/toArray"; import toArray from "dayjs/plugin/toArray";
@ -60,7 +60,7 @@ export default function Success(props) {
return ( return (
isReady && ( isReady && (
<div> <div className="bg-neutral-50 dark:bg-neutral-900 h-screen">
<Head> <Head>
<title> <title>
Booking {props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"} | {eventName} | Booking {props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"} | {eventName} |
@ -68,69 +68,69 @@ export default function Success(props) {
</title> </title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
<main className="max-w-3xl mx-auto my-24"> <main className="max-w-3xl mx-auto py-24">
<div className="fixed z-10 inset-0 overflow-y-auto"> <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="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"> <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"> <span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203; &#8203;
</span> </span>
<div <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" role="dialog"
aria-modal="true" aria-modal="true"
aria-labelledby="modal-headline"> aria-labelledby="modal-headline">
<div> <div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100"> <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
{!props.eventType.requiresConfirmation && ( {!props.eventType.requiresConfirmation && (
<CheckIcon className="h-6 w-6 text-green-600" /> <CheckIcon className="h-8 w-8 text-green-600" />
)} )}
{props.eventType.requiresConfirmation && ( {props.eventType.requiresConfirmation && (
<ClockIcon className="h-6 w-6 text-green-600" /> <ClockIcon className="h-8 w-8 text-green-600" />
)} )}
</div> </div>
<div className="mt-3 text-center sm:mt-5"> <div className="mt-3 text-center sm:mt-5">
<h3 <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"> id="modal-headline">
Booking {props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"} {props.eventType.requiresConfirmation ? "Submitted" : "This meeting is scheduled"}
</h3> </h3>
<div className="mt-2"> <div className="mt-3">
<p className="text-sm text-gray-500 dark:text-gray-300"> <p className="text-sm text-neutral-600 dark:text-gray-300">
{props.eventType.requiresConfirmation {props.eventType.requiresConfirmation
? `${ ? `${
props.user.name || props.user.username props.user.name || props.user.username
} still needs to confirm or reject the booking.` } 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> </p>
</div> </div>
<div className="mt-4 border-t border-b dark:border-gray-900 py-4"> <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">
<h2 className="text-lg font-medium text-gray-600 dark:text-gray-100 mb-2"> <div className="font-medium">What</div>
{eventName} <div className="mb-6 col-span-2">{eventName}</div>
</h2> <div className="font-medium">When</div>
<p className="text-gray-500 dark:text-gray-50 mb-1"> <div className="mb-6 col-span-2">
<ClockIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> {date.format("dddd, DD MMMM YYYY")}
{props.eventType.length} minutes <br />
</p> {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 && ( {location && (
<p className="text-gray-500 mb-1"> <>
<LocationMarkerIcon className="inline-block w-4 h-4 mr-1 -mt-1" /> <div className="font-medium">Where</div>
{location} <div className="col-span-2">{location}</div>
</p> </>
)} )}
<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> </div>
</div> </div>
{!props.eventType.requiresConfirmation && ( {!props.eventType.requiresConfirmation && (
<div className="mt-5 sm:mt-0 pt-2 text-center"> <div className="mt-5 sm:mt-0 sm:pt-4 pt-2 text-center flex">
<span className="font-medium text-gray-500 dark:text-gray-50"> <span className="font-medium text-gray-700 dark:text-gray-50 flex self-center mr-6">
Add to your calendar Add to calendar
</span> </span>
<div className="flex mt-2"> <div className="flex">
<Link <Link
href={ href={
`https://calendar.google.com/calendar/r/eventedit?dates=${date `https://calendar.google.com/calendar/r/eventedit?dates=${date
@ -142,9 +142,9 @@ export default function Success(props) {
props.eventType.description props.eventType.description
}` + (location ? "&location=" + encodeURIComponent(location) : "") }` + (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 <svg
className="inline-block w-4 h-4 mr-1 -mt-1" className="inline-block w-4 h-4 -mt-1"
fill="currentColor" fill="currentColor"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"> viewBox="0 0 24 24">
@ -166,7 +166,9 @@ export default function Success(props) {
eventName eventName
) + (location ? "&location=" + location : "") ) + (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 <svg
className="inline-block w-4 h-4 mr-1 -mt-1" className="inline-block w-4 h-4 mr-1 -mt-1"
fill="currentColor" fill="currentColor"
@ -190,7 +192,9 @@ export default function Success(props) {
eventName eventName
) + (location ? "&location=" + location : "") ) + (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 <svg
className="inline-block w-4 h-4 mr-1 -mt-1" className="inline-block w-4 h-4 mr-1 -mt-1"
fill="currentColor" fill="currentColor"
@ -202,7 +206,9 @@ export default function Success(props) {
</a> </a>
</Link> </Link>
<Link href={"data:text/calendar," + eventLink()}> <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 <svg
version="1.1" version="1.1"
xmlns="http://www.w3.org/2000/svg" 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

@ -3,7 +3,7 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" <svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 427 97.5" style="enable-background:new 0 0 427 97.5;" xml:space="preserve"> viewBox="0 0 427 97.5" style="enable-background:new 0 0 427 97.5;" xml:space="preserve">
<style type="text/css"> <style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#104D86;} .st0{fillRule:evenodd;clipRule:evenodd;fill:#104D86;}
</style> </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 <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 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

Before

Width:  |  Height:  |  Size: 4.2 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

@ -82,6 +82,23 @@
} }
/* !important to style multi-email input */ /* !important to style multi-email input */
::-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'] { .react-multi-email > [type='text'] {
@apply 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; @apply 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;
} }
@ -200,7 +217,8 @@
height: 30px; height: 30px;
margin: 60px auto; margin: 60px auto;
position: relative; position: relative;
border: 4px solid #000; border-width: 4px;
border-style: solid;
animation: loader 2s infinite ease; animation: loader 2s infinite ease;
} }
@ -208,7 +226,6 @@
vertical-align: top; vertical-align: top;
display: inline-block; display: inline-block;
width: 100%; width: 100%;
background-color: #000;
animation: loader-inner 2s infinite ease-in; animation: loader-inner 2s infinite ease-in;
} }

371
yarn.lock
View file

@ -753,6 +753,306 @@
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.26.0-23.9b816b3aa13cc270074f172f30d6eda8a8ce867d.tgz#cfdacfad3acc0f3bf1d7710aa8f3852fd85ac6d9" resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.26.0-23.9b816b3aa13cc270074f172f30d6eda8a8ce867d.tgz#cfdacfad3acc0f3bf1d7710aa8f3852fd85ac6d9"
integrity sha512-a0jIhLvw9rFh6nZTr5Y3uzP28I2xNDu3pqxANvwMNnmIoYr1wYEcO1pMXn/36BGXldDdAWMmAbhfloHA3IB8DA== 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": "@sinonjs/commons@^1.7.0":
version "1.8.3" version "1.8.3"
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" 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" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== 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: array-includes@^3.1.2, array-includes@^3.1.3:
version "3.1.3" version "3.1.3"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.3.tgz#c7f619b382ad2afaf5326cddfdc0afc61af7690a"
@ -2006,7 +2313,7 @@ cssstyle@^2.3.0:
dependencies: dependencies:
cssom "~0.3.6" cssom "~0.3.6"
csstype@^3.0.2: csstype@^3.0.2, csstype@^3.0.4:
version "3.0.8" version "3.0.8"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340"
integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== 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" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== 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: detective@^5.2.0:
version "5.2.0" version "5.2.0"
resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.0.tgz#feb2a77e85b904ecdea459ad897cc90a99bd2a7b" 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 "^1.0.3"
has-symbols "^1.0.1" 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: get-orientation@1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/get-orientation/-/get-orientation-1.1.2.tgz#20507928951814f8a91ded0a0e67b29dfab98947" 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" has "^1.0.3"
side-channel "^1.0.4" 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: is-arguments@^1.0.4:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.0.tgz#62353031dfbee07ceb34656a6bde59efecae8dd9" 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" slice-ansi "^4.0.0"
wrap-ansi "^6.2.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" version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -5250,6 +5574,25 @@ react-refresh@0.8.3:
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f" resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.8.3.tgz#721d4657672d400c5e3c75d063c4a85fb2d5d68f"
integrity sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg== 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: react-select@^4.3.0, react-select@^4.3.1:
version "4.3.1" version "4.3.1"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81" resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81"
@ -5263,6 +5606,15 @@ react-select@^4.3.0, react-select@^4.3.1:
react-input-autosize "^3.0.0" react-input-autosize "^3.0.0"
react-transition-group "^4.3.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: react-timezone-select@^1.0.2:
version "1.0.4" version "1.0.4"
resolved "https://registry.yarnpkg.com/react-timezone-select/-/react-timezone-select-1.0.4.tgz#66664f508f927e9f9c0f051aea51fd3196534401" resolved "https://registry.yarnpkg.com/react-timezone-select/-/react-timezone-select-1.0.4.tgz#66664f508f927e9f9c0f051aea51fd3196534401"
@ -6108,7 +6460,7 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== 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" version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@ -6261,6 +6613,19 @@ url@^0.11.0:
punycode "1.3.2" punycode "1.3.2"
querystring "0.2.0" 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: use-subscription@1.5.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1" resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1"