Merge branch 'main' into main
|
@ -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
|
@ -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;
|
|
@ -1,3 +1,7 @@
|
|||
export default function Loader() {
|
||||
return <div className="loader"><span className="loader-inner"></span></div>
|
||||
return (
|
||||
<div className="loader border-black dark:border-white">
|
||||
<span className="loader-inner bg-black dark:bg-white"></span>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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. */}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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,10 +68,11 @@ 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">
|
||||
<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">
|
||||
|
@ -94,7 +93,9 @@ export default function Shell(props) {
|
|||
)}>
|
||||
<item.icon
|
||||
className={classNames(
|
||||
item.current ? "text-neutral-500" : "text-neutral-400 group-hover:text-neutral-500",
|
||||
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"
|
||||
|
@ -105,13 +106,15 @@ export default function Shell(props) {
|
|||
))}
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex-shrink-0 flex border-t border-gray-200 p-4">
|
||||
<div className="flex-shrink-0 flex p-4">
|
||||
<UserDropdown session={session} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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">
|
||||
|
@ -133,12 +136,10 @@ export default function Shell(props) {
|
|||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main className="flex-1 relative z-0 overflow-y-auto focus:outline-none">
|
||||
<div className="py-6">
|
||||
<div className="py-8">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -146,7 +147,7 @@ export default function Shell(props) {
|
|||
<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">
|
||||
<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" ? (
|
||||
|
@ -181,6 +182,7 @@ export default function Shell(props) {
|
|||
</main>
|
||||
</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
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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,
|
||||
|
@ -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">
|
||||
|
|
|
@ -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) => (
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">​</span>
|
||||
|
||||
|
|
|
@ -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">
|
||||
​
|
||||
|
|
|
@ -1,15 +1,26 @@
|
|||
import { useState } from 'react';
|
||||
|
||||
export default function Button(props) {
|
||||
return (
|
||||
<button type="submit" className="btn btn-primary">
|
||||
<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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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{" "}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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">
|
||||
​
|
||||
|
|
|
@ -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>`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
@ -0,0 +1,3 @@
|
|||
export default function classNames(...classes) {
|
||||
return classes.filter(Boolean).join(" ");
|
||||
}
|
|
@ -32,6 +32,10 @@ module.exports = withTM({
|
|||
future: {
|
||||
webpack5: true,
|
||||
},
|
||||
i18n: {
|
||||
locales: ["en"],
|
||||
defaultLocale: "en",
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
|
|
|
@ -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
|
@ -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"> →</span>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
<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't set up any event types yet.</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
|
@ -76,6 +99,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
|||
select: {
|
||||
slug: true,
|
||||
title: true,
|
||||
length: true,
|
||||
description: true,
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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">
|
||||
|
||||
<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" />
|
||||
</button>
|
||||
{isTimeOptionsOpen && (
|
||||
)}
|
||||
</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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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. ",
|
||||
});
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
/>
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
@ -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'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">
|
||||
|
|
|
@ -10,6 +10,7 @@ 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());
|
||||
|
|
|
@ -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,33 +58,31 @@ 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 className="hidden lg:block text-sm text-neutral-500">
|
||||
You and {booking.attendees[0].name}
|
||||
</div>
|
||||
<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 className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
|
@ -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 } };
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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";
|
||||
|
@ -102,7 +100,6 @@ export default function EventTypePage({
|
|||
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">
|
||||
<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">
|
||||
<Shell
|
||||
heading={
|
||||
<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"
|
||||
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}
|
||||
/>
|
||||
</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" />
|
||||
}
|
||||
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="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"
|
||||
/>
|
||||
|
||||
|
|
|
@ -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 />;
|
||||
}
|
||||
|
||||
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="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">
|
||||
<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,52 +196,63 @@ 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 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" />
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<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="text-neutral-400">
|
||||
<LinkIcon className="w-5 h-5" />
|
||||
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>
|
||||
</div>
|
||||
<div className="ml-5 flex-shrink-0">
|
||||
<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">
|
||||
<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>
|
||||
|
@ -181,7 +282,7 @@ export default function Availability({ user, types }) {
|
|||
"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"
|
||||
className="mr-3 h-4 w-4 text-neutral-400 group-hover:text-neutral-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Preview
|
||||
|
@ -205,7 +306,7 @@ export default function Availability({ user, types }) {
|
|||
"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"
|
||||
className="mr-3 h-4 w-4 text-neutral-400 group-hover:text-neutral-500"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
Copy link to event
|
||||
|
@ -221,7 +322,7 @@ export default function Availability({ user, types }) {
|
|||
{/* "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"*/}
|
||||
{/* className="mr-3 h-4 w-4 text-neutral-400 group-hover:text-neutral-500"*/}
|
||||
{/* aria-hidden="true"*/}
|
||||
{/* />*/}
|
||||
{/* Duplicate*/}
|
||||
|
@ -254,16 +355,15 @@ export default function Availability({ user, types }) {
|
|||
</Menu>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</Link>
|
||||
</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>
|
||||
|
||||
<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>
|
||||
</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">
|
||||
​
|
||||
</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>
|
||||
<CreateNewEventDialog />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
@ -5,11 +5,13 @@ 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) {
|
||||
|
@ -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({
|
||||
|
|
|
@ -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't have any integrations added.
|
||||
You don'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">→</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">
|
||||
​
|
||||
</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">
|
||||
​
|
||||
</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>
|
||||
);
|
||||
|
|
|
@ -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,6 +6,7 @@ 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) {
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
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>();
|
||||
|
@ -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" />
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
​
|
||||
</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"
|
||||
|
|
BIN
public/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 4.4 KiB |
BIN
public/android-chrome-256x256.png
Normal file
After Width: | Height: | Size: 6.2 KiB |
BIN
public/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
9
public/browserconfig.xml
Normal 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>
|
|
@ -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
After Width: | Height: | Size: 736 B |
BIN
public/favicon-32x32.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
BIN
public/mstile-150x150.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
21
public/safari-pinned-tab.svg
Normal 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
|
@ -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"
|
||||
}
|
|
@ -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
|
@ -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"
|
||||
|
|