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".
3. On "OAuth", select "Create".
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.
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.
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.
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:
1. account:write:admin
2. meeting:write:admin
3. user:write:admin
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`.
12. Click "Done".
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(){
return <div className="loader"><span className="loader-inner"></span></div>
}
export default function Loader() {
return (
<div className="loader border-black dark:border-white">
<span className="loader-inner bg-black dark:bg-white"></span>
</div>
);
}

View file

@ -5,7 +5,7 @@ import { CheckIcon } from '@heroicons/react/outline'
export default function Modal(props) {
return (
<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">
<Transition.Child
as={Fragment}
@ -16,7 +16,7 @@ export default function Modal(props) {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
<Dialog.Overlay className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity" />
</Transition.Child>
{/* This element is to trick the browser into centering the modal contents. */}

View file

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

View file

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

36
components/Tooltip.tsx Normal file
View file

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

View file

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

View file

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

View file

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

View file

@ -32,9 +32,9 @@ export default function EditTeamModal(props) {
}).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="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>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -17,6 +17,10 @@
"@heroicons/react": "^1.0.1",
"@jitsu/sdk-js": "^2.0.1",
"@prisma/client": "^2.23.0",
"@radix-ui/react-collapsible": "^0.0.16",
"@radix-ui/react-dialog": "^0.0.19",
"@radix-ui/react-slot": "^0.0.12",
"@radix-ui/react-tooltip": "^0.0.21",
"@tailwindcss/forms": "^0.2.1",
"async": "^3.2.0",
"bcryptjs": "^2.4.3",

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ export default function Error() {
const { error } = router.query;
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>
<title>{error} - Calendso</title>
<link rel="icon" href="/favicon.ico" />

View file

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

View file

@ -4,7 +4,7 @@ import { CheckIcon } from '@heroicons/react/outline';
export default function Logout() {
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>
<title>Logged out - Calendso</title>
<link rel="icon" href="/favicon.ico" />
@ -29,7 +29,7 @@ export default function Logout() {
</div>
<div className="mt-5 sm:mt-6">
<Link href="/auth/login">
<a className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black sm:text-sm">
<a className="inline-flex justify-center w-full rounded-md border border-transparent shadow-sm px-4 py-2 bg-black text-base font-medium text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black sm:text-sm">
Go back to the login page
</a>
</Link>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,4 @@
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 Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
@ -9,10 +6,11 @@ import { useSession, getSession } from "next-auth/client";
import Loader from '@components/Loader';
export default function Embed(props) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [session, loading] = useSession();
if (loading) {
return <Loader/>;
return <Loader />;
}
return (

View file

@ -1,21 +1,22 @@
import Head from "next/head";
import Link from "next/link";
import { useRef, useState } from "react";
import prisma from "../../lib/prisma";
import Modal from "../../components/Modal";
import Shell from "../../components/Shell";
import SettingsShell from "../../components/Settings";
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 [successModalOpen, setSuccessModalOpen] = useState(false);
const oldPasswordRef = useRef<HTMLInputElement>();
const newPasswordRef = useRef<HTMLInputElement>();
if (loading) {
return <Loader/>;
return <Loader />;
}
const closeSuccessModal = () => {
@ -30,6 +31,7 @@ export default function Settings(props) {
// TODO: Add validation
/*eslint-disable */
const response = await fetch("/api/auth/changepw", {
method: "PATCH",
body: JSON.stringify({ oldPassword: enteredOldPassword, newPassword: enteredNewPassword }),
@ -37,12 +39,13 @@ export default function Settings(props) {
"Content-Type": "application/json",
},
});
/*eslint-enable */
setSuccessModalOpen(true);
}
return (
<Shell heading="Password" subtitle="Change the password that you use to sign in.">
<Shell heading="Password" subtitle="Change the password that you use to sign in to your account.">
<Head>
<title>Change Password | Calendso</title>
<link rel="icon" href="/favicon.ico" />

View file

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

View file

@ -8,6 +8,7 @@ import { getSession, useSession } from "next-auth/client";
import { UsersIcon } from "@heroicons/react/outline";
import TeamList from "../../components/team/TeamList";
import TeamListItem from "../../components/team/TeamListItem";
import Loader from "@components/Loader";
export default function Teams() {
const [, loading] = useSession();
@ -38,7 +39,7 @@ export default function Teams() {
}, []);
if (loading) {
return <p className="text-gray-400">Loading...</p>;
return <Loader />;
}
const createTeam = (e) => {
@ -126,13 +127,13 @@ export default function Teams() {
</div>
{showCreateTeamModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
className="fixed z-50 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
className="fixed inset-0 bg-gray-500 z-0 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

9
public/browserconfig.xml Normal file
View file

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

View file

@ -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"
viewBox="0 0 427 97.5" style="enable-background:new 0 0 427 97.5;" xml:space="preserve">
<style type="text/css">
.st0{fill-rule:evenodd;clip-rule:evenodd;fill:#104D86;}
.st0{fillRule:evenodd;clipRule:evenodd;fill:#104D86;}
</style>
<path class="st0" d="M27.5,88.2c-4.9,0-9.7-1.2-14-3.6c-4.2-2.4-7.6-5.8-9.9-10c-4.8-8.8-4.8-19.4,0-28.2c2.3-4.2,5.8-7.7,10-10
c4.3-2.4,9.1-3.7,14-3.6c6-0.1,11.8,1.7,16.5,5.3s8,8.7,9.9,15.4H42.8c-1.3-3-3.4-5.5-6.2-7.2c-2.6-1.6-5.6-2.5-8.7-2.5

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